diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e55c86e..cfdd3750f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +### r5-dev + +* reverse buffer for Player and Sampler. +* Tone.Volume for simple volume control in Decibels. +* Panner uses StereoPannerNode when available. +* AutoFilter effect +* Made many attributes read-only. preventing this common type of error: `oscillator.frequency = 200` when it should be `oscillator.frequency.value = 200`. +* Envelope supports "linear" and "exponential" attack curves. +* Renamed Tone.EQ -> Tone.EQ3. +* Tone.DrumSynth makes kick and tom sounds. +* Tone.MidSideCompressor and Tone.MidSideSplit/Tone.MidSideMerge +* Tone.Oscillator - can specify the number of partials in the type: i.e. "sine10", "triangle3", "square4", etc. + ### r4 - Cool is cool * `toFrequency` accepts notes by name (i.e. `"C4"`) @@ -7,7 +20,7 @@ * Sampler accepts multiple samples as an object. * `setPitch` in sampler -> `setNote` * Deprecated MultiSampler - use Sampler with PolySynth instead -* Added [cdn](cdn.tonejs.org/latest/Tone.min.js) - please don't use for production code +* Added [cdn](http://cdn.tonejs.org/latest/Tone.min.js) - please don't use for production code * Renamed DryWet to CrossFade * Functions return `this` to allow for chaining. i.e. `player.toMaster().start(2)`. * Added `units` to Signal class which allows signals to be set in terms of Tone.Time, Tone.Frequency, Numbers, or Decibels. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..d6b0a9504 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +[The MIT License](http://opensource.org/licenses/MIT) + +Copyright © 2014-2015 Yotam Mann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index d56790459..12b17ea0d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Tone.js Tone.js is a Web Audio framework for creating interactive music in the browser. The architecture of Tone.js aims to be familiar to both musicians and audio programmers looking to create web-based audio applications. On the high-level, Tone offers common DAW (digital audio workstation) features like a global transport, prebuilt synths and effects, as well as presets for those synths and effects. For signal-processing programmers (coming from languages like Max/MSP), Tone provides a wealth of high performance, low latency building blocks and DSP modules to build your own synthesizers, effects, and complex control signals. -[Examples](http://tonenotone.github.io/Tone.js/examples/) +[Examples](http://tonejs.org/examples/) [API](http://tonejs.org/docs/Tone.html) @@ -14,6 +14,7 @@ Tone.js is a Web Audio framework for creating interactive music in the browser. * [Hypercube by @eddietree](http://eddietree.github.io/hypercube/) * [randomcommander.io by Jake Albaugh](http://randomcommander.io/) * [Tone.js + NexusUI by taylorbf](http://taylorbf.github.io/Tone-Rack/) +* [Solarbeat - Luke Twyman](http://www.whitevinyldesign.com/solarbeat/) Using Tone.js? I'd love to hear it: yotammann@gmail.com @@ -104,7 +105,7 @@ Tone also let's you set your own AudioContext using `Tone.setContext`. # Performance -Tone.js uses very few ScriptProcessorNodes. Nearly all of the Tone Modules find a native Web Audio component workaround, making extensive use of the GainNode and WaveShaperNode especially, which enables Tone.js to work well on both desktop and mobile browsers. While the ScripProcessorNode is extremely powerful, it introduces a lot of latency and the potential for glitches more than any other node. +Tone.js uses very few ScriptProcessorNodes. Nearly all of the Tone Modules find a native Web Audio component workaround, making extensive use of the GainNode and WaveShaperNode especially, which enables Tone.js to work well on both desktop and mobile browsers. While the ScriptProcessorNode is extremely powerful, it introduces a lot of latency and the potential for glitches more than any other node. # References and Inspiration diff --git a/Tone/component/Compressor.js b/Tone/component/Compressor.js index 4f48d46f9..92620554f 100644 --- a/Tone/component/Compressor.js +++ b/Tone/component/Compressor.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ "use strict"; @@ -21,12 +21,7 @@ define(["Tone/core/Tone"], function(Tone){ * @type {DynamicsCompressorNode} * @private */ - this._compressor = this.context.createDynamicsCompressor(); - - /** - * the input and output - */ - this.input = this.output = this._compressor; + this._compressor = this.input = this.output = this.context.createDynamicsCompressor(); /** * the threshold vaue @@ -59,6 +54,9 @@ define(["Tone/core/Tone"], function(Tone){ this.ratio = this._compressor.ratio; //set the defaults + this.attack.connect(this._compressor.attack); + this.release.connect(this._compressor.release); + this._readOnly(["knee", "release", "attack", "ratio", "threshold"]); this.set(options); }; @@ -83,6 +81,7 @@ define(["Tone/core/Tone"], function(Tone){ */ 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(); diff --git a/Tone/component/CrossFade.js b/Tone/component/CrossFade.js index a9b7ffeb4..7d91b563c 100644 --- a/Tone/component/CrossFade.js +++ b/Tone/component/CrossFade.js @@ -71,6 +71,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Expr", "Tone/signal 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); @@ -81,6 +82,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Expr", "Tone/signal */ Tone.CrossFade.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable("fade"); this._equalPowerA.dispose(); this._equalPowerA = null; this._equalPowerB.dispose(); diff --git a/Tone/component/EQ.js b/Tone/component/EQ3.js similarity index 88% rename from Tone/component/EQ.js rename to Tone/component/EQ3.js index 4aa06346b..ed176b6b7 100644 --- a/Tone/component/EQ.js +++ b/Tone/component/EQ3.js @@ -13,11 +13,11 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] * @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.EQ(-10, 3, -20); + * var eq = new Tone.EQ3(-10, 3, -20); */ - Tone.EQ = function(){ + Tone.EQ3 = function(){ - var options = this.optionsObject(arguments, ["low", "mid", "high"], Tone.EQ.defaults); + var options = this.optionsObject(arguments, ["low", "mid", "high"], Tone.EQ3.defaults); /** * the output node @@ -95,16 +95,17 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] this.high.value = options.low; this.mid.value = options.mid; this.low.value = options.high; + this._readOnly(["low", "mid", "high", "lowFrequency", "highFrequency"]); }; - Tone.extend(Tone.EQ); + Tone.extend(Tone.EQ3); /** * the default values * @type {Object} * @static */ - Tone.EQ.defaults = { + Tone.EQ3.defaults = { "low" : 0, "mid" : 0, "high" : 0, @@ -114,10 +115,11 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] /** * clean up - * @returns {Tone.EQ} `this` + * @returns {Tone.EQ3} `this` */ - Tone.EQ.prototype.dispose = function(){ + 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; @@ -137,5 +139,5 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] return this; }; - return Tone.EQ; + return Tone.EQ3; }); \ No newline at end of file diff --git a/Tone/component/Envelope.js b/Tone/component/Envelope.js index 782bd42e7..57060c58a 100644 --- a/Tone/component/Envelope.js +++ b/Tone/component/Envelope.js @@ -50,12 +50,71 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton */ 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); @@ -70,6 +129,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton "decay" : 0.1, "sustain" : 0.5, "release" : 1, + "attackCurve" : "linear" }; /** @@ -79,6 +139,96 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton */ 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 + Tone.Envelope.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + + Tone.Envelope.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + + 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 {Tone.Time} [time=now] @@ -90,15 +240,35 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton * env.triggerAttack("+0.5", 0.2); */ Tone.Envelope.prototype.triggerAttack = function(time, velocity){ - velocity = this.defaultArg(velocity, 1); + //to seconds + time = this.toSeconds(time); var attack = this.toSeconds(this.attack); var decay = this.toSeconds(this.decay); - var scaledMax = velocity; + + //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; - time = this.toSeconds(time); + + //set the curve this._sig.cancelScheduledValues(time); - this._sig.setTargetAtTime(scaledMax, time, attack * this._timeMult); - this._sig.setTargetAtTime(sustainVal, time + attack, decay * this._timeMult); + 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; }; @@ -112,9 +282,31 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton */ Tone.Envelope.prototype.triggerRelease = function(time){ time = this.toSeconds(time); - this._sig.cancelScheduledValues(time); + var phase = this._phaseAtTime(time); var release = this.toSeconds(this.release); - this._sig.setTargetAtTime(0, time, release * this._timeMult); + + //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; }; @@ -152,5 +344,26 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton 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; }); diff --git a/Tone/component/FeedbackCombFilter.js b/Tone/component/FeedbackCombFilter.js index 83e9ad75d..b45b136f0 100644 --- a/Tone/component/FeedbackCombFilter.js +++ b/Tone/component/FeedbackCombFilter.js @@ -7,35 +7,13 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio * * @extends {Tone} * @constructor - * @param {number} [minDelay=0.01] the minimum delay time which the filter can have - * @param {number} [maxDelay=1] the maximum delay time which the filter can have + * @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, ["minDelay", "maxDelay"], Tone.FeedbackCombFilter.defaults); - - var minDelay = options.minDelay; - var maxDelay = options.maxDelay; - //the delay * samplerate = number of samples. - // buffersize / number of samples = number of delays needed per buffer frame - var delayCount = Math.ceil(this.bufferSize / (minDelay * this.context.sampleRate)); - //set some ranges - delayCount = Math.min(delayCount, 10); - delayCount = Math.max(delayCount, 1); - - /** - * the number of filter delays - * @type {number} - * @private - */ - this._delayCount = delayCount; - - /** - * @type {Array.} - * @private - */ - this._delays = new Array(this._delayCount); + var options = this.optionsObject(arguments, ["delayTime", "resonance"], Tone.FeedbackCombFilter.defaults); /** * the resonance control @@ -44,26 +22,17 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); /** - * scale the resonance value to the normal range - * @type {Tone.Scale} + * the delay node + * @type {DelayNode} * @private */ - this._resScale = new Tone.ScaleExp(0.01, 1 / this._delayCount - 0.001, 0.5); + this._delay = this.input = this.output = this.context.createDelay(1); /** - * internal flag for keeping track of when frequency - * correction has been used - * @type {boolean} - * @private - */ - this._highFrequencies = false; - - /** - * internal counter of delayTime - * @type {Tone.TIme} - * @private + * the delayTime + * @type {Tone.Signal} */ - this._delayTime = options.delayTime; + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); /** * the feedback node @@ -72,23 +41,10 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio */ this._feedback = this.context.createGain(); - //make the filters - for (var i = 0; i < this._delayCount; i++) { - var delay = this.context.createDelay(maxDelay); - delay.delayTime.value = minDelay; - delay.connect(this._feedback); - this._delays[i] = delay; - } - - //connections - this.connectSeries.apply(this, this._delays); - this.input.connect(this._delays[0]); - //set the delay to the min value initially - this._feedback.connect(this._delays[0]); - //resonance control - this.resonance.chain(this._resScale, this._feedback.gain); - this._feedback.connect(this.output); - this.delayTime = options.delayTime; + 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); @@ -100,63 +56,23 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio * @type {Object} */ Tone.FeedbackCombFilter.defaults = { - "resonance" : 0.5, - "minDelay" : 0.1, - "maxDelay" : 1, - "delayTime" : 0.1 + "delayTime" : 0.1, + "resonance" : 0.5 }; - /** - * the delay time of the FeedbackCombFilter - * @memberOf Tone.FeedbackCombFilter# - * @type {Tone.Time} - * @name delayTime - */ - Object.defineProperty(Tone.FeedbackCombFilter.prototype, "delayTime", { - get : function(){ - return this._delayTime; - }, - set : function(delayAmount){ - this._delayTime = delayAmount; - delayAmount = this.toSeconds(delayAmount); - //the number of samples to delay by - var sampleRate = this.context.sampleRate; - var delaySamples = sampleRate * delayAmount; - // delayTime corection when frequencies get high - var now = this.now() + this.bufferTime; - var cutoff = 100; - if (delaySamples < cutoff){ - this._highFrequencies = true; - var changeNumber = Math.round((delaySamples / cutoff) * this._delayCount); - for (var i = 0; i < changeNumber; i++) { - this._delays[i].delayTime.setValueAtTime(1 / sampleRate + delayAmount, now); - } - delayAmount = Math.floor(delaySamples) / sampleRate; - } else if (this._highFrequencies){ - this._highFrequencies = false; - for (var j = 0; j < this._delays.length; j++) { - this._delays[j].delayTime.setValueAtTime(delayAmount, now); - } - } - } - }); - /** * clean up * @returns {Tone.FeedbackCombFilter} `this` */ Tone.FeedbackCombFilter.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - //dispose the filter delays - for (var i = 0; i < this._delays.length; i++) { - this._delays[i].disconnect(); - this._delays[i] = null; - } - this._delays = null; + this._writable(["resonance", "delayTime"]); + this._delay.disconnect(); + this._delay = null; + this.delayTime.dispose(); + this.delayTime = null; this.resonance.dispose(); this.resonance = null; - this._resScale.dispose(); - this._resScale = null; this._feedback.disconnect(); this._feedback = null; return this; diff --git a/Tone/component/Filter.js b/Tone/component/Filter.js index 9f2cdfbcb..10c4ad3d2 100644 --- a/Tone/component/Filter.js +++ b/Tone/component/Filter.js @@ -43,7 +43,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ /** * the gain of the filter, only used in certain filter types - * @type {AudioParam} + * @type {Tone.Signal} */ this.gain = new Tone.Signal(options.gain, Tone.Signal.Units.Number); @@ -69,6 +69,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ //set the rolloff; this.rolloff = options.rolloff; + this._readOnly(["detune", "frequency", "gain", "Q"]); }; Tone.extend(Tone.Filter); @@ -164,6 +165,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ this._filters[i] = null; } this._filters = null; + this._writable(["detune", "frequency", "gain", "Q"]); this.frequency.dispose(); this.Q.dispose(); this.frequency = null; diff --git a/Tone/component/LFO.js b/Tone/component/LFO.js index c188829ad..f79e028c0 100644 --- a/Tone/component/LFO.js +++ b/Tone/component/LFO.js @@ -64,6 +64,7 @@ function(Tone){ //connect it up this.oscillator.chain(this._a2g, this._scaler); + this._readOnly(["amplitude", "frequency", "oscillator"]); }; Tone.extend(Tone.LFO, Tone.Oscillator); @@ -181,7 +182,7 @@ function(Tone){ /** * The phase of the LFO * @memberOf Tone.LFO# - * @type {string} + * @type {number} * @name phase */ Object.defineProperty(Tone.LFO.prototype, "phase", { @@ -206,6 +207,7 @@ function(Tone){ */ Tone.LFO.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable(["amplitude", "frequency", "oscillator"]); this.oscillator.dispose(); this.oscillator = null; this._scaler.dispose(); diff --git a/Tone/component/Limiter.js b/Tone/component/Limiter.js index 2877183d9..2e09b1503 100644 --- a/Tone/component/Limiter.js +++ b/Tone/component/Limiter.js @@ -30,6 +30,8 @@ define(["Tone/core/Tone", "Tone/component/Compressor"], function(Tone){ * @type {AudioParam} */ this.threshold = this._compressor.threshold; + + this._readOnly("threshold"); }; Tone.extend(Tone.Limiter); @@ -42,6 +44,7 @@ define(["Tone/core/Tone", "Tone/component/Compressor"], function(Tone){ Tone.prototype.dispose.call(this); this._compressor.dispose(); this._compressor = null; + this._writable("threshold"); this.threshold = null; return this; }; diff --git a/Tone/component/LowpassCombFilter.js b/Tone/component/LowpassCombFilter.js index e4ec165b7..c5502c16a 100644 --- a/Tone/component/LowpassCombFilter.js +++ b/Tone/component/LowpassCombFilter.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/component/Filter"], function(Tone){ "use strict"; @@ -8,92 +8,63 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio * * @extends {Tone} * @constructor - * @param {number} [minDelay=0.1] the minimum delay time which the filter can have - * @param {number} [maxDelay=1] the maximum delay time which the filter can have + * @param {number} [delayTime=0.1] The delay time of the comb filter + * @param {number} [resonance=0.5] The resonance (feedback) of the comb filter + * @param {Tone.Frequency} [dampening=3000] The dampending cutoff of the lowpass filter */ Tone.LowpassCombFilter = function(){ Tone.call(this); - var options = this.optionsObject(arguments, ["minDelay", "maxDelay"], Tone.LowpassCombFilter.defaults); - - //the delay * samplerate = number of samples. - // buffersize / number of samples = number of delays needed per buffer frame - var delayCount = Math.ceil(this.bufferSize / (options.minDelay * this.context.sampleRate)); - //set some ranges - delayCount = Math.min(delayCount, 10); - delayCount = Math.max(delayCount, 1); + var options = this.optionsObject(arguments, ["delayTime", "resonance", "dampening"], Tone.LowpassCombFilter.defaults); /** - * the number of filter delays - * @type {number} + * the delay node + * @type {DelayNode} * @private */ - this._filterDelayCount = delayCount; + this._delay = this.input = this.context.createDelay(1); /** - * @type {Array.} - * @private - */ - this._filterDelays = new Array(this._filterDelayCount); - - /** - * the dampening control + * the delayTime * @type {Tone.Signal} */ - this.dampening = new Tone.Signal(options.dampening, Tone.Signal.Units.Frequency); + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); /** - * the resonance control - * @type {Tone.Signal} - */ - this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); - - /** - * scale the resonance value to the normal range - * @type {Tone.Scale} + * the lowpass filter + * @type {BiquadFilterNode} * @private */ - this._resScale = new Tone.ScaleExp(0.01, 1 / this._filterDelayCount - 0.001, 0.5); + this._lowpass = this.output = this.context.createBiquadFilter(); + this._lowpass.Q.value = 0; + this._lowpass.type = "lowpass"; + this._lowpass.frequency.value = options.dampening; /** - * internal flag for keeping track of when frequency - * correction has been used - * @type {boolean} - * @private - */ - this._highFrequencies = false; - - /** - * internal counter of delayTime - * @type {Tone.Time} - * @private + * the dampening control + * @type {Tone.Signal} */ - this._delayTime = options.delayTime; + this.dampening = new Tone.Signal(this._lowpass.frequency, Tone.Signal.Units.Frequency); /** - * the feedback node + * the feedback gain * @type {GainNode} * @private */ this._feedback = this.context.createGain(); - //make the filters - for (var i = 0; i < this._filterDelayCount; i++) { - var filterDelay = new FilterDelay(options.minDelay, this.dampening); - filterDelay.connect(this._feedback); - this._filterDelays[i] = filterDelay; - } + /** + * the resonance control + * @type {Tone.Signal} + */ + this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); //connections - this.input.connect(this._filterDelays[0]); - this._feedback.connect(this._filterDelays[0]); - this.connectSeries.apply(this, this._filterDelays); - //resonance control - this.resonance.chain(this._resScale, this._feedback.gain); - this._feedback.connect(this.output); - //set the delay to the min value initially - this.delayTime = options.delayTime; + this._delay.chain(this._lowpass, this._feedback, this._delay); + this.delayTime.connect(this._delay.delayTime); + this.resonance.connect(this._feedback.gain); + this._readOnly(["dampening", "resonance", "delayTime"]); }; Tone.extend(Tone.LowpassCombFilter); @@ -105,55 +76,9 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio * @type {Object} */ Tone.LowpassCombFilter.defaults = { + "delayTime" : 0.1, "resonance" : 0.5, - "dampening" : 3000, - "minDelay" : 0.1, - "maxDelay" : 1, - "delayTime" : 0.1 - }; - - /** - * The delay time of the LowpassCombFilter. Auto corrects - * for sample offsets for small delay amounts. - * @memberOf Tone.LowpassCombFilter# - * @type {Tone.Time} - * @name delayTime - */ - Object.defineProperty(Tone.LowpassCombFilter.prototype, "delayTime", { - get : function(){ - return this._delayTime; - }, - set : function(delayAmount){ - this.setDelayTimeAtTime(delayAmount); - } - }); - - /** - * set the delay time for the comb filter at a specific time. - * @param {Tone.Time} delayAmount the amount of delay time - * @param {Tone.Time} [time=now] when the delay time should be set - */ - Tone.LowpassCombFilter.prototype.setDelayTimeAtTime = function(delayAmount, time){ - this._delayTime = this.toSeconds(delayAmount); - //the number of samples to delay by - var sampleRate = this.context.sampleRate; - var delaySamples = sampleRate * this._delayTime; - // delayTime corection when frequencies get high - time = this.toSeconds(time); - var cutoff = 100; - if (delaySamples < cutoff){ - this._highFrequencies = true; - var changeNumber = Math.round((delaySamples / cutoff) * this._filterDelayCount); - for (var i = 0; i < changeNumber; i++) { - this._filterDelays[i].setDelay(1 / sampleRate + this._delayTime, time); - } - this._delayTime = Math.floor(delaySamples) / sampleRate; - } else if (this._highFrequencies){ - this._highFrequencies = false; - for (var j = 0; j < this._filterDelays.length; j++) { - this._filterDelays[j].setDelay(this._delayTime, time); - } - } + "dampening" : 3000 }; /** @@ -162,61 +87,21 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio */ Tone.LowpassCombFilter.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - //dispose the filter delays - for (var i = 0; i < this._filterDelays.length; i++) { - this._filterDelays[i].dispose(); - this._filterDelays[i] = null; - } - this._filterDelays = null; + this._writable(["dampening", "resonance", "delayTime"]); this.dampening.dispose(); this.dampening = null; this.resonance.dispose(); this.resonance = null; - this._resScale.dispose(); - this._resScale = 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; }; - // BEGIN HELPER CLASS // - - /** - * FilterDelay - * @private - * @constructor - * @extends {Tone} - */ - var FilterDelay = function(maxDelay, filterFreq){ - this.delay = this.input = this.context.createDelay(maxDelay); - this.delay.delayTime.value = maxDelay; - - this.filter = this.output = this.context.createBiquadFilter(); - filterFreq.connect(this.filter.frequency); - - this.filter.type = "lowpass"; - this.filter.Q.value = 0; - - this.delay.connect(this.filter); - }; - - Tone.extend(FilterDelay); - - FilterDelay.prototype.setDelay = function(amount, time) { - this.delay.delayTime.setValueAtTime(amount, time); - }; - - /** - * clean up - */ - FilterDelay.prototype.dispose = function(){ - this.delay.disconnect(); - this.delay = null; - this.filter.disconnect(); - this.filter = null; - }; - - // END HELPER CLASS // - return Tone.LowpassCombFilter; }); \ No newline at end of file diff --git a/Tone/component/MidSideCompressor.js b/Tone/component/MidSideCompressor.js new file mode 100644 index 000000000..ca09c420a --- /dev/null +++ b/Tone/component/MidSideCompressor.js @@ -0,0 +1,91 @@ +define(["Tone/core/Tone", "Tone/component/MidSideSplit", "Tone/component/MidSideMerge", + "Tone/component/Compressor"], function(Tone){ + + "use strict"; + + /** + * @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; +}); \ No newline at end of file diff --git a/Tone/component/MidSideMerge.js b/Tone/component/MidSideMerge.js new file mode 100644 index 000000000..dd5982976 --- /dev/null +++ b/Tone/component/MidSideMerge.js @@ -0,0 +1,99 @@ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Expr", "Tone/component/Merge"], + function(Tone){ + + "use strict"; + + /** + * @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 {@link Tone.MidSideSplit}.
+ * M/S send/return
+ * L = (M+S)/sqrt(2); // obtain left signal from mid and side
+ * R = (M-S)/sqrt(2); // obtain right signal from mid and side
+ * + * @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 {Tone.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; +}); \ No newline at end of file diff --git a/Tone/component/MidSideSplit.js b/Tone/component/MidSideSplit.js new file mode 100644 index 000000000..c079c8d99 --- /dev/null +++ b/Tone/component/MidSideSplit.js @@ -0,0 +1,77 @@ +define(["Tone/core/Tone", "Tone/signal/Expr", "Tone/signal/Signal", "Tone/component/Split"], + function(Tone){ + + "use strict"; + + /** + * @class Seperates the mid channel from the side channel. Has two outputs labeled + * `mid` and `side` or `output[0]` and `output[1]`.
+ * http://musicdsp.org/showArchiveComment.php?ArchiveID=173
+ * http://www.kvraudio.com/forum/viewtopic.php?t=212587
+ * M = (L+R)/sqrt(2); // obtain mid-signal from left and right
+ * S = (L-R)/sqrt(2); // obtain side-signal from left and righ
+ * + * @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 {Tone.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; +}); \ No newline at end of file diff --git a/Tone/component/Mono.js b/Tone/component/Mono.js index 88dc80a8d..893d196a6 100644 --- a/Tone/component/Mono.js +++ b/Tone/component/Mono.js @@ -3,7 +3,7 @@ define(["Tone/core/Tone", "Tone/component/Merge"], function(Tone){ "use strict"; /** - * @class Coerces the incoming mono or stereo signal into a stereo signal + * @class Coerces the incoming mono or stereo signal into a mono signal * where both left and right channels have the same value. * * @extends {Tone} diff --git a/Tone/component/MultibandCompressor.js b/Tone/component/MultibandCompressor.js index 01e2756d4..8752c7d00 100644 --- a/Tone/component/MultibandCompressor.js +++ b/Tone/component/MultibandCompressor.js @@ -72,6 +72,8 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/component/Compr 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); @@ -96,6 +98,7 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/component/Compr 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(); diff --git a/Tone/component/MultibandSplit.js b/Tone/component/MultibandSplit.js index 553356b31..d3bb2d365 100644 --- a/Tone/component/MultibandSplit.js +++ b/Tone/component/MultibandSplit.js @@ -72,6 +72,8 @@ define(["Tone/core/Tone", "Tone/component/Filter", "Tone/signal/Signal"], functi this.lowFrequency.connect(this._lowMidFilter.frequency); this.highFrequency.connect(this.mid.frequency); this.highFrequency.connect(this.high.frequency); + + this._readOnly(["high", "mid", "low", "highFrequency", "lowFrequency"]); }; Tone.extend(Tone.MultibandSplit); @@ -92,6 +94,7 @@ define(["Tone/core/Tone", "Tone/component/Filter", "Tone/signal/Signal"], functi */ Tone.MultibandSplit.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable(["high", "mid", "low", "highFrequency", "lowFrequency"]); this.low.dispose(); this._lowMidFilter.dispose(); this.mid.dispose(); diff --git a/Tone/component/PanVol.js b/Tone/component/PanVol.js index 84956a932..180ecd3c4 100644 --- a/Tone/component/PanVol.js +++ b/Tone/component/PanVol.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/component/Panner", "Tone/core/Master"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/Panner", "Tone/component/Volume"], function(Tone){ "use strict"; @@ -7,10 +7,13 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/core/Master"], function * * @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} @@ -19,27 +22,29 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/core/Master"], function this._panner = this.input = new Tone.Panner(pan); /** - * the output node - * @type {GainNode} + * the panning control + * @type {Tone.Panner} + * @private */ - this.output = this.context.createGain(); + this.pan = this._panner.pan; /** - * The volume control in decibels. - * @type {Tone.Signal} + * the volume control + * @type {Tone.Volume} + * @private */ - this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); - this.volume.value = this.defaultArg(volume, 0); + this._volume = this.output = new Tone.Volume(volume); /** - * the panning control - * @type {Tone.Panner} - * @private + * The volume control in decibels. + * @type {Tone.Signal} */ - this.pan = this._panner.pan; + this.volume = this._volume.volume; //connections - this._panner.connect(this.output); + this._panner.connect(this._volume); + + this._readOnly(["pan", "volume"]); }; Tone.extend(Tone.PanVol); @@ -50,11 +55,13 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/core/Master"], function */ 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._volume.dispose(); + this._volume = null; this.pan = null; + this.volume = null; return this; }; diff --git a/Tone/component/Panner.js b/Tone/component/Panner.js index 0e56b5028..8db90d204 100644 --- a/Tone/component/Panner.js +++ b/Tone/component/Panner.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/component/CrossFade", "Tone/component/Merge", "Tone/component/Split"], +define(["Tone/core/Tone", "Tone/component/CrossFade", "Tone/component/Merge", + "Tone/component/Split", "Tone/signal/Signal", "Tone/signal/GainToAudio"], function(Tone){ "use strict"; @@ -19,44 +20,79 @@ function(Tone){ */ Tone.Panner = function(initialPan){ - Tone.call(this, 1, 0); - - /** - * the dry/wet knob - * @type {Tone.CrossFade} - * @private - */ - this._crossFade = new Tone.CrossFade(); - - /** - * @type {Tone.Merge} - * @private - */ - this._merger = this.output = new Tone.Merge(); - + Tone.call(this); + /** - * @type {Tone.Split} + * indicates if the panner is using the new StereoPannerNode internally + * @type {boolean} * @private */ - this._splitter = new Tone.Split(); - - /** - * the pan control - * @type {Tone.Signal} - */ - this.pan = this._crossFade.fade; + 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 {Tone.Signal} + */ + this.pan = new Tone.Signal(0, Tone.Signal.Units.Normal); + + /** + * 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 {Tone.Signal} + */ + this.pan = this._crossFade.fade; - //CONNECTIONS: - this.input.connect(this._splitter); - //left channel is dry, right channel is wet - this._splitter.connect(this._crossFade, 0, 0); - this._splitter.connect(this._crossFade, 1, 1); - //merge it back together - this._crossFade.a.connect(this._merger.left); - this._crossFade.b.connect(this._merger.right); + //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); @@ -67,13 +103,23 @@ function(Tone){ */ Tone.Panner.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._crossFade.dispose(); - this._crossFade = null; - this._splitter.dispose(); - this._splitter = null; - this._merger.dispose(); - this._merger = null; - this.pan = null; + 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; }; diff --git a/Tone/component/Recorder.js b/Tone/component/Recorder.js deleted file mode 100644 index 15e00b768..000000000 --- a/Tone/component/Recorder.js +++ /dev/null @@ -1,244 +0,0 @@ -define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ - - "use strict"; - - /** - * @deprecated - * @class Record an input into an array or AudioBuffer. - * it is limited in that the recording length needs to be known beforehand. - * Mostly used internally for testing. - * - * @constructor - * @extends {Tone} - * @param {number} channels - */ - Tone.Recorder = function(channels){ - - console.warn("Tone.Recorder is deprecated. It will be removed in next version"); - - Tone.call(this); - - /** - * the number of channels in the recording - * @type {number} - */ - this.channels = this.defaultArg(channels, 1); - - /** - * @private - * @type {ScriptProcessorNode} - */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, this.channels, 1); - this._jsNode.onaudioprocess = this._audioprocess.bind(this); - - /** - * Float32Array for each channel - * @private - * @type {Array} - */ - this._recordBuffers = new Array(this.channels); - - /** - * @type {number} - * @private - */ - this._recordStartSample = 0; - - /** - * @type {number} - * @private - */ - this._recordEndSample = 0; - - /** - * @type {number} - * @private - */ - this._recordDuration = 0; - - /** - * @type {RecordState} - */ - this.state = RecordState.STOPPED; - - /** - * @private - * @type {number} - */ - this._recordBufferOffset = 0; - - /** - * callback invoked when the recording is over - * @private - * @type {function(Float32Array)} - */ - this._callback = function(){}; - - //connect it up - this.input.connect(this._jsNode); - //pass thru audio - this.input.connect(this.output); - //so it doesn't get garbage collected - this._jsNode.noGC(); - //clear it to start - this.clear(); - }; - - Tone.extend(Tone.Recorder); - - /** - * internal method called on audio process - * - * @private - * @param {AudioProcessorEvent} event - */ - Tone.Recorder.prototype._audioprocess = function(event){ - if (this.state === RecordState.STOPPED){ - return; - } else if (this.state === RecordState.RECORDING){ - //check if it's time yet - var now = this.defaultArg(event.playbackTime, this.now()); - var processPeriodStart = this.toSamples(now); - var bufferSize = this._jsNode.bufferSize; - var processPeriodEnd = processPeriodStart + bufferSize; - var bufferOffset, len; - if (processPeriodStart > this._recordEndSample){ - this.state = RecordState.STOPPED; - this._callback(this._recordBuffers); - } else if (processPeriodStart > this._recordStartSample) { - bufferOffset = 0; - len = Math.min(this._recordEndSample - processPeriodStart, bufferSize); - this._recordChannels(event.inputBuffer, bufferOffset, len, bufferSize); - } else if (processPeriodEnd > this._recordStartSample) { - len = processPeriodEnd - this._recordStartSample; - bufferOffset = bufferSize - len; - this._recordChannels(event.inputBuffer, bufferOffset, len, bufferSize); - } - - } - }; - - /** - * record an input channel - * @param {AudioBuffer} inputBuffer - * @param {number} from - * @param {number} to - * @private - */ - Tone.Recorder.prototype._recordChannels = function(inputBuffer, from, to, bufferSize){ - var offset = this._recordBufferOffset; - var buffers = this._recordBuffers; - for (var channelNum = 0; channelNum < inputBuffer.numberOfChannels; channelNum++){ - var channel = inputBuffer.getChannelData(channelNum); - if ((from === 0) && (to === bufferSize)){ - //set the whole thing - this._recordBuffers[channelNum].set(channel, offset); - } else { - for (var i = from; i < from + to; i++){ - var zeroed = i - from; - buffers[channelNum][zeroed + offset] = channel[i]; - } - } - } - this._recordBufferOffset += to; - }; - - /** - * Record for a certain period of time - * - * will clear the internal buffer before starting - * - * @param {Tone.Time} duration - * @param {Tone.Time} wait the wait time before recording - * @param {function(Float32Array)} callback the callback to be invoked when the buffer is done recording - * @returns {Tone.Recorder} `this` - */ - Tone.Recorder.prototype.record = function(duration, startTime, callback){ - if (this.state === RecordState.STOPPED){ - this.clear(); - this._recordBufferOffset = 0; - startTime = this.defaultArg(startTime, 0); - this._recordDuration = this.toSamples(duration); - this._recordStartSample = this.toSamples("+"+startTime); - this._recordEndSample = this._recordStartSample + this._recordDuration; - for (var i = 0; i < this.channels; i++){ - this._recordBuffers[i] = new Float32Array(this._recordDuration); - } - this.state = RecordState.RECORDING; - this._callback = this.defaultArg(callback, function(){}); - } - return this; - }; - - /** - * clears the recording buffer - * @returns {Tone.PanVol} `this` - */ - Tone.Recorder.prototype.clear = function(){ - for (var i = 0; i < this.channels; i++){ - this._recordBuffers[i] = null; - } - this._recordBufferOffset = 0; - return this; - }; - - - /** - * true if there is nothing in the buffers - * @return {boolean} - */ - Tone.Recorder.prototype.isEmpty = function(){ - return this._recordBuffers[0] === null; - }; - - /** - * @return {Array} - */ - Tone.Recorder.prototype.getFloat32Array = function(){ - if (this.isEmpty()){ - return null; - } else { - return this._recordBuffers; - } - }; - - /** - * @return {AudioBuffer} - */ - Tone.Recorder.prototype.getAudioBuffer = function(){ - if (this.isEmpty()){ - return null; - } else { - var audioBuffer = this.context.createBuffer(this.channels, this._recordBuffers[0].length, this.context.sampleRate); - for (var channelNum = 0; channelNum < audioBuffer.numberOfChannels; channelNum++){ - var channel = audioBuffer.getChannelData(channelNum); - channel.set(this._recordBuffers[channelNum]); - } - return audioBuffer; - } - }; - - /** - * clean up - * @returns {Tone.PanVol} `this` - */ - Tone.Recorder.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._jsNode.disconnect(); - this._jsNode.onaudioprocess = undefined; - this._jsNode = null; - this._recordBuffers = null; - return this; - }; - - /** - * @enum {string} - */ - var RecordState = { - STOPPED : "stopped", - SCHEDULED : "scheduled", - RECORDING : "recording" - }; - - return Tone.Recorder; -}); \ No newline at end of file diff --git a/Tone/component/Split.js b/Tone/component/Split.js index 7f17bd294..a4c976ee0 100644 --- a/Tone/component/Split.js +++ b/Tone/component/Split.js @@ -13,13 +13,13 @@ define(["Tone/core/Tone"], function(Tone){ */ Tone.Split = function(){ - Tone.call(this, 1, 2); + Tone.call(this, 0, 2); /** * @type {ChannelSplitterNode} * @private */ - this._splitter = this.context.createChannelSplitter(2); + this._splitter = this.input = this.context.createChannelSplitter(2); /** * left channel output @@ -36,7 +36,6 @@ define(["Tone/core/Tone"], function(Tone){ this.right = this.output[1] = this.context.createGain(); //connections - this.input.connect(this._splitter); this._splitter.connect(this.left, 0, 0); this._splitter.connect(this.right, 1, 0); }; diff --git a/Tone/component/Volume.js b/Tone/component/Volume.js new file mode 100644 index 000000000..ee917c1ce --- /dev/null +++ b/Tone/component/Volume.js @@ -0,0 +1,49 @@ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Master"], function(Tone){ + + "use strict"; + + /** + * @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(pan, 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.Signal.Units.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; +}); \ No newline at end of file diff --git a/Tone/core/Buffer.js b/Tone/core/Buffer.js index 11e30d118..94a7fbfb4 100644 --- a/Tone/core/Buffer.js +++ b/Tone/core/Buffer.js @@ -12,6 +12,7 @@ define(["Tone/core/Tone"], function(Tone){ * and `onerror`. * * @constructor + * @extends {Tone} * @param {AudioBuffer|string} url the url to load, or the audio buffer to set */ Tone.Buffer = function(){ @@ -25,6 +26,13 @@ define(["Tone/core/Tone"], function(Tone){ */ this._buffer = null; + /** + * indicates if the buffer should be reversed or not + * @type {boolean} + * @private + */ + this._reversed = options.reverse; + /** * the url of the buffer. `undefined` if it was * constructed with a buffer @@ -67,6 +75,7 @@ define(["Tone/core/Tone"], function(Tone){ Tone.Buffer.defaults = { "url" : undefined, "onload" : function(){}, + "reverse" : false }; /** @@ -113,7 +122,7 @@ define(["Tone/core/Tone"], function(Tone){ Tone.prototype.dispose.call(this); Tone.Buffer._removeFromQueue(this); this._buffer = null; - this.onload = null; + this.onload = Tone.Buffer.defaults.onload; return this; }; @@ -134,6 +143,38 @@ define(["Tone/core/Tone"], function(Tone){ }, }); + /** + * 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; + }; + + /** + * if the buffer is reversed or not + * @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 /////////////////////////////////////////////////////////////////////////// @@ -223,6 +264,9 @@ define(["Tone/core/Tone"], function(Tone){ 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(); diff --git a/Tone/core/Bus.js b/Tone/core/Bus.js index 0cf208f6e..d2e06cd4a 100644 --- a/Tone/core/Bus.js +++ b/Tone/core/Bus.js @@ -22,7 +22,7 @@ define(["Tone/core/Tone"], function(Tone){ * defined in "Tone/core/Bus" * * @param {string} channelName - * @param {number} amount + * @param {number} amount the amount of the source to send to the bus. in Decibels. * @return {GainNode} */ Tone.prototype.send = function(channelName, amount){ @@ -30,7 +30,7 @@ define(["Tone/core/Tone"], function(Tone){ Buses[channelName] = this.context.createGain(); } var sendKnob = this.context.createGain(); - sendKnob.gain.value = this.defaultArg(amount, 1); + sendKnob.gain.value = this.dbToGain(this.defaultArg(amount, 1)); this.output.chain(sendKnob, Buses[channelName]); return sendKnob; }; diff --git a/Tone/core/Clock.js b/Tone/core/Clock.js index 19bd262d4..b0ca9251a 100644 --- a/Tone/core/Clock.js +++ b/Tone/core/Clock.js @@ -6,7 +6,6 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * @class a sample accurate clock built on an oscillator. * Invokes the tick method at the set rate * - * @private * @constructor * @extends {Tone} * @param {Tone.Frequency} frequency the rate of the callback @@ -33,7 +32,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * the rate control signal * @type {Tone.Signal} */ - this.frequency = new Tone.Signal(frequency); + this.frequency = new Tone.Signal(frequency, Tone.Signal.Units.Frequency); /** * whether the tick is on the up or down @@ -49,6 +48,16 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ */ 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 = function(){}; + //setup this._jsNode.noGC(); }; @@ -57,7 +66,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ /** * start the clock - * @param {Tone.Time} time the time when the clock should start + * @param {Tone.Time} [time=now] the time when the clock should start * @returns {Tone.Clock} `this` */ Tone.Clock.prototype.start = function(time){ @@ -76,21 +85,20 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ /** * stop the clock - * @param {Tone.Time} time the time when the clock should stop - * @param {function} onend called when the oscilator stops + * @param {Tone.Time} [time=now] The time when the clock should stop. * @returns {Tone.Clock} `this` */ - Tone.Clock.prototype.stop = function(time, onend){ + 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; - //set a timeout for when it stops if (time){ - setTimeout(onend, (stopTime - now) * 1000); + //set a timeout for when it stops + setTimeout(this.onended.bind(this), (stopTime - now) * 1000); } else { - onend(); + this.onended(); } } return this; @@ -142,6 +150,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ this._jsNode.onaudioprocess = function(){}; this._jsNode = null; this.tick = null; + this.onended = function(){}; return this; }; diff --git a/Tone/core/Master.js b/Tone/core/Master.js index 1981114d8..050a35628 100644 --- a/Tone/core/Master.js +++ b/Tone/core/Master.js @@ -25,6 +25,13 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ */ this._unmutedVolume = 1; + /** + * if the master is muted + * @type {boolean} + * @private + */ + this._muted = false; + /** * the volume of the output in decibels * @type {Tone.Signal} @@ -38,13 +45,16 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ Tone.extend(Tone.Master); /** - * Mutethe output + * Mute the output * @returns {Tone.Master} `this` */ Tone.Master.prototype.mute = function(){ - this._unmutedVolume = this.volume.value; - //maybe it should ramp here? - this.volume.value = -Infinity; + if (!this._muted){ + this._muted = true; + this._unmutedVolume = this.volume.value; + //maybe it should ramp here? + this.volume.value = -Infinity; + } return this; }; @@ -53,8 +63,11 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * the output was muted. * @returns {Tone.Master} `this` */ - Tone.Master.prototype.mute = function(){ - this.volume.value = this._unmutedVolume; + Tone.Master.prototype.unmute = function(){ + if (this._muted){ + this._muted = false; + this.volume.value = this._unmutedVolume; + } return this; }; diff --git a/Tone/core/Note.js b/Tone/core/Note.js index 2a09a385b..c99119a17 100644 --- a/Tone/core/Note.js +++ b/Tone/core/Note.js @@ -226,7 +226,8 @@ define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){ * @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.isNotation + * @lends Tone.prototype.isNote + * @function */ Tone.prototype.isNote = ( function(){ var noteFormat = new RegExp(/[a-g]{1}([b#]{1}|[b#]{0})[0-9]+$/i); diff --git a/Tone/core/Tone.js b/Tone/core/Tone.js index bf9659d67..17bcd49b2 100644 --- a/Tone/core/Tone.js +++ b/Tone/core/Tone.js @@ -128,6 +128,10 @@ define(function(){ * 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. + * @param {Object|string} params + * @param {number=} value + * @param {Tone.Time=} rampTime + * @returns {Tone} `this` * @example * //set values using an object * filter.set({ @@ -140,10 +144,6 @@ define(function(){ * oscillator.set({ * "frequency" : 220 * }, 3); - * @param {Object|string} params - * @param {number=} value - * @param {Tone.Time=} rampTime - * @returns {Tone} `this` */ Tone.prototype.set = function(params, value, rampTime){ if (typeof params === "object"){ @@ -186,18 +186,30 @@ define(function(){ * osc.get(); * //returns {"type" : "sine", "frequency" : 440, ...etc} * osc.get("type"); //returns { "type" : "sine"} - * @param {Array=} params the parameters to get, otherwise will return - * all available.r + * @param {Array=|string|Object} params the parameters to get, otherwise will return + * all available. */ Tone.prototype.get = function(params){ if (isUndef(params)){ params = this._collectDefaults(this.constructor); + } else if (typeof params === "string"){ + var obj = {}; + obj[params] = 0; + params = obj; + } else if (Array.isArray(params)){ + //use the objects as keys + var keyObj = {}; + for (var i = 0; i < params.length; i++){ + keyObj[params[i]] = 0; + } + params = keyObj; } var ret = {}; - for (var i = 0; i < params.length; i++){ - var attr = params[i]; + for (var attr in params){ var param = this[attr]; - if (param instanceof Tone.Signal){ + if (typeof params[attr] === "object"){ + ret[attr] = param.get(params[attr]); + } else if (param instanceof Tone.Signal){ ret[attr] = param.value; } else if (param instanceof AudioParam){ ret[attr] = param.value; @@ -214,15 +226,18 @@ define(function(){ * 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 + * @return {Object} all of the attributes which belong to the class */ Tone.prototype._collectDefaults = function(constr){ - var ret = []; + var ret = {}; if (!isUndef(constr.defaults)){ - ret = Object.keys(constr.defaults); + ret = constr.defaults; } if (!isUndef(constr._super)){ - ret = ret.concat(this._collectDefaults(constr._super)); + var superDefs = this._collectDefaults(constr._super); + for (var attr in superDefs){ + ret[attr] = superDefs[attr]; + } } return ret; }; @@ -239,6 +254,20 @@ define(function(){ 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 /////////////////////////////////////////////////////////////////////////// @@ -503,33 +532,38 @@ define(function(){ Tone.prototype.isFunction = isFunction; /** - * interpolate the input value (0-1) to be between outputMin and outputMax - * @param {number} input - * @param {number} outputMin - * @param {number} outputMax - * @return {number} + * Make the property not writable. Internal use only. + * @private + * @param {string} property the property to make not writable */ - Tone.prototype.interpolate = function(input, outputMin, outputMax){ - return input*(outputMax - outputMin) + outputMin; + 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, + }); + } }; /** - * normalize the input to 0-1 from between inputMin to inputMax - * @param {number} input - * @param {number} inputMin - * @param {number} inputMax - * @return {number} + * Make an attribute writeable. Interal use only. + * @private + * @param {string} property the property to make writable */ - Tone.prototype.normalize = function(input, inputMin, inputMax){ - //make sure that min < max - if (inputMin > inputMax){ - var tmp = inputMax; - inputMax = inputMin; - inputMin = tmp; - } else if (inputMin == inputMax){ - return 0; + 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, + }); } - return (input - inputMin) / (inputMax - inputMin); }; /////////////////////////////////////////////////////////////////////////// @@ -764,7 +798,7 @@ define(function(){ _silentNode.connect(audioContext.destination); }); - console.log("%c * Tone.js r4 * ", "background: #000; color: #fff"); + console.log("%c * Tone.js r5-dev * ", "background: #000; color: #fff"); return Tone; }); diff --git a/Tone/core/Transport.js b/Tone/core/Transport.js index e63fa34bc..baaf700eb 100644 --- a/Tone/core/Transport.js +++ b/Tone/core/Transport.js @@ -50,6 +50,7 @@ function(Tone){ * @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. @@ -566,7 +567,7 @@ function(Tone){ Tone.Transport.prototype.stop = function(time){ if (this.state === TransportState.STARTED || this.state === TransportState.PAUSED){ var stopTime = this.toSeconds(time); - this._clock.stop(stopTime, this._onended.bind(this)); + 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; diff --git a/Tone/effect/AutoFilter.js b/Tone/effect/AutoFilter.js new file mode 100644 index 000000000..32a3b6abb --- /dev/null +++ b/Tone/effect/AutoFilter.js @@ -0,0 +1,177 @@ +define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/component/Filter"], function(Tone){ + + "use strict"; + + /** + * @class AutoFilter is a Tone.Panner with an LFO connected to the pan amount + * + * @constructor + * @extends {Tone.Effect} + * @param {Tone.Time} [frequency=1] (optional) rate in HZ of the filter + * @param {number} [depth=0.5] The depth of the effect + * @example + * var autoPanner = new Tone.AutoFilter("4n"); + */ + Tone.AutoFilter = function(){ + + var options = this.optionsObject(arguments, ["frequency"], Tone.AutoFilter.defaults); + Tone.Effect.call(this, options); + + /** + * the lfo which drives the panning + * @type {Tone.LFO} + * @private + */ + this._lfo = new Tone.LFO(options.frequency, options.min, options.max); + + /** + * The amount of panning between left and right. + * 0 = always center. 1 = full range between left and right. + * @type {Tone.Signal} + */ + this.depth = this._lfo.amplitude; + + /** + * How fast the filter modulates between min and max. + * @type {Tone.Signal} + */ + this.frequency = this._lfo.frequency; + + /** + * the filter node + * @type {Tone.Filter} + * @private + */ + 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 {Tone.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 {Tone.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 {Tone.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 miniumum output of the AutoFilter. + * @memberOf Tone.AutoFilter# + * @type {number} + * @name min + */ + Object.defineProperty(Tone.AutoFilter.prototype, "min", { + get : function(){ + return this._lfo.min; + }, + set : function(min){ + this._lfo.min = min; + } + }); + + /** + * The maximum output of the AutoFilter. + * @memberOf Tone.AutoFilter# + * @type {number} + * @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; +}); diff --git a/Tone/effect/AutoPanner.js b/Tone/effect/AutoPanner.js index 86e7a2df3..72085d971 100644 --- a/Tone/effect/AutoPanner.js +++ b/Tone/effect/AutoPanner.js @@ -28,7 +28,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp * 0 = always center. 1 = full range between left and right. * @type {Tone.Signal} */ - this.amount = this._lfo.amplitude; + this.depth = this._lfo.amplitude; /** * the panner node which does the panning @@ -47,6 +47,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp this.connectEffect(this._panner); this._lfo.connect(this._panner.pan); this.type = options.type; + this._readOnly(["depth", "frequency"]); }; //extend Effect @@ -60,7 +61,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp Tone.AutoPanner.defaults = { "frequency" : 1, "type" : "sine", - "amount" : 1 + "depth" : 1 }; /** @@ -85,10 +86,12 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp /** * Sync the panner to the transport. - * @returns {Tone.AutoPanner} `this` + * @param {Tone.Time} [delay=0] Delay time before starting the effect after the + * Transport has started. + * @returns {Tone.AutoFilter} `this` */ - Tone.AutoPanner.prototype.sync = function(){ - this._lfo.sync(); + Tone.AutoPanner.prototype.sync = function(delay){ + this._lfo.sync(delay); return this; }; @@ -126,8 +129,9 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp this._lfo = null; this._panner.dispose(); this._panner = null; + this._writable(["depth", "frequency"]); this.frequency = null; - this.amount = null; + this.depth = null; return this; }; diff --git a/Tone/effect/AutoWah.js b/Tone/effect/AutoWah.js index 48f8c0103..5ed2e3507 100644 --- a/Tone/effect/AutoWah.js +++ b/Tone/effect/AutoWah.js @@ -95,6 +95,8 @@ function(Tone){ //set the initial value this._setSweepRange(); this.sensitivity = options.sensitivity; + + this._readOnly(["gain", "Q"]); }; Tone.extend(Tone.AutoWah, Tone.Effect); @@ -188,6 +190,7 @@ function(Tone){ this._peaking = null; this._inputBoost.disconnect(); this._inputBoost = null; + this._writable(["gain", "Q"]); this.gain = null; this.Q = null; return this; diff --git a/Tone/effect/Chebyshev.js b/Tone/effect/Chebyshev.js index ae8154f50..d35aae0a6 100644 --- a/Tone/effect/Chebyshev.js +++ b/Tone/effect/Chebyshev.js @@ -113,14 +113,6 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/WaveShaper"], funct } }); - - /** - * @return {string} the current oversampling - */ - Tone.Chebyshev.prototype.getOversample = function() { - return this._shaper.getOversample(); - }; - /** * clean up * @returns {Tone.Chebyshev} `this` diff --git a/Tone/effect/Chorus.js b/Tone/effect/Chorus.js index 1987e40d7..0409a79c9 100644 --- a/Tone/effect/Chorus.js +++ b/Tone/effect/Chorus.js @@ -86,6 +86,8 @@ function(Tone){ this.depth = this._depth; this.frequency.value = options.frequency; this.type = options.type; + + this._readOnly(["frequency"]); }; Tone.extend(Tone.Chorus, Tone.StereoXFeedbackEffect); @@ -115,9 +117,9 @@ function(Tone){ set : function(depth){ this._depth = depth; var deviation = this._delayTime * depth; - this._lfoL.min = this._delayTime - deviation; + this._lfoL.min = Math.max(this._delayTime - deviation, 0); this._lfoL.max = this._delayTime + deviation; - this._lfoR.min = this._delayTime - deviation; + this._lfoR.min = Math.max(this._delayTime - deviation, 0); this._lfoR.max = this._delayTime + deviation; } }); @@ -168,6 +170,7 @@ function(Tone){ this._delayNodeL = null; this._delayNodeR.disconnect(); this._delayNodeR = null; + this._writable("frequency"); this.frequency = null; return this; }; diff --git a/Tone/effect/Convolver.js b/Tone/effect/Convolver.js index 499793ea8..17793f8d9 100644 --- a/Tone/effect/Convolver.js +++ b/Tone/effect/Convolver.js @@ -11,7 +11,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/effect/Effect"], function(To * @example * var convolver = new Tone.Convolver("./path/to/ir.wav"); */ - Tone.Convolver = function(url){ + Tone.Convolver = function(){ var options = this.optionsObject(arguments, ["url"], Tone.Convolver.defaults); Tone.Effect.call(this, options); @@ -28,8 +28,9 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/effect/Effect"], function(To * @type {Tone.Buffer} * @private */ - this._buffer = new Tone.Buffer(url, function(buffer){ + this._buffer = new Tone.Buffer(options.url, function(buffer){ this.buffer = buffer; + options.onload(); }.bind(this)); this.connectEffect(this._convolver); @@ -37,6 +38,16 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/effect/Effect"], function(To Tone.extend(Tone.Convolver, Tone.Effect); + /** + * @static + * @const + * @type {Object} + */ + Tone.Convolver.defaults = { + "url" : "", + "onload" : function(){} + }; + /** * The convolver's buffer * @memberOf Tone.Convolver# diff --git a/Tone/effect/Effect.js b/Tone/effect/Effect.js index 9d0b9906e..06f3062d8 100644 --- a/Tone/effect/Effect.js +++ b/Tone/effect/Effect.js @@ -54,6 +54,7 @@ define(["Tone/core/Tone", "Tone/component/CrossFade"], function(Tone){ this.input.connect(this.effectSend); this.effectReturn.connect(this._dryWet.b); this._dryWet.connect(this.output); + this._readOnly(["wet"]); }; Tone.extend(Tone.Effect); @@ -98,6 +99,7 @@ define(["Tone/core/Tone", "Tone/component/CrossFade"], function(Tone){ this.effectSend = null; this.effectReturn.disconnect(); this.effectReturn = null; + this._writable(["wet"]); this.wet = null; return this; }; diff --git a/Tone/effect/FeedbackDelay.js b/Tone/effect/FeedbackDelay.js index 6ffe31b0a..a5b77c12f 100644 --- a/Tone/effect/FeedbackDelay.js +++ b/Tone/effect/FeedbackDelay.js @@ -34,6 +34,7 @@ define(["Tone/core/Tone", "Tone/effect/FeedbackEffect", "Tone/signal/Signal"], f // connect it up this.connectEffect(this._delayNode); this.delayTime.connect(this._delayNode.delayTime); + this._readOnly(["delayTime"]); }; Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); @@ -57,6 +58,7 @@ define(["Tone/core/Tone", "Tone/effect/FeedbackEffect", "Tone/signal/Signal"], f this.delayTime.dispose(); this._delayNode.disconnect(); this._delayNode = null; + this._writable(["delayTime"]); this.delayTime = null; return this; }; diff --git a/Tone/effect/FeedbackEffect.js b/Tone/effect/FeedbackEffect.js index e9a4ef50e..5bcf5b852 100644 --- a/Tone/effect/FeedbackEffect.js +++ b/Tone/effect/FeedbackEffect.js @@ -32,6 +32,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/Signal", "Tone/sign //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); @@ -50,6 +51,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/Signal", "Tone/sign */ Tone.FeedbackEffect.prototype.dispose = function(){ Tone.Effect.prototype.dispose.call(this); + this._writable(["feedback"]); this.feedback.dispose(); this.feedback = null; this._feedbackGain.disconnect(); diff --git a/Tone/effect/Freeverb.js b/Tone/effect/Freeverb.js index 87a1a2869..c890a3cac 100644 --- a/Tone/effect/Freeverb.js +++ b/Tone/effect/Freeverb.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/component/LowpassCombFilter", "Tone/effect/StereoEffect", "Tone/signal/Signal", "Tone/component/Split", "Tone/component/Merge"], +define(["Tone/core/Tone", "Tone/component/LowpassCombFilter", "Tone/effect/StereoEffect", + "Tone/signal/Signal", "Tone/component/Split", "Tone/component/Merge", "Tone/signal/ScaleExp"], function(Tone){ "use strict"; @@ -26,10 +27,10 @@ function(Tone){ * @constructor * @param {number} [roomSize=0.7] correlated to the decay time. * value between (0,1) - * @param {number} [dampening=0.5] filtering which is applied to the reverb. - * 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, 0.2); + * var freeverb = new Tone.Freeverb(0.4, 2000); */ Tone.Freeverb = function(){ @@ -37,24 +38,16 @@ function(Tone){ Tone.StereoEffect.call(this, options); /** - * the roomSize value between (0,1) + * The roomSize value between (0,1) * @type {Tone.Signal} */ - this.roomSize = new Tone.Signal(options.roomSize); + this.roomSize = new Tone.Signal(options.roomSize, Tone.Signal.Units.Normal); /** - * the amount of dampening - * value between [0,1] + * The amount of dampening as a value in Hertz. * @type {Tone.Signal} */ - this.dampening = new Tone.Signal(options.dampening); - - /** - * scale the dampening - * @type {Tone.ScaleExp} - * @private - */ - this._dampeningScale = new Tone.ScaleExp(100, 8000, 0.5); + this.dampening = new Tone.Signal(options.dampening, Tone.Signal.Units.Frequency); /** * the comb filters @@ -102,7 +95,7 @@ function(Tone){ this.effectSendR.chain(lfpf, this._allpassFiltersR[0]); } this.roomSize.connect(lfpf.resonance); - this._dampeningScale.connect(lfpf.dampening); + this.dampening.connect(lfpf.dampening); this._combFilters.push(lfpf); } @@ -111,7 +104,7 @@ function(Tone){ 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.dampening.connect(this._dampeningScale); + this._readOnly(["roomSize", "dampening"]); }; Tone.extend(Tone.Freeverb, Tone.StereoEffect); @@ -122,7 +115,7 @@ function(Tone){ */ Tone.Freeverb.defaults = { "roomSize" : 0.7, - "dampening" : 0.5 + "dampening" : 3000 }; /** @@ -146,12 +139,11 @@ function(Tone){ this._combFilters[cf] = null; } this._combFilters = null; + this._writable(["roomSize", "dampening"]); this.roomSize.dispose(); - this.dampening.dispose(); - this._dampeningScale.dispose(); this.roomSize = null; + this.dampening.dispose(); this.dampening = null; - this._dampeningScale = null; return this; }; diff --git a/Tone/effect/JCReverb.js b/Tone/effect/JCReverb.js index 9e5e55f3a..b23862186 100644 --- a/Tone/effect/JCReverb.js +++ b/Tone/effect/JCReverb.js @@ -97,6 +97,7 @@ function(Tone){ 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); @@ -127,6 +128,7 @@ function(Tone){ this._feedbackCombFilters[fbcf] = null; } this._feedbackCombFilters = null; + this._writable(["roomSize"]); this.roomSize.dispose(); this.roomSize = null; this._scaleRoomSize.dispose(); diff --git a/Tone/effect/MidSideEffect.js b/Tone/effect/MidSideEffect.js index 5e63e2327..c1b0e13da 100644 --- a/Tone/effect/MidSideEffect.js +++ b/Tone/effect/MidSideEffect.js @@ -1,109 +1,87 @@ -define(["Tone/core/Tone", "Tone/effect/StereoEffect"], function(Tone){ +define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/MidSideSplit", "Tone/component/MidSideMerge"], + function(Tone){ "use strict"; /** - * @class Applies a Mid/Side seperation and recombination - * http://musicdsp.org/showArchiveComment.php?ArchiveID=173 - * http://www.kvraudio.com/forum/viewtopic.php?t=212587 - * M = (L+R)/sqrt(2); // obtain mid-signal from left and right - * S = (L-R)/sqrt(2); // obtain side-signal from left and righ - * // amplify mid and side signal seperately: - * M/S send/return - * L = (M+S)/sqrt(2); // obtain left signal from mid and side - * R = (M-S)/sqrt(2); // obtain right signal from mid and side + * @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.
+ * Applies a Mid/Side seperation and recombination.
+ * http://musicdsp.org/showArchiveComment.php?ArchiveID=173
+ * http://www.kvraudio.com/forum/viewtopic.php?t=212587
+ * M = (L+R)/sqrt(2); // obtain mid-signal from left and right
+ * S = (L-R)/sqrt(2); // obtain side-signal from left and righ
+ * // amplify mid and side signal seperately:
+ * M/S send/return
+ * L = (M+S)/sqrt(2); // obtain left signal from mid and side
+ * R = (M-S)/sqrt(2); // obtain right signal from mid and side
* - * @extends {Tone.StereoEffect} + * @extends {Tone.Effect} * @constructor */ Tone.MidSideEffect = function(){ - Tone.StereoEffect.call(this); + Tone.Effect.call(this); /** - * a constant signal equal to 1 / sqrt(2) - * @type {Tone.Signal} + * The mid/side split + * @type {Tone.MidSideSplit} * @private */ - this._sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + this._midSideSplit = new Tone.MidSideSplit(); /** - * the mid send. - * connect to mid processing - * @type {Tone.Expr} - */ - this.midSend = new Tone.Expr("($0 + $1) * $2"); - - /** - * the side send. - * connect to side processing - * @type {Tone.Expr} + * The mid/side merge + * @type {Tone.MidSideMerge} + * @private */ - this.sideSend = new Tone.Expr("($0 - $1) * $2"); + this._midSideMerge = new Tone.MidSideMerge(); /** - * recombine the mid/side into Left + * The mid send. Connect to mid processing * @type {Tone.Expr} - * @private */ - this._left = new Tone.Expr("($0 + $1) * $2"); + this.midSend = this._midSideSplit.mid; /** - * recombine the mid/side into Right + * The side send. Connect to side processing * @type {Tone.Expr} - * @private */ - this._right = new Tone.Expr("($0 - $1) * $2"); + this.sideSend = this._midSideSplit.side; /** - * the mid return connection + * The mid return connection * @type {GainNode} */ - this.midReturn = this.context.createGain(); + this.midReturn = this._midSideMerge.mid; /** - * the side return connection + * The side return connection * @type {GainNode} */ - this.sideReturn = this.context.createGain(); + this.sideReturn = this._midSideMerge.side; - //connections - this.effectSendL.connect(this.midSend, 0, 0); - this.effectSendR.connect(this.midSend, 0, 1); - this.effectSendL.connect(this.sideSend, 0, 0); - this.effectSendR.connect(this.sideSend, 0, 1); - this._left.connect(this.effectReturnL); - this._right.connect(this.effectReturnR); - this.midReturn.connect(this._left, 0, 0); - this.sideReturn.connect(this._left, 0, 1); - this.midReturn.connect(this._right, 0, 0); - this.sideReturn.connect(this._right, 0, 1); - this._sqrtTwo.connect(this.midSend, 0, 2); - this._sqrtTwo.connect(this.sideSend, 0, 2); - this._sqrtTwo.connect(this._left, 0, 2); - this._sqrtTwo.connect(this._right, 0, 2); + //the connections + this.effectSend.connect(this._midSideSplit); + this._midSideMerge.connect(this.effectReturn); }; - Tone.extend(Tone.MidSideEffect, Tone.StereoEffect); + Tone.extend(Tone.MidSideEffect, Tone.Effect); /** * clean up * @returns {Tone.MidSideEffect} `this` */ Tone.MidSideEffect.prototype.dispose = function(){ - Tone.StereoEffect.prototype.dispose.call(this); - this._sqrtTwo.dispose(); - this._sqrtTwo = null; - this.midSend.dispose(); + Tone.Effect.prototype.dispose.call(this); + this._midSideSplit.dispose(); + this._midSideSplit = null; + this._midSideMerge.dispose(); + this._midSideMerge = null; this.midSend = null; - this.sideSend.dispose(); this.sideSend = null; - this._left.dispose(); - this._left = null; - this._right.dispose(); - this._right = null; - this.midReturn.disconnect(); this.midReturn = null; - this.sideReturn.disconnect(); this.sideReturn = null; return this; }; diff --git a/Tone/effect/Phaser.js b/Tone/effect/Phaser.js index f48980460..f0305d5bf 100644 --- a/Tone/effect/Phaser.js +++ b/Tone/effect/Phaser.js @@ -85,6 +85,7 @@ function(Tone){ //start the lfo this._lfoL.start(); this._lfoR.start(); + this._readOnly(["frequency"]); }; Tone.extend(Tone.Phaser, Tone.StereoEffect); @@ -142,7 +143,7 @@ function(Tone){ /** * The the base frequency of the filters. * @memberOf Tone.Phaser# - * @type {string} + * @type {number} * @name baseFrequency */ Object.defineProperty(Tone.Phaser.prototype, "baseFrequency", { @@ -177,6 +178,7 @@ function(Tone){ this._filtersR[j] = null; } this._filtersR = null; + this._writable(["frequency"]); this.frequency = null; return this; }; diff --git a/Tone/effect/PingPongDelay.js b/Tone/effect/PingPongDelay.js index d5358be41..dc5c36759 100644 --- a/Tone/effect/PingPongDelay.js +++ b/Tone/effect/PingPongDelay.js @@ -54,6 +54,7 @@ function(Tone){ //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); @@ -79,6 +80,7 @@ function(Tone){ this._rightDelay = null; this._rightPreDelay.disconnect(); this._rightPreDelay = null; + this._writable(["delayTime"]); this.delayTime.dispose(); this.delayTime = null; return this; diff --git a/Tone/effect/StereoEffect.js b/Tone/effect/StereoEffect.js index 73e1dacb5..2950a81d4 100644 --- a/Tone/effect/StereoEffect.js +++ b/Tone/effect/StereoEffect.js @@ -76,6 +76,7 @@ function(Tone){ 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); @@ -96,6 +97,7 @@ function(Tone){ this.effectSendR = null; this.effectReturnL = null; this.effectReturnR = null; + this._writable(["wet"]); this.wet = null; return this; }; diff --git a/Tone/effect/StereoFeedbackEffect.js b/Tone/effect/StereoFeedbackEffect.js index 27a3709e0..f08ded53d 100644 --- a/Tone/effect/StereoFeedbackEffect.js +++ b/Tone/effect/StereoFeedbackEffect.js @@ -38,6 +38,7 @@ function(Tone){ 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); @@ -48,6 +49,7 @@ function(Tone){ */ Tone.StereoFeedbackEffect.prototype.dispose = function(){ Tone.StereoEffect.prototype.dispose.call(this); + this._writable(["feedback"]); this.feedback.dispose(); this.feedback = null; this._feedbackL.disconnect(); diff --git a/Tone/effect/StereoWidener.js b/Tone/effect/StereoWidener.js index 89c1e7cb0..ab1c7f4c9 100644 --- a/Tone/effect/StereoWidener.js +++ b/Tone/effect/StereoWidener.js @@ -57,6 +57,7 @@ define(["Tone/core/Tone", "Tone/effect/MidSideEffect", "Tone/signal/Signal", //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); @@ -76,6 +77,7 @@ define(["Tone/core/Tone", "Tone/effect/MidSideEffect", "Tone/signal/Signal", */ Tone.StereoWidener.prototype.dispose = function(){ Tone.MidSideEffect.prototype.dispose.call(this); + this._writable(["width"]); this.width.dispose(); this.width = null; this._midMult.dispose(); diff --git a/Tone/effect/StereoXFeedbackEffect.js b/Tone/effect/StereoXFeedbackEffect.js index f5edb3716..89e110eba 100644 --- a/Tone/effect/StereoXFeedbackEffect.js +++ b/Tone/effect/StereoXFeedbackEffect.js @@ -39,6 +39,7 @@ function(Tone){ 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); @@ -49,6 +50,7 @@ function(Tone){ */ Tone.StereoXFeedbackEffect.prototype.dispose = function(){ Tone.StereoEffect.prototype.dispose.call(this); + this._writable(["feedback"]); this.feedback.dispose(); this.feedback = null; this._feedbackLR.disconnect(); diff --git a/Tone/effect/Tremolo.js b/Tone/effect/Tremolo.js new file mode 100644 index 000000000..2af3be7fb --- /dev/null +++ b/Tone/effect/Tremolo.js @@ -0,0 +1,138 @@ +define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function(Tone){ + + "use strict"; + + /** + * @class A tremolo is a modulation in the amplitude of the incoming signal using an LFO. + * The type, frequency, and depth of the LFO is controllable. + * + * @extends {Tone.Effect} + * @constructor + * @param {Tone.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(options.frequency, 1, 0); + + /** + * Where the gain is multiplied + * @type {GainNode} + * @private + */ + this._amplitude = this.context.createGain(); + + /** + * The frequency of the tremolo. + * @type {Tone.Signal} + */ + this.frequency = this._lfo.frequency; + + /** + * The depth of the effect. + * @type {Tone.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 {Tone.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 {Tone.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 {Tone.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; +}); \ No newline at end of file diff --git a/Tone/instrument/AMSynth.js b/Tone/instrument/AMSynth.js index 20954f5ef..dfcff86c4 100644 --- a/Tone/instrument/AMSynth.js +++ b/Tone/instrument/AMSynth.js @@ -67,6 +67,7 @@ function(Tone){ 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"]); }; Tone.extend(Tone.AMSynth, Tone.Monophonic); @@ -79,7 +80,6 @@ function(Tone){ "harmonicity" : 3, "carrier" : { "volume" : -10, - "portamento" : 0, "oscillator" : { "type" : "sine" }, @@ -96,11 +96,15 @@ function(Tone){ "release" : 0.5, "min" : 20000, "max" : 20000 - } + }, + "filter" : { + "Q" : 6, + "type" : "lowpass", + "rolloff" : -24 + }, }, "modulator" : { "volume" : -10, - "portamento" : 0, "oscillator" : { "type" : "square" }, @@ -117,7 +121,12 @@ function(Tone){ "release" : 0.5, "min" : 20, "max" : 1500 - } + }, + "filter" : { + "Q" : 6, + "type" : "lowpass", + "rolloff" : -24 + }, } }; @@ -172,6 +181,7 @@ function(Tone){ */ Tone.AMSynth.prototype.dispose = function(){ Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); this.carrier.dispose(); this.carrier = null; this.modulator.dispose(); diff --git a/Tone/instrument/DrumSynth.js b/Tone/instrument/DrumSynth.js new file mode 100644 index 000000000..6843098cb --- /dev/null +++ b/Tone/instrument/DrumSynth.js @@ -0,0 +1,118 @@ +define(["Tone/core/Tone", "Tone/source/Oscillator", "Tone/instrument/Instrument", + "Tone/component/AmplitudeEnvelope"], +function(Tone){ + + "use strict"; + + /** + * @class 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(); + */ + 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 {number} + */ + this.octaves = options.octaves; + + /** + * The amount of time of the pitch decay. + * @type {Tone.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 {Tone.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 {Tone.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; +}); \ No newline at end of file diff --git a/Tone/instrument/DuoSynth.js b/Tone/instrument/DuoSynth.js index c6d37f5f0..5befce0de 100644 --- a/Tone/instrument/DuoSynth.js +++ b/Tone/instrument/DuoSynth.js @@ -89,6 +89,7 @@ function(Tone){ 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); @@ -191,6 +192,7 @@ function(Tone){ */ 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(); diff --git a/Tone/instrument/FMSynth.js b/Tone/instrument/FMSynth.js index aa0740e37..3f3ce1187 100644 --- a/Tone/instrument/FMSynth.js +++ b/Tone/instrument/FMSynth.js @@ -69,6 +69,7 @@ function(Tone){ this._modulationNode.gain.value = 0; this._modulationNode.connect(this.carrier.frequency); this.carrier.connect(this.output); + this._readOnly(["carrier", "modulator", "frequency"]); }; Tone.extend(Tone.FMSynth, Tone.Monophonic); @@ -192,6 +193,7 @@ function(Tone){ */ Tone.FMSynth.prototype.dispose = function(){ Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); this.carrier.dispose(); this.carrier = null; this.modulator.dispose(); diff --git a/Tone/instrument/Instrument.js b/Tone/instrument/Instrument.js index 9ec1e1b32..e4824eb53 100644 --- a/Tone/instrument/Instrument.js +++ b/Tone/instrument/Instrument.js @@ -22,6 +22,7 @@ define(["Tone/core/Tone", "Tone/core/Master", "Tone/core/Note"], function(Tone){ * @type {Tone.Signal} */ this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); + this._readOnly(["volume"]); }; Tone.extend(Tone.Instrument); @@ -62,6 +63,7 @@ define(["Tone/core/Tone", "Tone/core/Master", "Tone/core/Note"], function(Tone){ */ Tone.Instrument.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable(["volume"]); this.volume.dispose(); this.volume = null; return this; diff --git a/Tone/instrument/MonoSynth.js b/Tone/instrument/MonoSynth.js index 5a983dcbb..0b60d43b5 100644 --- a/Tone/instrument/MonoSynth.js +++ b/Tone/instrument/MonoSynth.js @@ -71,6 +71,7 @@ function(Tone){ 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); @@ -81,6 +82,8 @@ function(Tone){ * @type {Object} */ Tone.MonoSynth.defaults = { + "frequency" : "C4", + "detune" : 0, "oscillator" : { "type" : "square" }, @@ -137,6 +140,7 @@ function(Tone){ */ 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(); diff --git a/Tone/instrument/MultiSampler.js b/Tone/instrument/MultiSampler.js deleted file mode 100644 index 28174f21b..000000000 --- a/Tone/instrument/MultiSampler.js +++ /dev/null @@ -1,134 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/Sampler", "Tone/instrument/Instrument"], -function(Tone){ - - "use strict"; - - /** - * @class Deprecated. - * - * @constructor - * @deprecated Use Tone.PolySynth with Tone.Sampler as the voice. - * @extends {Tone.Instrument} - * @param {Object} samples the samples used in this - * @param {function} onload the callback to invoke when all - * of the samples have been loaded - */ - Tone.MultiSampler = function(samples, onload){ - - console.warn("Tone.MultiSampler is deprecated - use Tone.PolySynth with Tone.Sampler as the voice"); - Tone.Instrument.call(this); - - /** - * the array of voices - * @type {Tone.Sampler} - */ - this.samples = {}; - - //make the samples - this._createSamples(samples, onload); - }; - - Tone.extend(Tone.MultiSampler, Tone.Instrument); - - /** - * creates all of the samples and tracks their loading - * - * @param {Object} samples the samples - * @param {function} onload the onload callback - * @private - */ - Tone.MultiSampler.prototype._createSamples = function(samples, onload){ - //object which tracks the number of loaded samples - var loadCounter = { - total : 0, - loaded : 0 - }; - //get the count - for (var s in samples){ //jshint ignore:line - loadCounter.total++; - } - //the function to invoke when a sample is loaded - var onSampleLoad = function(){ - loadCounter.loaded++; - if (loadCounter.loaded === loadCounter.total){ - if (onload){ - onload(); - } - } - }; - for (var samp in samples){ - var url = samples[samp]; - var sampler = new Tone.Sampler(url, onSampleLoad); - sampler.connect(this.output); - this.samples[samp] = sampler; - } - }; - - /** - * start a sample - * - * @param {string} sample the note name to start - * @param {Tone.Time} [time=now] the time when the note should start - * @param {number} [velocity=1] the velocity of the note - */ - Tone.MultiSampler.prototype.triggerAttack = function(sample, time, velocity){ - if (this.samples.hasOwnProperty(sample)){ - this.samples[sample].triggerAttack(0, time, velocity); - } - }; - - /** - * start the release portion of the note - * - * @param {string} sample the note name to release - * @param {Tone.Time} [time=now] the time when the note should release - */ - Tone.MultiSampler.prototype.triggerRelease = function(sample, time){ - if (this.samples.hasOwnProperty(sample)){ - this.samples[sample].triggerRelease(time); - } - }; - - /** - * start the release portion of the note - * - * @param {string} sample the note name to release - * @param {Tone.Time} duration the duration of the note - * @param {Tone.Time} [time=now] the time when the note should start - * @param {number} [velocity=1] the velocity of the note - */ - Tone.MultiSampler.prototype.triggerAttackRelease = function(sample, duration, time, velocity){ - if (this.samples.hasOwnProperty(sample)){ - time = this.toSeconds(time); - duration = this.toSeconds(duration); - var samp = this.samples[sample]; - samp.triggerAttack(0, time, velocity); - samp.triggerRelease(time + duration); - } - }; - - /** - * sets all the samplers with these settings - * @param {object} params the parameters to be applied - * to all internal samplers - */ - Tone.MultiSampler.prototype.set = function(params){ - for (var samp in this.samples){ - this.samples[samp].set(params); - } - }; - - /** - * clean up - */ - Tone.MultiSampler.prototype.dispose = function(){ - Tone.Instrument.prototype.dispose.call(this); - for (var samp in this.samples){ - this.samples[samp].dispose(); - this.samples[samp] = null; - } - this.samples = null; - }; - - return Tone.MultiSampler; -}); diff --git a/Tone/instrument/NoiseSynth.js b/Tone/instrument/NoiseSynth.js index 5ef1d444f..389bad992 100644 --- a/Tone/instrument/NoiseSynth.js +++ b/Tone/instrument/NoiseSynth.js @@ -52,6 +52,7 @@ function(Tone){ 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); @@ -121,7 +122,6 @@ function(Tone){ time = this.toSeconds(time); duration = this.toSeconds(duration); this.triggerAttack(time, velocity); - console.log(time + duration); this.triggerRelease(time + duration); return this; }; @@ -132,6 +132,7 @@ function(Tone){ */ 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(); diff --git a/Tone/instrument/PluckSynth.js b/Tone/instrument/PluckSynth.js index 54cdbaaaa..e085d676b 100644 --- a/Tone/instrument/PluckSynth.js +++ b/Tone/instrument/PluckSynth.js @@ -51,6 +51,7 @@ define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/source/Noise", "To //connections this._noise.connect(this._lfcf); this._lfcf.connect(this.output); + this._readOnly(["resonance", "dampening"]); }; Tone.extend(Tone.PluckSynth, Tone.Instrument); @@ -76,7 +77,7 @@ define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/source/Noise", "To note = this.toFrequency(note); time = this.toSeconds(time); var delayAmount = 1 / note; - this._lfcf.setDelayTimeAtTime(delayAmount, time); + this._lfcf.delayTime.setValueAtTime(delayAmount, time); this._noise.start(time); this._noise.stop(time + delayAmount * this.attackNoise); return this; @@ -92,6 +93,7 @@ define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/source/Noise", "To this._lfcf.dispose(); this._noise = null; this._lfcf = null; + this._writable(["resonance", "dampening"]); this.dampening = null; this.resonance = null; return this; diff --git a/Tone/instrument/PolySynth.js b/Tone/instrument/PolySynth.js index 087cfba49..671366979 100644 --- a/Tone/instrument/PolySynth.js +++ b/Tone/instrument/PolySynth.js @@ -137,7 +137,8 @@ function(Tone){ if (voice){ voice.triggerRelease(time); this._freeVoices.push(voice); - this._activeVoices[stringified] = null; + delete this._activeVoices[stringified]; + voice = null; } } return this; @@ -145,12 +146,14 @@ function(Tone){ /** * set the options on all of the voices - * @param {Object} params + * @param {Object|string} params + * @param {number=} value + * @param {Tone.Time=} rampTime * @returns {Tone.PolySynth} `this` */ - Tone.PolySynth.prototype.set = function(params){ + Tone.PolySynth.prototype.set = function(params, value, rampTime){ for (var i = 0; i < this.voices.length; i++){ - this.voices[i].set(params); + this.voices[i].set(params, value, rampTime); } return this; }; diff --git a/Tone/instrument/Sampler.js b/Tone/instrument/Sampler.js index 81b3827e7..109c20ebe 100644 --- a/Tone/instrument/Sampler.js +++ b/Tone/instrument/Sampler.js @@ -79,6 +79,7 @@ function(Tone){ 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); @@ -170,7 +171,7 @@ function(Tone){ if (name){ this.sample = name; } - this.player.start(time, 0); + this.player.start(time); this.envelope.triggerAttack(time, velocity); this.filterEnvelope.triggerAttack(time); return this; @@ -210,6 +211,25 @@ function(Tone){ } }); + /** + * 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). @@ -236,6 +256,7 @@ function(Tone){ */ 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(); diff --git a/Tone/signal/AudioToGain.js b/Tone/signal/AudioToGain.js index a87b47535..b2c0531ac 100644 --- a/Tone/signal/AudioToGain.js +++ b/Tone/signal/AudioToGain.js @@ -16,18 +16,20 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/signal/Signal"], funct * @type {WaveShaperNode} * @private */ - this._norm = this.input = this.output = new Tone.WaveShaper([0,1]); + 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.AND} `this` + * @returns {Tone.AudioToGain} `this` */ Tone.AudioToGain.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._norm.disconnect(); + this._norm.dispose(); this._norm = null; return this; }; diff --git a/Tone/signal/Clip.js b/Tone/signal/Clip.js index 25e54229d..bf692e366 100644 --- a/Tone/signal/Clip.js +++ b/Tone/signal/Clip.js @@ -27,12 +27,14 @@ define(["Tone/core/Tone", "Tone/signal/Max", "Tone/signal/Min", "Tone/signal/Sig * @type {Tone.Signal} */ this.min = this.input = new Tone.Min(max); + this._readOnly("min"); /** * The max clip value * @type {Tone.Signal} */ this.max = this.output = new Tone.Max(min); + this._readOnly("max"); this.min.connect(this.max); }; @@ -45,8 +47,10 @@ define(["Tone/core/Tone", "Tone/signal/Max", "Tone/signal/Min", "Tone/signal/Sig */ 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; diff --git a/Tone/signal/Divide.js b/Tone/signal/Divide.js deleted file mode 100644 index e6adf36bc..000000000 --- a/Tone/signal/Divide.js +++ /dev/null @@ -1,101 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Inverse", "Tone/signal/Multiply"], -function(Tone){ - - "use strict"; - - /** - * @class Divide by a value or signal. - * input 0: numerator. input 1: divisor. - * - * @deprecated - * @extends {Tone.SignalBase} - * @constructor - * @param {number=} divisor if no value is provided, Tone.Divide will divide the first - * and second inputs. - * @param {number} [precision=3] the precision of the calculation - */ - Tone.Divide = function(divisor, precision){ - - console.warn("Tone.Divide has been deprecated. If possible, it's much more efficient to multiply by the inverse value."); - - Tone.call(this, 2, 0); - - /** - * the denominator value - * @type {Tone.Signal} - * @private - */ - this._denominator = null; - - /** - * the inverse - * @type {Tone} - * @private - */ - this._inverse = new Tone.Inverse(precision); - - /** - * multiply input 0 by the inverse - * @type {Tone.Multiply} - * @private - */ - this._mult = new Tone.Multiply(); - - if (isFinite(divisor)){ - this._denominator = new Tone.Signal(divisor); - this._denominator.connect(this._inverse); - } - this.input[1] = this._inverse; - this._inverse.connect(this._mult, 0, 1); - this.input[0] = this.output = this._mult.input[0]; - }; - - Tone.extend(Tone.Divide, Tone.SignalBase); - - /** - * The value being divided from the incoming signal. Note, that - * if Divide was constructed without a divisor, it expects - * that the signals to numberator will be connected to input 0 and - * the denominator to input 1 and therefore will throw an error when - * trying to set/get the value. - * - * @memberOf Tone.Divide# - * @type {number} - * @name value - */ - Object.defineProperty(Tone.Divide.prototype, "value", { - get : function(){ - if (this._denominator !== null){ - return this._denominator.value; - } else { - throw new Error("cannot switch from signal to number"); - } - }, - set : function(value){ - if (this._denominator !== null){ - this._denominator.value = value; - } else { - throw new Error("cannot switch from signal to number"); - } - } - }); - - /** - * clean up - * @returns {Tone.Divide} `this` - */ - Tone.Divide.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - if (this._denominator){ - this._denominator.dispose(); - this._denominator = null; - } - this._inverse.dispose(); - this._inverse = null; - this._mult.dispose(); - this._mult = null; - return this; - }; - - return Tone.Divide; -}); \ No newline at end of file diff --git a/Tone/signal/Equal.js b/Tone/signal/Equal.js index 7380f1104..69e6dab07 100644 --- a/Tone/signal/Equal.js +++ b/Tone/signal/Equal.js @@ -59,7 +59,7 @@ define(["Tone/core/Tone", "Tone/signal/EqualZero", "Tone/signal/Subtract", "Tone */ Tone.Equal.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._equals.disconnect(); + this._equals.dispose(); this._equals = null; this._sub.dispose(); this._sub = null; diff --git a/Tone/signal/GainToAudio.js b/Tone/signal/GainToAudio.js new file mode 100644 index 000000000..8c133c17e --- /dev/null +++ b/Tone/signal/GainToAudio.js @@ -0,0 +1,38 @@ +define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/signal/Signal"], function(Tone){ + + "use strict"; + + /** + * @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; +}); \ No newline at end of file diff --git a/Tone/signal/Inverse.js b/Tone/signal/Inverse.js deleted file mode 100644 index f1c81ff52..000000000 --- a/Tone/signal/Inverse.js +++ /dev/null @@ -1,149 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Subtract", "Tone/signal/Multiply", "Tone/signal/WaveShaper"], -function(Tone){ - - "use strict"; - - /** - * this is the maximum value that the divide can handle - * @type {number} - * @const - */ - var MAX_VALUE = Math.pow(2, 13); - - /** - * @private - * @static - * @type {Array} - */ - var guessCurve = new Array(MAX_VALUE); - //set the value - for (var i = 0; i < guessCurve.length; i++){ - var normalized = (i / (guessCurve.length - 1)) * 2 - 1; - if (normalized === 0){ - guessCurve[i] = 0; - } else { - guessCurve[i] = 1 / (normalized * MAX_VALUE); - } - } - - /** - * @class Compute the inverse of the input. - * Uses this approximation algorithm: - * http://en.wikipedia.org/wiki/Multiplicative_inverse#Algorithms - * - * @deprecated - * @extends {Tone.SignalBase} - * @constructor - * @param {number} [precision=3] the precision of the calculation - */ - Tone.Inverse = function(precision){ - - console.warn("Tone.Inverse has been deprecated. Multiply is always more efficient than dividing."); - - Tone.call(this); - - precision = this.defaultArg(precision, 3); - - /** - * a constant generator of the value 2 - * @private - * @type {Tone.Signal} - */ - this._two = new Tone.Signal(2); - - /** - * starting guess is 0.1 times the input - * @type {Tone.Multiply} - * @private - */ - this._guessMult = new Tone.Multiply(1/MAX_VALUE); - - /** - * produces a starting guess based on the input - * @type {WaveShaperNode} - * @private - */ - this._guess = new Tone.WaveShaper(guessCurve); - this.input.chain(this._guessMult, this._guess); - - /** - * the array of inverse helpers - * @type {Array} - * @private - */ - this._inverses = new Array(precision); - - //create the helpers - for (var i = 0; i < precision; i++){ - var guess; - if (i === 0){ - guess = this._guess; - } else { - guess = this._inverses[i-1]; - } - var inv = new InverseHelper(guess, this._two); - this.input.connect(inv); - this._inverses[i] = inv; - } - this._inverses[precision-1].connect(this.output); - }; - - Tone.extend(Tone.Inverse, Tone.SignalBase); - - /** - * clean up - * @returns {Tone.Inverse} `this` - */ - Tone.Inverse.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - for (var i = 0; i < this._inverses.length; i++){ - this._inverses[i].dispose(); - this._inverses[i] = null; - } - this._inverses = null; - this._two.dispose(); - this._two = null; - this._guessMult.dispose(); - this._guessMult = null; - this._guess.disconnect(); - this._guess = null; - return this; - }; - - // BEGIN INVERSE HELPER /////////////////////////////////////////////////// - - /** - * internal helper function for computing the inverse of a signal - * @extends {Tone} - * @constructor - * @private - */ - var InverseHelper = function(guess, two){ - this._outerMultiply = new Tone.Multiply(); - this._innerMultiply = new Tone.Multiply(); - this._subtract = new Tone.Subtract(); - //connections - guess.connect(this._innerMultiply, 0, 1); - two.connect(this._subtract, 0, 0); - this._innerMultiply.connect(this._subtract, 0, 1); - this._subtract.connect(this._outerMultiply, 0, 1); - guess.connect(this._outerMultiply, 0, 0); - this.output = this._outerMultiply; - this.input = this._innerMultiply; - }; - - Tone.extend(InverseHelper); - - InverseHelper.prototype.dispose = function(){ - this._outerMultiply.dispose(); - this._outerMultiply = null; - this._innerMultiply.dispose(); - this._innerMultiply = null; - this._subtract.dispose(); - this._subtract = null; - }; - - // END INVERSE HELPER ///////////////////////////////////////////////////// - - return Tone.Inverse; -}); \ No newline at end of file diff --git a/Tone/signal/Multiply.js b/Tone/signal/Multiply.js index 32f49cc90..1ed304a83 100644 --- a/Tone/signal/Multiply.js +++ b/Tone/signal/Multiply.js @@ -47,6 +47,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ */ Tone.Multiply.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._mult.disconnect(); this._mult = null; this._value = null; return this; diff --git a/Tone/signal/Negate.js b/Tone/signal/Negate.js index a536cbf56..657bd947b 100644 --- a/Tone/signal/Negate.js +++ b/Tone/signal/Negate.js @@ -18,7 +18,7 @@ define(["Tone/core/Tone", "Tone/signal/Multiply", "Tone/signal/Signal"], functio * @type {Tone.Multiply} * @private */ - this._multiply = this.input = this.output= new Tone.Multiply(-1); + this._multiply = this.input = this.output = new Tone.Multiply(-1); }; Tone.extend(Tone.Negate, Tone.SignalBase); diff --git a/Tone/signal/Normalize.js b/Tone/signal/Normalize.js index e5bbfbc77..28a62172d 100644 --- a/Tone/signal/Normalize.js +++ b/Tone/signal/Normalize.js @@ -7,6 +7,8 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Multiply"], function(T * * @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); diff --git a/Tone/signal/OR.js b/Tone/signal/OR.js index a9abc6a49..a1b9f53ee 100644 --- a/Tone/signal/OR.js +++ b/Tone/signal/OR.js @@ -7,6 +7,7 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThanZero"], function(Tone){ * * @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); @@ -30,14 +31,7 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThanZero"], function(Tone){ * @type {Tone.Equal} * @private */ - this._gtz = new Tone.GreaterThanZero(); - - /** - * the output - * @type {Tone.Equal} - * @private - */ - this.output = this._gtz; + this._gtz = this.output = new Tone.GreaterThanZero(); //make each of the inputs an alias for (var i = 0; i < inputCount; i++){ diff --git a/Tone/signal/Route.js b/Tone/signal/Route.js index 9ec79d3bd..a6ef528cc 100644 --- a/Tone/signal/Route.js +++ b/Tone/signal/Route.js @@ -26,6 +26,7 @@ define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(T * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); //make all the inputs and connect them for (var i = 0; i < outputCount; i++){ @@ -56,13 +57,14 @@ define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(T * @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); - this.gate = null; return this; }; @@ -101,8 +103,8 @@ define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(T RouteGate.prototype.dispose = function(){ Tone.prototype.dispose.call(this); this.selecter.dispose(); - this.gate.disconnect(); this.selecter = null; + this.gate.disconnect(); this.gate = null; }; diff --git a/Tone/signal/Select.js b/Tone/signal/Select.js index 8c304b587..02b81c900 100644 --- a/Tone/signal/Select.js +++ b/Tone/signal/Select.js @@ -29,6 +29,7 @@ define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(T * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); //make all the inputs and connect them for (var i = 0; i < sourceCount; i++){ @@ -62,13 +63,14 @@ define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(T * @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); - this.gate = null; return this; }; diff --git a/Tone/signal/Signal.js b/Tone/signal/Signal.js index adf5565dd..7fc2e1268 100644 --- a/Tone/signal/Signal.js +++ b/Tone/signal/Signal.js @@ -25,9 +25,18 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ */ this.units = this.defaultArg(units, Tone.Signal.Units.Number); + /** + * 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 = true; + /** * The node where the constant signal value is scaled. - * @type {AudioParam} + * @type {GainNode} * @private */ this.output = this._scaler = this.context.createGain(); @@ -87,19 +96,23 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * @return {number} the number which the value should be set to */ Tone.Signal.prototype._fromUnits = function(val){ - switch(this.units){ - case Tone.Signal.Units.Time: - return this.toSeconds(val); - case Tone.Signal.Units.Frequency: - return this.toFrequency(val); - case Tone.Signal.Units.Decibels: - return this.dbToGain(val); - case Tone.Signal.Units.Normal: - return Math.min(Math.max(val, 0), 1); - case Tone.Signal.Units.Audio: - return Math.min(Math.max(val, -1), 1); - default: - return val; + if (this.convert){ + switch(this.units){ + case Tone.Signal.Units.Time: + return this.toSeconds(val); + case Tone.Signal.Units.Frequency: + return this.toFrequency(val); + case Tone.Signal.Units.Decibels: + return this.dbToGain(val); + case Tone.Signal.Units.Normal: + return Math.min(Math.max(val, 0), 1); + case Tone.Signal.Units.Audio: + return Math.min(Math.max(val, -1), 1); + default: + return val; + } + } else { + return val; } }; @@ -110,11 +123,15 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * @return {number} */ Tone.Signal.prototype._toUnits = function(val){ - switch(this.units){ - case Tone.Signal.Units.Decibels: - return this.gainToDb(val); - default: - return val; + if (this.convert){ + switch(this.units){ + case Tone.Signal.Units.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; } }; @@ -168,7 +185,6 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ */ Tone.Signal.prototype.exponentialRampToValueAtTime = function(value, endTime){ value = this._fromUnits(value); - //can't go below a certain value value = Math.max(0.00001, value); this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); return this; @@ -186,10 +202,13 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * //exponentially ramp to the value 2 over 4 seconds. * signal.exponentialRampToValueNow(2, 4); */ - Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime ){ + Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime){ var now = this.now(); - this.setCurrentValueNow(now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime )); + // 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; }; @@ -222,6 +241,10 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ */ 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; }; @@ -312,7 +335,9 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ /** In half-step increments, i.e. 12 is an octave above the root. */ Interval : "interval", /** Beats per minute. */ - BPM : "bpm" + BPM : "bpm", + /** A value greater than 0 */ + Positive : "positive" }; /////////////////////////////////////////////////////////////////////////// diff --git a/Tone/signal/Switch.js b/Tone/signal/Switch.js index 4ecd3cf6d..cd59f7c18 100644 --- a/Tone/signal/Switch.js +++ b/Tone/signal/Switch.js @@ -28,6 +28,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase", "Tone/signal/GreaterThan"], * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); /** * thresh the control signal to either 0 or 1 @@ -76,9 +77,10 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase", "Tone/signal/GreaterThan"], */ Tone.Switch.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable("gate"); this.gate.dispose(); - this._thresh.dispose(); this.gate = null; + this._thresh.dispose(); this._thresh = null; return this; }; diff --git a/Tone/signal/WaveShaper.js b/Tone/signal/WaveShaper.js index 9b86246ec..b0dd7e69c 100644 --- a/Tone/signal/WaveShaper.js +++ b/Tone/signal/WaveShaper.js @@ -89,7 +89,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){ * The oversampling. Can either be "none", "2x" or "4x" * @memberOf Tone.WaveShaper# * @type {string} - * @name curve + * @name oversample */ Object.defineProperty(Tone.WaveShaper.prototype, "oversample", { get : function(){ diff --git a/Tone/source/Noise.js b/Tone/source/Noise.js index 169b90229..39ac5151b 100644 --- a/Tone/source/Noise.js +++ b/Tone/source/Noise.js @@ -30,13 +30,6 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ */ this._buffer = null; - /** - * the playback rate for pitching the noise - * @private - * @type {number} - */ - this._playbackRate = 1; - this.type = options.type; }; @@ -58,6 +51,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ * @memberOf Tone.Noise# * @type {string} * @name type + * @options ["white", "brown", "pink"] * @example * noise.type = "white"; */ @@ -98,28 +92,6 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ } }); - /** - * The playback speed. 1 is normal speed. - * Note that this is not a Tone.Signal because of a bug in Blink. - * Please star this issue if this an important thing to you: - * https://code.google.com/p/chromium/issues/detail?id=311284 - * - * @memberOf Tone.Noise# - * @type {number} - * @name playbackRate - */ - Object.defineProperty(Tone.Noise.prototype, "playbackRate", { - get : function(){ - return this._playbackRate; - }, - set : function(rate){ - this._playbackRate = rate; - if (this._source) { - this._source.playbackRate.value = rate; - } - } - }); - /** * internal start method * @@ -130,7 +102,6 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ this._source = this.context.createBufferSource(); this._source.buffer = this._buffer; this._source.loop = true; - this._source.playbackRate.value = this._playbackRate; this.connectSeries(this._source, this.output); this._source.start(this.toSeconds(time)); this._source.onended = this.onended; diff --git a/Tone/source/OmniOscillator.js b/Tone/source/OmniOscillator.js index a8a5767ac..60472e9aa 100644 --- a/Tone/source/OmniOscillator.js +++ b/Tone/source/OmniOscillator.js @@ -11,7 +11,7 @@ function(Tone){ * * @extends {Tone.Oscillator} * @constructor - * @param {frequency} frequency frequency of the oscillator (meaningless for noise types) + * @param {frequency} Tone.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"); @@ -48,6 +48,7 @@ function(Tone){ //set the oscillator this.type = options.type; + this._readOnly(["frequency", "detune"]); }; Tone.extend(Tone.OmniOscillator, Tone.Oscillator); @@ -210,6 +211,7 @@ function(Tone){ */ Tone.OmniOscillator.prototype.dispose = function(){ Tone.Source.prototype.dispose.call(this); + this._writable(["frequency", "detune"]); this.detune.dispose(); this.detune = null; this.frequency.dispose(); diff --git a/Tone/source/Oscillator.js b/Tone/source/Oscillator.js index d962f093e..162aecbd4 100644 --- a/Tone/source/Oscillator.js +++ b/Tone/source/Oscillator.js @@ -62,6 +62,7 @@ function(Tone){ //setup this.type = options.type; this.phase = this._phase; + this._readOnly(["frequency", "detune"]); }; Tone.extend(Tone.Oscillator, Tone.Source); @@ -73,9 +74,13 @@ function(Tone){ * @type {Object} */ Tone.Oscillator.defaults = { + /** @type {string} */ "type" : "sine", + /** @type {Tone.Frequency} */ "frequency" : 440, + /** @type {number} */ "detune" : 0, + /** @type {number} */ "phase" : 0 }; @@ -147,6 +152,7 @@ function(Tone){ * @memberOf Tone.Oscillator# * @type {string} * @name type + * @options ["sine", "square", "sawtooth", "triangle"] * @example * osc.type = "square"; * osc.type; //returns "square" @@ -156,57 +162,62 @@ function(Tone){ return this._type; }, set : function(type){ - if (this.type !== type){ - - var fftSize = 4096; - var halfSize = fftSize / 2; - - var real = new Float32Array(halfSize); - var imag = new Float32Array(halfSize); - - // Clear DC and Nyquist. - real[0] = 0; - imag[0] = 0; - - var shift = this._phase; - for (var n = 1; n < halfSize; ++n) { - var piFactor = 2 / (n * Math.PI); - var b; - switch (type) { - case "sine": - b = (n === 1) ? 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); - imag[n] = b * Math.cos(shift); - } else { - real[n] = 0; - imag[n] = 0; - } + + 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); } - var periodicWave = this.context.createPeriodicWave(real, imag); - this._wave = periodicWave; - if (this._oscillator !== null){ - this._oscillator.setPeriodicWave(this._wave); + if (b !== 0){ + real[n] = -b * Math.sin(shift * n); + imag[n] = b * Math.cos(shift * n); + } else { + real[n] = 0; + imag[n] = 0; } - this._type = type; } + var periodicWave = this.context.createPeriodicWave(real, imag); + this._wave = periodicWave; + if (this._oscillator !== null){ + this._oscillator.setPeriodicWave(this._wave); + } + this._type = originalType; } }); @@ -239,11 +250,12 @@ function(Tone){ 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; - this._wave = null; return this; }; diff --git a/Tone/source/PWMOscillator.js b/Tone/source/PWMOscillator.js index da2175f91..4480d189a 100644 --- a/Tone/source/PWMOscillator.js +++ b/Tone/source/PWMOscillator.js @@ -9,7 +9,7 @@ function(Tone){ * * @extends {Tone.Oscillator} * @constructor - * @param {frequency} frequency frequency of the oscillator (meaningless for noise types) + * @param {frequency} Tone.Frequency frequency of the oscillator (meaningless for noise types) * @param {number} modulationFrequency the modulation frequency of the oscillator * @example * var pwm = new Tone.PWMOscillator("Ab3", 0.3); @@ -20,6 +20,8 @@ function(Tone){ /** * the pulse oscillator + * @type {Tone.PulseOscillator} + * @private */ this._pulse = new Tone.PulseOscillator(options.modulationFrequency); //change the pulse oscillator type @@ -64,6 +66,7 @@ function(Tone){ //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); @@ -142,6 +145,7 @@ function(Tone){ this._scale = null; this._modulator.dispose(); this._modulator = null; + this._writable(["modulationFrequency", "frequency", "detune"]); this.frequency = null; this.detune = null; this.modulationFrequency = null; diff --git a/Tone/source/Player.js b/Tone/source/Player.js index 72ff1c238..33d03a944 100644 --- a/Tone/source/Player.js +++ b/Tone/source/Player.js @@ -24,13 +24,24 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To * @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(options.url, options.onload.bind(null, this)); + 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 @@ -80,9 +91,11 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To "onload" : function(){}, "playbackRate" : 1, "loop" : false, + "autostart" : false, "loopStart" : 0, "loopEnd" : 0, "retrigger" : false, + "reverse" : false, }; /** @@ -99,10 +112,21 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To * @returns {Tone.Player} `this` */ Tone.Player.prototype.load = function(url, callback){ - this._buffer.load(url, callback.bind(this, this)); + 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 * @@ -120,11 +144,11 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To //if it's a loop the default offset is the loopstart point if (this._loop){ offset = this.defaultArg(offset, this._loopStart); - offset = this.toSeconds(offset); } 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); @@ -137,6 +161,9 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To 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; } @@ -147,7 +174,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To //start it this._source.start(startTime, offset, duration); } else { - //throw Error("tried to start Player before the buffer was loaded"); + throw Error("tried to start Player before the buffer was loaded"); } return this; }; @@ -273,6 +300,21 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To } }); + /** + * 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` diff --git a/Tone/source/PulseOscillator.js b/Tone/source/PulseOscillator.js index d7f63a1fa..6369e2458 100644 --- a/Tone/source/PulseOscillator.js +++ b/Tone/source/PulseOscillator.js @@ -71,6 +71,7 @@ function(Tone){ //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); @@ -148,10 +149,12 @@ function(Tone){ 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; diff --git a/Tone/source/Source.js b/Tone/source/Source.js index 54ce102db..f54dab28b 100644 --- a/Tone/source/Source.js +++ b/Tone/source/Source.js @@ -17,7 +17,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T options = this.defaultArg(options, Tone.Source.defaults); /** - * The onended callback when the source is done playing. + * Callback is invoked when the source is done playing. * @type {function} * @example * source.onended = function(){ @@ -47,6 +47,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * source.volume.value = -6; */ this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); + this._readOnly("volume"); /** * keeps track of the timeout for chaning the state @@ -55,6 +56,10 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * @private */ this._timeout = -1; + + //make the output explicitly stereo + this.output.channelCount = 2; + this.output.channelCountMode = "explicit"; }; Tone.extend(Tone.Source); @@ -196,6 +201,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T this.stop(); clearTimeout(this._timeout); this.onended = function(){}; + this._writable("volume"); this.volume.dispose(); this.volume = null; }; diff --git a/build/Tone.js b/build/Tone.js index a4fdc7010..5803e1ba8 100644 --- a/build/Tone.js +++ b/build/Tone.js @@ -2,7 +2,7 @@ "use strict"; var Tone; //constructs the main Tone object - function MainModule(func){ + function ToneCore(func){ Tone = func(); } //invokes each of the modules with the main Tone object as the argument @@ -16,7 +16,7 @@ * @license http://opensource.org/licenses/MIT MIT License * @copyright 2014-2015 Yotam Mann */ - MainModule(function(){ + ToneCore(function(){ @@ -140,6 +140,10 @@ * 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. + * @param {Object|string} params + * @param {number=} value + * @param {Tone.Time=} rampTime + * @returns {Tone} `this` * @example * //set values using an object * filter.set({ @@ -152,10 +156,6 @@ * oscillator.set({ * "frequency" : 220 * }, 3); - * @param {Object|string} params - * @param {number=} value - * @param {Tone.Time=} rampTime - * @returns {Tone} `this` */ Tone.prototype.set = function(params, value, rampTime){ if (typeof params === "object"){ @@ -198,18 +198,30 @@ * osc.get(); * //returns {"type" : "sine", "frequency" : 440, ...etc} * osc.get("type"); //returns { "type" : "sine"} - * @param {Array=} params the parameters to get, otherwise will return - * all available.r + * @param {Array=|string|Object} params the parameters to get, otherwise will return + * all available. */ Tone.prototype.get = function(params){ if (isUndef(params)){ params = this._collectDefaults(this.constructor); + } else if (typeof params === "string"){ + var obj = {}; + obj[params] = 0; + params = obj; + } else if (Array.isArray(params)){ + //use the objects as keys + var keyObj = {}; + for (var i = 0; i < params.length; i++){ + keyObj[params[i]] = 0; + } + params = keyObj; } var ret = {}; - for (var i = 0; i < params.length; i++){ - var attr = params[i]; + for (var attr in params){ var param = this[attr]; - if (param instanceof Tone.Signal){ + if (typeof params[attr] === "object"){ + ret[attr] = param.get(params[attr]); + } else if (param instanceof Tone.Signal){ ret[attr] = param.value; } else if (param instanceof AudioParam){ ret[attr] = param.value; @@ -226,15 +238,18 @@ * 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 + * @return {Object} all of the attributes which belong to the class */ Tone.prototype._collectDefaults = function(constr){ - var ret = []; + var ret = {}; if (!isUndef(constr.defaults)){ - ret = Object.keys(constr.defaults); + ret = constr.defaults; } if (!isUndef(constr._super)){ - ret = ret.concat(this._collectDefaults(constr._super)); + var superDefs = this._collectDefaults(constr._super); + for (var attr in superDefs){ + ret[attr] = superDefs[attr]; + } } return ret; }; @@ -251,6 +266,20 @@ 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 /////////////////////////////////////////////////////////////////////////// @@ -515,33 +544,38 @@ Tone.prototype.isFunction = isFunction; /** - * interpolate the input value (0-1) to be between outputMin and outputMax - * @param {number} input - * @param {number} outputMin - * @param {number} outputMax - * @return {number} + * Make the property not writable. Internal use only. + * @private + * @param {string} property the property to make not writable */ - Tone.prototype.interpolate = function(input, outputMin, outputMax){ - return input*(outputMax - outputMin) + outputMin; + 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, + }); + } }; /** - * normalize the input to 0-1 from between inputMin to inputMax - * @param {number} input - * @param {number} inputMin - * @param {number} inputMax - * @return {number} + * Make an attribute writeable. Interal use only. + * @private + * @param {string} property the property to make writable */ - Tone.prototype.normalize = function(input, inputMin, inputMax){ - //make sure that min < max - if (inputMin > inputMax){ - var tmp = inputMax; - inputMax = inputMin; - inputMin = tmp; - } else if (inputMin == inputMax){ - return 0; + 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, + }); } - return (input - inputMin) / (inputMax - inputMin); }; /////////////////////////////////////////////////////////////////////////// @@ -776,7 +810,7 @@ _silentNode.connect(audioContext.destination); }); - console.log("%c * Tone.js r4 * ", "background: #000; color: #fff"); + console.log("%c * Tone.js r5-dev * ", "background: #000; color: #fff"); return Tone; }); @@ -815,6 +849,8 @@ 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; @@ -916,7 +952,7 @@ * The oversampling. Can either be "none", "2x" or "4x" * @memberOf Tone.WaveShaper# * @type {string} - * @name curve + * @name oversample */ Object.defineProperty(Tone.WaveShaper.prototype, "oversample", { get : function(){ @@ -978,9 +1014,26 @@ */ this.units = this.defaultArg(units, Tone.Signal.Units.Number); + /** + * 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 = true; + + /** + * 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 {AudioParam} + * @type {GainNode} * @private */ this.output = this._scaler = this.context.createGain(); @@ -1040,19 +1093,23 @@ * @return {number} the number which the value should be set to */ Tone.Signal.prototype._fromUnits = function(val){ - switch(this.units){ - case Tone.Signal.Units.Time: - return this.toSeconds(val); - case Tone.Signal.Units.Frequency: - return this.toFrequency(val); - case Tone.Signal.Units.Decibels: - return this.dbToGain(val); - case Tone.Signal.Units.Normal: - return Math.min(Math.max(val, 0), 1); - case Tone.Signal.Units.Audio: - return Math.min(Math.max(val, -1), 1); - default: - return val; + if (this.convert){ + switch(this.units){ + case Tone.Signal.Units.Time: + return this.toSeconds(val); + case Tone.Signal.Units.Frequency: + return this.toFrequency(val); + case Tone.Signal.Units.Decibels: + return this.dbToGain(val); + case Tone.Signal.Units.Normal: + return Math.min(Math.max(val, 0), 1); + case Tone.Signal.Units.Audio: + return Math.min(Math.max(val, -1), 1); + default: + return val; + } + } else { + return val; } }; @@ -1063,11 +1120,15 @@ * @return {number} */ Tone.Signal.prototype._toUnits = function(val){ - switch(this.units){ - case Tone.Signal.Units.Decibels: - return this.gainToDb(val); - default: - return val; + if (this.convert){ + switch(this.units){ + case Tone.Signal.Units.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; } }; @@ -1121,7 +1182,6 @@ */ Tone.Signal.prototype.exponentialRampToValueAtTime = function(value, endTime){ value = this._fromUnits(value); - //can't go below a certain value value = Math.max(0.00001, value); this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); return this; @@ -1139,10 +1199,13 @@ * //exponentially ramp to the value 2 over 4 seconds. * signal.exponentialRampToValueNow(2, 4); */ - Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime ){ + Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime){ var now = this.now(); - this.setCurrentValueNow(now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime )); + // 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; }; @@ -1175,6 +1238,10 @@ */ 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; }; @@ -1265,7 +1332,9 @@ /** In half-step increments, i.e. 12 is an octave above the root. */ Interval : "interval", /** Beats per minute. */ - BPM : "bpm" + BPM : "bpm", + /** A value greater than 0 */ + Positive : "positive" }; /////////////////////////////////////////////////////////////////////////// @@ -1432,12 +1501,71 @@ */ 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); @@ -1452,6 +1580,7 @@ "decay" : 0.1, "sustain" : 0.5, "release" : 1, + "attackCurve" : "linear" }; /** @@ -1461,6 +1590,96 @@ */ Tone.Envelope.prototype._timeMult = 0.25; + /** + * The slope of the attack. Either "linear" or "exponential" + * @memberOf Tone.Envelope# + * @type {number} + * @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 + Tone.Envelope.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + + Tone.Envelope.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + + 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 {Tone.Time} [time=now] @@ -1472,15 +1691,35 @@ * env.triggerAttack("+0.5", 0.2); */ Tone.Envelope.prototype.triggerAttack = function(time, velocity){ - velocity = this.defaultArg(velocity, 1); + //to seconds + time = this.toSeconds(time); var attack = this.toSeconds(this.attack); var decay = this.toSeconds(this.decay); - var scaledMax = velocity; + + //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; - time = this.toSeconds(time); + + //set the curve this._sig.cancelScheduledValues(time); - this._sig.setTargetAtTime(scaledMax, time, attack * this._timeMult); - this._sig.setTargetAtTime(sustainVal, time + attack, decay * this._timeMult); + 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; }; @@ -1494,9 +1733,31 @@ */ Tone.Envelope.prototype.triggerRelease = function(time){ time = this.toSeconds(time); - this._sig.cancelScheduledValues(time); + var phase = this._phaseAtTime(time); var release = this.toSeconds(this.release); - this._sig.setTargetAtTime(0, time, release * this._timeMult); + + //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; }; @@ -1534,6 +1795,27 @@ 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; }); @@ -1598,12 +1880,7 @@ * @type {DynamicsCompressorNode} * @private */ - this._compressor = this.context.createDynamicsCompressor(); - - /** - * the input and output - */ - this.input = this.output = this._compressor; + this._compressor = this.input = this.output = this.context.createDynamicsCompressor(); /** * the threshold vaue @@ -1636,6 +1913,9 @@ this.ratio = this._compressor.ratio; //set the defaults + this.attack.connect(this._compressor.attack); + this.release.connect(this._compressor.release); + this._readOnly(["knee", "release", "attack", "ratio", "threshold"]); this.set(options); }; @@ -1660,6 +1940,7 @@ */ 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(); @@ -1788,6 +2069,7 @@ */ Tone.Multiply.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._mult.disconnect(); this._mult = null; this._value = null; return this; @@ -1816,7 +2098,7 @@ * @type {Tone.Multiply} * @private */ - this._multiply = this.input = this.output= new Tone.Multiply(-1); + this._multiply = this.input = this.output = new Tone.Multiply(-1); }; Tone.extend(Tone.Negate, Tone.SignalBase); @@ -2087,7 +2369,7 @@ */ Tone.Equal.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._equals.disconnect(); + this._equals.dispose(); this._equals = null; this._sub.dispose(); this._sub = null; @@ -2127,6 +2409,7 @@ * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); //make all the inputs and connect them for (var i = 0; i < sourceCount; i++){ @@ -2160,13 +2443,14 @@ * @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); - this.gate = null; return this; }; @@ -2280,6 +2564,7 @@ * * @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); @@ -2303,14 +2588,7 @@ * @type {Tone.Equal} * @private */ - this._gtz = new Tone.GreaterThanZero(); - - /** - * the output - * @type {Tone.Equal} - * @private - */ - this.output = this._gtz; + this._gtz = this.output = new Tone.GreaterThanZero(); //make each of the inputs an alias for (var i = 0; i < inputCount; i++){ @@ -3463,6 +3741,7 @@ 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); @@ -3473,6 +3752,7 @@ */ Tone.CrossFade.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable("fade"); this._equalPowerA.dispose(); this._equalPowerA = null; this._equalPowerB.dispose(); @@ -3536,7 +3816,7 @@ /** * the gain of the filter, only used in certain filter types - * @type {AudioParam} + * @type {Tone.Signal} */ this.gain = new Tone.Signal(options.gain, Tone.Signal.Units.Decibels); @@ -3562,6 +3842,7 @@ //set the rolloff; this.rolloff = options.rolloff; + this._readOnly(["detune", "frequency", "gain", "Q"]); }; Tone.extend(Tone.Filter); @@ -3587,6 +3868,7 @@ * @type {string} * @name type */ + Object.defineProperty(Tone.Filter.prototype, "type", { get : function(){ return this._type; @@ -3611,6 +3893,7 @@ * @type {number} * @name rolloff */ + Object.defineProperty(Tone.Filter.prototype, "rolloff", { get : function(){ return this._rolloff; @@ -3655,6 +3938,7 @@ this._filters[i] = null; } this._filters = null; + this._writable(["detune", "frequency", "gain", "Q"]); this.frequency.dispose(); this.Q.dispose(); this.frequency = null; @@ -3742,6 +4026,8 @@ this.lowFrequency.connect(this._lowMidFilter.frequency); this.highFrequency.connect(this.mid.frequency); this.highFrequency.connect(this.high.frequency); + + this._readOnly(["high", "mid", "low", "highFrequency", "lowFrequency"]); }; Tone.extend(Tone.MultibandSplit); @@ -3762,6 +4048,7 @@ */ Tone.MultibandSplit.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable(["high", "mid", "low", "highFrequency", "lowFrequency"]); this.low.dispose(); this._lowMidFilter.dispose(); this.mid.dispose(); @@ -3794,11 +4081,11 @@ * @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.EQ(-10, 3, -20); + * var eq = new Tone.EQ3(-10, 3, -20); */ - Tone.EQ = function(){ + Tone.EQ3 = function(){ - var options = this.optionsObject(arguments, ["low", "mid", "high"], Tone.EQ.defaults); + var options = this.optionsObject(arguments, ["low", "mid", "high"], Tone.EQ3.defaults); /** * the output node @@ -3876,16 +4163,17 @@ this.high.value = options.low; this.mid.value = options.mid; this.low.value = options.high; + this._readOnly(["low", "mid", "high", "lowFrequency", "highFrequency"]); }; - Tone.extend(Tone.EQ); + Tone.extend(Tone.EQ3); /** * the default values * @type {Object} * @static */ - Tone.EQ.defaults = { + Tone.EQ3.defaults = { "low" : 0, "mid" : 0, "high" : 0, @@ -3895,10 +4183,11 @@ /** * clean up - * @returns {Tone.EQ} `this` + * @returns {Tone.EQ3} `this` */ - Tone.EQ.prototype.dispose = function(){ + 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; @@ -3918,7 +4207,7 @@ return this; }; - return Tone.EQ; + return Tone.EQ3; }); ToneModule( function(Tone){ @@ -4136,35 +4425,13 @@ * * @extends {Tone} * @constructor - * @param {number} [minDelay=0.01] the minimum delay time which the filter can have - * @param {number} [maxDelay=1] the maximum delay time which the filter can have + * @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, ["minDelay", "maxDelay"], Tone.FeedbackCombFilter.defaults); - - var minDelay = options.minDelay; - var maxDelay = options.maxDelay; - //the delay * samplerate = number of samples. - // buffersize / number of samples = number of delays needed per buffer frame - var delayCount = Math.ceil(this.bufferSize / (minDelay * this.context.sampleRate)); - //set some ranges - delayCount = Math.min(delayCount, 10); - delayCount = Math.max(delayCount, 1); - - /** - * the number of filter delays - * @type {number} - * @private - */ - this._delayCount = delayCount; - - /** - * @type {Array.} - * @private - */ - this._delays = new Array(this._delayCount); + var options = this.optionsObject(arguments, ["delayTime", "resonance"], Tone.FeedbackCombFilter.defaults); /** * the resonance control @@ -4173,26 +4440,17 @@ this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); /** - * scale the resonance value to the normal range - * @type {Tone.Scale} - * @private - */ - this._resScale = new Tone.ScaleExp(0.01, 1 / this._delayCount - 0.001, 0.5); - - /** - * internal flag for keeping track of when frequency - * correction has been used - * @type {boolean} + * the delay node + * @type {DelayNode} * @private */ - this._highFrequencies = false; + this._delay = this.input = this.output = this.context.createDelay(1); /** - * internal counter of delayTime - * @type {Tone.TIme} - * @private + * the delayTime + * @type {Tone.Signal} */ - this._delayTime = options.delayTime; + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); /** * the feedback node @@ -4201,23 +4459,10 @@ */ this._feedback = this.context.createGain(); - //make the filters - for (var i = 0; i < this._delayCount; i++) { - var delay = this.context.createDelay(maxDelay); - delay.delayTime.value = minDelay; - delay.connect(this._feedback); - this._delays[i] = delay; - } - - //connections - this.connectSeries.apply(this, this._delays); - this.input.connect(this._delays[0]); - //set the delay to the min value initially - this._feedback.connect(this._delays[0]); - //resonance control - this.resonance.chain(this._resScale, this._feedback.gain); - this._feedback.connect(this.output); - this.delayTime = options.delayTime; + 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); @@ -4229,63 +4474,23 @@ * @type {Object} */ Tone.FeedbackCombFilter.defaults = { - "resonance" : 0.5, - "minDelay" : 0.1, - "maxDelay" : 1, - "delayTime" : 0.1 + "delayTime" : 0.1, + "resonance" : 0.5 }; - /** - * the delay time of the FeedbackCombFilter - * @memberOf Tone.FeedbackCombFilter# - * @type {Tone.Time} - * @name delayTime - */ - Object.defineProperty(Tone.FeedbackCombFilter.prototype, "delayTime", { - get : function(){ - return this._delayTime; - }, - set : function(delayAmount){ - this._delayTime = delayAmount; - delayAmount = this.toSeconds(delayAmount); - //the number of samples to delay by - var sampleRate = this.context.sampleRate; - var delaySamples = sampleRate * delayAmount; - // delayTime corection when frequencies get high - var now = this.now() + this.bufferTime; - var cutoff = 100; - if (delaySamples < cutoff){ - this._highFrequencies = true; - var changeNumber = Math.round((delaySamples / cutoff) * this._delayCount); - for (var i = 0; i < changeNumber; i++) { - this._delays[i].delayTime.setValueAtTime(1 / sampleRate + delayAmount, now); - } - delayAmount = Math.floor(delaySamples) / sampleRate; - } else if (this._highFrequencies){ - this._highFrequencies = false; - for (var j = 0; j < this._delays.length; j++) { - this._delays[j].delayTime.setValueAtTime(delayAmount, now); - } - } - } - }); - /** * clean up * @returns {Tone.FeedbackCombFilter} `this` */ Tone.FeedbackCombFilter.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - //dispose the filter delays - for (var i = 0; i < this._delays.length; i++) { - this._delays[i].disconnect(); - this._delays[i] = null; - } - this._delays = null; + this._writable(["resonance", "delayTime"]); + this._delay.disconnect(); + this._delay = null; + this.delayTime.dispose(); + this.delayTime = null; this.resonance.dispose(); this.resonance = null; - this._resScale.dispose(); - this._resScale = null; this._feedback.disconnect(); this._feedback = null; return this; @@ -4596,7 +4801,6 @@ * @class a sample accurate clock built on an oscillator. * Invokes the tick method at the set rate * - * @private * @constructor * @extends {Tone} * @param {Tone.Frequency} frequency the rate of the callback @@ -4623,7 +4827,7 @@ * the rate control signal * @type {Tone.Signal} */ - this.frequency = new Tone.Signal(frequency); + this.frequency = new Tone.Signal(frequency, Tone.Signal.Units.Frequency); /** * whether the tick is on the up or down @@ -4639,6 +4843,16 @@ */ 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 = function(){}; + //setup this._jsNode.noGC(); }; @@ -4647,7 +4861,7 @@ /** * start the clock - * @param {Tone.Time} time the time when the clock should start + * @param {Tone.Time} [time=now] the time when the clock should start * @returns {Tone.Clock} `this` */ Tone.Clock.prototype.start = function(time){ @@ -4666,21 +4880,20 @@ /** * stop the clock - * @param {Tone.Time} time the time when the clock should stop - * @param {function} onend called when the oscilator stops + * @param {Tone.Time} [time=now] The time when the clock should stop. * @returns {Tone.Clock} `this` */ - Tone.Clock.prototype.stop = function(time, onend){ + 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; - //set a timeout for when it stops if (time){ - setTimeout(onend, (stopTime - now) * 1000); + //set a timeout for when it stops + setTimeout(this.onended.bind(this), (stopTime - now) * 1000); } else { - onend(); + this.onended(); } } return this; @@ -4732,6 +4945,7 @@ this._jsNode.onaudioprocess = function(){}; this._jsNode = null; this.tick = null; + this.onended = function(){}; return this; }; @@ -4789,6 +5003,7 @@ * @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. @@ -5305,7 +5520,7 @@ Tone.Transport.prototype.stop = function(time){ if (this.state === TransportState.STARTED || this.state === TransportState.PAUSED){ var stopTime = this.toSeconds(time); - this._clock.stop(stopTime, this._onended.bind(this)); + 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; @@ -5890,6 +6105,13 @@ */ this._unmutedVolume = 1; + /** + * if the master is muted + * @type {boolean} + * @private + */ + this._muted = false; + /** * the volume of the output in decibels * @type {Tone.Signal} @@ -5903,13 +6125,16 @@ Tone.extend(Tone.Master); /** - * Mutethe output + * Mute the output * @returns {Tone.Master} `this` */ Tone.Master.prototype.mute = function(){ - this._unmutedVolume = this.volume.value; - //maybe it should ramp here? - this.volume.value = -Infinity; + if (!this._muted){ + this._muted = true; + this._unmutedVolume = this.volume.value; + //maybe it should ramp here? + this.volume.value = -Infinity; + } return this; }; @@ -5918,8 +6143,11 @@ * the output was muted. * @returns {Tone.Master} `this` */ - Tone.Master.prototype.mute = function(){ - this.volume.value = this._unmutedVolume; + Tone.Master.prototype.unmute = function(){ + if (this._muted){ + this._muted = false; + this.volume.value = this._unmutedVolume; + } return this; }; @@ -6007,7 +6235,7 @@ options = this.defaultArg(options, Tone.Source.defaults); /** - * The onended callback when the source is done playing. + * Callback is invoked when the source is done playing. * @type {function} * @example * source.onended = function(){ @@ -6037,6 +6265,7 @@ * source.volume.value = -6; */ this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); + this._readOnly("volume"); /** * keeps track of the timeout for chaning the state @@ -6045,6 +6274,10 @@ * @private */ this._timeout = -1; + + //make the output explicitly stereo + this.output.channelCount = 2; + this.output.channelCountMode = "explicit"; }; Tone.extend(Tone.Source); @@ -6186,6 +6419,7 @@ this.stop(); clearTimeout(this._timeout); this.onended = function(){}; + this._writable("volume"); this.volume.dispose(); this.volume = null; }; @@ -6256,6 +6490,7 @@ //setup this.type = options.type; this.phase = this._phase; + this._readOnly(["frequency", "detune"]); }; Tone.extend(Tone.Oscillator, Tone.Source); @@ -6267,9 +6502,13 @@ * @type {Object} */ Tone.Oscillator.defaults = { + /** @type {string} */ "type" : "sine", + /** @type {Tone.Frequency} */ "frequency" : 440, + /** @type {number} */ "detune" : 0, + /** @type {number} */ "phase" : 0 }; @@ -6341,6 +6580,7 @@ * @memberOf Tone.Oscillator# * @type {string} * @name type + * @options ["sine", "square", "sawtooth", "triangle"] * @example * osc.type = "square"; * osc.type; //returns "square" @@ -6350,57 +6590,62 @@ return this._type; }, set : function(type){ - if (this.type !== type){ - var fftSize = 4096; - var halfSize = fftSize / 2; - - var real = new Float32Array(halfSize); - var imag = new Float32Array(halfSize); - - // Clear DC and Nyquist. - real[0] = 0; - imag[0] = 0; - - var shift = this._phase; - for (var n = 1; n < halfSize; ++n) { - var piFactor = 2 / (n * Math.PI); - var b; - switch (type) { - case "sine": - b = (n === 1) ? 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); - imag[n] = b * Math.cos(shift); - } else { - real[n] = 0; - imag[n] = 0; - } + 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); } - var periodicWave = this.context.createPeriodicWave(real, imag); - this._wave = periodicWave; - if (this._oscillator !== null){ - this._oscillator.setPeriodicWave(this._wave); + if (b !== 0){ + real[n] = -b * Math.sin(shift * n); + imag[n] = b * Math.cos(shift * n); + } else { + real[n] = 0; + imag[n] = 0; } - this._type = type; } + var periodicWave = this.context.createPeriodicWave(real, imag); + this._wave = periodicWave; + if (this._oscillator !== null){ + this._oscillator.setPeriodicWave(this._wave); + } + this._type = originalType; } }); @@ -6433,11 +6678,12 @@ 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; - this._wave = null; return this; }; @@ -6461,18 +6707,20 @@ * @type {WaveShaperNode} * @private */ - this._norm = this.input = this.output = new Tone.WaveShaper([0,1]); + 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.AND} `this` + * @returns {Tone.AudioToGain} `this` */ Tone.AudioToGain.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._norm.disconnect(); + this._norm.dispose(); this._norm = null; return this; }; @@ -6545,6 +6793,7 @@ //connect it up this.oscillator.chain(this._a2g, this._scaler); + this._readOnly(["amplitude", "frequency", "oscillator"]); }; Tone.extend(Tone.LFO, Tone.Oscillator); @@ -6662,7 +6911,7 @@ /** * The phase of the LFO * @memberOf Tone.LFO# - * @type {string} + * @type {number} * @name phase */ Object.defineProperty(Tone.LFO.prototype, "phase", { @@ -6687,6 +6936,7 @@ */ Tone.LFO.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable(["amplitude", "frequency", "oscillator"]); this.oscillator.dispose(); this.oscillator = null; this._scaler.dispose(); @@ -6732,6 +6982,8 @@ * @type {AudioParam} */ this.threshold = this._compressor.threshold; + + this._readOnly("threshold"); }; Tone.extend(Tone.Limiter); @@ -6744,6 +6996,7 @@ Tone.prototype.dispose.call(this); this._compressor.dispose(); this._compressor = null; + this._writable("threshold"); this.threshold = null; return this; }; @@ -6760,92 +7013,63 @@ * * @extends {Tone} * @constructor - * @param {number} [minDelay=0.1] the minimum delay time which the filter can have - * @param {number} [maxDelay=1] the maximum delay time which the filter can have + * @param {number} [delayTime=0.1] The delay time of the comb filter + * @param {number} [resonance=0.5] The resonance (feedback) of the comb filter + * @param {Tone.Frequency} [dampening=3000] The dampending cutoff of the lowpass filter */ Tone.LowpassCombFilter = function(){ Tone.call(this); - var options = this.optionsObject(arguments, ["minDelay", "maxDelay"], Tone.LowpassCombFilter.defaults); - - //the delay * samplerate = number of samples. - // buffersize / number of samples = number of delays needed per buffer frame - var delayCount = Math.ceil(this.bufferSize / (options.minDelay * this.context.sampleRate)); - //set some ranges - delayCount = Math.min(delayCount, 10); - delayCount = Math.max(delayCount, 1); + var options = this.optionsObject(arguments, ["delayTime", "resonance", "dampening"], Tone.LowpassCombFilter.defaults); /** - * the number of filter delays - * @type {number} - * @private - */ - this._filterDelayCount = delayCount; - - /** - * @type {Array.} + * the delay node + * @type {DelayNode} * @private */ - this._filterDelays = new Array(this._filterDelayCount); - - /** - * the dampening control - * @type {Tone.Signal} - */ - this.dampening = new Tone.Signal(options.dampening, Tone.Signal.Units.Frequency); + this._delay = this.input = this.context.createDelay(1); /** - * the resonance control + * the delayTime * @type {Tone.Signal} */ - this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); - - /** - * scale the resonance value to the normal range - * @type {Tone.Scale} - * @private - */ - this._resScale = new Tone.ScaleExp(0.01, 1 / this._filterDelayCount - 0.001, 0.5); + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); /** - * internal flag for keeping track of when frequency - * correction has been used - * @type {boolean} + * the lowpass filter + * @type {BiquadFilterNode} * @private */ - this._highFrequencies = false; + this._lowpass = this.output = this.context.createBiquadFilter(); + this._lowpass.Q.value = 0; + this._lowpass.type = "lowpass"; + this._lowpass.frequency.value = options.dampening; /** - * internal counter of delayTime - * @type {Tone.Time} - * @private + * the dampening control + * @type {Tone.Signal} */ - this._delayTime = options.delayTime; + this.dampening = new Tone.Signal(this._lowpass.frequency, Tone.Signal.Units.Frequency); /** - * the feedback node + * the feedback gain * @type {GainNode} * @private */ this._feedback = this.context.createGain(); - //make the filters - for (var i = 0; i < this._filterDelayCount; i++) { - var filterDelay = new FilterDelay(options.minDelay, this.dampening); - filterDelay.connect(this._feedback); - this._filterDelays[i] = filterDelay; - } + /** + * the resonance control + * @type {Tone.Signal} + */ + this.resonance = new Tone.Signal(options.resonance, Tone.Signal.Units.Normal); //connections - this.input.connect(this._filterDelays[0]); - this._feedback.connect(this._filterDelays[0]); - this.connectSeries.apply(this, this._filterDelays); - //resonance control - this.resonance.chain(this._resScale, this._feedback.gain); - this._feedback.connect(this.output); - //set the delay to the min value initially - this.delayTime = options.delayTime; + this._delay.chain(this._lowpass, this._feedback, this._delay); + this.delayTime.connect(this._delay.delayTime); + this.resonance.connect(this._feedback.gain); + this._readOnly(["dampening", "resonance", "delayTime"]); }; Tone.extend(Tone.LowpassCombFilter); @@ -6857,55 +7081,9 @@ * @type {Object} */ Tone.LowpassCombFilter.defaults = { + "delayTime" : 0.1, "resonance" : 0.5, - "dampening" : 3000, - "minDelay" : 0.1, - "maxDelay" : 1, - "delayTime" : 0.1 - }; - - /** - * The delay time of the LowpassCombFilter. Auto corrects - * for sample offsets for small delay amounts. - * @memberOf Tone.LowpassCombFilter# - * @type {Tone.Time} - * @name delayTime - */ - Object.defineProperty(Tone.LowpassCombFilter.prototype, "delayTime", { - get : function(){ - return this._delayTime; - }, - set : function(delayAmount){ - this.setDelayTimeAtTime(delayAmount); - } - }); - - /** - * set the delay time for the comb filter at a specific time. - * @param {Tone.Time} delayAmount the amount of delay time - * @param {Tone.Time} [time=now] when the delay time should be set - */ - Tone.LowpassCombFilter.prototype.setDelayTimeAtTime = function(delayAmount, time){ - this._delayTime = this.toSeconds(delayAmount); - //the number of samples to delay by - var sampleRate = this.context.sampleRate; - var delaySamples = sampleRate * this._delayTime; - // delayTime corection when frequencies get high - time = this.toSeconds(time); - var cutoff = 100; - if (delaySamples < cutoff){ - this._highFrequencies = true; - var changeNumber = Math.round((delaySamples / cutoff) * this._filterDelayCount); - for (var i = 0; i < changeNumber; i++) { - this._filterDelays[i].setDelay(1 / sampleRate + this._delayTime, time); - } - this._delayTime = Math.floor(delaySamples) / sampleRate; - } else if (this._highFrequencies){ - this._highFrequencies = false; - for (var j = 0; j < this._filterDelays.length; j++) { - this._filterDelays[j].setDelay(this._delayTime, time); - } - } + "dampening" : 3000 }; /** @@ -6914,62 +7092,22 @@ */ Tone.LowpassCombFilter.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - //dispose the filter delays - for (var i = 0; i < this._filterDelays.length; i++) { - this._filterDelays[i].dispose(); - this._filterDelays[i] = null; - } - this._filterDelays = null; + this._writable(["dampening", "resonance", "delayTime"]); this.dampening.dispose(); this.dampening = null; this.resonance.dispose(); this.resonance = null; - this._resScale.dispose(); - this._resScale = 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; }; - // BEGIN HELPER CLASS // - - /** - * FilterDelay - * @private - * @constructor - * @extends {Tone} - */ - var FilterDelay = function(maxDelay, filterFreq){ - this.delay = this.input = this.context.createDelay(maxDelay); - this.delay.delayTime.value = maxDelay; - - this.filter = this.output = this.context.createBiquadFilter(); - filterFreq.connect(this.filter.frequency); - - this.filter.type = "lowpass"; - this.filter.Q.value = 0; - - this.delay.connect(this.filter); - }; - - Tone.extend(FilterDelay); - - FilterDelay.prototype.setDelay = function(amount, time) { - this.delay.delayTime.setValueAtTime(amount, time); - }; - - /** - * clean up - */ - FilterDelay.prototype.dispose = function(){ - this.delay.disconnect(); - this.delay = null; - this.filter.disconnect(); - this.filter = null; - }; - - // END HELPER CLASS // - return Tone.LowpassCombFilter; }); ToneModule( function(Tone){ @@ -7212,159 +7350,6 @@ - /** - * @class Coerces the incoming mono or stereo signal into a stereo 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; - }); - ToneModule( 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 {Tone.Signal} - */ - this.lowFrequency = this._splitter.lowFrequency; - - /** - * mid/high crossover frequency - * @type {Tone.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); - }; - - 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.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; - }); - ToneModule( function(Tone){ - - - /** * @class Split the incoming signal into left and right channels * @@ -7376,13 +7361,13 @@ */ Tone.Split = function(){ - Tone.call(this, 1, 2); + Tone.call(this, 0, 2); /** * @type {ChannelSplitterNode} * @private */ - this._splitter = this.context.createChannelSplitter(2); + this._splitter = this.input = this.context.createChannelSplitter(2); /** * left channel output @@ -7399,7 +7384,6 @@ this.right = this.output[1] = this.context.createGain(); //connections - this.input.connect(this._splitter); this._splitter.connect(this.left, 0, 0); this._splitter.connect(this.right, 1, 0); }; @@ -7424,1249 +7408,2451 @@ return Tone.Split; }); ToneModule( - function(Tone){ + function(Tone){ /** - * Panner. - * - * @class Equal Power Gain L/R Panner. Not 3D. - * 0 = 100% Left - * 1 = 100% Right - * - * @constructor + * @class Seperates the mid channel from the side channel. Has two outputs labeled + * `mid` and `side` or `output[0]` and `output[1]`.
+ * http://musicdsp.org/showArchiveComment.php?ArchiveID=173
+ * http://www.kvraudio.com/forum/viewtopic.php?t=212587
+ * M = (L+R)/sqrt(2); // obtain mid-signal from left and right
+ * S = (L-R)/sqrt(2); // obtain side-signal from left and righ
+ * * @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. + * @constructor */ - Tone.Panner = function(initialPan){ + Tone.MidSideSplit = function(){ + Tone.call(this, 0, 2); - Tone.call(this, 1, 0); - /** - * the dry/wet knob - * @type {Tone.CrossFade} + * split the incoming signal into left and right channels + * @type {Tone.Split} * @private */ - this._crossFade = new Tone.CrossFade(); - + this._split = this.input = new Tone.Split(); + /** - * @type {Tone.Merge} - * @private + * The mid send. Connect to mid processing. + * @type {Tone.Expr} */ - this._merger = this.output = new Tone.Merge(); - + this.mid = this.output[0] = new Tone.Expr("($0 + $1) * $2"); + /** - * @type {Tone.Split} - * @private + * The side output. Connect to side processing. + * @type {Tone.Expr} */ - this._splitter = new Tone.Split(); - - /** - * the pan control - * @type {Tone.Signal} - */ - this.pan = this._crossFade.fade; - - //CONNECTIONS: - this.input.connect(this._splitter.left); - this.input.connect(this._splitter.right); - //left channel is dry, right channel is wet - this._splitter.connect(this._crossFade, 0, 0); - this._splitter.connect(this._crossFade, 1, 1); - //merge it back together - this._crossFade.a.connect(this._merger.left); - this._crossFade.b.connect(this._merger.right); + this.side = this.output[1] = new Tone.Expr("($0 - $1) * $2"); - //initial value - this.pan.value = this.defaultArg(initialPan, 0.5); + 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.Panner); + Tone.extend(Tone.MidSideSplit); + + /** + * a constant signal equal to 1 / sqrt(2) + * @type {Tone.Signal} + * @private + * @static + */ + var sqrtTwo = null; + + Tone._initAudioContext(function(){ + sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + }); /** * clean up - * @returns {Tone.Panner} `this` + * @returns {Tone.MidSideSplit} `this` */ - Tone.Panner.prototype.dispose = function(){ + Tone.MidSideSplit.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._crossFade.dispose(); - this._crossFade = null; - this._splitter.dispose(); - this._splitter = null; - this._merger.dispose(); - this._merger = null; - this.pan = null; + this.mid.dispose(); + this.mid = null; + this.side.dispose(); + this.side = null; + this._split.dispose(); + this._split = null; return this; }; - return Tone.Panner; + return Tone.MidSideSplit; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ /** - * @class A Panner and volume in one. + * @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 {@link Tone.MidSideSplit}.
+ * M/S send/return
+ * L = (M+S)/sqrt(2); // obtain left signal from mid and side
+ * R = (M-S)/sqrt(2); // obtain right signal from mid and side
* - * @extends {Tone} + * @extends {Tone.StereoEffect} * @constructor - * @example - * var panVol = new Tone.PanVol(0.25, -12); */ - Tone.PanVol = function(pan, volume){ + Tone.MidSideMerge = function(){ + Tone.call(this, 2, 0); + /** - * the panning node - * @type {Tone.Panner} + * 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._panner = this.input = new Tone.Panner(pan); + this._left = new Tone.Expr("($0 + $1) * $2"); /** - * the output node - * @type {GainNode} + * The side signal input. + * @type {GainNode} */ - this.output = this.context.createGain(); + this.side = this.input[1] = this.context.createGain(); /** - * The volume control in decibels. - * @type {Tone.Signal} + * recombine the mid/side into Right + * @type {Tone.Expr} + * @private */ - this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); - this.volume.value = this.defaultArg(volume, 0); + this._right = new Tone.Expr("($0 - $1) * $2"); /** - * the panning control - * @type {Tone.Panner} + * Merge the left/right signal back into a stereo signal. + * @type {Tone.Merge} * @private */ - this.pan = this._panner.pan; + this._merge = this.output = new Tone.Merge(); - //connections - this._panner.connect(this.output); + 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.PanVol); + Tone.extend(Tone.MidSideMerge); + + /** + * A constant signal equal to 1 / sqrt(2). + * @type {Tone.Signal} + * @private + * @static + */ + var sqrtTwo = null; + + Tone._initAudioContext(function(){ + sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + }); /** * clean up - * @returns {Tone.PanVol} `this` + * @returns {Tone.MidSideMerge} `this` */ - Tone.PanVol.prototype.dispose = function(){ + Tone.MidSideMerge.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._panner.dispose(); - this._panner = null; - this.volume.dispose(); - this.volume = null; - this.pan = null; + 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.PanVol; + return Tone.MidSideMerge; }); ToneModule( function(Tone){ /** - * @deprecated - * @class Record an input into an array or AudioBuffer. - * it is limited in that the recording length needs to be known beforehand. - * Mostly used internally for testing. + * @class MidSideCompressor applies two different compressors to the mid + * and side signal components. * + * @extends {Tone.MidSideEffect} * @constructor - * @extends {Tone} - * @param {number} channels */ - Tone.Recorder = function(channels){ - - console.warn("Tone.Recorder is deprecated. It will be removed in next version"); - - Tone.call(this); - - /** - * the number of channels in the recording - * @type {number} - */ - this.channels = this.defaultArg(channels, 1); - - /** - * @private - * @type {ScriptProcessorNode} - */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, this.channels, 1); - this._jsNode.onaudioprocess = this._audioprocess.bind(this); + Tone.MidSideCompressor = function(options){ - /** - * Float32Array for each channel - * @private - * @type {Array} - */ - this._recordBuffers = new Array(this.channels); + options = this.defaultArg(options, Tone.MidSideCompressor.defaults); /** - * @type {number} + * the mid/side split + * @type {Tone.MidSideSplit} * @private */ - this._recordStartSample = 0; + this._midSideSplit = this.input = new Tone.MidSideSplit(); /** - * @type {number} + * the mid/side recombination + * @type {Tone.MidSideMerge} * @private */ - this._recordEndSample = 0; + this._midSideMerge = this.output = new Tone.MidSideMerge(); /** - * @type {number} - * @private + * The compressor applied to the mid signal + * @type {Tone.Compressor} */ - this._recordDuration = 0; + this.mid = new Tone.Compressor(options.mid); /** - * @type {RecordState} - */ - this.state = RecordState.STOPPED; - - /** - * @private - * @type {number} - */ - this._recordBufferOffset = 0; - - /** - * callback invoked when the recording is over - * @private - * @type {function(Float32Array)} + * The compressor applied to the side signal + * @type {Tone.Compressor} */ - this._callback = function(){}; + this.side = new Tone.Compressor(options.side); - //connect it up - this.input.connect(this._jsNode); - //pass thru audio - this.input.connect(this.output); - //so it doesn't get garbage collected - this._jsNode.noGC(); - //clear it to start - this.clear(); + 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.Recorder); - - /** - * internal method called on audio process - * - * @private - * @param {AudioProcessorEvent} event - */ - Tone.Recorder.prototype._audioprocess = function(event){ - if (this.state === RecordState.STOPPED){ - return; - } else if (this.state === RecordState.RECORDING){ - //check if it's time yet - var now = this.defaultArg(event.playbackTime, this.now()); - var processPeriodStart = this.toSamples(now); - var bufferSize = this._jsNode.bufferSize; - var processPeriodEnd = processPeriodStart + bufferSize; - var bufferOffset, len; - if (processPeriodStart > this._recordEndSample){ - this.state = RecordState.STOPPED; - this._callback(this._recordBuffers); - } else if (processPeriodStart > this._recordStartSample) { - bufferOffset = 0; - len = Math.min(this._recordEndSample - processPeriodStart, bufferSize); - this._recordChannels(event.inputBuffer, bufferOffset, len, bufferSize); - } else if (processPeriodEnd > this._recordStartSample) { - len = processPeriodEnd - this._recordStartSample; - bufferOffset = bufferSize - len; - this._recordChannels(event.inputBuffer, bufferOffset, len, bufferSize); - } - - } - }; + Tone.extend(Tone.MidSideCompressor); /** - * record an input channel - * @param {AudioBuffer} inputBuffer - * @param {number} from - * @param {number} to - * @private + * @const + * @static + * @type {Object} */ - Tone.Recorder.prototype._recordChannels = function(inputBuffer, from, to, bufferSize){ - var offset = this._recordBufferOffset; - var buffers = this._recordBuffers; - for (var channelNum = 0; channelNum < inputBuffer.numberOfChannels; channelNum++){ - var channel = inputBuffer.getChannelData(channelNum); - if ((from === 0) && (to === bufferSize)){ - //set the whole thing - this._recordBuffers[channelNum].set(channel, offset); - } else { - for (var i = from; i < from + to; i++){ - var zeroed = i - from; - buffers[channelNum][zeroed + offset] = channel[i]; - } - } - } - this._recordBufferOffset += to; - }; - - /** - * Record for a certain period of time - * - * will clear the internal buffer before starting - * - * @param {Tone.Time} duration - * @param {Tone.Time} wait the wait time before recording - * @param {function(Float32Array)} callback the callback to be invoked when the buffer is done recording - * @returns {Tone.Recorder} `this` - */ - Tone.Recorder.prototype.record = function(duration, startTime, callback){ - if (this.state === RecordState.STOPPED){ - this.clear(); - this._recordBufferOffset = 0; - startTime = this.defaultArg(startTime, 0); - this._recordDuration = this.toSamples(duration); - this._recordStartSample = this.toSamples("+"+startTime); - this._recordEndSample = this._recordStartSample + this._recordDuration; - for (var i = 0; i < this.channels; i++){ - this._recordBuffers[i] = new Float32Array(this._recordDuration); - } - this.state = RecordState.RECORDING; - this._callback = this.defaultArg(callback, function(){}); + 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 } - return this; }; /** - * clears the recording buffer - * @returns {Tone.PanVol} `this` + * clean up + * @returns {Tone.MidSideCompressor} `this` */ - Tone.Recorder.prototype.clear = function(){ - for (var i = 0; i < this.channels; i++){ - this._recordBuffers[i] = null; - } - this._recordBufferOffset = 0; + 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; + }); + ToneModule( function(Tone){ - /** - * true if there is nothing in the buffers - * @return {boolean} - */ - Tone.Recorder.prototype.isEmpty = function(){ - return this._recordBuffers[0] === null; - }; + /** - * @return {Array} + * @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.Recorder.prototype.getFloat32Array = function(){ - if (this.isEmpty()){ - return null; - } else { - return this._recordBuffers; - } - }; + Tone.Mono = function(){ + Tone.call(this, 1, 0); - /** - * @return {AudioBuffer} - */ - Tone.Recorder.prototype.getAudioBuffer = function(){ - if (this.isEmpty()){ - return null; - } else { - var audioBuffer = this.context.createBuffer(this.channels, this._recordBuffers[0].length, this.context.sampleRate); - for (var channelNum = 0; channelNum < audioBuffer.numberOfChannels; channelNum++){ - var channel = audioBuffer.getChannelData(channelNum); - channel.set(this._recordBuffers[channelNum]); - } - return audioBuffer; - } + /** + * 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.PanVol} `this` + * @returns {Tone.Mono} `this` */ - Tone.Recorder.prototype.dispose = function(){ + Tone.Mono.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._jsNode.disconnect(); - this._jsNode.onaudioprocess = undefined; - this._jsNode = null; - this._recordBuffers = null; + this._merge.dispose(); + this._merge = null; return this; }; - /** - * @enum {string} - */ - var RecordState = { - STOPPED : "stopped", - SCHEDULED : "scheduled", - RECORDING : "recording" - }; - - return Tone.Recorder; + return Tone.Mono; }); - ToneModule( - function(Tone){ + ToneModule( function(Tone){ /** - * @class An envelope which can be scaled to any range. - * Useful for applying an envelope to a filter + * @class A compressor with seperate controls over low/mid/high dynamics * - * @extends {Tone.Envelope} + * @extends {Tone} * @constructor - * @param {Tone.Time|Object} [attack=0.01] the attack time in seconds - * @param {Tone.Time} [decay=0.1] the decay time in seconds - * @param {number} [sustain=0.5] a percentage (0-1) of the full amplitude - * @param {Tone.Time} [release=1] the release time in seconds + * @param {Object} options the low/mid/high compressor settings in a single object * @example - * var scaledEnv = new Tone.ScaledEnvelope({ - * "attack" : 0.2, - * "min" : 200, - * "max" : 2000 - * }); - * scaledEnv.connect(oscillator.frequency); + * var multiband = new Tone.MultibandCompressor({ + * "lowFrequency" : 200, + * "highFrequency" : 1300 + * "low" : { + * "threshold" : -12 + * } + * }) */ - Tone.ScaledEnvelope = function(){ + Tone.MultibandCompressor = function(options){ - //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); + options = this.defaultArg(arguments, Tone.MultibandCompressor.defaults); - /** - * scale the incoming signal by an exponent - * @type {Tone.Pow} + /** + * split the incoming signal into high/mid/low + * @type {Tone.MultibandSplit} * @private */ - this._exp = this.output = new Tone.Pow(options.exponent); + this._splitter = this.input = new Tone.MultibandSplit({ + "lowFrequency" : options.lowFrequency, + "highFrequency" : options.highFrequency + }); /** - * scale the signal to the desired range - * @type {Tone.Multiply} - * @private + * low/mid crossover frequency + * @type {Tone.Signal} */ - this._scale = this.output = new Tone.Scale(options.min, options.max); - - this._sig.chain(this._exp, this._scale); - }; - - Tone.extend(Tone.ScaledEnvelope, Tone.Envelope); + this.lowFrequency = this._splitter.lowFrequency; + + /** + * mid/high crossover frequency + * @type {Tone.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); /** - * the default parameters + * @const * @static + * @type {Object} */ - Tone.ScaledEnvelope.defaults = { - "min" : 0, - "max" : 1, - "exponent" : 1 + Tone.MultibandCompressor.defaults = { + "low" : Tone.Compressor.defaults, + "mid" : Tone.Compressor.defaults, + "high" : Tone.Compressor.defaults, + "lowFrequency" : 250, + "highFrequency" : 2000 }; /** - * The envelope's min output value. This is the value which it - * starts at. - * @memberOf Tone.ScaledEnvelope# - * @type {number} - * @name min + * clean up + * @returns {Tone.MultibandCompressor} `this` */ - Object.defineProperty(Tone.ScaledEnvelope.prototype, "min", { - get : function(){ - return this._scale.min; - }, - set : function(min){ - this._scale.min = min; - } - }); + 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; + }; - /** - * 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; - } - }); + return Tone.MultibandCompressor; + }); + ToneModule( function(Tone){ + + /** - * The envelope's exponent value. - * @memberOf Tone.ScaledEnvelope# - * @type {number} - * @name exponent + * @class Maps a gain value [0, 1] to an audio value [-1, 1] + * + * @extends {Tone.SignalBase} + * @constructor + * @example + * var g2a = new Tone.GainToAudio(); */ - Object.defineProperty(Tone.ScaledEnvelope.prototype, "exponent", { - get : function(){ - return this._exp.value; - }, - set : function(exp){ - this._exp.value = exp; - } - }); - + 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.ScaledEnvelope} `this` + * @returns {Tone.GainToAudio} `this` */ - Tone.ScaledEnvelope.prototype.dispose = function(){ - Tone.Envelope.prototype.dispose.call(this); - this._scale.dispose(); - this._scale = null; - this._exp.dispose(); - this._exp = null; + Tone.GainToAudio.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._norm.dispose(); + this._norm = null; return this; }; - return Tone.ScaledEnvelope; + return Tone.GainToAudio; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ /** - * @class Buffer loading and storage. Tone.Buffer is used internally by all - * classes that make requests for audio files such as {@link Tone.Player}, - * {@link Tone.Sampler} and {@link Tone.Convolver} . - *

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 `onload`, `onprogress`, - * and `onerror`. - * - * @constructor - * @param {AudioBuffer|string} url the url to load, or the audio buffer to set + * 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.Buffer = function(){ + Tone.Panner = function(initialPan){ - var options = this.optionsObject(arguments, ["url", "onload"], Tone.Buffer.defaults); + Tone.call(this); /** - * stores the loaded AudioBuffer - * @type {AudioBuffer} + * indicates if the panner is using the new StereoPannerNode internally + * @type {boolean} * @private */ - this._buffer = null; + this._hasStereoPanner = this.isFunction(this.context.createStereoPanner); - /** - * the url of the buffer. `undefined` if it was - * constructed with a buffer - * @type {string} - * @readOnly - */ - this.url = undefined; + if (this._hasStereoPanner){ - /** - * indicates if the buffer is loaded or not - * @type {boolean} - * @readOnly - */ - this.loaded = false; + /** + * the panner node + * @type {StereoPannerNode} + * @private + */ + this._panner = this.input = this.output = this.context.createStereoPanner(); - /** - * the callback to invoke when everything is loaded - * @type {function} - */ - this.onload = options.onload.bind(this, this); + /** + * the pan control + * @type {Tone.Signal} + */ + this.pan = new Tone.Signal(0, Tone.Signal.Units.Normal); - 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); - } - }; + /** + * scale the pan signal to between -1 and 1 + * @type {Tone.WaveShaper} + * @private + */ + this._scalePan = new Tone.GainToAudio(); - Tone.extend(Tone.Buffer); + //connections + this.pan.chain(this._scalePan, this._panner.pan); + + } else { - /** - * the default parameters - * - * @static - * @const - * @type {Object} - */ - Tone.Buffer.defaults = { - "url" : undefined, - "onload" : function(){}, + /** + * 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 {Tone.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); + /** - * set the buffer - * @param {AudioBuffer|Tone.Buffer} buffer the buffer - * @returns {Tone.Buffer} `this` + * clean up + * @returns {Tone.Panner} `this` */ - Tone.Buffer.prototype.set = function(buffer){ - if (buffer instanceof Tone.Buffer){ - this._buffer = buffer.get(); + 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._buffer = buffer; + this._crossFade.dispose(); + this._crossFade = null; + this._splitter.dispose(); + this._splitter = null; + this._merger.dispose(); + this._merger = null; + this.pan = null; } - this.loaded = true; return this; }; + return Tone.Panner; + }); + ToneModule( function(Tone){ + + + /** - * @return {AudioBuffer} the audio buffer + * @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.Buffer.prototype.get = function(){ - return this._buffer; + Tone.Volume = function(pan, 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.Signal.Units.Decibels); + this.volume.value = this.defaultArg(volume, 0); + + this._readOnly("volume"); }; + Tone.extend(Tone.Volume); + /** - * @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` + * clean up + * @returns {Tone.Volume} `this` */ - Tone.Buffer.prototype.load = function(url, callback){ - this.url = url; - this.onload = this.defaultArg(callback, this.onload); - Tone.Buffer._addToQueue(url, 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; + }); + ToneModule( function(Tone){ + + + /** - * dispose and disconnect - * @returns {Tone.Buffer} `this` + * @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.Buffer.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - Tone.Buffer._removeFromQueue(this); - this._buffer = null; - this.onload = null; - return this; + 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 {Tone.Signal} + */ + this.volume = this._volume.volume; + + //connections + this._panner.connect(this._volume); + + this._readOnly(["pan", "volume"]); }; - /** - * 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; - } - }, - }); + Tone.extend(Tone.PanVol); - /////////////////////////////////////////////////////////////////////////// - // STATIC METHODS - /////////////////////////////////////////////////////////////////////////// - /** - * the static queue for all of the xhr requests - * @type {Array} - * @private + * clean up + * @returns {Tone.PanVol} `this` */ - Tone.Buffer._queue = []; + 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; + }; - /** - * the array of current downloads - * @type {Array} - * @private - */ - Tone.Buffer._currentDownloads = []; + return Tone.PanVol; + }); + ToneModule( + function(Tone){ + + /** - * the total number of downloads - * @type {number} - * @private + * @class An envelope which can be scaled to any range. + * Useful for applying an envelope to a filter + * + * @extends {Tone.Envelope} + * @constructor + * @param {Tone.Time|Object} [attack=0.01] the attack time in seconds + * @param {Tone.Time} [decay=0.1] the decay time in seconds + * @param {number} [sustain=0.5] a percentage (0-1) of the full amplitude + * @param {Tone.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.Buffer._totalDownloads = 0; + 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 maximum number of simultaneous downloads + * the default parameters * @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(); + Tone.ScaledEnvelope.defaults = { + "min" : 0, + "max" : 1, + "exponent" : 1 }; /** - * 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 + * The envelope's min output value. This is the value which it + * starts at. + * @memberOf Tone.ScaledEnvelope# + * @type {number} + * @name min */ - 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; - } + Object.defineProperty(Tone.ScaledEnvelope.prototype, "min", { + get : function(){ + return this._scale.min; + }, + set : function(min){ + this._scale.min = min; } - }; + }); /** - * load the next buffer in the queue - * @private + * 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 */ - 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); - 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; + Object.defineProperty(Tone.ScaledEnvelope.prototype, "max", { + get : function(){ + return this._scale.max; + }, + set : function(max){ + this._scale.max = max; } - }; + }); /** - * internal progress event handler - * @private + * The envelope's exponent value. + * @memberOf Tone.ScaledEnvelope# + * @type {number} + * @name exponent */ - 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; + Object.defineProperty(Tone.ScaledEnvelope.prototype, "exponent", { + get : function(){ + return this._exp.value; + }, + set : function(exp){ + this._exp.value = exp; } - var currentDownloadProgress = currentDLLen - inprogress; - var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress; - Tone.Buffer.onprogress(completed / Tone.Buffer._totalDownloads); + }); + + /** + * 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; + }); + ToneModule( function(Tone){ + + + /** - * makes an xhr reqest for the selected url - * Load the audio file 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 function - * @returns {XMLHttpRequest} returns the XHR + * @class Buffer loading and storage. Tone.Buffer is used internally by all + * classes that make requests for audio files such as {@link Tone.Player}, + * {@link Tone.Sampler} and {@link Tone.Convolver} . + *

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 `onload`, `onprogress`, + * and `onerror`. + * + * @constructor + * @extends {Tone} + * @param {AudioBuffer|string} url the url to load, or the audio buffer to set */ - 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; + 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. `undefined` 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); + /** - * callback when all of the buffers in the queue have loaded + * the default parameters + * * @static - * @type {function} - * @example - * //invoked when all of the queued samples are done loading - * Tone.Buffer.onload = function(){ - * console.log("everything is loaded"); - * }; + * @const + * @type {Object} */ - Tone.Buffer.onload = function(){}; + Tone.Buffer.defaults = { + "url" : undefined, + "onload" : function(){}, + "reverse" : false + }; /** - * 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) + "%"); - * }; + * set the buffer + * @param {AudioBuffer|Tone.Buffer} buffer the buffer + * @returns {Tone.Buffer} `this` */ - Tone.Buffer.onprogress = function(){}; + Tone.Buffer.prototype.set = function(buffer){ + if (buffer instanceof Tone.Buffer){ + this._buffer = buffer.get(); + } else { + this._buffer = buffer; + } + this.loaded = true; + return this; + }; /** - * 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); - * } + * @return {AudioBuffer} the audio buffer */ - Tone.Buffer.onerror = function(){}; - - return Tone.Buffer; - }); - ToneModule( function(Tone){ - - + Tone.Buffer.prototype.get = function(){ + return this._buffer; + }; /** - * buses are another way of routing audio - * - * augments Tone.prototype to include send and recieve + * @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` */ - - /** - * All of the routes - * - * @type {Object} - * @static - * @private - */ - var Buses = {}; + Tone.Buffer.prototype.load = function(url, callback){ + this.url = url; + this.onload = this.defaultArg(callback, this.onload); + Tone.Buffer._addToQueue(url, this); + return this; + }; /** - * send signal to a channel name - * defined in "Tone/core/Bus" - * - * @param {string} channelName - * @param {number} amount - * @return {GainNode} + * dispose and disconnect + * @returns {Tone.Buffer} `this` */ - Tone.prototype.send = function(channelName, amount){ - if (!Buses.hasOwnProperty(channelName)){ - Buses[channelName] = this.context.createGain(); - } - var sendKnob = this.context.createGain(); - sendKnob.gain.value = this.defaultArg(amount, 1); - this.output.chain(sendKnob, Buses[channelName]); - return sendKnob; + 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; }; /** - * recieve the input from the desired channelName to the input - * defined in "Tone/core/Bus" - * - * @param {string} channelName - * @param {AudioNode} [input=this.input] if no input is selected, the - * input of the current node is - * chosen. - * @returns {Tone} `this` + * the duration of the buffer + * @memberOf Tone.Buffer# + * @type {number} + * @name duration + * @readOnly */ - Tone.prototype.receive = function(channelName, input){ - if (!Buses.hasOwnProperty(channelName)){ - Buses[channelName] = this.context.createGain(); - } - if (this.isUndef(input)){ - input = this.input; + 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)); + } } - Buses[channelName].connect(input); return this; }; - return Tone; - }); - ToneModule( function(Tone){ - - - /** - * Frequency can be described similar to time, except ultimately the - * values are converted to frequency instead of seconds. A number + * if the buffer is reversed or not + * @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 + * Load the audio file 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 function + * @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 = function(){}; + + /** + * 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 = function(){}; + + /** + * 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 = function(){}; + + return Tone.Buffer; + }); + ToneModule( 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 signal to a channel name + * defined in "Tone/core/Bus" + * + * @param {string} channelName + * @param {number} amount the amount of the source to send to the bus. in Decibels. + * @return {GainNode} + */ + 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 + * defined in "Tone/core/Bus" + * + * @param {string} channelName + * @param {AudioNode} [input=this.input] if no input is selected, the + * input of the current node is + * chosen. + * @returns {Tone} `this` + */ + 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; + }); + ToneModule( function(Tone){ + + + + /** + * 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 * {@link Tone.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 {number|string|Tone.Time} Tone.Frequency + * @typedef {number|string|Tone.Time} Tone.Frequency + */ + + /** + * @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 {Tone.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. + * + * 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. + * + * 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)`. + * + * 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 (i.e. A4 to 440) + * defined in "Tone/core/Note" + * + * @param {string} note + * @return {number} + */ + 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 Tone.Time and converts them to their frequency as a number in hertz. + * @param {Tone.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). + * Defined in "Tone/core/Note" + * @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 + * defined in "Tone/core/Note" + * + * @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; + }); + ToneModule( 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 {Tone.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; + }); + ToneModule( function(Tone){ + + + + /** + * @class AutoFilter is a Tone.Panner with an LFO connected to the pan amount + * + * @constructor + * @extends {Tone.Effect} + * @param {Tone.Time} [frequency=1] (optional) rate in HZ of the filter + * @param {number} [depth=0.5] The depth of the effect + * @example + * var autoPanner = new Tone.AutoFilter("4n"); + */ + Tone.AutoFilter = function(){ + + var options = this.optionsObject(arguments, ["frequency"], Tone.AutoFilter.defaults); + Tone.Effect.call(this, options); + + /** + * the lfo which drives the panning + * @type {Tone.LFO} + * @private + */ + this._lfo = new Tone.LFO(options.frequency, options.min, options.max); + + /** + * The amount of panning between left and right. + * 0 = always center. 1 = full range between left and right. + * @type {Tone.Signal} + */ + this.depth = this._lfo.amplitude; + + /** + * How fast the filter modulates between min and max. + * @type {Tone.Signal} + */ + this.frequency = this._lfo.frequency; + + /** + * the filter node + * @type {Tone.Filter} + * @private + */ + 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 {Tone.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 {Tone.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 {Tone.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 miniumum output of the AutoFilter. + * @memberOf Tone.AutoFilter# + * @type {number} + * @name min + */ + Object.defineProperty(Tone.AutoFilter.prototype, "min", { + get : function(){ + return this._lfo.min; + }, + set : function(min){ + this._lfo.min = min; + } + }); + + /** + * The maximum output of the AutoFilter. + * @memberOf Tone.AutoFilter# + * @type {number} + * @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; + }); + + ToneModule( 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(options.frequency, 0, 1); + + /** + * The amount of panning between left and right. + * 0 = always center. 1 = full range between left and right. + * @type {Tone.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 {Tone.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 {Tone.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 {Tone.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 {Tone.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; + }); + + ToneModule( + 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 {number} [baseFrequency=100] the frequency the filter is set + * to at the low point of the wah + * @param {number} [octaves=5] the number of octaves above the baseFrequency + * the filter will sweep to when fully open + * @param {number} [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 {Tone.Signal} + */ + this.gain = this._peaking.gain; + + /** + * The quality of the filter. + * @type {Tone.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 {Tone.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; + }); + ToneModule( + 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. + * @class Downsample incoming signal to a different bitdepth. * * @constructor - * @param {number|string} channel the channel name of the note - * @param {Tone.Time} time the time when the note will occur - * @param {string|number|Object|Array} value the value of the note + * @extends {Tone.Effect} + * @param {number} bits 1-8. + * @example + * var crusher = new Tone.BitCrusher(4); */ - Tone.Note = function(channel, time, value){ + 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); /** - * the value of the note. This value is returned - * when the channel callback is invoked. - * - * @type {string|number|Object} + * Subtract the input signal and the modulus of the input signal + * @type {Tone.Subtract} + * @private */ - this.value = value; + this._subtract = new Tone.Subtract(); /** - * the channel name or number - * - * @type {string|number} + * The mod function + * @type {Tone.Modulo} * @private */ - this._channel = channel; + this._modulo = new Tone.Modulo(invStepSize); /** - * an internal reference to the id of the timeline - * callback which is set. - * + * keeps track of the bits * @type {number} * @private */ - this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time); - }; + this._bits = options.bits; - /** - * 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); + //connect it up + this.effectSend.fan(this._subtract, this._modulo); + this._modulo.connect(this._subtract, 0, 1); + this._subtract.connect(this.effectReturn); }; - /** - * clean up - * @returns {Tone.Note} `this` - */ - Tone.Note.prototype.dispose = function(){ - Tone.Tranport.clearTimeline(this._timelineID); - this.value = null; - return this; - }; + Tone.extend(Tone.BitCrusher, Tone.Effect); /** - * @private + * the default values * @static * @type {Object} */ - var NoteChannels = {}; + Tone.BitCrusher.defaults = { + "bits" : 4 + }; /** - * invoke all of the callbacks on a specific channel - * @private + * The bit depth of the BitCrusher + * @memberOf Tone.BitCrusher# + * @type {number} + * @name bits */ - 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); - } - } + 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; } - } + }); /** - * 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 + * clean up + * @returns {Tone.BitCrusher} `this` */ - Tone.Note.route = function(channel, callback){ - if (NoteChannels.hasOwnProperty(channel)){ - NoteChannels[channel].push(callback); - } else { - NoteChannels[channel] = [callback]; - } - }; + 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; + }; - /** - * 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); - } - } - }; + return Tone.BitCrusher; + }); + + ToneModule( function(Tone){ + + /** - * Parses a score and registers all of the notes along the timeline. - * - * 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. - * - * 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)`. - * - * To convert MIDI files to score notation, take a look at utils/MidiToScore.js + * @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. + * http://music.columbia.edu/cmc/musicandcomputers/chapter4/04_06.php * + * @extends {Tone.Effect} + * @constructor + * @param {number} order The order of the chebyshev polynomial. Normal range between 1-100. * @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 + * var cheby = new Tone.Chebyshev(50); */ - 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 - /////////////////////////////////////////////////////////////////////////// + Tone.Chebyshev = function(){ - 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 options = this.optionsObject(arguments, ["order"], Tone.Chebyshev.defaults); + Tone.Effect.call(this); - var noteIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; + /** + * @type {WaveShaperNode} + * @private + */ + this._shaper = new Tone.WaveShaper(4096); - var middleC = 261.6255653005986; + /** + * holds onto the order of the filter + * @type {number} + * @private + */ + this._order = options.order; - /** - * convert a note name to frequency (i.e. A4 to 440) - * defined in "Tone/core/Note" - * - * @param {string} note - * @return {number} - */ - 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; - } + this.connectEffect(this._shaper); + this.order = options.order; + this.oversample = options.oversample; }; - /** - * 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.isNotation - */ - 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; + Tone.extend(Tone.Chebyshev, Tone.Effect); /** - * A method which accepts frequencies in the form - * of notes (`"C#4"`), frequencies as strings ("49hz"), frequency numbers, - * or Tone.Time and converts them to their frequency as a number in hertz. - * @param {Tone.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 + * @static + * @const + * @type {Object} */ - Tone.prototype.toFrequency = function(note, now){ - if (this.isNote(note)){ - note = this.noteToFrequency(note); - } - return this._overwrittenToFrequency(note, now); + Tone.Chebyshev.defaults = { + "order" : 1, + "oversample" : "none" }; /** - * Convert a note name (i.e. A4, C#5, etc to a frequency). - * Defined in "Tone/core/Note" - * @param {number} freq - * @return {string} + * 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.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(); + 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]; }; /** - * 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 + * 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 */ - Tone.prototype.intervalToFrequencyRatio = function(interval){ - return Math.pow(2,(interval/12)); - }; + 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; + } + }); /** - * 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" + * The oversampling of the effect. Can either be "none", "2x" or "4x". + * @memberOf Tone.Chebyshev# + * @type {string} + * @name oversample */ - Tone.prototype.midiToNote = function(midiNumber){ - var octave = Math.floor(midiNumber / 12) - 2; - var note = midiNumber % 12; - return noteIndexToNote[note] + octave; - }; + Object.defineProperty(Tone.Chebyshev.prototype, "oversample", { + get : function(){ + return this._shaper.oversample; + }, + set : function(oversampling){ + this._shaper.oversample = oversampling; + } + }); /** - * convert a note to it's midi value - * defined in "Tone/core/Note" - * - * @param {string} note the note name (i.e. "C3") - * @return {number} the midi value of that note - * @example - * tone.noteToMidi("C3"); // returns 60 + * clean up + * @returns {Tone.Chebyshev} `this` */ - 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; - } + Tone.Chebyshev.prototype.dispose = function(){ + Tone.Effect.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; + return this; }; - return Tone.Note; + return Tone.Chebyshev; }); - ToneModule( function(Tone){ + ToneModule( + 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. + * @class Creates an effect with an effectSendL/R and effectReturnL/R * - * @constructor - * @extends {Tone} - * @param {number} [initialWet=0] the starting wet value - * defaults to 100% wet + * @constructor + * @extends {Tone.Effect} */ - Tone.Effect = function(){ + Tone.StereoEffect = function(){ Tone.call(this); - - //get all of the defaults + //get the defaults var options = this.optionsObject(arguments, ["wet"], Tone.Effect.defaults); /** @@ -8684,210 +9870,141 @@ this.wet = this._dryWet.fade; /** - * connect the effectSend to the input of hte effect - * + * then split it + * @type {Tone.Split} + * @private + */ + this._split = new Tone.Split(); + + /** + * the effects send LEFT * @type {GainNode} * @private */ - this.effectSend = this.context.createGain(); + this.effectSendL = this._split.left; /** - * connect the output of the effect to the effectReturn - * + * the effects send RIGHT * @type {GainNode} * @private */ - this.effectReturn = this.context.createGain(); + this.effectSendR = this._split.right; - //connections - this.input.connect(this._dryWet.a); - this.input.connect(this.effectSend); - this.effectReturn.connect(this._dryWet.b); - this._dryWet.connect(this.output); - }; + /** + * the stereo effect merger + * @type {Tone.Merge} + * @private + */ + this._merge = new Tone.Merge(); - Tone.extend(Tone.Effect); + /** + * the effect return LEFT + * @type {GainNode} + */ + this.effectReturnL = this._merge.left; - /** - * @static - * @type {Object} - */ - Tone.Effect.defaults = { - "wet" : 1 - }; + /** + * the effect return RIGHT + * @type {GainNode} + */ + this.effectReturnR = this._merge.right; - /** - * bypass the effect - * @returns {Tone.Effect} `this` - */ - Tone.Effect.prototype.bypass = function(){ - this.wet.value = 0; - return this; + //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"]); }; - /** - * 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; - }; + Tone.extend(Tone.StereoEffect, Tone.Effect); /** - * tear down - * @returns {Tone.Effect} `this` + * clean up + * @returns {Tone.StereoEffect} `this` */ - Tone.Effect.prototype.dispose = function(){ + Tone.StereoEffect.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._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.Effect; + return Tone.StereoEffect; }); ToneModule( function(Tone){ - + /** - * @class AutoPanner is a Tone.Panner with an LFO connected to the pan amount + * @class Feedback Effect (a sound loop between an audio source and its own output) * * @constructor * @extends {Tone.Effect} - * @param {number} [frequency=1] (optional) rate in HZ of the left-right pan - * @example - * var autoPanner = new Tone.AutoPanner("4n"); + * @param {number|Object} [initialFeedback=0.125] the initial feedback value */ - Tone.AutoPanner = function(){ + Tone.FeedbackEffect = function(){ - var options = this.optionsObject(arguments, ["frequency"], Tone.AutoPanner.defaults); - Tone.Effect.call(this, options); + var options = this.optionsObject(arguments, ["feedback"]); + options = this.defaultArg(options, Tone.FeedbackEffect.defaults); - /** - * the lfo which drives the panning - * @type {Tone.LFO} - * @private - */ - this._lfo = new Tone.LFO(options.frequency, 0, 1); + Tone.Effect.call(this, options); /** - * The amount of panning between left and right. - * 0 = always center. 1 = full range between left and right. - * @type {Tone.Signal} + * controls the amount of feedback + * @type {Tone.Signal} */ - this.amount = this._lfo.amplitude; - + this.feedback = new Tone.Signal(options.feedback, Tone.Signal.Units.Normal); + /** - * the panner node which does the panning - * @type {Tone.Panner} + * the gain which controls the feedback + * @type {GainNode} * @private */ - this._panner = new Tone.Panner(); - - /** - * How fast the panner modulates - * @type {Tone.Signal} - */ - this.frequency = this._lfo.frequency; + this._feedbackGain = this.context.createGain(); - //connections - this.connectEffect(this._panner); - this._lfo.connect(this._panner.pan); - this.type = options.type; + //the feedback loop + this.effectReturn.chain(this._feedbackGain, this.effectSend); + this.feedback.connect(this._feedbackGain.gain); + this._readOnly(["feedback"]); }; - //extend Effect - Tone.extend(Tone.AutoPanner, Tone.Effect); + Tone.extend(Tone.FeedbackEffect, Tone.Effect); /** - * defaults * @static * @type {Object} */ - Tone.AutoPanner.defaults = { - "frequency" : 1, - "type" : "sine", - "amount" : 1 - }; - - /** - * Start the panner. - * @param {Tone.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 {Tone.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. - * @returns {Tone.AutoPanner} `this` - */ - Tone.AutoPanner.prototype.sync = function(){ - this._lfo.sync(); - return this; - }; - - /** - * Unsync the panner from the transport - * @returns {Tone.AutoPanner} `this` - */ - Tone.AutoPanner.prototype.unsync = function(){ - this._lfo.unsync(); - return this; + Tone.FeedbackEffect.defaults = { + "feedback" : 0.125 }; - /** - * 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` + * @returns {Tone.FeedbackEffect} `this` */ - Tone.AutoPanner.prototype.dispose = function(){ + Tone.FeedbackEffect.prototype.dispose = function(){ Tone.Effect.prototype.dispose.call(this); - this._lfo.dispose(); - this._lfo = null; - this._panner.dispose(); - this._panner = null; - this.frequency = null; - this.amount = null; + this._writable(["feedback"]); + this.feedback.dispose(); + this.feedback = null; + this._feedbackGain.disconnect(); + this._feedbackGain = null; return this; }; - return Tone.AutoPanner; + return Tone.FeedbackEffect; }); ToneModule( @@ -8896,394 +10013,426 @@ /** - * @class AutoWah connects an envelope follower to a bandpass filter. - * Some inspiration from Tuna.js https://github.com/Dinahmoe/tuna + * @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.Effect} - * @param {number} [baseFrequency=100] the frequency the filter is set - * to at the low point of the wah - * @param {number} [octaves=5] the number of octaves above the baseFrequency - * the filter will sweep to when fully open - * @param {number} [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); + * @constructor + * @extends {Tone.FeedbackEffect} */ - Tone.AutoWah = function(){ + Tone.StereoXFeedbackEffect = function(){ - var options = this.optionsObject(arguments, ["baseFrequency", "octaves", "sensitivity"], Tone.AutoWah.defaults); - Tone.Effect.call(this, options); + var options = this.optionsObject(arguments, ["feedback"], Tone.FeedbackEffect.defaults); + Tone.StereoEffect.call(this, options); /** - * the envelope follower - * @type {Tone.Follower} + * controls the amount of feedback + * @type {Tone.Signal} + */ + this.feedback = new Tone.Signal(options.feedback); + + /** + * the left side feeback + * @type {GainNode} * @private */ - this.follower = new Tone.Follower(options.follower); + this._feedbackLR = this.context.createGain(); /** - * scales the follower value to the frequency domain - * @type {Tone} + * the right side feeback + * @type {GainNode} * @private */ - this._sweepRange = new Tone.ScaleExp(0, 1, 0.5); + 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; + }); + ToneModule( + function(Tone){ + + + + /** + * @class A Chorus effect with feedback. inspiration from https://github.com/Dinahmoe/tuna/blob/master/tuna.js + * + * @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._baseFrequency = options.baseFrequency; + this._depth = options.depth; /** + * the delayTime * @type {number} * @private */ - this._octaves = options.octaves; + this._delayTime = options.delayTime / 1000; /** - * the input gain to adjust the senstivity - * @type {GainNode} + * the lfo which controls the delayTime + * @type {Tone.LFO} * @private */ - this._inputBoost = this.context.createGain(); + this._lfoL = new Tone.LFO(options.rate, 0, 1); /** - * @type {BiquadFilterNode} + * another LFO for the right side with a 180 degree phase diff + * @type {Tone.LFO} * @private */ - this._bandpass = new Tone.Filter({ - "rolloff" : -48, - "frequency" : 0, - "Q" : options.Q, - }); - + this._lfoR = new Tone.LFO(options.rate, 0, 1); + this._lfoR.phase = 180; + /** - * @type {Tone.Filter} + * delay for left + * @type {DelayNode} * @private */ - this._peaking = new Tone.Filter(0, "peaking"); - this._peaking.gain.value = options.gain; + this._delayNodeL = this.context.createDelay(); /** - * the gain of the filter. - * @type {Tone.Signal} + * delay for right + * @type {DelayNode} + * @private */ - this.gain = this._peaking.gain; + this._delayNodeR = this.context.createDelay(); /** - * The quality of the filter. + * The frequency the chorus will modulate at. * @type {Tone.Signal} */ - this.Q = this._bandpass.Q; + this.frequency = this._lfoL.frequency; - //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; + //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.AutoWah, Tone.Effect); + Tone.extend(Tone.Chorus, Tone.StereoXFeedbackEffect); /** * @static * @type {Object} */ - Tone.AutoWah.defaults = { - "baseFrequency" : 100, - "octaves" : 6, - "sensitivity" : 0, - "Q" : 2, - "gain" : 2, - "follower" : { - "attack" : 0.3, - "release" : 0.5 - } + Tone.Chorus.defaults = { + "frequency" : 1.5, + "delayTime" : 3.5, + "depth" : 0.7, + "feedback" : 0.1, + "type" : "sine" }; /** - * The number of octaves that the filter will sweep. - * @memberOf Tone.AutoWah# + * The depth of the effect. + * @memberOf Tone.Chorus# * @type {number} - * @name octaves + * @name depth */ - Object.defineProperty(Tone.AutoWah.prototype, "octaves", { + Object.defineProperty(Tone.Chorus.prototype, "depth", { get : function(){ - return this._octaves; - }, - set : function(octaves){ - this._octaves = octaves; - this._setSweepRange(); + 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 base frequency from which the sweep will start from. - * @memberOf Tone.AutoWah# - * @type {Tone.Frequency} - * @name baseFrequency + * The delayTime in milliseconds + * @memberOf Tone.Chorus# + * @type {number} + * @name delayTime */ - Object.defineProperty(Tone.AutoWah.prototype, "baseFrequency", { + Object.defineProperty(Tone.Chorus.prototype, "delayTime", { get : function(){ - return this._baseFrequency; - }, - set : function(baseFreq){ - this._baseFrequency = baseFreq; - this._setSweepRange(); + return this._delayTime * 1000; + }, + set : function(delayTime){ + this._delayTime = delayTime / 1000; + this.depth = this._depth; } }); /** - * The sensitivity to control how responsive to the input signal the filter is. - * in Decibels. - * @memberOf Tone.AutoWah# - * @type {number} - * @name sensitivity + * The lfo type for the chorus. + * @memberOf Tone.Chorus# + * @type {string} + * @name type */ - Object.defineProperty(Tone.AutoWah.prototype, "sensitivity", { + Object.defineProperty(Tone.Chorus.prototype, "type", { get : function(){ - return this.gainToDb(1 / this._inputBoost.gain.value); - }, - set : function(sensitivy){ - this._inputBoost.gain.value = 1 / this.dbToGain(sensitivy); + return this._lfoL.type; + }, + set : function(type){ + this._lfoL.type = type; + this._lfoR.type = type; } }); - /** - * 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` + * @returns {Tone.Chorus} `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.gain = null; - this.Q = null; + 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.AutoWah; + return Tone.Chorus; }); - ToneModule( - function(Tone){ + ToneModule( function(Tone){ /** - * @class Downsample incoming signal to a different bitdepth. - * + * @class Convolver wrapper for reverb and emulation. + * * @constructor * @extends {Tone.Effect} - * @param {number} bits 1-8. + * @param {string|AudioBuffer=} url * @example - * var crusher = new Tone.BitCrusher(4); + * var convolver = new Tone.Convolver("./path/to/ir.wav"); */ - Tone.BitCrusher = function(){ + Tone.Convolver = function(){ - var options = this.optionsObject(arguments, ["bits"], Tone.BitCrusher.defaults); + var options = this.optionsObject(arguments, ["url"], Tone.Convolver.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} + /** + * convolver node + * @type {ConvolverNode} * @private */ - this._modulo = new Tone.Modulo(invStepSize); + this._convolver = this.context.createConvolver(); /** - * keeps track of the bits - * @type {number} + * the convolution buffer + * @type {Tone.Buffer} * @private */ - this._bits = options.bits; + this._buffer = new Tone.Buffer(options.url, function(buffer){ + this.buffer = buffer; + options.onload(); + }.bind(this)); - //connect it up - this.effectSend.fan(this._subtract, this._modulo); - this._modulo.connect(this._subtract, 0, 1); - this._subtract.connect(this.effectReturn); + this.connectEffect(this._convolver); }; - Tone.extend(Tone.BitCrusher, Tone.Effect); + Tone.extend(Tone.Convolver, Tone.Effect); /** - * the default values * @static - * @type {Object} + * @const + * @type {Object} */ - Tone.BitCrusher.defaults = { - "bits" : 4 + Tone.Convolver.defaults = { + "url" : "", + "onload" : function(){} }; /** - * The bit depth of the BitCrusher - * @memberOf Tone.BitCrusher# - * @type {number} - * @name bits + * The convolver's buffer + * @memberOf Tone.Convolver# + * @type {AudioBuffer} + * @name buffer */ - Object.defineProperty(Tone.BitCrusher.prototype, "bits", { + Object.defineProperty(Tone.Convolver.prototype, "buffer", { get : function(){ - return this._bits; + return this._buffer.get(); }, - set : function(bits){ - this._bits = bits; - var invStepSize = 1 / Math.pow(2, bits - 1); - this._modulo.value = invStepSize; + set : function(buffer){ + this._buffer.set(buffer); + this._convolver.buffer = this._buffer.get(); } }); /** - * clean up - * @returns {Tone.BitCrusher} `this` + * 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.BitCrusher.prototype.dispose = function(){ + 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._subtract.dispose(); - this._subtract = null; - this._modulo.dispose(); - this._modulo = null; + this._convolver.disconnect(); + this._convolver = null; + this._buffer.dispose(); + this._buffer = null; return this; }; - return Tone.BitCrusher; + return Tone.Convolver; }); ToneModule( 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. - * http://music.columbia.edu/cmc/musicandcomputers/chapter4/04_06.php + * @class A simple distortion effect using the waveshaper node + * algorithm from http://stackoverflow.com/a/22313408 * * @extends {Tone.Effect} * @constructor - * @param {number} order The order of the chebyshev polynomial. Normal range between 1-100. + * @param {number} distortion the amount of distortion (nominal range of 0-1) * @example - * var cheby = new Tone.Chebyshev(50); + * var dist = new Tone.Distortion(0.8); */ - Tone.Chebyshev = function(){ + Tone.Distortion = function(){ + + var options = this.optionsObject(arguments, ["distortion"], Tone.Distortion.defaults); - var options = this.optionsObject(arguments, ["order"], Tone.Chebyshev.defaults); Tone.Effect.call(this); /** - * @type {WaveShaperNode} + * @type {Tone.WaveShaper} * @private */ this._shaper = new Tone.WaveShaper(4096); /** - * holds onto the order of the filter + * holds the distortion amount * @type {number} * @private */ - this._order = options.order; + this._distortion = options.distortion; this.connectEffect(this._shaper); - this.order = options.order; + this.distortion = options.distortion; this.oversample = options.oversample; }; - Tone.extend(Tone.Chebyshev, Tone.Effect); + Tone.extend(Tone.Distortion, Tone.Effect); /** * @static * @const * @type {Object} */ - Tone.Chebyshev.defaults = { - "order" : 1, + Tone.Distortion.defaults = { + "distortion" : 0.4, "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# + * The amount of distortion. Range between 0-1. + * @memberOf Tone.Distortion# * @type {number} - * @name order + * @name distortion */ - Object.defineProperty(Tone.Chebyshev.prototype, "order", { + Object.defineProperty(Tone.Distortion.prototype, "distortion", { get : function(){ - return this._order; + return this._distortion; }, - 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){ + 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 - curve[i] = 0; + return 0; } else { - curve[i] = this._getCoefficient(x, order, {}); + return ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); } - } - this._shaper.curve = curve; + }); } }); /** * The oversampling of the effect. Can either be "none", "2x" or "4x". - * @memberOf Tone.Chebyshev# + * @memberOf Tone.Distortion# * @type {string} * @name oversample */ - Object.defineProperty(Tone.Chebyshev.prototype, "oversample", { + Object.defineProperty(Tone.Distortion.prototype, "oversample", { get : function(){ return this._shaper.oversample; }, @@ -9292,19 +10441,235 @@ } }); + /** + * 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; + }); + ToneModule( function(Tone){ + + + + /** + * @class A feedback delay + * + * @constructor + * @extends {Tone.FeedbackEffect} + * @param {Tone.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 {Tone.Signal} + */ + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.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; + }); + ToneModule( + 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 the Freeverb + * + * @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 {Tone.Signal} + */ + this.roomSize = new Tone.Signal(options.roomSize, Tone.Signal.Units.Normal); + + /** + * The amount of dampening as a value in Hertz. + * @type {Tone.Signal} + */ + this.dampening = new Tone.Signal(options.dampening, Tone.Signal.Units.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.Chebyshev} `this` + * @returns {Tone.Freeverb} `this` */ - Tone.Chebyshev.prototype.dispose = function(){ - Tone.Effect.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; + 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.Chebyshev; + return Tone.Freeverb; }); ToneModule( function(Tone){ @@ -9312,226 +10677,416 @@ /** - * @class Creates an effect with an effectSendL/R and effectReturnL/R - * - * @constructor - * @extends {Tone.Effect} + * an array of the comb filter delay time values + * @private + * @static + * @type {Array} */ - Tone.StereoEffect = function(){ + var combFilterDelayTimes = [1687 / 25000, 1601 / 25000, 2053 / 25000, 2251 / 25000]; - Tone.call(this); - //get the defaults - var options = this.optionsObject(arguments, ["wet"], Tone.Effect.defaults); + /** + * the resonances of each of the comb filters + * @private + * @static + * @type {Array} + */ + var combFilterResonances = [0.773, 0.802, 0.753, 0.733]; - /** - * the drywet knob to control the amount of effect - * @type {Tone.CrossFade} - * @private - */ - this._dryWet = new Tone.CrossFade(options.wet); + /** + * the allpass filter frequencies + * @private + * @static + * @type {Array} + */ + var allpassFilterFreqs = [347, 113, 37]; - /** - * The wet control, i.e. how much of the effected - * will pass through to the output. - * @type {Tone.Signal} - */ - this.wet = this._dryWet.fade; + /** + * @class a simple Schroeder Reverberators tuned by John Chowning in 1970 + * made up of 3 allpass filters and 4 feedback comb filters. + * https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html + * + * @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); /** - * then split it - * @type {Tone.Split} - * @private + * room size control values between [0,1] + * @type {Tone.Signal} */ - this._split = new Tone.Split(); + this.roomSize = new Tone.Signal(options.roomSize, Tone.Signal.Units.Normal); /** - * the effects send LEFT - * @type {GainNode} + * scale the room size + * @type {Tone.Scale} * @private */ - this.effectSendL = this._split.left; + this._scaleRoomSize = new Tone.Scale(-0.733, 0.197); /** - * the effects send RIGHT - * @type {GainNode} + * a series of allpass filters + * @type {Array.} * @private */ - this.effectSendR = this._split.right; + this._allpassFilters = []; /** - * the stereo effect merger - * @type {Tone.Merge} + * parallel feedback comb filters + * @type {Array.} * @private */ - this._merge = new Tone.Merge(); + this._feedbackCombFilters = []; - /** - * the effect return LEFT - * @type {GainNode} - */ - this.effectReturnL = this._merge.left; + //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); + } - /** - * the effect return RIGHT - * @type {GainNode} - */ - this.effectReturnR = this._merge.right; + //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); + } - //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); + //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.StereoEffect, Tone.Effect); + Tone.extend(Tone.JCReverb, Tone.StereoEffect); + + /** + * the default values + * @static + * @const + * @type {Object} + */ + Tone.JCReverb.defaults = { + "roomSize" : 0.5 + }; /** * clean up - * @returns {Tone.StereoEffect} `this` + * @returns {Tone.JCReverb} `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.wet = null; + 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.StereoEffect; + return Tone.JCReverb; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ - + /** - * @class Feedback Effect (a sound loop between an audio source and its own output) + * @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.
+ * Applies a Mid/Side seperation and recombination.
+ * http://musicdsp.org/showArchiveComment.php?ArchiveID=173
+ * http://www.kvraudio.com/forum/viewtopic.php?t=212587
+ * M = (L+R)/sqrt(2); // obtain mid-signal from left and right
+ * S = (L-R)/sqrt(2); // obtain side-signal from left and righ
+ * // amplify mid and side signal seperately:
+ * M/S send/return
+ * L = (M+S)/sqrt(2); // obtain left signal from mid and side
+ * R = (M-S)/sqrt(2); // obtain right signal from mid and side
* - * @constructor * @extends {Tone.Effect} - * @param {number|Object} [initialFeedback=0.125] the initial feedback value + * @constructor */ - Tone.FeedbackEffect = function(){ - - var options = this.optionsObject(arguments, ["feedback"]); - options = this.defaultArg(options, Tone.FeedbackEffect.defaults); - - Tone.Effect.call(this, options); + Tone.MidSideEffect = function(){ + Tone.Effect.call(this); /** - * controls the amount of feedback - * @type {Tone.Signal} + * The mid/side split + * @type {Tone.MidSideSplit} + * @private */ - this.feedback = new Tone.Signal(options.feedback, Tone.Signal.Units.Normal); - + this._midSideSplit = new Tone.MidSideSplit(); + /** - * the gain which controls the feedback - * @type {GainNode} + * The mid/side merge + * @type {Tone.MidSideMerge} * @private */ - this._feedbackGain = this.context.createGain(); + this._midSideMerge = new Tone.MidSideMerge(); - //the feedback loop - this.effectReturn.chain(this._feedbackGain, this.effectSend); - this.feedback.connect(this._feedbackGain.gain); - }; + /** + * The mid send. Connect to mid processing + * @type {Tone.Expr} + */ + this.midSend = this._midSideSplit.mid; - Tone.extend(Tone.FeedbackEffect, Tone.Effect); + /** + * The side send. Connect to side processing + * @type {Tone.Expr} + */ + this.sideSend = this._midSideSplit.side; - /** - * @static - * @type {Object} - */ - Tone.FeedbackEffect.defaults = { - "feedback" : 0.125 + /** + * 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.FeedbackEffect} `this` + * @returns {Tone.MidSideEffect} `this` */ - Tone.FeedbackEffect.prototype.dispose = function(){ + Tone.MidSideEffect.prototype.dispose = function(){ Tone.Effect.prototype.dispose.call(this); - this.feedback.dispose(); - this.feedback = null; - this._feedbackGain.disconnect(); - this._feedbackGain = null; + 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.FeedbackEffect; + return Tone.MidSideEffect; }); - ToneModule( 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. + * @class A Phaser effect. inspiration from https://github.com/Dinahmoe/tuna/ * + * @extends {Tone.StereoEffect} * @constructor - * @extends {Tone.FeedbackEffect} + * @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.StereoXFeedbackEffect = function(){ + Tone.Phaser = function(){ - var options = this.optionsObject(arguments, ["feedback"], Tone.FeedbackEffect.defaults); + //set the defaults + var options = this.optionsObject(arguments, ["frequency", "depth", "baseFrequency"], Tone.Phaser.defaults); Tone.StereoEffect.call(this, options); /** - * controls the amount of feedback - * @type {Tone.Signal} + * the lfo which controls the frequency on the left side + * @type {Tone.LFO} + * @private */ - this.feedback = new Tone.Signal(options.feedback); + this._lfoL = new Tone.LFO(options.frequency, 0, 1); /** - * the left side feeback - * @type {GainNode} + * the lfo which controls the frequency on the right side + * @type {Tone.LFO} * @private */ - this._feedbackLR = this.context.createGain(); + this._lfoR = new Tone.LFO(options.frequency, 0, 1); + this._lfoR.phase = 180; /** - * the right side feeback - * @type {GainNode} + * the base modulation frequency + * @type {number} * @private */ - this._feedbackRL = this.context.createGain(); + this._baseFrequency = options.baseFrequency; - //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); + /** + * 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.StereoXFeedbackEffect, Tone.FeedbackEffect); + 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.StereoXFeedbackEffect} `this` + * @returns {Tone.Phaser} `this` */ - Tone.StereoXFeedbackEffect.prototype.dispose = function(){ + Tone.Phaser.prototype.dispose = function(){ Tone.StereoEffect.prototype.dispose.call(this); - this.feedback.dispose(); - this.feedback = null; - this._feedbackLR.disconnect(); - this._feedbackLR = null; - this._feedbackRL.disconnect(); - this._feedbackRL = null; + 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.StereoXFeedbackEffect; + return Tone.Phaser; }); ToneModule( function(Tone){ @@ -9539,432 +11094,548 @@ /** - * @class A Chorus effect with feedback. inspiration from https://github.com/Dinahmoe/tuna/blob/master/tuna.js + * @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 {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); + * @constructor + * @extends {Tone.StereoXFeedbackEffect} + * @param {Tone.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.Chorus = function(){ - - var options = this.optionsObject(arguments, ["frequency", "delayTime", "depth"], Tone.Chorus.defaults); + Tone.PingPongDelay = function(){ + + var options = this.optionsObject(arguments, ["delayTime", "feedback"], Tone.PingPongDelay.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} + * the delay node on the left side + * @type {DelayNode} * @private */ - this._lfoR = new Tone.LFO(options.rate, 0, 1); - this._lfoR.phase = 180; + this._leftDelay = this.context.createDelay(options.maxDelayTime); /** - * delay for left + * the delay node on the right side * @type {DelayNode} * @private */ - this._delayNodeL = this.context.createDelay(); + this._rightDelay = this.context.createDelay(options.maxDelayTime); /** - * delay for right + * the predelay on the right side * @type {DelayNode} * @private */ - this._delayNodeR = this.context.createDelay(); + this._rightPreDelay = this.context.createDelay(options.maxDelayTime); /** - * The frequency the chorus will modulate at. - * @type {Tone.Signal} + * the delay time signal + * @type {Tone.Signal} */ - this.frequency = this._lfoL.frequency; + this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); - //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; + //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.Chorus, Tone.StereoXFeedbackEffect); + Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect); /** * @static * @type {Object} */ - Tone.Chorus.defaults = { - "frequency" : 1.5, - "delayTime" : 3.5, - "depth" : 0.7, - "feedback" : 0.1, - "type" : "sine" + Tone.PingPongDelay.defaults = { + "delayTime" : 0.25, + "maxDelayTime" : 1 }; /** - * The depth of the effect. - * @memberOf Tone.Chorus# - * @type {number} - * @name depth + * clean up + * @returns {Tone.PingPongDelay} `this` */ - 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 = this._delayTime - deviation; - this._lfoL.max = this._delayTime + deviation; - this._lfoR.min = this._delayTime - deviation; - this._lfoR.max = this._delayTime + deviation; - } - }); + 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; + }; - /** - * 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; - } - }); + return Tone.PingPongDelay; + }); + ToneModule( + function(Tone){ + + /** - * The lfo type for the chorus. - * @memberOf Tone.Chorus# - * @type {string} - * @name type + * @class A stereo feedback effect where the feedback is on the same channel + * + * @constructor + * @extends {Tone.FeedbackEffect} */ - Object.defineProperty(Tone.Chorus.prototype, "type", { - get : function(){ - return this._lfoL.type; - }, - set : function(type){ - this._lfoL.type = type; - this._lfoR.type = type; - } - }); + Tone.StereoFeedbackEffect = 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._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.Chorus} `this` + * @returns {Tone.StereoFeedbackEffect} `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.frequency = null; + 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.Chorus; + return Tone.StereoFeedbackEffect; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ /** - * @class Convolver wrapper for reverb and emulation. - * + * @class Applies a width factor (0-1) to the mid/side seperation. + * 0 is all mid and 1 is all side.

+ * http://musicdsp.org/showArchiveComment.php?ArchiveID=173

+ * http://www.kvraudio.com/forum/viewtopic.php?t=212587

+ * M *= 2*(1-width)

+ * S *= 2*width

+ * + * @extends {Tone.MidSideEffect} * @constructor - * @extends {Tone.Effect} - * @param {string|AudioBuffer=} url - * @example - * var convolver = new Tone.Convolver("./path/to/ir.wav"); + * @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.Convolver = function(url){ + Tone.StereoWidener = function(){ - Tone.Effect.apply(this, arguments); + var options = this.optionsObject(arguments, ["width"], Tone.StereoWidener.defaults); + Tone.MidSideEffect.call(this, options); - /** - * convolver node - * @type {ConvolverNode} + /** + * The width control. 0 = 100% mid. 1 = 100% side. + * @type {Tone.Signal} + */ + this.width = new Tone.Signal(0.5, Tone.Signal.Units.Normal); + + /** + * Mid multiplier + * @type {Tone.Expr} * @private */ - this._convolver = this.context.createConvolver(); + this._midMult = new Tone.Expr("$0 * ($1 * (1 - $2))"); /** - * the convolution buffer - * @type {Tone.Buffer} + * Side multiplier + * @type {Tone.Expr} * @private */ - this._buffer = new Tone.Buffer(url, function(buffer){ - this.buffer = buffer; - }.bind(this)); + this._sideMult = new Tone.Expr("$0 * ($1 * $2)"); - this.connectEffect(this._convolver); - }; + /** + * constant output of 2 + * @type {Tone} + * @private + */ + this._two = new Tone.Signal(2); - Tone.extend(Tone.Convolver, Tone.Effect); + //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"]); + }; - /** - * 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 = buffer; - } - }); + Tone.extend(Tone.StereoWidener, Tone.MidSideEffect); /** - * 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; + * the default values + * @static + * @type {Object} + */ + Tone.StereoWidener.defaults = { + "width" : 0.5 }; /** - * dispose and disconnect - * @returns {Tone.Convolver} `this` + * clean up + * @returns {Tone.StereoWidener} `this` */ - Tone.Convolver.prototype.dispose = function(){ - Tone.Effect.prototype.dispose.call(this); - this._convolver.disconnect(); - this._convolver = null; - this._buffer.dispose(); - this._buffer = null; + 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.Convolver; + return Tone.StereoWidener; }); ToneModule( function(Tone){ /** - * @class A simple distortion effect using the waveshaper node - * algorithm from http://stackoverflow.com/a/22313408 + * @class A tremolo is a modulation in the amplitude of the incoming signal using an LFO. + * The type, frequency, and depth of the LFO is controllable. * * @extends {Tone.Effect} * @constructor - * @param {number} distortion the amount of distortion (nominal range of 0-1) + * @param {Tone.Time} [frequency=10] The rate of the effect. + * @param {number} [depth=0.5] The depth of the wavering. * @example - * var dist = new Tone.Distortion(0.8); + * var tremolo = new Tone.Tremolo(9, 0.75); */ - Tone.Distortion = function(){ + Tone.Tremolo = function(){ - var options = this.optionsObject(arguments, ["distortion"], Tone.Distortion.defaults); + var options = this.optionsObject(arguments, ["frequency", "depth"], Tone.Tremolo.defaults); + Tone.Effect.call(this, options); - Tone.Effect.call(this); + /** + * The tremelo LFO + * @type {Tone.LFO} + * @private + */ + this._lfo = new Tone.LFO(options.frequency, 1, 0); /** - * @type {Tone.WaveShaper} + * Where the gain is multiplied + * @type {GainNode} * @private */ - this._shaper = new Tone.WaveShaper(4096); + this._amplitude = this.context.createGain(); /** - * holds the distortion amount - * @type {number} - * @private + * The frequency of the tremolo. + * @type {Tone.Signal} */ - this._distortion = options.distortion; + this.frequency = this._lfo.frequency; - this.connectEffect(this._shaper); - this.distortion = options.distortion; - this.oversample = options.oversample; + /** + * The depth of the effect. + * @type {Tone.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.Distortion, Tone.Effect); + Tone.extend(Tone.Tremolo, Tone.Effect); /** * @static * @const * @type {Object} */ - Tone.Distortion.defaults = { - "distortion" : 0.4, - "oversample" : "none" + Tone.Tremolo.defaults = { + "frequency" : 10, + "type" : "sine", + "depth" : 0.5 }; /** - * The amount of distortion. Range between 0-1. - * @memberOf Tone.Distortion# - * @type {number} - * @name distortion + * Start the tremolo. + * @param {Tone.Time} [time=now] When the tremolo begins. + * @returns {Tone.Tremolo} `this` */ - 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) ); - } - }); - } - }); + Tone.Tremolo.prototype.start = function(time){ + this._lfo.start(time); + return this; + }; /** - * The oversampling of the effect. Can either be "none", "2x" or "4x". - * @memberOf Tone.Distortion# + * Stop the tremolo. + * @param {Tone.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 {Tone.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 oversample + * @name type */ - Object.defineProperty(Tone.Distortion.prototype, "oversample", { + Object.defineProperty(Tone.Tremolo.prototype, "type", { get : function(){ - return this._shaper.oversample; + return this._lfo.type; }, - set : function(oversampling){ - this._shaper.oversample = oversampling; - } + set : function(type){ + this._lfo.type = type; + } }); /** * clean up - * @returns {Tone.Distortion} `this` + * @returns {Tone.Tremolo} `this` */ - Tone.Distortion.prototype.dispose = function(){ + Tone.Tremolo.prototype.dispose = function(){ Tone.Effect.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; + 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.Distortion; + return Tone.Tremolo; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ - + /** - * @class A feedback delay + * @class Pulse Oscillator with control over width * * @constructor - * @extends {Tone.FeedbackEffect} - * @param {Tone.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. + * @extends {Tone.Oscillator} + * @param {number} [frequency=440] the frequency of the oscillator + * @param {number} [width = 0.2] the width of the pulse * @example - * var feedbackDelay = new Tone.FeedbackDelay("8n", 0.25); + * var pulse = new Tone.PulseOscillator("E5", 0.4); */ - Tone.FeedbackDelay = function(){ - - var options = this.optionsObject(arguments, ["delayTime", "feedback"], Tone.FeedbackDelay.defaults); - Tone.FeedbackEffect.call(this, options); + Tone.PulseOscillator = function(){ + + var options = this.optionsObject(arguments, ["frequency", "width"], Tone.Oscillator.defaults); + Tone.Source.call(this, options); /** - * Tone.Signal to control the delay amount + * the width of the pulse * @type {Tone.Signal} */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); + this.width = new Tone.Signal(options.width, Tone.Signal.Units.Normal); /** - * the delay node - * @type {DelayNode} + * gate the width amount + * @type {GainNode} * @private */ - this._delayNode = this.context.createDelay(4); + this._widthGate = this.context.createGain(); - // connect it up - this.connectEffect(this._delayNode); - this.delayTime.connect(this._delayNode.delayTime); + /** + * 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 hertz + * @type {Tone.Signal} + */ + this.frequency = this._sawtooth.frequency; + + /** + * The detune in cents. + * @type {Tone.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.FeedbackDelay, Tone.FeedbackEffect); + 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 {Tone.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 {Tone.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 {number} + * @name phase + */ + Object.defineProperty(Tone.PulseOscillator.prototype, "phase", { + get : function(){ + return this._sawtooth.phase; + }, + set : function(phase){ + this._sawtooth.phase = phase; + } + }); /** - * The default values. - * @const - * @static - * @type {Object} + * The type of the oscillator. Always returns "pulse". + * @readOnly + * @memberOf Tone.PulseOscillator# + * @type {string} + * @name type */ - Tone.FeedbackDelay.defaults = { - "delayTime" : 0.25, - }; - + Object.defineProperty(Tone.PulseOscillator.prototype, "type", { + get : function(){ + return "pulse"; + } + }); + /** - * clean up - * @returns {Tone.FeedbackDelay} `this` + * Clean up method + * @return {Tone.PulseOscillator} `this` */ - Tone.FeedbackDelay.prototype.dispose = function(){ - Tone.FeedbackEffect.prototype.dispose.call(this); - this.delayTime.dispose(); - this._delayNode.disconnect(); - this._delayNode = null; - this.delayTime = null; + 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.FeedbackDelay; + return Tone.PulseOscillator; }); ToneModule( function(Tone){ @@ -9972,593 +11643,555 @@ /** - * 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 the Freeverb + * @class takes an array of Oscillator descriptions and mixes them together + * with the same detune and frequency controls. * - * @extends {Tone.Effect} + * @extends {Tone.Oscillator} * @constructor - * @param {number} [roomSize=0.7] correlated to the decay time. - * value between (0,1) - * @param {number} [dampening=0.5] filtering which is applied to the reverb. - * value between [0,1] + * @param {frequency} Tone.Frequency frequency of the oscillator (meaningless for noise types) + * @param {number} modulationFrequency the modulation frequency of the oscillator * @example - * var freeverb = new Tone.Freeverb(0.4, 0.2); + * var pwm = new Tone.PWMOscillator("Ab3", 0.3); */ - Tone.Freeverb = function(){ - - var options = this.optionsObject(arguments, ["roomSize", "dampening"], Tone.Freeverb.defaults); - Tone.StereoEffect.call(this, options); + Tone.PWMOscillator = function(){ + var options = this.optionsObject(arguments, ["frequency", "modulationFrequency"], Tone.PWMOscillator.defaults); + Tone.Source.call(this, options); /** - * the roomSize value between (0,1) - * @type {Tone.Signal} + * the pulse oscillator + * @type {Tone.PulseOscillator} + * @private */ - this.roomSize = new Tone.Signal(options.roomSize); + this._pulse = new Tone.PulseOscillator(options.modulationFrequency); + //change the pulse oscillator type + this._pulse._sawtooth.type = "sine"; /** - * the amount of dampening - * value between [0,1] - * @type {Tone.Signal} + * the modulator + * @type {Tone.Oscillator} + * @private */ - this.dampening = new Tone.Signal(options.dampening); + this._modulator = new Tone.Oscillator({ + "frequency" : options.frequency, + "detune" : options.detune + }); /** - * scale the dampening - * @type {Tone.ScaleExp} + * Scale the oscillator so it doesn't go silent + * at the extreme values. + * @type {Tone.Multiply} * @private */ - this._dampeningScale = new Tone.ScaleExp(100, 8000, 0.5); + this._scale = new Tone.Multiply(1.01); /** - * the comb filters - * @type {Array.} - * @private + * the frequency control + * @type {Tone.Signal} */ - this._combFilters = []; + this.frequency = this._modulator.frequency; /** - * the allpass filters on the left - * @type {Array.} - * @private + * the detune control + * @type {Tone.Signal} */ - this._allpassFiltersL = []; + this.detune = this._modulator.detune; /** - * the allpass filters on the right - * @type {Array.} - * @private + * the modulation rate of the oscillator + * @type {Tone.Signal} */ - 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._dampeningScale.connect(lfpf.dampening); - this._combFilters.push(lfpf); - } + this.modulationFrequency = this._pulse.frequency; - //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.dampening.connect(this._dampeningScale); + //connections + this._modulator.chain(this._scale, this._pulse.width); + this._pulse.connect(this.output); + this._readOnly(["modulationFrequency", "frequency", "detune"]); }; - Tone.extend(Tone.Freeverb, Tone.StereoEffect); + Tone.extend(Tone.PWMOscillator, Tone.Oscillator); /** + * default values * @static * @type {Object} + * @const */ - Tone.Freeverb.defaults = { - "roomSize" : 0.7, - "dampening" : 0.5 + Tone.PWMOscillator.defaults = { + "frequency" : 440, + "detune" : 0, + "modulationFrequency" : 0.4, }; /** - * clean up - * @returns {Tone.Freeverb} `this` + * start the oscillator + * @param {Tone.Time} [time=now] + * @private */ - 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.roomSize.dispose(); - this.dampening.dispose(); - this._dampeningScale.dispose(); - this.roomSize = null; - this.dampening = null; - this._dampeningScale = null; - return this; + Tone.PWMOscillator.prototype._start = function(time){ + time = this.toSeconds(time); + this._modulator.start(time); + this._pulse.start(time); }; - return Tone.Freeverb; - }); - ToneModule( - function(Tone){ + /** + * stop the oscillator + * @param {Tone.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"; + } + }); /** - * an array of the comb filter delay time values - * @private - * @static - * @type {Array} + * The phase of the oscillator in degrees. + * @memberOf Tone.PWMOscillator# + * @type {number} + * @name phase */ - var combFilterDelayTimes = [1687 / 25000, 1601 / 25000, 2053 / 25000, 2251 / 25000]; + Object.defineProperty(Tone.PWMOscillator.prototype, "phase", { + get : function(){ + return this._modulator.phase; + }, + set : function(phase){ + this._modulator.phase = phase; + } + }); /** - * the resonances of each of the comb filters - * @private - * @static - * @type {Array} + * clean up + * @return {Tone.PWMOscillator} `this` */ - var combFilterResonances = [0.773, 0.802, 0.753, 0.733]; + 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; + }; - /** - * the allpass filter frequencies - * @private - * @static - * @type {Array} - */ - var allpassFilterFreqs = [347, 113, 37]; + return Tone.PWMOscillator; + }); + ToneModule( + function(Tone){ + + /** - * @class a simple Schroeder Reverberators tuned by John Chowning in 1970 - * made up of 3 allpass filters and 4 feedback comb filters. - * https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html + * @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.Effect} + * @extends {Tone.Oscillator} * @constructor - * @param {number} roomSize Coorelates to the decay time. Value between 0,1 + * @param {frequency} Tone.Frequency frequency of the oscillator (meaningless for noise types) + * @param {string} type the type of the oscillator * @example - * var freeverb = new Tone.Freeverb(0.4); + * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); */ - Tone.JCReverb = function(){ - - var options = this.optionsObject(arguments, ["roomSize"], Tone.JCReverb.defaults); - Tone.StereoEffect.call(this, options); + Tone.OmniOscillator = function(){ + var options = this.optionsObject(arguments, ["frequency", "type"], Tone.OmniOscillator.defaults); + Tone.Source.call(this, options); /** - * room size control values between [0,1] + * the frequency control * @type {Tone.Signal} */ - this.roomSize = new Tone.Signal(options.roomSize, Tone.Signal.Units.Normal); + this.frequency = new Tone.Signal(options.frequency, Tone.Signal.Units.Frequency); /** - * scale the room size - * @type {Tone.Scale} - * @private + * the detune control + * @type {Tone.Signal} */ - this._scaleRoomSize = new Tone.Scale(-0.733, 0.197); + this.detune = new Tone.Signal(options.detune); /** - * a series of allpass filters - * @type {Array.} + * the type of the oscillator source + * @type {string} * @private */ - this._allpassFilters = []; + this._sourceType = undefined; /** - * parallel feedback comb filters - * @type {Array.} + * the oscillator + * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} * @private */ - this._feedbackCombFilters = []; + this._oscillator = null; - //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); - } + //set the oscillator + this.type = options.type; + this._readOnly(["frequency", "detune"]); + }; - //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); + 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, //only applies if the oscillator is set to "pwm", + }; + + /** + * @enum {string} + * @private + */ + var OmniOscType = { + PulseOscillator : "PulseOscillator", + PWMOscillator : "PWMOscillator", + Oscillator : "Oscillator" + }; + + /** + * start the oscillator + * @param {Tone.Time} [time=now] the time to start the oscillator + * @private + */ + Tone.OmniOscillator.prototype._start = function(time){ + this._oscillator.start(time); + }; + + /** + * start the oscillator + * @param {Tone.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 === "sine" || type === "square" || type === "triangle" || type === "sawtooth"){ + 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 { - fbcf.connect(this.effectReturnR); + throw new TypeError("Tone.OmniOscillator does not support type "+type); } - 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]); + /** + * 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.Source.State.STARTED){ + this._oscillator.start(now); + } }; - Tone.extend(Tone.JCReverb, Tone.StereoEffect); - /** - * the default values - * @static - * @const - * @type {Object} + * The phase of the oscillator in degrees + * @memberOf Tone.OmniOscillator# + * @type {number} + * @name phase */ - Tone.JCReverb.defaults = { - "roomSize" : 0.5 - }; + Object.defineProperty(Tone.OmniOscillator.prototype, "phase", { + get : function(){ + return this._oscillator.phase; + }, + set : function(phase){ + this._oscillator.phase = phase; + } + }); /** - * clean up - * @returns {Tone.JCReverb} `this` + * The width of the oscillator (only if the oscillator is set to pulse) + * @memberOf Tone.OmniOscillator# + * @type {Tone.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; */ - 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; + Object.defineProperty(Tone.OmniOscillator.prototype, "width", { + get : function(){ + if (this._sourceType === OmniOscType.PulseOscillator){ + return this._oscillator.width; + } } - this._allpassFilters = null; - for (var fbcf = 0; fbcf < this._feedbackCombFilters.length; fbcf++) { - this._feedbackCombFilters[fbcf].dispose(); - this._feedbackCombFilters[fbcf] = null; + }); + + /** + * The modulationFrequency Signal of the oscillator + * (only if the oscillator type is set to pwm). + * @memberOf Tone.OmniOscillator# + * @type {Tone.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; + } } - this._feedbackCombFilters = null; - this.roomSize.dispose(); - this.roomSize = null; - this._scaleRoomSize.dispose(); - this._scaleRoomSize = null; + }); + + /** + * 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.JCReverb; + return Tone.OmniOscillator; }); ToneModule( function(Tone){ /** - * @class Applies a Mid/Side seperation and recombination - * http://musicdsp.org/showArchiveComment.php?ArchiveID=173 - * http://www.kvraudio.com/forum/viewtopic.php?t=212587 - * M = (L+R)/sqrt(2); // obtain mid-signal from left and right - * S = (L-R)/sqrt(2); // obtain side-signal from left and righ - * // amplify mid and side signal seperately: - * M/S send/return - * L = (M+S)/sqrt(2); // obtain left signal from mid and side - * R = (M-S)/sqrt(2); // obtain right signal from mid and side - * - * @extends {Tone.StereoEffect} + * @class Base-class for all instruments + * * @constructor + * @extends {Tone} */ - Tone.MidSideEffect = function(){ - Tone.StereoEffect.call(this); + Tone.Instrument = function(){ /** - * a constant signal equal to 1 / sqrt(2) - * @type {Tone.Signal} + * the output + * @type {GainNode} * @private */ - this._sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); - - /** - * the mid send. - * connect to mid processing - * @type {Tone.Expr} - */ - this.midSend = new Tone.Expr("($0 + $1) * $2"); - - /** - * the side send. - * connect to side processing - * @type {Tone.Expr} - */ - this.sideSend = new Tone.Expr("($0 - $1) * $2"); + this.output = this.context.createGain(); /** - * recombine the mid/side into Left - * @type {Tone.Expr} - * @private + * the volume of the output in decibels + * @type {Tone.Signal} */ - this._left = new Tone.Expr("($0 + $1) * $2"); + this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); + this._readOnly(["volume"]); + }; - /** - * recombine the mid/side into Right - * @type {Tone.Expr} - * @private - */ - this._right = new Tone.Expr("($0 - $1) * $2"); + Tone.extend(Tone.Instrument); - /** - * the mid return connection - * @type {GainNode} - */ - this.midReturn = this.context.createGain(); + /** + * @abstract + * @param {string|number} note the note to trigger + * @param {Tone.Time} [time=now] the time to trigger the ntoe + * @param {number} [velocity=1] the velocity to trigger the note + */ + Tone.Instrument.prototype.triggerAttack = function(){}; - /** - * the side return connection - * @type {GainNode} - */ - this.sideReturn = this.context.createGain(); + /** + * @abstract + * @param {Tone.Time} [time=now] when to trigger the release + */ + Tone.Instrument.prototype.triggerRelease = function(){}; - //connections - this.effectSendL.connect(this.midSend, 0, 0); - this.effectSendR.connect(this.midSend, 0, 1); - this.effectSendL.connect(this.sideSend, 0, 0); - this.effectSendR.connect(this.sideSend, 0, 1); - this._left.connect(this.effectReturnL); - this._right.connect(this.effectReturnR); - this.midReturn.connect(this._left, 0, 0); - this.sideReturn.connect(this._left, 0, 1); - this.midReturn.connect(this._right, 0, 0); - this.sideReturn.connect(this._right, 0, 1); - this._sqrtTwo.connect(this.midSend, 0, 2); - this._sqrtTwo.connect(this.sideSend, 0, 2); - this._sqrtTwo.connect(this._left, 0, 2); - this._sqrtTwo.connect(this._right, 0, 2); + /** + * trigger the attack and then the release + * @param {string|number} note the note to trigger + * @param {Tone.Time} duration the duration of the note + * @param {Tone.Time} [time=now] the time of the attack + * @param {number} velocity the velocity + * @returns {Tone.Instrument} `this` + */ + 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; }; - Tone.extend(Tone.MidSideEffect, Tone.StereoEffect); - /** * clean up - * @returns {Tone.MidSideEffect} `this` + * @returns {Tone.Instrument} `this` */ - Tone.MidSideEffect.prototype.dispose = function(){ - Tone.StereoEffect.prototype.dispose.call(this); - this._sqrtTwo.dispose(); - this._sqrtTwo = null; - this.midSend.dispose(); - this.midSend = null; - this.sideSend.dispose(); - this.sideSend = null; - this._left.dispose(); - this._left = null; - this._right.dispose(); - this._right = null; - this.midReturn.disconnect(); - this.midReturn = null; - this.sideReturn.disconnect(); - this.sideReturn = null; + Tone.Instrument.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._writable(["volume"]); + this.volume.dispose(); + this.volume = null; return this; }; - return Tone.MidSideEffect; + return Tone.Instrument; }); - ToneModule( - function(Tone){ + ToneModule( function(Tone){ /** - * @class A Phaser effect. inspiration from https://github.com/Dinahmoe/tuna/ + * @class this is a base class for monophonic instruments. + * it defines their interfaces * - * @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); + * @constructor + * @abstract + * @extends {Tone.Instrument} */ - 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; + Tone.Monophonic = function(options){ - /** - * 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); + Tone.Instrument.call(this); - /** - * the array of filters for the left side - * @type {Array.} - * @private - */ - this._filtersR = this._makeFilters(options.stages, this._lfoR, options.Q); + //get the defaults + options = this.defaultArg(options, Tone.Monophonic.defaults); /** - * the frequency of the effect - * @type {Tone.Signal} + * The glide time between notes. + * @type {Tone.Time} */ - 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.portamento = options.portamento; }; - Tone.extend(Tone.Phaser, Tone.StereoEffect); + Tone.extend(Tone.Monophonic, Tone.Instrument); /** - * defaults * @static - * @type {object} + * @const + * @type {Object} */ - Tone.Phaser.defaults = { - "frequency" : 0.5, - "depth" : 10, - "stages" : 4, - "Q" : 100, - "baseFrequency" : 400, + Tone.Monophonic.defaults = { + "portamento" : 0 }; /** - * @param {number} stages - * @returns {Array} the number of filters all connected together - * @private + * trigger the attack. start the note, at the time with the velocity + * + * @param {string|string} note the note + * @param {Tone.Time} [time=now] the time, if not given is now + * @param {number} [velocity=1] velocity defaults to 1 + * @returns {Tone.Monophonic} `this` */ - 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; + Tone.Monophonic.prototype.triggerAttack = function(note, time, velocity) { + time = this.toSeconds(time); + this.triggerEnvelopeAttack(time, velocity); + this.setNote(note, time); + return this; }; /** - * 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 {string} - * @name baseFrequency + * trigger the release portion of the envelope + * @param {Tone.Time} [time=now] if no time is given, the release happens immediatly + * @returns {Tone.Monophonic} `this` */ - 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; - } - }); + Tone.Monophonic.prototype.triggerRelease = function(time){ + this.triggerEnvelopeRelease(time); + return this; + }; /** - * clean up - * @returns {Tone.Phaser} `this` + * override this method with the actual method + * @abstract + * @param {Tone.Time} [time=now] the time the attack should happen + * @param {number} [velocity=1] the velocity of the envelope + * @returns {Tone.Monophonic} `this` + */ + Tone.Monophonic.prototype.triggerEnvelopeAttack = function() {}; + + /** + * override this method with the actual method + * @abstract + * @param {Tone.Time} [time=now] the time the attack should happen + * @param {number} [velocity=1] the velocity of the envelope + * @returns {Tone.Monophonic} `this` + */ + Tone.Monophonic.prototype.triggerEnvelopeRelease = function() {}; + + /** + * set the note to happen at a specific time + * @param {number|string} 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 + * @returns {Tone.Monophonic} `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; + 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); } - this._filtersR = null; - this.frequency = null; return this; }; - return Tone.Phaser; + return Tone.Monophonic; }); ToneModule( function(Tone){ @@ -10566,553 +12199,584 @@ /** - * @class PingPongDelay is a dual delay effect where the echo is heard - * first in one channel and next in the opposite channel + * @class the MonoSynth is a single oscillator, monophonic synthesizer + * with a filter, and two envelopes (on the filter and the amplitude). * - * @constructor - * @extends {Tone.StereoXFeedbackEffect} - * @param {Tone.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); + * Flow: + * + *
+		 * OmniOscillator+-->AmplitudeEnvelope+-->Filter 
+		 *                                          ^    
+		 *                                          |    
+		 *                         ScaledEnvelope+--+
+		 * 
+ * + * + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below */ - Tone.PingPongDelay = function(){ - - var options = this.optionsObject(arguments, ["delayTime", "feedback"], Tone.PingPongDelay.defaults); - Tone.StereoXFeedbackEffect.call(this, options); + Tone.MonoSynth = function(options){ + + //get the defaults + options = this.defaultArg(options, Tone.MonoSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the delay node on the left side - * @type {DelayNode} - * @private + * the first oscillator + * @type {Tone.OmniOscillator} */ - this._leftDelay = this.context.createDelay(options.maxDelayTime); + this.oscillator = new Tone.OmniOscillator(options.oscillator); /** - * the delay node on the right side - * @type {DelayNode} - * @private + * the frequency control signal + * @type {Tone.Signal} */ - this._rightDelay = this.context.createDelay(options.maxDelayTime); + this.frequency = this.oscillator.frequency; /** - * the predelay on the right side - * @type {DelayNode} - * @private + * the detune control signal + * @type {Tone.Signal} */ - this._rightPreDelay = this.context.createDelay(options.maxDelayTime); + this.detune = this.oscillator.detune; /** - * the delay time signal - * @type {Tone.Signal} + * the filter + * @type {Tone.Filter} */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Signal.Units.Time); + this.filter = new Tone.Filter(options.filter); - //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); + /** + * the filter envelope + * @type {Tone.Envelope} + */ + this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + + /** + * the amplitude envelope + * @type {Tone.Envelope} + */ + 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.PingPongDelay, Tone.StereoXFeedbackEffect); + Tone.extend(Tone.MonoSynth, Tone.Monophonic); /** + * @const * @static * @type {Object} */ - Tone.PingPongDelay.defaults = { - "delayTime" : 0.25, - "maxDelayTime" : 1 + 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 + } }; /** - * clean up - * @returns {Tone.PingPongDelay} `this` + * start the attack portion of the envelope + * @param {Tone.Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.MonoSynth} `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.delayTime.dispose(); - this.delayTime = null; - return this; + Tone.MonoSynth.prototype.triggerEnvelopeAttack = function(time, velocity){ + //the envelopes + this.envelope.triggerAttack(time, velocity); + this.filterEnvelope.triggerAttack(time); + return this; }; - return Tone.PingPongDelay; - }); - ToneModule( - function(Tone){ - - - /** - * @class A stereo feedback effect where the feedback is on the same channel - * - * @constructor - * @extends {Tone.FeedbackEffect} + * start the release portion of the envelope + * @param {Tone.Time} [time=now] the time the release should start + * @returns {Tone.MonoSynth} `this` */ - Tone.StereoFeedbackEffect = 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._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); + Tone.MonoSynth.prototype.triggerEnvelopeRelease = function(time){ + this.envelope.triggerRelease(time); + this.filterEnvelope.triggerRelease(time); + return this; }; - Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect); - - /** - * clean up - * @returns {Tone.StereoFeedbackEffect} `this` - */ - Tone.StereoFeedbackEffect.prototype.dispose = function(){ - Tone.StereoEffect.prototype.dispose.call(this); - this.feedback.dispose(); - this.feedback = null; - this._feedbackL.disconnect(); - this._feedbackL = null; - this._feedbackR.disconnect(); - this._feedbackR = null; + + /** + * 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.StereoFeedbackEffect; + return Tone.MonoSynth; }); ToneModule( - function(Tone){ + function(Tone){ /** - * @class Applies a width factor (0-1) to the mid/side seperation. - * 0 is all mid and 1 is all side.

- * http://musicdsp.org/showArchiveComment.php?ArchiveID=173

- * http://www.kvraudio.com/forum/viewtopic.php?t=212587

- * M *= 2*(1-width)

- * S *= 2*width

+ * @class the AMSynth is an amplitude modulation synthesizer + * composed of two MonoSynths where one MonoSynth is the + * carrier and the second is the modulator. * - * @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. + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below + * @example + * var synth = new Tone.AMSynth(); */ - Tone.StereoWidener = function(){ + Tone.AMSynth = function(options){ - var options = this.optionsObject(arguments, ["width"], Tone.StereoWidener.defaults); - Tone.MidSideEffect.call(this, options); + options = this.defaultArg(options, Tone.AMSynth.defaults); + Tone.Monophonic.call(this, options); /** - * The width control. 0 = 100% mid. 1 = 100% side. + * the first voice + * @type {Tone.MonoSynth} + */ + this.carrier = new Tone.MonoSynth(options.carrier); + this.carrier.volume.value = -10; + + /** + * the second voice + * @type {Tone.MonoSynth} + */ + this.modulator = new Tone.MonoSynth(options.modulator); + this.modulator.volume.value = -10; + + /** + * the frequency control * @type {Tone.Signal} */ - this.width = new Tone.Signal(0.5, Tone.Signal.Units.Normal); + this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); /** - * Mid multiplier - * @type {Tone.Expr} + * the ratio between the two voices + * @type {Tone.Multiply} * @private */ - this._midMult = new Tone.Expr("$0 * ($1 * (1 - $2))"); + this._harmonicity = new Tone.Multiply(options.harmonicity); /** - * Side multiplier - * @type {Tone.Expr} + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} * @private */ - this._sideMult = new Tone.Expr("$0 * ($1 * $2)"); + this._modulationScale = new Tone.AudioToGain(); /** - * constant output of 2 - * @type {Tone} + * the node where the modulation happens + * @type {GainNode} * @private */ - this._two = new Tone.Signal(2); + this._modulationNode = this.context.createGain(); - //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); + //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"]); }; - Tone.extend(Tone.StereoWidener, Tone.MidSideEffect); + Tone.extend(Tone.AMSynth, Tone.Monophonic); /** - * the default values * @static * @type {Object} */ - Tone.StereoWidener.defaults = { - "width" : 0.5 + 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.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.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 {Tone.Time} [time=now] the time the note will occur + * @param {number} [velocity=1] the velocity of the note + * @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 {Tone.Time} [time=now] the time the note will release + * @returns {Tone.AMSynth} `this` + */ + Tone.AMSynth.prototype.triggerEnvelopeRelease = function(time){ + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); + return this; + }; + + /** + * The ratio between the two carrier and the modulator. + * @memberOf Tone.AMSynth# + * @type {number} + * @name harmonicity + */ + Object.defineProperty(Tone.AMSynth.prototype, "harmonicity", { + get : function(){ + return this._harmonicity.value; + }, + set : function(harm){ + this._harmonicity.value = harm; + } + }); + /** * clean up - * @returns {Tone.StereoWidener} `this` + * @returns {Tone.AMSynth} `this` */ - Tone.StereoWidener.prototype.dispose = function(){ - Tone.MidSideEffect.prototype.dispose.call(this); - this.width.dispose(); - this.width = null; - this._midMult.dispose(); - this._midMult = null; - this._sideMult.dispose(); - this._sideMult = null; - this._two.dispose(); - this._two = null; + Tone.AMSynth.prototype.dispose = function(){ + Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); + 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.StereoWidener; + return Tone.AMSynth; }); - ToneModule( + ToneModule( function(Tone){ /** - * @class Pulse Oscillator with control over width + * @class the DroneSynth is a single oscillator, monophonic synthesizer + * with an OmniOscillator and an Amplitude Envelope * * @constructor - * @extends {Tone.Oscillator} - * @param {number} [frequency=440] the frequency of the oscillator - * @param {number} [width = 0.2] the width of the pulse - * @example - * var pulse = new Tone.PulseOscillator("E5", 0.4); + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below */ - Tone.PulseOscillator = function(){ - - var options = this.optionsObject(arguments, ["frequency", "width"], Tone.Oscillator.defaults); - Tone.Source.call(this, options); - - /** - * the width of the pulse - * @type {Tone.Signal} - */ - this.width = new Tone.Signal(options.width, Tone.Signal.Units.Normal); + Tone.DroneSynth = function(options){ - /** - * gate the width amount - * @type {GainNode} - * @private - */ - this._widthGate = this.context.createGain(); + //get the defaults + options = this.defaultArg(options, Tone.DroneSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the sawtooth oscillator - * @type {Tone.Oscillator} - * @private - */ - this._sawtooth = new Tone.Oscillator({ - frequency : options.frequency, - detune : options.detune, - type : "sawtooth", - phase : options.phase - }); + * the first oscillator + * @type {Tone.OmniOscillator} + */ + this.oscillator = new Tone.OmniOscillator(options.oscillator); /** - * The frequency in hertz + * the frequency control signal * @type {Tone.Signal} */ - this.frequency = this._sawtooth.frequency; + this.frequency = this.oscillator.frequency; /** - * The detune in cents. + * the detune control signal * @type {Tone.Signal} */ - this.detune = this._sawtooth.detune; + this.detune = this.oscillator.detune; /** - * Threshold the signal to turn it into a square - * @type {Tone.WaveShaper} - * @private + * the amplitude envelope + * @type {Tone.Envelope} */ - this._thresh = new Tone.WaveShaper(function(val){ - if (val < 0){ - return -1; - } else { - return 1; - } - }); + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - //connections - this._sawtooth.chain(this._thresh, this.output); - this.width.chain(this._widthGate, this._thresh); + //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.PulseOscillator, Tone.Oscillator); + Tone.extend(Tone.DroneSynth, Tone.Monophonic); /** - * The default parameters. - * @static * @const + * @static * @type {Object} */ - Tone.PulseOscillator.defaults = { - "frequency" : 440, - "detune" : 0, - "phase" : 0, - "width" : 0.2, + Tone.DroneSynth.defaults = { + "oscillator" : { + "type" : "triangle" + }, + "envelope" : { + "attack" : 0.005, + "decay" : 0.1, + "sustain" : 0.3, + "release" : 1 + } }; /** - * start the oscillator - * @param {Tone.Time} time - * @private + * start the attack portion of the envelope + * @param {Tone.Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.DroneSynth} `this` */ - Tone.PulseOscillator.prototype._start = function(time){ - time = this.toSeconds(time); - this._sawtooth.start(time); - this._widthGate.gain.setValueAtTime(1, time); + Tone.DroneSynth.prototype.triggerEnvelopeAttack = function(time, velocity){ + //the envelopes + this.envelope.triggerAttack(time, velocity); + return this; }; /** - * stop the oscillator - * @param {Tone.Time} time - * @private + * start the release portion of the envelope + * @param {Tone.Time} [time=now] the time the release should start + * @returns {Tone.DroneSynth} `this` */ - 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); + Tone.DroneSynth.prototype.triggerEnvelopeRelease = function(time){ + this.envelope.triggerRelease(time); + return this; }; - /** - * The phase of the oscillator in degrees. - * @memberOf Tone.PulseOscillator# - * @type {number} - * @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` + * clean up + * @returns {Tone.DroneSynth} `this` */ - Tone.PulseOscillator.prototype.dispose = function(){ - Tone.Source.prototype.dispose.call(this); - this._sawtooth.dispose(); - this._sawtooth = null; - this.width.dispose(); - this.width = null; - this._widthGate.disconnect(); - this._widthGate = null; - this._thresh.disconnect(); - this._thresh = null; + Tone.DroneSynth.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.PulseOscillator; + return Tone.DroneSynth; }); - ToneModule( + ToneModule( function(Tone){ /** - * @class takes an array of Oscillator descriptions and mixes them together - * with the same detune and frequency controls. + * @class DrumSynth makes kick and tom sounds using a single oscillator + * with an amplitude envelope and frequency ramp. * - * @extends {Tone.Oscillator} * @constructor - * @param {frequency} frequency frequency of the oscillator (meaningless for noise types) - * @param {number} modulationFrequency the modulation frequency of the oscillator + * @extends {Tone.Instrument} + * @param {Object} options the options available for the synth + * see defaults below * @example - * var pwm = new Tone.PWMOscillator("Ab3", 0.3); + * var synth = new Tone.DrumSynth(); */ - Tone.PWMOscillator = function(){ - var options = this.optionsObject(arguments, ["frequency", "modulationFrequency"], Tone.PWMOscillator.defaults); - Tone.Source.call(this, options); + Tone.DrumSynth = function(options){ - /** - * the pulse oscillator - */ - this._pulse = new Tone.PulseOscillator(options.modulationFrequency); - //change the pulse oscillator type - this._pulse._sawtooth.type = "sine"; + options = this.defaultArg(options, Tone.DrumSynth.defaults); + Tone.Instrument.call(this, options); /** - * the modulator + * The oscillator. * @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); + this.oscillator = new Tone.Oscillator(options.oscillator).start(); /** - * the frequency control - * @type {Tone.Signal} + * The envelope. + * @type {Tone.AmplitudeEnvelope} */ - this.frequency = this._modulator.frequency; + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); /** - * the detune control - * @type {Tone.Signal} + * The number of octaves the pitch envelope ramps. + * @type {number} */ - this.detune = this._modulator.detune; + this.octaves = options.octaves; /** - * the modulation rate of the oscillator - * @type {Tone.Signal} + * The amount of time of the pitch decay. + * @type {Tone.Time} */ - this.modulationFrequency = this._pulse.frequency; + this.pitchDecay = options.pitchDecay; - //connections - this._modulator.chain(this._scale, this._pulse.width); - this._pulse.connect(this.output); + this.oscillator.chain(this.envelope, this.output); + this._readOnly(["oscillator", "envelope"]); }; - Tone.extend(Tone.PWMOscillator, Tone.Oscillator); + Tone.extend(Tone.DrumSynth, Tone.Instrument); /** - * default values * @static * @type {Object} - * @const */ - Tone.PWMOscillator.defaults = { - "frequency" : 440, - "detune" : 0, - "modulationFrequency" : 0.4, + 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" + } }; /** - * start the oscillator - * @param {Tone.Time} [time=now] - * @private + * trigger the attack. start the note, at the time with the velocity + * + * @param {string|string} note the note + * @param {Tone.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.PWMOscillator.prototype._start = function(time){ + Tone.DrumSynth.prototype.triggerAttack = function(note, time, velocity) { time = this.toSeconds(time); - this._modulator.start(time); - this._pulse.start(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; }; /** - * stop the oscillator - * @param {Tone.Time} time (optional) timing parameter - * @private + * trigger the release portion of the note + * + * @param {Tone.Time} [time=now] the time the note will release + * @returns {Tone.DrumSynth} `this` */ - Tone.PWMOscillator.prototype._stop = function(time){ - time = this.toSeconds(time); - this._modulator.stop(time); - this._pulse.stop(time); + Tone.DrumSynth.prototype.triggerRelease = function(time){ + this.envelope.triggerRelease(time); + return this; }; - /** - * 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` + * @returns {Tone.DrumSynth} `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.frequency = null; - this.detune = null; - this.modulationFrequency = null; + 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.PWMOscillator; + return Tone.DrumSynth; }); ToneModule( function(Tone){ @@ -11120,496 +12784,718 @@ /** - * @class OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator, - * and Tone.PWMOscillator which allows it to have the types: - * sine, square, triangle, sawtooth, pulse or pwm. + * @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. * - * @extends {Tone.Oscillator} * @constructor - * @param {frequency} frequency frequency of the oscillator (meaningless for noise types) - * @param {string} type the type of the oscillator + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below * @example - * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); + * var duoSynth = new Tone.DuoSynth(); */ - Tone.OmniOscillator = function(){ - var options = this.optionsObject(arguments, ["frequency", "type"], Tone.OmniOscillator.defaults); - Tone.Source.call(this, options); + Tone.DuoSynth = function(options){ + + options = this.defaultArg(options, Tone.DuoSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the frequency control - * @type {Tone.Signal} + * the first voice + * @type {Tone.MonoSynth} */ - this.frequency = new Tone.Signal(options.frequency, Tone.Signal.Units.Frequency); + this.voice0 = new Tone.MonoSynth(options.voice0); + this.voice0.volume.value = -10; /** - * the detune control - * @type {Tone.Signal} + * the second voice + * @type {Tone.MonoSynth} */ - this.detune = new Tone.Signal(options.detune); + this.voice1 = new Tone.MonoSynth(options.voice1); + this.voice1.volume.value = -10; /** - * the type of the oscillator source - * @type {string} + * The vibrato LFO. + * @type {Tone.LFO} * @private */ - this._sourceType = undefined; + this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50); + this._vibrato.start(); /** - * the oscillator - * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} + * the vibrato frequency + * @type {Tone.Signal} + */ + this.vibratoRate = this._vibrato.frequency; + + /** + * the vibrato gain + * @type {GainNode} * @private */ - this._oscillator = null; + this._vibratoGain = this.context.createGain(); - //set the oscillator - this.type = options.type; - }; + /** + * The amount of vibrato + * @type {Tone.Signal} + */ + this.vibratoAmount = new Tone.Signal(this._vibratoGain.gain, Tone.Signal.Units.Gain); + this.vibratoAmount.value = options.vibratoAmount; - Tone.extend(Tone.OmniOscillator, Tone.Oscillator); + /** + * the delay before the vibrato starts + * @type {number} + * @private + */ + this._vibratoDelay = this.toSeconds(options.vibratoDelay); - /** - * 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, //only applies if the oscillator is set to "pwm", - }; + /** + * the frequency control + * @type {Tone.Signal} + */ + this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); - /** - * @enum {string} - * @private - */ - var OmniOscType = { - PulseOscillator : "PulseOscillator", - PWMOscillator : "PWMOscillator", - Oscillator : "Oscillator" - }; + /** + * the ratio between the two voices + * @type {Tone.Multiply} + * @private + */ + this._harmonicity = new Tone.Multiply(options.harmonicity); - /** - * start the oscillator - * @param {Tone.Time} [time=now] the time to start the oscillator - * @private - */ - Tone.OmniOscillator.prototype._start = function(time){ - this._oscillator.start(time); + //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"]); }; - /** - * start the oscillator - * @param {Tone.Time} [time=now] the time to start the oscillator - * @private - */ - Tone.OmniOscillator.prototype._stop = function(time){ - this._oscillator.stop(time); - }; + Tone.extend(Tone.DuoSynth, Tone.Monophonic); /** - * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse. - * - * @memberOf Tone.OmniOscillator# - * @type {string} - * @name type + * @static + * @type {Object} */ - Object.defineProperty(Tone.OmniOscillator.prototype, "type", { - get : function(){ - return this._oscillator.type; - }, - set : function(type){ - if (type === "sine" || type === "square" || type === "triangle" || type === "sawtooth"){ - 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); + 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.0, + "sustain" : 1, + "release" : 0.5 + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 + } + }, + "voice1" : { + "volume" : -10, + "portamento" : 0, + "oscillator" : { + "type" : "sine" + }, + "filterEnvelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 } - } - }); - - /** - * 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.Source.State.STARTED){ - this._oscillator.start(now); } }; /** - * The phase of the oscillator in degrees - * @memberOf Tone.OmniOscillator# - * @type {number} - * @name phase + * start the attack portion of the envelopes + * + * @param {Tone.Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.DuoSynth} `this` */ - Object.defineProperty(Tone.OmniOscillator.prototype, "phase", { - get : function(){ - return this._oscillator.phase; - }, - set : function(phase){ - this._oscillator.phase = phase; - } - }); + 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; + }; /** - * The width of the oscillator (only if the oscillator is set to pulse) - * @memberOf Tone.OmniOscillator# - * @type {Tone.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; - } - } - }); + * start the release portion of the envelopes + * + * @param {Tone.Time} [time=now] the time the release should start + * @returns {Tone.DuoSynth} `this` + */ + Tone.DuoSynth.prototype.triggerEnvelopeRelease = function(time){ + this.voice0.triggerRelease(time); + this.voice1.triggerRelease(time); + return this; + }; /** - * The modulationFrequency Signal of the oscillator - * (only if the oscillator type is set to pwm). - * @memberOf Tone.OmniOscillator# - * @type {Tone.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; + * The ratio between the two carrier and the modulator. + * @memberOf Tone.DuoSynth# + * @type {number} + * @name harmonicity */ - Object.defineProperty(Tone.OmniOscillator.prototype, "modulationFrequency", { + Object.defineProperty(Tone.DuoSynth.prototype, "harmonicity", { get : function(){ - if (this._sourceType === OmniOscType.PWMOscillator){ - return this._oscillator.modulationFrequency; - } + return this._harmonicity.value; + }, + set : function(harm){ + this._harmonicity.value = harm; } }); /** * clean up - * @return {Tone.OmniOscillator} `this` + * @returns {Tone.DuoSynth} `this` */ - Tone.OmniOscillator.prototype.dispose = function(){ - Tone.Source.prototype.dispose.call(this); - this.detune.dispose(); - this.detune = null; + 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._oscillator.dispose(); - this._oscillator = null; - this._sourceType = 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.OmniOscillator; + return Tone.DuoSynth; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ /** - * @class Base-class for all instruments - * + * @class the FMSynth is composed of two MonoSynths where one MonoSynth is the + * carrier and the second is the modulator. + * * @constructor - * @extends {Tone} + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below + * @example + * var fmSynth = new Tone.FMSynth(); */ - Tone.Instrument = function(){ + Tone.FMSynth = function(options){ + + options = this.defaultArg(options, Tone.FMSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the output - * @type {GainNode} + * the first voice + * @type {Tone.MonoSynth} + */ + this.carrier = new Tone.MonoSynth(options.carrier); + this.carrier.volume.value = -10; + + /** + * the second voice + * @type {Tone.MonoSynth} + */ + this.modulator = new Tone.MonoSynth(options.modulator); + this.modulator.volume.value = -10; + + /** + * the frequency control + * @type {Tone.Signal} + */ + this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); + + /** + * the ratio between the two voices + * @type {Tone.Multiply} * @private */ - this.output = this.context.createGain(); + this._harmonicity = new Tone.Multiply(options.harmonicity); /** - * the volume of the output in decibels - * @type {Tone.Signal} + * + * + * @type {Tone.Multiply} + * @private */ - this.volume = new Tone.Signal(this.output.gain, Tone.Signal.Units.Decibels); + this._modulationIndex = new Tone.Multiply(options.modulationIndex); + + /** + * 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"]); }; - Tone.extend(Tone.Instrument); + Tone.extend(Tone.FMSynth, Tone.Monophonic); /** - * @abstract - * @param {string|number} note the note to trigger - * @param {Tone.Time} [time=now] the time to trigger the ntoe - * @param {number} [velocity=1] the velocity to trigger the note + * @static + * @type {Object} */ - Tone.Instrument.prototype.triggerAttack = function(){}; + Tone.FMSynth.defaults = { + "harmonicity" : 3, + "modulationIndex" : 10, + "carrier" : { + "volume" : -10, + "portamento" : 0, + "oscillator" : { + "type" : "sine" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 + }, + "filterEnvelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5, + "min" : 20000, + "max" : 20000 + } + }, + "modulator" : { + "volume" : -10, + "portamento" : 0, + "oscillator" : { + "type" : "triangle" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 + }, + "filterEnvelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5, + "min" : 20000, + "max" : 20000 + } + } + }; /** - * @abstract - * @param {Tone.Time} [time=now] when to trigger the release + * trigger the attack portion of the note + * + * @param {Tone.Time} [time=now] the time the note will occur + * @param {number} [velocity=1] the velocity of the note + * @returns {Tone.FMSynth} `this` */ - Tone.Instrument.prototype.triggerRelease = function(){}; + 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 attack and then the release - * @param {string|number} note the note to trigger - * @param {Tone.Time} duration the duration of the note - * @param {Tone.Time} [time=now] the time of the attack - * @param {number} velocity the velocity - * @returns {Tone.Instrument} `this` + * trigger the release portion of the note + * + * @param {Tone.Time} [time=now] the time the note will release + * @returns {Tone.FMSynth} `this` */ - 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); + Tone.FMSynth.prototype.triggerEnvelopeRelease = function(time){ + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); return this; }; + /** + * The ratio between the two carrier and the modulator. + * @memberOf Tone.FMSynth# + * @type {number} + * @name harmonicity + */ + Object.defineProperty(Tone.FMSynth.prototype, "harmonicity", { + get : function(){ + return this._harmonicity.value; + }, + set : function(harm){ + this._harmonicity.value = harm; + } + }); + + /** + * 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. + * @memberOf Tone.FMSynth# + * @type {number} + * @name modulationIndex + */ + Object.defineProperty(Tone.FMSynth.prototype, "modulationIndex", { + get : function(){ + return this._modulationIndex.value; + }, + set : function(mod){ + this._modulationIndex.value = mod; + } + }); + /** * clean up - * @returns {Tone.Instrument} `this` + * @returns {Tone.FMSynth} `this` */ - Tone.Instrument.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this.volume.dispose(); - this.volume = null; + Tone.FMSynth.prototype.dispose = function(){ + Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); + 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.Instrument; + return Tone.FMSynth; }); ToneModule( function(Tone){ /** - * @class this is a base class for monophonic instruments. - * it defines their interfaces + * @class Noise generator. + * Uses looped noise buffers to save on performance. * * @constructor - * @abstract - * @extends {Tone.Instrument} + * @extends {Tone.Source} + * @param {string} type the noise type (white|pink|brown) + * @example + * var noise = new Tone.Noise("pink"); */ - Tone.Monophonic = function(options){ + Tone.Noise = function(){ - Tone.Instrument.call(this); + var options = this.optionsObject(arguments, ["type"], Tone.Noise.defaults); + Tone.Source.call(this, options); - //get the defaults - options = this.defaultArg(options, Tone.Monophonic.defaults); + /** + * @private + * @type {AudioBufferSourceNode} + */ + this._source = null; /** - * The glide time between notes. - * @type {Tone.Time} + * the buffer + * @private + * @type {AudioBuffer} */ - this.portamento = options.portamento; + this._buffer = null; + + this.type = options.type; }; - Tone.extend(Tone.Monophonic, Tone.Instrument); + Tone.extend(Tone.Noise, Tone.Source); /** + * the default parameters + * * @static * @const * @type {Object} */ - Tone.Monophonic.defaults = { - "portamento" : 0 + Tone.Noise.defaults = { + "type" : "white", }; /** - * trigger the attack. start the note, at the time with the velocity - * - * @param {string|string} note the note - * @param {Tone.Time} [time=now] the time, if not given is now - * @param {number} [velocity=1] velocity defaults to 1 - * @returns {Tone.Monophonic} `this` + * The type of the noise. Can be "white", "brown", or "pink". + * @memberOf Tone.Noise# + * @type {string} + * @name type + * @options ["white", "brown", "pink"] + * @example + * noise.type = "white"; */ - Tone.Monophonic.prototype.triggerAttack = function(note, time, velocity) { - time = this.toSeconds(time); - this.triggerEnvelopeAttack(time, velocity); - this.setNote(note, time); - return this; - }; + 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.Source.State.STARTED){ + var now = this.now() + this.bufferTime; + //remove the listener + this._source.onended = undefined; + this._stop(now); + this._start(now); + } + } + } + }); /** - * trigger the release portion of the envelope - * @param {Tone.Time} [time=now] if no time is given, the release happens immediatly - * @returns {Tone.Monophonic} `this` + * internal start method + * + * @param {Tone.Time} time + * @private */ - Tone.Monophonic.prototype.triggerRelease = function(time){ - this.triggerEnvelopeRelease(time); - return this; + 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; }; /** - * override this method with the actual method - * @abstract - * @param {Tone.Time} [time=now] the time the attack should happen - * @param {number} [velocity=1] the velocity of the envelope - * @returns {Tone.Monophonic} `this` - */ - Tone.Monophonic.prototype.triggerEnvelopeAttack = function() {}; - - /** - * override this method with the actual method - * @abstract - * @param {Tone.Time} [time=now] the time the attack should happen - * @param {number} [velocity=1] the velocity of the envelope - * @returns {Tone.Monophonic} `this` - */ - Tone.Monophonic.prototype.triggerEnvelopeRelease = function() {}; + * internal stop method + * + * @param {Tone.Time} time + * @private + */ + Tone.Noise.prototype._stop = function(time){ + if (this._source){ + this._source.stop(this.toSeconds(time)); + } + }; /** - * set the note to happen at a specific time - * @param {number|string} 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 - * @returns {Tone.Monophonic} `this` + * dispose all the components + * @returns {Tone.Noise} `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); + 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; }; - return Tone.Monophonic; + + /////////////////////////////////////////////////////////////////////////// + // 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.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.96900 * b2 + white * 0.1538520; + b3 = 0.86650 * b3 + white * 0.3104856; + b4 = 0.55000 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.0168980; + 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.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; }); ToneModule( function(Tone){ - /** - * @class the MonoSynth is a single oscillator, monophonic synthesizer - * with a filter, and two envelopes (on the filter and the amplitude). - * - * Flow: - * - *
-		 * OmniOscillator+-->AmplitudeEnvelope+-->Filter 
-		 *                                          ^    
-		 *                                          |    
-		 *                         ScaledEnvelope+--+
-		 * 
- * + /** + * @class the NoiseSynth is a single oscillator, monophonic synthesizer + * with a filter, and two envelopes (on the filter and the amplitude) * * @constructor - * @extends {Tone.Monophonic} + * @extends {Tone.Instrument} * @param {Object} options the options available for the synth * see defaults below + * @example + * var noiseSynth = new Tone.NoiseSynth(); */ - Tone.MonoSynth = function(options){ + Tone.NoiseSynth = function(options){ //get the defaults - options = this.defaultArg(options, Tone.MonoSynth.defaults); - Tone.Monophonic.call(this, options); - - /** - * the first oscillator - * @type {Tone.OmniOscillator} - */ - this.oscillator = new Tone.OmniOscillator(options.oscillator); - - /** - * the frequency control signal - * @type {Tone.Signal} - */ - this.frequency = this.oscillator.frequency; + options = this.defaultArg(options, Tone.NoiseSynth.defaults); + Tone.Instrument.call(this); /** - * the detune control signal - * @type {Tone.Signal} + * The noise source. Set the type by setting + * `noiseSynth.noise.type`. + * @type {Tone.Noise} */ - this.detune = this.oscillator.detune; + this.noise = new Tone.Noise(); /** - * the filter + * The filter . * @type {Tone.Filter} */ this.filter = new Tone.Filter(options.filter); /** - * the filter envelope + * The filter envelope. * @type {Tone.Envelope} */ this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); /** - * the amplitude envelope + * The amplitude envelope. * @type {Tone.Envelope} */ 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 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.MonoSynth, Tone.Monophonic); + Tone.extend(Tone.NoiseSynth, Tone.Instrument); /** * @const * @static * @type {Object} */ - Tone.MonoSynth.defaults = { - "oscillator" : { - "type" : "square" + Tone.NoiseSynth.defaults = { + "noise" : { + "type" : "white" }, "filter" : { "Q" : 6, - "type" : "lowpass", + "type" : "highpass", "rolloff" : -24 }, "envelope" : { "attack" : 0.005, "decay" : 0.1, - "sustain" : 0.9, - "release" : 1 + "sustain" : 0.0, }, "filterEnvelope" : { "attack" : 0.06, "decay" : 0.2, - "sustain" : 0.5, + "sustain" : 0, "release" : 2, "min" : 20, "max" : 4000, @@ -11621,9 +13507,9 @@ * start the attack portion of the envelope * @param {Tone.Time} [time=now] the time the attack should start * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.MonoSynth} `this` + * @returns {Tone.NoiseSynth} `this` */ - Tone.MonoSynth.prototype.triggerEnvelopeAttack = function(time, velocity){ + Tone.NoiseSynth.prototype.triggerAttack = function(time, velocity){ //the envelopes this.envelope.triggerAttack(time, velocity); this.filterEnvelope.triggerAttack(time); @@ -11633,438 +13519,151 @@ /** * start the release portion of the envelope * @param {Tone.Time} [time=now] the time the release should start - * @returns {Tone.MonoSynth} `this` + * @returns {Tone.NoiseSynth} `this` */ - Tone.MonoSynth.prototype.triggerEnvelopeRelease = function(time){ + Tone.NoiseSynth.prototype.triggerRelease = 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.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; - }); - ToneModule( - function(Tone){ - - - - /** - * @class the AMSynth is an amplitude modulation synthesizer - * composed of two MonoSynths where one MonoSynth is the - * carrier and the second is the modulator. - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} options the options available for the synth - * see defaults below - * @example - * var synth = new Tone.AMSynth(); - */ - Tone.AMSynth = function(options){ - - options = this.defaultArg(options, Tone.AMSynth.defaults); - Tone.Monophonic.call(this, options); - - /** - * the first voice - * @type {Tone.MonoSynth} - */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; - - /** - * the second voice - * @type {Tone.MonoSynth} - */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; - - /** - * the frequency control - * @type {Tone.Signal} - */ - this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); - - /** - * the ratio between the two voices - * @type {Tone.Multiply} - * @private - */ - this._harmonicity = new Tone.Multiply(options.harmonicity); - - /** - * 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); - }; - - Tone.extend(Tone.AMSynth, Tone.Monophonic); - - /** - * @static - * @type {Object} - */ - Tone.AMSynth.defaults = { - "harmonicity" : 3, - "carrier" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.01, - "sustain" : 1, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5, - "min" : 20000, - "max" : 20000 - } - }, - "modulator" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "envelope" : { - "attack" : 2, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 4, - "decay" : 0.2, - "sustain" : 0.5, - "release" : 0.5, - "min" : 20, - "max" : 1500 - } - } - }; - - /** - * trigger the attack portion of the note - * - * @param {Tone.Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @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 {Tone.Time} [time=now] the time the note will release - * @returns {Tone.AMSynth} `this` - */ - Tone.AMSynth.prototype.triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); - return this; - }; - /** - * The ratio between the two carrier and the modulator. - * @memberOf Tone.AMSynth# - * @type {number} - * @name harmonicity - */ - Object.defineProperty(Tone.AMSynth.prototype, "harmonicity", { - get : function(){ - return this._harmonicity.value; - }, - set : function(harm){ - this._harmonicity.value = harm; - } - }); + * trigger the attack and then the release + * @param {Tone.Time} duration the duration of the note + * @param {Tone.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.AMSynth} `this` + * @returns {Tone.NoiseSynth} `this` */ - Tone.AMSynth.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - 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; + 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.AMSynth; + return Tone.NoiseSynth; }); - ToneModule( - function(Tone){ + ToneModule( 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. - * + * @class Karplus-String string synthesis. + * * @constructor - * @extends {Tone.Monophonic} - * @param {Object} options the options available for the synth - * see defaults below + * @extends {Tone.Instrument} + * @param {Object} options see the defaults * @example - * var duoSynth = new Tone.DuoSynth(); + * var plucky = new Tone.PluckSynth(); */ - 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(); + Tone.PluckSynth = function(options){ - /** - * the vibrato frequency - * @type {Tone.Signal} - */ - this.vibratoRate = this._vibrato.frequency; + options = this.defaultArg(options, Tone.PluckSynth.defaults); + Tone.Instrument.call(this); /** - * the vibrato gain - * @type {GainNode} + * @type {Tone.Noise} * @private */ - this._vibratoGain = this.context.createGain(); + this._noise = new Tone.Noise("pink"); /** - * The amount of vibrato - * @type {Tone.Signal} + * The amount of noise at the attack. + * Nominal range of [0.1, 20] + * @type {number} */ - this.vibratoAmount = new Tone.Signal(this._vibratoGain.gain, Tone.Signal.Units.Gain); - this.vibratoAmount.value = options.vibratoAmount; + this.attackNoise = 1; /** - * the delay before the vibrato starts - * @type {number} + * the LFCF + * @type {Tone.LowpassCombFilter} * @private */ - this._vibratoDelay = this.toSeconds(options.vibratoDelay); + this._lfcf = new Tone.LowpassCombFilter(1 / 440); /** - * the frequency control + * the resonance control * @type {Tone.Signal} */ - this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); + this.resonance = this._lfcf.resonance; /** - * the ratio between the two voices - * @type {Tone.Multiply} - * @private + * the dampening control. i.e. the lowpass filter frequency of the comb filter + * @type {Tone.Signal} */ - this._harmonicity = new Tone.Multiply(options.harmonicity); + this.dampening = this._lfcf.dampening; - //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); + //connections + this._noise.connect(this._lfcf); + this._lfcf.connect(this.output); + this._readOnly(["resonance", "dampening"]); }; - Tone.extend(Tone.DuoSynth, Tone.Monophonic); + Tone.extend(Tone.PluckSynth, Tone.Instrument); /** * @static + * @const * @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.0, - "sustain" : 1, - "release" : 0.5 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - } - }, - "voice1" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - } - } + Tone.PluckSynth.defaults = { + "attackNoise" : 1, + "dampening" : 4000, + "resonance" : 0.5 }; /** - * start the attack portion of the envelopes - * - * @param {Tone.Time} [time=now] the time the attack should start - * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.DuoSynth} `this` + * trigger the attack portion + * @param {string|number} note the note name or frequency + * @param {Tone.Time} [time=now] the time of the note + * @returns {Tone.PluckSynth} `this` */ - Tone.DuoSynth.prototype.triggerEnvelopeAttack = function(time, velocity){ + Tone.PluckSynth.prototype.triggerAttack = function(note, time) { + note = this.toFrequency(note); 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 {Tone.Time} [time=now] the time the release should start - * @returns {Tone.DuoSynth} `this` - */ - Tone.DuoSynth.prototype.triggerEnvelopeRelease = function(time){ - this.voice0.triggerRelease(time); - this.voice1.triggerRelease(time); + var delayAmount = 1 / note; + this._lfcf.delayTime.setValueAtTime(delayAmount, time); + this._noise.start(time); + this._noise.stop(time + delayAmount * this.attackNoise); return this; }; - /** - * The ratio between the two carrier and the modulator. - * @memberOf Tone.DuoSynth# - * @type {number} - * @name harmonicity - */ - Object.defineProperty(Tone.DuoSynth.prototype, "harmonicity", { - get : function(){ - return this._harmonicity.value; - }, - set : function(harm){ - this._harmonicity.value = harm; - } - }); - /** * clean up - * @returns {Tone.DuoSynth} `this` + * @returns {Tone.PluckSynth} `this` */ - Tone.DuoSynth.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - 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; + 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.DuoSynth; + return Tone.PluckSynth; }); ToneModule( function(Tone){ @@ -12072,210 +13671,197 @@ /** - * @class the FMSynth is composed of two MonoSynths where one MonoSynth is the - * carrier and the second is the modulator. + * @class Creates a polyphonic synthesizer out of + * the monophonic voice which is passed in. * * @constructor - * @extends {Tone.Monophonic} - * @param {Object} options the options available for the synth - * see defaults below + * @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 - * var fmSynth = new Tone.FMSynth(); + * //a polysynth composed of 6 Voices of MonoSynth + * var synth = new Tone.PolySynth(6, Tone.MonoSynth); + * //set a MonoSynth preset + * synth.setPreset("Pianoetta"); */ - Tone.FMSynth = function(options){ - - options = this.defaultArg(options, Tone.FMSynth.defaults); - Tone.Monophonic.call(this, options); + Tone.PolySynth = function(){ - /** - * the first voice - * @type {Tone.MonoSynth} - */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + Tone.Instrument.call(this); - /** - * the second voice - * @type {Tone.MonoSynth} - */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + var options = this.optionsObject(arguments, ["polyphony", "voice"], Tone.PolySynth.defaults); /** - * the frequency control - * @type {Tone.Signal} + * the array of voices + * @type {Array} */ - this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); + this.voices = new Array(options.polyphony); /** - * the ratio between the two voices - * @type {Tone.Multiply} + * the queue of free voices * @private + * @type {Array} */ - this._harmonicity = new Tone.Multiply(options.harmonicity); - - /** - * - * - * @type {Tone.Multiply} - * @private - */ - this._modulationIndex = new Tone.Multiply(options.modulationIndex); + this._freeVoices = []; /** - * the node where the modulation happens - * @type {GainNode} + * keeps track of which notes are down * @private + * @type {Object} */ - this._modulationNode = this.context.createGain(); + this._activeVoices = {}; - //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); + //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.FMSynth, Tone.Monophonic); + Tone.extend(Tone.PolySynth, Tone.Instrument); /** + * the defaults + * @const * @static * @type {Object} */ - Tone.FMSynth.defaults = { - "harmonicity" : 3, - "modulationIndex" : 10, - "carrier" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5, - "min" : 20000, - "max" : 20000 - } - }, - "modulator" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "triangle" - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5, - "min" : 20000, - "max" : 20000 + 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 {Tone.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 portion of the note + * trigger the attack and release after the specified duration * - * @param {Tone.Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.FMSynth} `this` + * @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 {Tone.Time} duration the duration of the note + * @param {Tone.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.FMSynth.prototype.triggerEnvelopeAttack = function(time, velocity){ - //the port glide + Tone.PolySynth.prototype.triggerAttackRelease = function(value, duration, time, velocity){ 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); + this.triggerAttack(value, time, velocity); + this.triggerRelease(value, time + this.toSeconds(duration)); return this; }; /** - * trigger the release portion of the note - * - * @param {Tone.Time} [time=now] the time the note will release - * @returns {Tone.FMSynth} `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 {Tone.Time} [time=now] the release time of the note + * @returns {Tone.PolySynth} `this` */ - Tone.FMSynth.prototype.triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + 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; }; /** - * The ratio between the two carrier and the modulator. - * @memberOf Tone.FMSynth# - * @type {number} - * @name harmonicity + * set the options on all of the voices + * @param {Object|string} params + * @param {number=} value + * @param {Tone.Time=} rampTime + * @returns {Tone.PolySynth} `this` */ - Object.defineProperty(Tone.FMSynth.prototype, "harmonicity", { - get : function(){ - return this._harmonicity.value; - }, - set : function(harm){ - this._harmonicity.value = harm; + 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; + }; /** - * 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. - * @memberOf Tone.FMSynth# - * @type {number} - * @name modulationIndex + * get a group of parameters + * @param {Array=} params the parameters to get, otherwise will return + * all available. */ - Object.defineProperty(Tone.FMSynth.prototype, "modulationIndex", { - get : function(){ - return this._modulationIndex.value; - }, - set : function(mod){ - this._modulationIndex.value = mod; + 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.FMSynth} `this` + * @returns {Tone.PolySynth} `this` */ - Tone.FMSynth.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - 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; + 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.FMSynth; + return Tone.PolySynth; }); ToneModule( function(Tone){ @@ -12303,13 +13889,24 @@ * @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(options.url, options.onload.bind(null, this)); + 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 @@ -12359,9 +13956,11 @@ "onload" : function(){}, "playbackRate" : 1, "loop" : false, + "autostart" : false, "loopStart" : 0, "loopEnd" : 0, "retrigger" : false, + "reverse" : false, }; /** @@ -12378,10 +13977,21 @@ * @returns {Tone.Player} `this` */ Tone.Player.prototype.load = function(url, callback){ - this._buffer.load(url, callback.bind(this, this)); + 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 * @@ -12399,11 +14009,11 @@ //if it's a loop the default offset is the loopstart point if (this._loop){ offset = this.defaultArg(offset, this._loopStart); - offset = this.toSeconds(offset); } 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); @@ -12416,6 +14026,9 @@ 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; } @@ -12552,6 +14165,21 @@ } }); + /** + * 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` @@ -12650,6 +14278,7 @@ 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); @@ -12741,7 +14370,7 @@ if (name){ this.sample = name; } - this.player.start(time, 0); + this.player.start(time); this.envelope.triggerAttack(time, velocity); this.filterEnvelope.triggerAttack(time); return this; @@ -12782,477 +14411,137 @@ }); /** - * Repitch the sampled note by some interval (measured - * in semi-tones). + * The direction the buffer should play in * @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.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; - }); - - ToneModule( - function(Tone){ - - - - /** - * @class Deprecated. - * - * @constructor - * @deprecated Use Tone.PolySynth with Tone.Sampler as the voice. - * @extends {Tone.Instrument} - * @param {Object} samples the samples used in this - * @param {function} onload the callback to invoke when all - * of the samples have been loaded - */ - Tone.MultiSampler = function(samples, onload){ - - console.warn("Tone.MultiSampler is deprecated - use Tone.PolySynth with Tone.Sampler as the voice"); - Tone.Instrument.call(this); - - /** - * the array of voices - * @type {Tone.Sampler} - */ - this.samples = {}; - - //make the samples - this._createSamples(samples, onload); - }; - - Tone.extend(Tone.MultiSampler, Tone.Instrument); - - /** - * creates all of the samples and tracks their loading - * - * @param {Object} samples the samples - * @param {function} onload the onload callback - * @private - */ - Tone.MultiSampler.prototype._createSamples = function(samples, onload){ - //object which tracks the number of loaded samples - var loadCounter = { - total : 0, - loaded : 0 - }; - //get the count - for (var s in samples){ //jshint ignore:line - loadCounter.total++; - } - //the function to invoke when a sample is loaded - var onSampleLoad = function(){ - loadCounter.loaded++; - if (loadCounter.loaded === loadCounter.total){ - if (onload){ - onload(); - } - } - }; - for (var samp in samples){ - var url = samples[samp]; - var sampler = new Tone.Sampler(url, onSampleLoad); - sampler.connect(this.output); - this.samples[samp] = sampler; - } - }; - - /** - * start a sample - * - * @param {string} sample the note name to start - * @param {Tone.Time} [time=now] the time when the note should start - * @param {number} [velocity=1] the velocity of the note - */ - Tone.MultiSampler.prototype.triggerAttack = function(sample, time, velocity){ - if (this.samples.hasOwnProperty(sample)){ - this.samples[sample].triggerAttack(0, time, velocity); - } - }; - - /** - * start the release portion of the note - * - * @param {string} sample the note name to release - * @param {Tone.Time} [time=now] the time when the note should release - */ - Tone.MultiSampler.prototype.triggerRelease = function(sample, time){ - if (this.samples.hasOwnProperty(sample)){ - this.samples[sample].triggerRelease(time); - } - }; - - /** - * start the release portion of the note - * - * @param {string} sample the note name to release - * @param {Tone.Time} duration the duration of the note - * @param {Tone.Time} [time=now] the time when the note should start - * @param {number} [velocity=1] the velocity of the note - */ - Tone.MultiSampler.prototype.triggerAttackRelease = function(sample, duration, time, velocity){ - if (this.samples.hasOwnProperty(sample)){ - time = this.toSeconds(time); - duration = this.toSeconds(duration); - var samp = this.samples[sample]; - samp.triggerAttack(0, time, velocity); - samp.triggerRelease(time + duration); - } - }; - - /** - * sets all the samplers with these settings - * @param {object} params the parameters to be applied - * to all internal samplers - */ - Tone.MultiSampler.prototype.set = function(params){ - for (var samp in this.samples){ - this.samples[samp].set(params); - } - }; - - /** - * clean up - */ - Tone.MultiSampler.prototype.dispose = function(){ - Tone.Instrument.prototype.dispose.call(this); - for (var samp in this.samples){ - this.samples[samp].dispose(); - this.samples[samp] = null; - } - this.samples = null; - }; - - return Tone.MultiSampler; - }); - - ToneModule( 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.Source.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 {Tone.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; - }; + * @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; + } + } + }); /** - * internal stop method - * - * @param {Tone.Time} time - * @private + * 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 */ - Tone.Noise.prototype._stop = function(time){ - if (this._source){ - this._source.stop(this.toSeconds(time)); + Object.defineProperty(Tone.Sampler.prototype, "pitch", { + get : function(){ + return this._pitch; + }, + set : function(interval){ + this._pitch = interval; + this.player.playbackRate = this.intervalToFrequencyRatio(interval); } - }; + }); /** - * dispose all the components - * @returns {Tone.Noise} `this` + * clean up + * @returns {Tone.Sampler} `this` */ - Tone.Noise.prototype.dispose = function(){ - Tone.Source.prototype.dispose.call(this); - if (this._source !== null){ - this._source.disconnect(); - this._source = null; + 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._buffer = null; + this._buffers = null; return this; }; - - /////////////////////////////////////////////////////////////////////////// - // THE BUFFERS - // borred 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.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.96900 * b2 + white * 0.1538520; - b3 = 0.86650 * b3 + white * 0.3104856; - b4 = 0.55000 * b4 + white * 0.5329522; - b5 = -0.7616 * b5 - white * 0.0168980; - 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.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; + return Tone.Sampler; }); + ToneModule( function(Tone){ /** - * @class the NoiseSynth is a single oscillator, monophonic synthesizer - * with a filter, and two envelopes (on the filter and the amplitude) + * @class the SimpleSynth is a single oscillator, monophonic synthesizer + * with an OmniOscillator and an Amplitude Envelope * * @constructor - * @extends {Tone.Instrument} + * @extends {Tone.Monophonic} * @param {Object} options the options available for the synth * see defaults below - * @example - * var noiseSynth = new Tone.NoiseSynth(); */ - Tone.NoiseSynth = function(options){ + Tone.SimpleSynth = function(options){ //get the defaults - options = this.defaultArg(options, Tone.NoiseSynth.defaults); - Tone.Instrument.call(this); + options = this.defaultArg(options, Tone.SimpleSynth.defaults); + Tone.Monophonic.call(this, options); /** - * The noise source. Set the type by setting - * `noiseSynth.noise.type`. - * @type {Tone.Noise} + * the first oscillator + * @type {Tone.OmniOscillator} */ - this.noise = new Tone.Noise(); + this.oscillator = new Tone.OmniOscillator(options.oscillator); /** - * The filter . - * @type {Tone.Filter} + * the frequency control signal + * @type {Tone.Signal} */ - this.filter = new Tone.Filter(options.filter); + this.frequency = this.oscillator.frequency; /** - * The filter envelope. - * @type {Tone.Envelope} + * the detune control signal + * @type {Tone.Signal} */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + this.detune = this.oscillator.detune; /** - * The amplitude envelope. + * the amplitude envelope * @type {Tone.Envelope} */ 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); + //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.NoiseSynth, Tone.Instrument); + Tone.extend(Tone.SimpleSynth, Tone.Monophonic); /** * @const * @static * @type {Object} */ - Tone.NoiseSynth.defaults = { - "noise" : { - "type" : "white" - }, - "filter" : { - "Q" : 6, - "type" : "highpass", - "rolloff" : -24 + Tone.SimpleSynth.defaults = { + "oscillator" : { + "type" : "triangle" }, "envelope" : { "attack" : 0.005, "decay" : 0.1, - "sustain" : 0.0, - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.2, - "sustain" : 0, - "release" : 2, - "min" : 20, - "max" : 4000, - "exponent" : 2 + "sustain" : 0.3, + "release" : 1 } }; @@ -13260,161 +14549,216 @@ * start the attack portion of the envelope * @param {Tone.Time} [time=now] the time the attack should start * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.NoiseSynth} `this` + * @returns {Tone.SimpleSynth} `this` */ - Tone.NoiseSynth.prototype.triggerAttack = function(time, velocity){ + Tone.SimpleSynth.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 {Tone.Time} [time=now] the time the release should start - * @returns {Tone.NoiseSynth} `this` + * @returns {Tone.SimpleSynth} `this` */ - Tone.NoiseSynth.prototype.triggerRelease = function(time){ + Tone.SimpleSynth.prototype.triggerEnvelopeRelease = function(time){ this.envelope.triggerRelease(time); - this.filterEnvelope.triggerRelease(time); return this; }; - /** - * trigger the attack and then the release - * @param {Tone.Time} duration the duration of the note - * @param {Tone.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); - console.log(time + duration); - this.triggerRelease(time + duration); - return this; - }; /** * clean up - * @returns {Tone.NoiseSynth} `this` + * @returns {Tone.SimpleSynth} `this` */ - Tone.NoiseSynth.prototype.dispose = function(){ - Tone.Instrument.prototype.dispose.call(this); - this.noise.dispose(); - this.noise = null; + 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.filterEnvelope.dispose(); - this.filterEnvelope = null; - this.filter.dispose(); - this.filter = null; + this.frequency = null; + this.detune = null; return this; }; - return Tone.NoiseSynth; + return Tone.SimpleSynth; }); - ToneModule( function(Tone){ + ToneModule( + function(Tone){ /** - * @class Karplus-String string synthesis. - * + * @class the SimpleAM is an amplitude modulation synthesizer + * composed of two SimpleSynths where one SimpleSynth is the + * carrier and the second is the modulator. + * * @constructor - * @extends {Tone.Instrument} - * @param {Object} options see the defaults + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below * @example - * var plucky = new Tone.PluckSynth(); + * var synth = new Tone.SimpleAM(); */ - Tone.PluckSynth = function(options){ + Tone.SimpleAM = function(options){ - options = this.defaultArg(options, Tone.PluckSynth.defaults); - Tone.Instrument.call(this); + options = this.defaultArg(options, Tone.SimpleAM.defaults); + Tone.Monophonic.call(this, options); /** - * @type {Tone.Noise} - * @private + * the first voice + * @type {Tone.SimpleSynth} */ - this._noise = new Tone.Noise("pink"); + this.carrier = new Tone.SimpleSynth(options.carrier); + this.carrier.volume.value = -10; /** - * The amount of noise at the attack. - * Nominal range of [0.1, 20] - * @type {number} + * the second voice + * @type {Tone.SimpleSynth} + */ + this.modulator = new Tone.SimpleSynth(options.modulator); + this.modulator.volume.value = -10; + + /** + * the frequency control + * @type {Tone.Signal} */ - this.attackNoise = 1; + this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); /** - * the LFCF - * @type {Tone.LowpassCombFilter} + * the ratio between the two voices + * @type {Tone.Multiply} * @private */ - this._lfcf = new Tone.LowpassCombFilter(1 / 440); + this._harmonicity = new Tone.Multiply(options.harmonicity); /** - * the resonance control - * @type {Tone.Signal} + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} + * @private */ - this.resonance = this._lfcf.resonance; + this._modulationScale = new Tone.AudioToGain(); /** - * the dampening control. i.e. the lowpass filter frequency of the comb filter - * @type {Tone.Signal} + * the node where the modulation happens + * @type {GainNode} + * @private */ - this.dampening = this._lfcf.dampening; + this._modulationNode = this.context.createGain(); - //connections - this._noise.connect(this._lfcf); - this._lfcf.connect(this.output); + //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"]); }; - Tone.extend(Tone.PluckSynth, Tone.Instrument); + Tone.extend(Tone.SimpleAM, Tone.Monophonic); /** * @static - * @const * @type {Object} */ - Tone.PluckSynth.defaults = { - "attackNoise" : 1, - "dampening" : 4000, - "resonance" : 0.5 + 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.0, + "sustain" : 1, + "release" : 0.5 + } + } }; /** - * trigger the attack portion - * @param {string|number} note the note name or frequency - * @param {Tone.Time} [time=now] the time of the note - * @returns {Tone.PluckSynth} `this` + * trigger the attack portion of the note + * + * @param {Tone.Time} [time=now] the time the note will occur + * @param {number} [velocity=1] the velocity of the note + * @returns {Tone.SimpleAM} `this` */ - Tone.PluckSynth.prototype.triggerAttack = function(note, time) { - note = this.toFrequency(note); + Tone.SimpleAM.prototype.triggerEnvelopeAttack = function(time, velocity){ + //the port glide time = this.toSeconds(time); - var delayAmount = 1 / note; - this._lfcf.setDelayTimeAtTime(delayAmount, time); - this._noise.start(time); - this._noise.stop(time + delayAmount * this.attackNoise); + //the envelopes + this.carrier.envelope.triggerAttack(time, velocity); + this.modulator.envelope.triggerAttack(time); + return this; + }; + + /** + * trigger the release portion of the note + * + * @param {Tone.Time} [time=now] the time the note will release + * @returns {Tone.SimpleAM} `this` + */ + Tone.SimpleAM.prototype.triggerEnvelopeRelease = function(time){ + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); return this; }; + /** + * The ratio between the two carrier and the modulator. + * @memberOf Tone.SimpleAM# + * @type {number} + * @name harmonicity + */ + Object.defineProperty(Tone.SimpleAM.prototype, "harmonicity", { + get : function(){ + return this._harmonicity.value; + }, + set : function(harm){ + this._harmonicity.value = harm; + } + }); + /** * clean up - * @returns {Tone.PluckSynth} `this` + * @returns {Tone.SimpleAM} `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.dampening = null; - this.resonance = null; + Tone.SimpleAM.prototype.dispose = function(){ + Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); + 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.PluckSynth; + return Tone.SimpleAM; }); ToneModule( function(Tone){ @@ -13422,194 +14766,194 @@ /** - * @class Creates a polyphonic synthesizer out of - * the monophonic voice which is passed in. + * @class SimpleFM is composed of two SimpleSynths where one SimpleSynth is the + * carrier and the second is the modulator. * * @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 + * @extends {Tone.Monophonic} + * @param {Object} options the options available for the synth + * see defaults below * @example - * //a polysynth composed of 6 Voices of MonoSynth - * var synth = new Tone.PolySynth(6, Tone.MonoSynth); - * //set a MonoSynth preset - * synth.setPreset("Pianoetta"); + * var fmSynth = new Tone.SimpleFM(); */ - Tone.PolySynth = function(){ + Tone.SimpleFM = function(options){ - Tone.Instrument.call(this); + options = this.defaultArg(options, Tone.SimpleFM.defaults); + Tone.Monophonic.call(this, options); - var options = this.optionsObject(arguments, ["polyphony", "voice"], Tone.PolySynth.defaults); + /** + * the first voice + * @type {Tone.SimpleSynth} + */ + this.carrier = new Tone.SimpleSynth(options.carrier); + this.carrier.volume.value = -10; /** - * the array of voices - * @type {Array} + * the second voice + * @type {Tone.SimpleSynth} */ - this.voices = new Array(options.polyphony); + this.modulator = new Tone.SimpleSynth(options.modulator); + this.modulator.volume.value = -10; /** - * the queue of free voices - * @private - * @type {Array} + * the frequency control + * @type {Tone.Signal} */ - this._freeVoices = []; + this.frequency = new Tone.Signal(440, Tone.Signal.Units.Frequency); /** - * keeps track of which notes are down + * the ratio between the two voices + * @type {Tone.Multiply} * @private - * @type {Object} */ - this._activeVoices = {}; + this._harmonicity = new Tone.Multiply(options.harmonicity); - //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); - } + /** + * + * + * @type {Tone.Multiply} + * @private + */ + this._modulationIndex = new Tone.Multiply(options.modulationIndex); - //make a copy of the voices - this._freeVoices = this.voices.slice(0); - //get the prototypes and properties + /** + * 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"]); }; - Tone.extend(Tone.PolySynth, Tone.Instrument); + Tone.extend(Tone.SimpleFM, Tone.Monophonic); /** - * 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 {Tone.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; + Tone.SimpleFM.defaults = { + "harmonicity" : 3, + "modulationIndex" : 10, + "carrier" : { + "volume" : -10, + "portamento" : 0, + "oscillator" : { + "type" : "sine" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 + } + }, + "modulator" : { + "volume" : -10, + "portamento" : 0, + "oscillator" : { + "type" : "triangle" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 } } - return this; }; /** - * trigger the attack and release after the specified duration + * trigger the attack portion of the note * - * @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 {Tone.Time} duration the duration of the note - * @param {Tone.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` + * @param {Tone.Time} [time=now] the time the note will occur + * @param {number} [velocity=1] the velocity of the note + * @returns {Tone.SimpleFM} `this` */ - Tone.PolySynth.prototype.triggerAttackRelease = function(value, duration, time, velocity){ + Tone.SimpleFM.prototype.triggerEnvelopeAttack = function(time, velocity){ + //the port glide time = this.toSeconds(time); - this.triggerAttack(value, time, velocity); - this.triggerRelease(value, time + this.toSeconds(duration)); + //the envelopes + this.carrier.envelope.triggerAttack(time, velocity); + this.modulator.envelope.triggerAttack(time); 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 {Tone.Time} [time=now] the release time of the note - * @returns {Tone.PolySynth} `this` + * trigger the release portion of the note + * + * @param {Tone.Time} [time=now] the time the note will release + * @returns {Tone.SimpleFM} `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); - this._activeVoices[stringified] = null; - } - } + Tone.SimpleFM.prototype.triggerEnvelopeRelease = function(time){ + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); return this; }; /** - * set the options on all of the voices - * @param {Object} params - * @returns {Tone.PolySynth} `this` + * The ratio between the two carrier and the modulator. + * @memberOf Tone.SimpleFM# + * @type {number} + * @name harmonicity */ - Tone.PolySynth.prototype.set = function(params){ - for (var i = 0; i < this.voices.length; i++){ - this.voices[i].set(params); + Object.defineProperty(Tone.SimpleFM.prototype, "harmonicity", { + get : function(){ + return this._harmonicity.value; + }, + set : function(harm){ + this._harmonicity.value = harm; } - 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` + * 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. + * @memberOf Tone.SimpleFM# + * @type {number} + * @name modulationIndex */ - Tone.PolySynth.prototype.setPreset = function(presetName){ - for (var i = 0; i < this.voices.length; i++){ - this.voices[i].setPreset(presetName); + Object.defineProperty(Tone.SimpleFM.prototype, "modulationIndex", { + get : function(){ + return this._modulationIndex.value; + }, + set : function(mod){ + this._modulationIndex.value = mod; } - return this; - }; + }); /** * clean up - * @returns {Tone.PolySynth} `this` + * @returns {Tone.SimpleFM} `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; + Tone.SimpleFM.prototype.dispose = function(){ + Tone.Monophonic.prototype.dispose.call(this); + this._writable(["carrier", "modulator", "frequency"]); + 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.PolySynth; + return Tone.SimpleFM; }); ToneModule( function(Tone){ @@ -13640,12 +14984,14 @@ * @type {Tone.Signal} */ this.min = this.input = new Tone.Min(max); + this._readOnly("min"); /** * The max clip value * @type {Tone.Signal} */ this.max = this.output = new Tone.Max(min); + this._readOnly("max"); this.min.connect(this.max); }; @@ -13658,8 +15004,10 @@ */ 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; @@ -13667,256 +15015,6 @@ return Tone.Clip; }); - ToneModule( - function(Tone){ - - - - /** - * this is the maximum value that the divide can handle - * @type {number} - * @const - */ - var MAX_VALUE = Math.pow(2, 13); - - /** - * @private - * @static - * @type {Array} - */ - var guessCurve = new Array(MAX_VALUE); - //set the value - for (var i = 0; i < guessCurve.length; i++){ - var normalized = (i / (guessCurve.length - 1)) * 2 - 1; - if (normalized === 0){ - guessCurve[i] = 0; - } else { - guessCurve[i] = 1 / (normalized * MAX_VALUE); - } - } - - /** - * @class Compute the inverse of the input. - * Uses this approximation algorithm: - * http://en.wikipedia.org/wiki/Multiplicative_inverse#Algorithms - * - * @deprecated - * @extends {Tone.SignalBase} - * @constructor - * @param {number} [precision=3] the precision of the calculation - */ - Tone.Inverse = function(precision){ - - console.warn("Tone.Inverse has been deprecated. Multiply is always more efficient than dividing."); - - Tone.call(this); - - precision = this.defaultArg(precision, 3); - - /** - * a constant generator of the value 2 - * @private - * @type {Tone.Signal} - */ - this._two = new Tone.Signal(2); - - /** - * starting guess is 0.1 times the input - * @type {Tone.Multiply} - * @private - */ - this._guessMult = new Tone.Multiply(1/MAX_VALUE); - - /** - * produces a starting guess based on the input - * @type {WaveShaperNode} - * @private - */ - this._guess = new Tone.WaveShaper(guessCurve); - this.input.chain(this._guessMult, this._guess); - - /** - * the array of inverse helpers - * @type {Array} - * @private - */ - this._inverses = new Array(precision); - - //create the helpers - for (var i = 0; i < precision; i++){ - var guess; - if (i === 0){ - guess = this._guess; - } else { - guess = this._inverses[i-1]; - } - var inv = new InverseHelper(guess, this._two); - this.input.connect(inv); - this._inverses[i] = inv; - } - this._inverses[precision-1].connect(this.output); - }; - - Tone.extend(Tone.Inverse, Tone.SignalBase); - - /** - * clean up - * @returns {Tone.Inverse} `this` - */ - Tone.Inverse.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - for (var i = 0; i < this._inverses.length; i++){ - this._inverses[i].dispose(); - this._inverses[i] = null; - } - this._inverses = null; - this._two.dispose(); - this._two = null; - this._guessMult.dispose(); - this._guessMult = null; - this._guess.disconnect(); - this._guess = null; - return this; - }; - - // BEGIN INVERSE HELPER /////////////////////////////////////////////////// - - /** - * internal helper function for computing the inverse of a signal - * @extends {Tone} - * @constructor - * @private - */ - var InverseHelper = function(guess, two){ - this._outerMultiply = new Tone.Multiply(); - this._innerMultiply = new Tone.Multiply(); - this._subtract = new Tone.Subtract(); - //connections - guess.connect(this._innerMultiply, 0, 1); - two.connect(this._subtract, 0, 0); - this._innerMultiply.connect(this._subtract, 0, 1); - this._subtract.connect(this._outerMultiply, 0, 1); - guess.connect(this._outerMultiply, 0, 0); - this.output = this._outerMultiply; - this.input = this._innerMultiply; - }; - - Tone.extend(InverseHelper); - - InverseHelper.prototype.dispose = function(){ - this._outerMultiply.dispose(); - this._outerMultiply = null; - this._innerMultiply.dispose(); - this._innerMultiply = null; - this._subtract.dispose(); - this._subtract = null; - }; - - // END INVERSE HELPER ///////////////////////////////////////////////////// - - return Tone.Inverse; - }); - ToneModule( - function(Tone){ - - - - /** - * @class Divide by a value or signal. - * input 0: numerator. input 1: divisor. - * - * @deprecated - * @extends {Tone.SignalBase} - * @constructor - * @param {number=} divisor if no value is provided, Tone.Divide will divide the first - * and second inputs. - * @param {number} [precision=3] the precision of the calculation - */ - Tone.Divide = function(divisor, precision){ - - console.warn("Tone.Divide has been deprecated. If possible, it's much more efficient to multiply by the inverse value."); - - Tone.call(this, 2, 0); - - /** - * the denominator value - * @type {Tone.Signal} - * @private - */ - this._denominator = null; - - /** - * the inverse - * @type {Tone} - * @private - */ - this._inverse = new Tone.Inverse(precision); - - /** - * multiply input 0 by the inverse - * @type {Tone.Multiply} - * @private - */ - this._mult = new Tone.Multiply(); - - if (isFinite(divisor)){ - this._denominator = new Tone.Signal(divisor); - this._denominator.connect(this._inverse); - } - this.input[1] = this._inverse; - this._inverse.connect(this._mult, 0, 1); - this.input[0] = this.output = this._mult.input[0]; - }; - - Tone.extend(Tone.Divide, Tone.SignalBase); - - /** - * The value being divided from the incoming signal. Note, that - * if Divide was constructed without a divisor, it expects - * that the signals to numberator will be connected to input 0 and - * the denominator to input 1 and therefore will throw an error when - * trying to set/get the value. - * - * @memberOf Tone.Divide# - * @type {number} - * @name value - */ - Object.defineProperty(Tone.Divide.prototype, "value", { - get : function(){ - if (this._denominator !== null){ - return this._denominator.value; - } else { - throw new Error("cannot switch from signal to number"); - } - }, - set : function(value){ - if (this._denominator !== null){ - this._denominator.value = value; - } else { - throw new Error("cannot switch from signal to number"); - } - } - }); - - /** - * clean up - * @returns {Tone.Divide} `this` - */ - Tone.Divide.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - if (this._denominator){ - this._denominator.dispose(); - this._denominator = null; - } - this._inverse.dispose(); - this._inverse = null; - this._mult.dispose(); - this._mult = null; - return this; - }; - - return Tone.Divide; - }); ToneModule( function(Tone){ @@ -13926,6 +15024,8 @@ * * @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); @@ -14051,6 +15151,7 @@ * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); //make all the inputs and connect them for (var i = 0; i < outputCount; i++){ @@ -14081,13 +15182,14 @@ * @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); - this.gate = null; return this; }; @@ -14126,8 +15228,8 @@ RouteGate.prototype.dispose = function(){ Tone.prototype.dispose.call(this); this.selecter.dispose(); - this.gate.disconnect(); this.selecter = null; + this.gate.disconnect(); this.gate = null; }; @@ -14166,6 +15268,7 @@ * @type {Tone.Signal} */ this.gate = new Tone.Signal(0); + this._readOnly("gate"); /** * thresh the control signal to either 0 or 1 @@ -14214,9 +15317,10 @@ */ Tone.Switch.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._writable("gate"); this.gate.dispose(); - this._thresh.dispose(); this.gate = null; + this._thresh.dispose(); this._thresh = null; return this; }; @@ -14336,7 +15440,9 @@ define( "Tone", [], function() { return Tone; }); - } else { + } else if (typeof module === "object") { + module.exports = Tone; + } else { root.Tone = Tone; } } (this)); \ No newline at end of file diff --git a/build/Tone.min.js b/build/Tone.min.js index 9c496702e..86454a849 100644 --- a/build/Tone.min.js +++ b/build/Tone.min.js @@ -1,11 +1,11 @@ -!function(root){"use strict";function MainModule(a){Tone=a()}function ToneModule(a){a(Tone)}var Tone;/** +!function(root){"use strict";function ToneCore(a){Tone=a()}function ToneModule(a){a(Tone)}var Tone;/** * Tone.js * @author Yotam Mann * @license http://opensource.org/licenses/MIT MIT License * @copyright 2014-2015 Yotam Mann */ -MainModule(function(){function a(a){return void 0===a}function b(a){return"function"==typeof a}var c,d,e,f;if(a(window.AudioContext)&&(window.AudioContext=window.webkitAudioContext),a(window.OfflineAudioContext)&&(window.OfflineAudioContext=window.webkitOfflineAudioContext),a(AudioContext))throw new Error("Web Audio is not supported in this browser");return c=new AudioContext,b(AudioContext.prototype.createGain)||(AudioContext.prototype.createGain=AudioContext.prototype.createGainNode),b(AudioContext.prototype.createDelay)||(AudioContext.prototype.createDelay=AudioContext.prototype.createDelayNode),b(AudioContext.prototype.createPeriodicWave)||(AudioContext.prototype.createPeriodicWave=AudioContext.prototype.createWaveTable),b(AudioBufferSourceNode.prototype.start)||(AudioBufferSourceNode.prototype.start=AudioBufferSourceNode.prototype.noteGrainOn),b(AudioBufferSourceNode.prototype.stop)||(AudioBufferSourceNode.prototype.stop=AudioBufferSourceNode.prototype.noteOff),b(OscillatorNode.prototype.start)||(OscillatorNode.prototype.start=OscillatorNode.prototype.noteOn),b(OscillatorNode.prototype.stop)||(OscillatorNode.prototype.stop=OscillatorNode.prototype.noteOff),b(OscillatorNode.prototype.setPeriodicWave)||(OscillatorNode.prototype.setPeriodicWave=OscillatorNode.prototype.setWaveTable),AudioNode.prototype._nativeConnect=AudioNode.prototype.connect,AudioNode.prototype.connect=function(b,c,d){if(b.input)Array.isArray(b.input)?(a(d)&&(d=0),this.connect(b.input[d])):this.connect(b.input,c,d);else try{b instanceof AudioNode?this._nativeConnect(b,c,d):this._nativeConnect(b,c)}catch(e){throw new Error("error connecting to node: "+b)}},d=function(b,c){a(b)||1===b?this.input=this.context.createGain():b>1&&(this.input=new Array(b)),a(c)||1===c?this.output=this.context.createGain():c>1&&(this.output=new Array(b))},d.prototype.set=function(b,c,e){var f,g,h;"object"==typeof b?e=c:"string"==typeof b&&(f={},f[b]=c,b=f);for(g in b)h=this[g],a(h)||(c=b[g],h instanceof d.Signal?h.value!==c&&(a(e)?h.value=c:h.rampTo(c,e)):h instanceof AudioParam?h.value!==c&&(h.value=c):h instanceof d?h.set(c):h!==c&&(this[g]=c));return this},d.prototype.get=function(c){var e,f,g,h;for(a(c)&&(c=this._collectDefaults(this.constructor)),e={},f=0;f1)for(a=arguments[0],b=1;b1)for(a=1;a0)for(a=this,b=0;b0)for(var a=0;ac){var d=c;c=b,b=d}else if(b==c)return 0;return(a-b)/(c-b)},d.prototype.equalPowerScale=function(a){var b=.5*Math.PI;return Math.sin(a*b)},d.prototype.dbToGain=function(a){return Math.pow(2,a/6)},d.prototype.gainToDb=function(a){return 20*(Math.log(a)/Math.LN10)},d.prototype.now=function(){return this.context.currentTime},d.prototype.samplesToSeconds=function(a){return a/this.context.sampleRate},d.prototype.toSamples=function(a){var b=this.toSeconds(a);return Math.round(b*this.context.sampleRate)},d.prototype.toSeconds=function(a,b){if(b=this.defaultArg(b,this.now()),"number"==typeof a)return a;if("string"==typeof a){var c=0;return"+"===a.charAt(0)&&(a=a.slice(1),c=b),parseFloat(a)+c}return b},d.prototype.isFrequency=function(){var a=new RegExp(/\d*\.?\d+hz$/i);return function(b){return a.test(b)}}(),d.prototype.frequencyToSeconds=function(a){return 1/parseFloat(a)},d.prototype.secondsToFrequency=function(a){return 1/a},d.extend=function(b,c){function e(){}a(c)&&(c=d),e.prototype=c.prototype,b.prototype=new e,b.prototype.constructor=b,b._super=c},f=[],d._initAudioContext=function(a){a(d.context),f.push(a)},d.setContext=function(a){d.prototype.context=a,d.context=a;for(var b=0;bb;b++)d=b/c*2-1,this._curve[b]=a(d,b);return this._shaper.curve=this._curve,this},Object.defineProperty(a.WaveShaper.prototype,"curve",{get:function(){return this._shaper.curve},set:function(a){if(this._isSafari()){var b=a[0];a.unshift(b)}this._curve=new Float32Array(a),this._shaper.curve=this._curve}}),Object.defineProperty(a.WaveShaper.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.WaveShaper.prototype._isSafari=function(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("safari")&&-1===a.indexOf("chrome")},a.WaveShaper.prototype.dispose=function(){return a.prototype.dispose.call(this),this._shaper.disconnect(),this._shaper=null,this._curve=null,this},a.WaveShaper}),ToneModule(function(a){return a.Signal=function(b,c){this.units=this.defaultArg(c,a.Signal.Units.Number),this.output=this._scaler=this.context.createGain(),this.input=this._value=this._scaler.gain,b instanceof AudioParam?(this._scaler.connect(b),b.value=0):this.value=this.defaultArg(b,a.Signal.defaults.value),a.Signal._constant.chain(this._scaler)},a.extend(a.Signal,a.SignalBase),a.Signal.defaults={value:0},Object.defineProperty(a.Signal.prototype,"value",{get:function(){return this._toUnits(this._value.value)},set:function(a){var b=this._fromUnits(a);this.cancelScheduledValues(0),this._value.value=b}}),a.Signal.prototype._fromUnits=function(b){switch(this.units){case a.Signal.Units.Time:return this.toSeconds(b);case a.Signal.Units.Frequency:return this.toFrequency(b);case a.Signal.Units.Decibels:return this.dbToGain(b);case a.Signal.Units.Normal:return Math.min(Math.max(b,0),1);case a.Signal.Units.Audio:return Math.min(Math.max(b,-1),1);default:return b}},a.Signal.prototype._toUnits=function(b){switch(this.units){case a.Signal.Units.Decibels:return this.gainToDb(b);default:return b}},a.Signal.prototype.setValueAtTime=function(a,b){return a=this._fromUnits(a),this._value.setValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.setCurrentValueNow=function(a){a=this.defaultArg(a,this.now());var b=this._value.value;return this.cancelScheduledValues(a),this._value.setValueAtTime(b,a),this},a.Signal.prototype.linearRampToValueAtTime=function(a,b){return a=this._fromUnits(a),this._value.linearRampToValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.exponentialRampToValueAtTime=function(a,b){return a=this._fromUnits(a),a=Math.max(1e-5,a),this._value.exponentialRampToValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.exponentialRampToValueNow=function(a,b){var c=this.now();return this.setCurrentValueNow(c),this.exponentialRampToValueAtTime(a,c+this.toSeconds(b)),this},a.Signal.prototype.linearRampToValueNow=function(a,b){var c=this.now();return this.setCurrentValueNow(c),this.linearRampToValueAtTime(a,c+this.toSeconds(b)),this},a.Signal.prototype.setTargetAtTime=function(a,b,c){return a=this._fromUnits(a),this._value.setTargetAtTime(a,this.toSeconds(b),c),this},a.Signal.prototype.setValueCurveAtTime=function(a,b,c){for(var d=0;d=a?0:1}),this._scale=this.input=new a.Multiply(1e4),this._scale.connect(this._thresh)},a.extend(a.GreaterThanZero,a.SignalBase),a.GreaterThanZero.prototype.dispose=function(){return a.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},a.GreaterThanZero}),ToneModule(function(a){return a.EqualZero=function(){this._scale=this.input=new a.Multiply(1e4),this._thresh=new a.WaveShaper(function(a){return 0===a?1:0},128),this._gtz=this.output=new a.GreaterThanZero,this._scale.chain(this._thresh,this._gtz)},a.extend(a.EqualZero,a.SignalBase),a.EqualZero.prototype.dispose=function(){return a.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},a.EqualZero}),ToneModule(function(a){return a.Equal=function(b){a.call(this,2,0),this._sub=this.input[0]=new a.Subtract(b),this._equals=this.output=new a.EqualZero,this._sub.connect(this._equals),this.input[1]=this._sub.input[1]},a.extend(a.Equal,a.SignalBase),Object.defineProperty(a.Equal.prototype,"value",{get:function(){return this._sub.value},set:function(a){this._sub.value=a}}),a.Equal.prototype.dispose=function(){return a.prototype.dispose.call(this),this._equals.disconnect(),this._equals=null,this._sub.dispose(),this._sub=null,this},a.Equal}),ToneModule(function(a){a.Select=function(c){var d,e;for(c=this.defaultArg(c,2),a.call(this,c,1),this.gate=new a.Signal(0),d=0;c>d;d++)e=new b(d),this.input[d]=e,this.gate.connect(e.selecter),e.connect(this.output)},a.extend(a.Select,a.SignalBase),a.Select.prototype.select=function(a,b){return a=Math.floor(a),this.gate.setValueAtTime(a,this.toSeconds(b)),this},a.Select.prototype.dispose=function(){this.gate.dispose();for(var b=0;bc;c++)this.input[c]=this._sum;this._sum.connect(this._gtz)},a.extend(a.OR,a.SignalBase),a.OR.prototype.dispose=function(){return a.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._sum.disconnect(),this._sum=null,this},a.OR}),ToneModule(function(a){return a.AND=function(b){b=this.defaultArg(b,2),a.call(this,b,0),this._equals=this.output=new a.Equal(b);for(var c=0;b>c;c++)this.input[c]=this._equals},a.extend(a.AND,a.SignalBase),a.AND.prototype.dispose=function(){return a.prototype.dispose.call(this),this._equals.dispose(),this._equals=null,this},a.AND}),ToneModule(function(a){return a.NOT=a.EqualZero,a.NOT}),ToneModule(function(a){return a.GreaterThan=function(b){a.call(this,2,0),this._value=this.input[0]=new a.Subtract(b),this.input[1]=this._value.input[1],this._gtz=this.output=new a.GreaterThanZero,this._value.connect(this._gtz)},a.extend(a.GreaterThan,a.Signal),a.GreaterThan.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._value=null,this._gtz.dispose(),this._gtz=null,this},a.GreaterThan}),ToneModule(function(a){return a.LessThan=function(b){a.call(this,2,0),this._neg=this.input[0]=new a.Negate,this._gt=this.output=new a.GreaterThan,this._rhNeg=new a.Negate,this._value=this.input[1]=new a.Signal(b),this._neg.connect(this._gt),this._value.connect(this._rhNeg),this._rhNeg.connect(this._gt,0,1)},a.extend(a.LessThan,a.Signal),a.LessThan.prototype.dispose=function(){return a.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,this},a.LessThan}),ToneModule(function(a){return a.Abs=function(){a.call(this,1,0),this._ltz=new a.LessThan(0),this._switch=this.output=new a.Select(2),this._negate=new a.Negate,this.input.connect(this._switch,0,0),this.input.connect(this._negate),this._negate.connect(this._switch,0,1),this.input.chain(this._ltz,this._switch.gate)},a.extend(a.Abs,a.SignalBase),a.Abs.prototype.dispose=function(){return a.prototype.dispose.call(this),this._switch.dispose(),this._switch=null,this._ltz.dispose(),this._ltz=null,this._negate.dispose(),this._negate=null,this},a.Abs}),ToneModule(function(a){return a.Max=function(b){a.call(this,2,0),this.input[0]=this.context.createGain(),this._value=this.input[1]=new a.Signal(b),this._ifThenElse=this.output=new a.IfThenElse,this._gt=new a.GreaterThan,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)},a.extend(a.Max,a.Signal),a.Max.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._gt.dispose(),this._value=null,this._ifThenElse=null,this._gt=null,this},a.Max}),ToneModule(function(a){return a.Min=function(b){a.call(this,2,0),this.input[0]=this.context.createGain(),this._ifThenElse=this.output=new a.IfThenElse,this._lt=new a.LessThan,this._value=this.input[1]=new a.Signal(b),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)},a.extend(a.Min,a.Signal),a.Min.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._lt.dispose(),this._value=null,this._ifThenElse=null,this._lt=null,this},a.Min}),ToneModule(function(a){return a.Modulo=function(b){a.call(this,1,1),this._shaper=new a.WaveShaper(Math.pow(2,16)),this._multiply=new a.Multiply,this._subtract=this.output=new a.Subtract,this._modSignal=new a.Signal(b),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(b)},a.extend(a.Modulo,a.SignalBase),a.Modulo.prototype._setWaveShaper=function(a){this._shaper.setMap(function(b){var c=Math.floor((b+1e-4)/a);return c})},Object.defineProperty(a.Modulo.prototype,"value",{get:function(){return this._modSignal.value},set:function(a){this._modSignal.value=a,this._setWaveShaper(a)}}),a.Modulo.prototype.dispose=function(){return a.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,this},a.Modulo}),ToneModule(function(a){function b(a,b,c){var d=new a;return c._eval(b[0]).connect(d,0,0),c._eval(b[1]).connect(d,0,1),d}function c(a,b,c){var d=new a;return c._eval(b[0]).connect(d,0,0),d}function d(a){return a?parseFloat(a):void 0}function e(a){return a&&a.args?parseFloat(a.args):void 0}return a.Expr=function(){var a,b,c,d=this._replacements(Array.prototype.slice.call(arguments)),e=this._parseInputs(d);for(this._nodes=[],this.input=new Array(e),a=0;e>a;a++)this.input[a]=this.context.createGain();b=this._parseTree(d);try{c=this._eval(b)}catch(f){throw this._disposeNodes(),new Error("Could evaluate expression: "+d)}this.output=c},a.extend(a.Expr,a.SignalBase),a.Expr._Expressions={value:{signal:{regexp:/^\d+\.\d+|^\d+/,method:function(b){var c=new a.Signal(d(b));return c}},input:{regexp:/^\$\d/,method:function(a,b){return b.input[d(a.substr(1))]}}},glue:{"(":{regexp:/^\(/},")":{regexp:/^\)/},",":{regexp:/^,/}},func:{abs:{regexp:/^abs/,method:c.bind(this,a.Abs)},min:{regexp:/^min/,method:b.bind(this,a.Min)},max:{regexp:/^max/,method:b.bind(this,a.Max)},"if":{regexp:/^if/,method:function(b,c){var d=new a.IfThenElse;return c._eval(b[0]).connect(d.if),c._eval(b[1]).connect(d.then),c._eval(b[2]).connect(d.else),d}},gt0:{regexp:/^gt0/,method:c.bind(this,a.GreaterThanZero)},eq0:{regexp:/^eq0/,method:c.bind(this,a.EqualZero)},mod:{regexp:/^mod/,method:function(b,c){var d=e(b[1]),f=new a.Modulo(d);return c._eval(b[0]).connect(f),f}},pow:{regexp:/^pow/,method:function(b,c){var d=e(b[1]),f=new a.Pow(d);return c._eval(b[0]).connect(f),f}}},binary:{"+":{regexp:/^\+/,precedence:1,method:b.bind(this,a.Add)},"-":{regexp:/^\-/,precedence:1,method:function(d,e){return 1===d.length?c(a.Negate,d,e):b(a.Subtract,d,e)}},"*":{regexp:/^\*/,precedence:0,method:b.bind(this,a.Multiply)},">":{regexp:/^\>/,precedence:2,method:b.bind(this,a.GreaterThan)},"<":{regexp:/^0;)b=b.trim(),d=c(b),f.push(d),b=b.substr(d.value.length);return{next:function(){return f[++e]},peek:function(){return f[e+1]}}},a.Expr.prototype._parseTree=function(b){function c(a,b){return!k(a)&&"glue"===a.type&&a.value===b}function d(b,c,d){var e,f,g=!1,h=a.Expr._Expressions[c];if(!k(b))for(e in h)if(f=h[e],f.regexp.test(b.value)){if(k(d))return!0;if(f.precedence===d)return!0}return g}function e(a){var b,c;for(k(a)&&(a=5),b=0>a?f():e(a-1),c=j.peek();d(c,"binary",a);)c=j.next(),b={operator:c.value,method:c.method,args:[b,e(a)]},c=j.peek();return b}function f(){var a,b;return a=j.peek(),d(a,"unary")?(a=j.next(),b=f(),{operator:a.value,method:a.method,args:[b]}):g()}function g(){var a,b;if(a=j.peek(),k(a))throw new SyntaxError("Unexpected termination of expression");if("func"===a.type)return a=j.next(),h(a);if("value"===a.type)return a=j.next(),{method:a.method,args:a.value};if(c(a,"(")){if(j.next(),b=e(),a=j.next(),!c(a,")"))throw new SyntaxError("Expected )");return b}throw new SyntaxError("Parse error, cannot process token "+a.value)}function h(a){var b,d=[];if(b=j.next(),!c(b,"("))throw new SyntaxError('Expected ( in a function call "'+a.value+'"');if(b=j.peek(),c(b,")")||(d=i()),b=j.next(),!c(b,")"))throw new SyntaxError('Expected ) in a function call "'+a.value+'"');return{method:a.method,args:d,name:name}}function i(){for(var a,b,d=[];;){if(b=e(),k(b))break;if(d.push(b),a=j.peek(),!c(a,","))break;j.next()}return d}var j=this._tokenize(b),k=this.isUndef.bind(this);return e()},a.Expr.prototype._eval=function(a){if(!this.isUndef(a)){var b=a.method(a.args,this);return this._nodes.push(b),b}},a.Expr.prototype._disposeNodes=function(){var a,b;for(a=0;ac;c++)d=this.context.createBiquadFilter(),d.type=this._type,this.frequency.connect(d.frequency),this.detune.connect(d.detune),this.Q.connect(d.Q),this.gain.connect(d.gain),this._filters[c]=d;e=[this.input].concat(this._filters).concat([this.output]),this.connectSeries.apply(this,e)}}),a.Filter.prototype.dispose=function(){a.prototype.dispose.call(this);for(var b=0;bc){for(this._highFrequencies=!0,f=Math.round(c/e*this._delayCount),g=0;f>g;g++)this._delays[g].delayTime.setValueAtTime(1/b+a,d);a=Math.floor(c)/b}else if(this._highFrequencies)for(this._highFrequencies=!1,h=0;h=c?a:b})},Object.defineProperty(a.Follower.prototype,"attack",{get:function(){return this._attack},set:function(a){this._attack=a,this._setAttackRelease(this._attack,this._release)}}),Object.defineProperty(a.Follower.prototype,"release",{get:function(){return this._release},set:function(a){this._release=a,this._setAttackRelease(this._attack,this._release)}}),a.Follower.prototype.connect=a.Signal.prototype.connect,a.Follower.prototype.dispose=function(){return a.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,this},a.Follower}),ToneModule(function(a){return a.Gate=function(){a.call(this);var b=this.optionsObject(arguments,["threshold","attack","release"],a.Gate.defaults);this._follower=new a.Follower(b.attack,b.release),this._gt=new a.GreaterThan(this.dbToGain(b.threshold)),this.input.connect(this.output),this.input.chain(this._gt,this._follower,this.output.gain)},a.extend(a.Gate),a.Gate.defaults={attack:.1,release:.1,threshold:-40},Object.defineProperty(a.Gate.prototype,"threshold",{get:function(){return this.gainToDb(this._gt.value)},set:function(a){this._gt.value=this.dbToGain(a)}}),Object.defineProperty(a.Gate.prototype,"attack",{get:function(){return this._follower.attack},set:function(a){this._follower.attack=a}}),Object.defineProperty(a.Gate.prototype,"release",{get:function(){return this._follower.release},set:function(a){this._follower.release=a}}),a.Gate.prototype.dispose=function(){return a.prototype.dispose.call(this),this._follower.dispose(),this._gt.dispose(),this._follower=null,this._gt=null,this},a.Gate}),ToneModule(function(a){return a.Clock=function(b,c){this._oscillator=null,this._jsNode=this.context.createScriptProcessor(this.bufferSize,1,1),this._jsNode.onaudioprocess=this._processBuffer.bind(this),this.frequency=new a.Signal(b),this._upTick=!1,this.tick=c,this._jsNode.noGC()},a.extend(a.Clock),a.Clock.prototype.start=function(a){if(!this._oscillator){this._oscillator=this.context.createOscillator(),this._oscillator.type="square",this._oscillator.connect(this._jsNode),this.frequency.connect(this._oscillator.frequency),this._upTick=!1;var b=this.toSeconds(a);this._oscillator.start(b)}return this},a.Clock.prototype.stop=function(a,b){var c,d;return this._oscillator&&(c=this.now(),d=this.toSeconds(a,c),this._oscillator.stop(d),this._oscillator=null,a?setTimeout(b,1e3*(d-c)):b()),this},a.Clock.prototype._processBuffer=function(a){var b,c,d=this.defaultArg(a.playbackTime,this.now()),e=this._jsNode.bufferSize,f=a.inputBuffer.getChannelData(0),g=this._upTick,h=this;for(b=0;e>b;b++)c=f[b],c>0&&!g?(g=!0,setTimeout(function(){var a=d+h.samplesToSeconds(b+2*e);return function(){h.tick&&h.tick(a)}}(),0)):0>c&&g&&(g=!1);this._upTick=g},a.Clock.prototype.dispose=function(){return this._jsNode.disconnect(),this.frequency.dispose(),this.frequency=null,this._oscillator&&(this._oscillator.disconnect(),this._oscillator=null),this._jsNode.onaudioprocess=function(){},this._jsNode=null,this.tick=null,this},a.Clock}),ToneModule(function(Tone){var tatum,timelineTicks,transportTicks,swingSubdivision,swingTatum,swingAmount,transportTimeSignature,loopStart,loopEnd,intervals,timeouts,transportTimeline,timelineProgress,SyncedSources,SyncedSignals,TransportState,processIntervals,processTimeouts,processTimeline,TimelineEventIDCounter,TimelineEvent,TransportConstructor;return Tone.Transport=function(){this._clock=new Tone.Clock(0,this._processTick.bind(this)),this.loop=!1,this.bpm=new Tone.Signal(120,Tone.Signal.Units.BPM),this._bpmMult=new Tone.Multiply(1/60*tatum),this.state=TransportState.STOPPED,this.bpm.chain(this._bpmMult,this._clock.frequency)},Tone.extend(Tone.Transport),Tone.Transport.defaults={bpm:120,swing:0,swingSubdivision:"16n",timeSignature:4,loopStart:0,loopEnd:"4m"},tatum=12,timelineTicks=0,transportTicks=0,swingSubdivision="16n",swingTatum=3,swingAmount=0,transportTimeSignature=4,loopStart=0,loopEnd=4*tatum,intervals=[],timeouts=[],transportTimeline=[],timelineProgress=0,SyncedSources=[],SyncedSignals=[],TransportState={STARTED:"started",PAUSED:"paused",STOPPED:"stopped"},Tone.Transport.prototype._processTick=function(a){this.state===TransportState.STARTED&&(swingAmount>0&&timelineTicks%tatum!==0&&timelineTicks%swingTatum===0&&(a+=this._ticksToSeconds(swingTatum)*swingAmount),processIntervals(a),processTimeouts(a),processTimeline(a),transportTicks+=1,timelineTicks+=1,this.loop&&timelineTicks===loopEnd&&this._setTicks(loopStart))},Tone.Transport.prototype._setTicks=function(a){var b,c;for(timelineTicks=a,b=0;b=a){timelineProgress=b;break}},processIntervals=function(a){var b,c,d;for(b=0,c=intervals.length;c>b;b++)d=intervals[b],d.testInterval(transportTicks)&&d.doCallback(a)},processTimeouts=function(a){var b,c,d,e,f=0;for(b=0,c=timeouts.length;c>b;b++)if(d=timeouts[b],e=d.callbackTick(),transportTicks>=e)d.doCallback(a),f++;else if(e>transportTicks)break;timeouts.splice(0,f)},processTimeline=function(a){var b,c,d,e;for(b=timelineProgress,c=transportTimeline.length;c>b;b++)if(d=transportTimeline[b],e=d.callbackTick(),e===timelineTicks)timelineProgress=b,d.doCallback(a);else if(e>timelineTicks)break},Tone.Transport.prototype.setInterval=function(a,b,c){var d=this._toTicks(b),e=new TimelineEvent(a,c,d,transportTicks);return intervals.push(e),e.id},Tone.Transport.prototype.clearInterval=function(a){var b,c;for(b=0;b0;return intervals=[],a},Tone.Transport.prototype.setTimeout=function(a,b,c){var d,e,f,g=this._toTicks(b),h=new TimelineEvent(a,c,g+transportTicks,0);for(d=0,e=timeouts.length;e>d;d++)if(f=timeouts[d],f.callbackTick()>h.callbackTick())return timeouts.splice(d,0,h),h.id;return timeouts.push(h),h.id},Tone.Transport.prototype.clearTimeout=function(a){var b,c;for(b=0;b0;return timeouts=[],a},Tone.Transport.prototype.setTimeline=function(a,b,c){var d,e,f,g=this._toTicks(b),h=new TimelineEvent(a,c,g,0);for(d=timelineProgress,e=transportTimeline.length;e>d;d++)if(f=transportTimeline[d],f.callbackTick()>h.callbackTick())return transportTimeline.splice(d,0,h),h.id;return transportTimeline.push(h),h.id},Tone.Transport.prototype.clearTimeline=function(a){var b,c;for(b=0;b0;return transportTimeline=[],a},Tone.Transport.prototype._toTicks=function(a){var b=this.toSeconds(a),c=this.notationToSeconds("4n"),d=b/c,e=d*tatum;return Math.round(e)},Tone.Transport.prototype._ticksToSeconds=function(a,b,c){a=Math.floor(a);var d=this.notationToSeconds("4n",b,c);return d*a/tatum},Tone.Transport.prototype.nextBeat=function(a){var b,c,d;return a=this.defaultArg(a,"4n"),b=this._toTicks(a),c=transportTicks%b,d=c,c>0&&(d=b-c),this._ticksToSeconds(d)},Tone.Transport.prototype.start=function(a,b){var c,d,e,f;if(this.state===TransportState.STOPPED||this.state===TransportState.PAUSED)for(this.isUndef(b)||this._setTicks(this._toTicks(b)),this.state=TransportState.STARTED,c=this.toSeconds(a),this._clock.start(c),d=0;d1){for(originalTime=time,i=0;ib?a.Source.State.STARTED:this._nextStop<=b?a.Source.State.STOPPED:a.Source.State.STOPPED},a.Source.prototype.start=function(b){return b=this.toSeconds(b),(this._stateAtTime(b)!==a.Source.State.STARTED||this.retrigger)&&(this._nextStart=b,this._nextStop=1/0,this._start.apply(this,arguments)),this},a.Source.prototype.stop=function(b){var c,d=this.now();return b=this.toSeconds(b,d),this._stateAtTime(b)===a.Source.State.STARTED&&(this._nextStop=this.toSeconds(b),clearTimeout(this._timeout),c=b-d,c>0?this._timeout=setTimeout(this.onended,1e3*c+20):this.onended(),this._stop.apply(this,arguments)),this},a.Source.prototype.pause=function(a){return this.stop(a),this},a.Source.prototype.sync=function(b){return a.Transport.syncSource(this,b),this},a.Source.prototype.unsync=function(){return a.Transport.unsyncSource(this),this},a.Source.prototype.dispose=function(){a.prototype.dispose.call(this),this.stop(),clearTimeout(this._timeout),this.onended=function(){},this.volume.dispose(),this.volume=null},a.Source}),ToneModule(function(a){return a.Oscillator=function(){var b=this.optionsObject(arguments,["frequency","type"],a.Oscillator.defaults);a.Source.call(this,b),this._oscillator=null,this.frequency=new a.Signal(b.frequency,a.Signal.Units.Frequency),this.detune=new a.Signal(b.detune),this._wave=null,this._phase=b.phase,this._type=null,this.type=b.type,this.phase=this._phase},a.extend(a.Oscillator,a.Source),a.Oscillator.defaults={type:"sine",frequency:440,detune:0,phase:0},a.Oscillator.prototype._start=function(a){this._oscillator=this.context.createOscillator(),this._oscillator.setPeriodicWave(this._wave),this._oscillator.connect(this.output),this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.start(this.toSeconds(a))},a.Oscillator.prototype._stop=function(a){return this._oscillator&&(this._oscillator.stop(this.toSeconds(a)),this._oscillator=null),this},a.Oscillator.prototype.syncFrequency=function(){return a.Transport.syncSignal(this.frequency),this},a.Oscillator.prototype.unsyncFrequency=function(){return a.Transport.unsyncSignal(this.frequency),this},Object.defineProperty(a.Oscillator.prototype,"type",{get:function(){return this._type},set:function(a){var b,c,d,e,f,g,h,i,j;if(this.type!==a){for(b=4096,c=b/2,d=new Float32Array(c),e=new Float32Array(c),d[0]=0,e[0]=0,f=this._phase,g=1;c>g;++g){switch(h=2/(g*Math.PI),a){case"sine":i=1===g?1:0;break;case"square":i=1&g?2*h:0;break;case"sawtooth":i=h*(1&g?1:-1);break;case"triangle":i=1&g?2*h*h*(g-1>>1&1?-1:1):0;break;default:throw new TypeError("invalid oscillator type: "+a)}0!==i?(d[g]=-i*Math.sin(f),e[g]=i*Math.cos(f)):(d[g]=0,e[g]=0)}j=this.context.createPeriodicWave(d,e),this._wave=j,null!==this._oscillator&&this._oscillator.setPeriodicWave(this._wave),this._type=a}}}),Object.defineProperty(a.Oscillator.prototype,"phase",{get:function(){return this._phase*(180/Math.PI)},set:function(a){this._phase=a*Math.PI/180,this.type=this._type}}),a.Oscillator.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),null!==this._oscillator&&(this._oscillator.disconnect(),this._oscillator=null),this.frequency.dispose(),this.frequency=null,this.detune.dispose(),this.detune=null,this._wave=null,this},a.Oscillator}),ToneModule(function(a){return a.AudioToGain=function(){this._norm=this.input=this.output=new a.WaveShaper([0,1])},a.extend(a.AudioToGain,a.SignalBase),a.AudioToGain.prototype.dispose=function(){return a.prototype.dispose.call(this),this._norm.disconnect(),this._norm=null,this},a.AudioToGain}),ToneModule(function(a){return a.LFO=function(){var b=this.optionsObject(arguments,["frequency","min","max"],a.LFO.defaults);this.oscillator=new a.Oscillator({frequency:b.frequency,type:b.type,phase:b.phase}),this.frequency=this.oscillator.frequency,this.amplitude=this.oscillator.volume,this.amplitude.units=a.Signal.Units.Normal,this.amplitude.value=b.amplitude,this._a2g=new a.AudioToGain,this._scaler=this.output=new a.Scale(b.min,b.max),this.oscillator.chain(this._a2g,this._scaler)},a.extend(a.LFO,a.Oscillator),a.LFO.defaults={type:"sine",min:0,max:1,phase:0,frequency:"4n",amplitude:1},a.LFO.prototype.start=function(a){return this.oscillator.start(a),this},a.LFO.prototype.stop=function(a){return this.oscillator.stop(a),this},a.LFO.prototype.sync=function(a){return this.oscillator.sync(a),this.oscillator.syncFrequency(),this},a.LFO.prototype.unsync=function(){return this.oscillator.unsync(),this.oscillator.unsyncFrequency(),this},Object.defineProperty(a.LFO.prototype,"min",{get:function(){return this._scaler.min},set:function(a){this._scaler.min=a}}),Object.defineProperty(a.LFO.prototype,"max",{get:function(){return this._scaler.max},set:function(a){this._scaler.max=a}}),Object.defineProperty(a.LFO.prototype,"type",{get:function(){return this.oscillator.type},set:function(a){this.oscillator.type=a}}),Object.defineProperty(a.LFO.prototype,"phase",{get:function(){return this.oscillator.phase},set:function(a){this.oscillator.phase=a}}),a.LFO.prototype.connect=a.Signal.prototype.connect,a.LFO.prototype.dispose=function(){return a.prototype.dispose.call(this),this.oscillator.dispose(),this.oscillator=null,this._scaler.dispose(),this._scaler=null,this._a2g.dispose(),this._a2g=null,this.frequency=null,this.amplitude=null,this},a.LFO}),ToneModule(function(a){return a.Limiter=function(b){this._compressor=this.input=this.output=new a.Compressor({attack:1e-4,decay:1e-4,threshold:b}),this.threshold=this._compressor.threshold},a.extend(a.Limiter),a.Limiter.prototype.dispose=function(){return a.prototype.dispose.call(this),this._compressor.dispose(),this._compressor=null,this.threshold=null,this},a.Limiter}),ToneModule(function(a){a.LowpassCombFilter=function(){var c,d,e,f;for(a.call(this),c=this.optionsObject(arguments,["minDelay","maxDelay"],a.LowpassCombFilter.defaults),d=Math.ceil(this.bufferSize/(c.minDelay*this.context.sampleRate)),d=Math.min(d,10),d=Math.max(d,1),this._filterDelayCount=d,this._filterDelays=new Array(this._filterDelayCount),this.dampening=new a.Signal(c.dampening,a.Signal.Units.Frequency),this.resonance=new a.Signal(c.resonance,a.Signal.Units.Normal),this._resScale=new a.ScaleExp(.01,1/this._filterDelayCount-.001,.5),this._highFrequencies=!1,this._delayTime=c.delayTime,this._feedback=this.context.createGain(),e=0;ed){for(this._highFrequencies=!0,f=Math.round(d/e*this._filterDelayCount),g=0;f>g;g++)this._filterDelays[g].setDelay(1/c+this._delayTime,b);this._delayTime=Math.floor(d)/c}else if(this._highFrequencies)for(this._highFrequencies=!1,h=0;hh;h++)f=c[h],!g&&f>.95&&(g=!0,this._lastClip=Date.now()),e+=f,d+=f*f;i=e/k,j=Math.sqrt(d/k),this._volume[b]=Math.max(j,this._volume[b]*l),this._values[b]=i}},a.Meter.prototype.getLevel=function(a){a=this.defaultArg(a,0);var b=this._volume[a];return 1e-5>b?0:b},a.Meter.prototype.getValue=function(a){return a=this.defaultArg(a,0),this._values[a]},a.Meter.prototype.getDb=function(a){return this.gainToDb(this.getLevel(a))},a.Meter.prototype.isClipped=function(){return Date.now()-this._lastClipthis._recordEndSample?(this.state=b.STOPPED,this._callback(this._recordBuffers)):d>this._recordStartSample?(g=0,h=Math.min(this._recordEndSample-d,e),this._recordChannels(a.inputBuffer,g,h,e)):f>this._recordStartSample&&(h=f-this._recordStartSample,g=e-h,this._recordChannels(a.inputBuffer,g,h,e)))},a.Recorder.prototype._recordChannels=function(a,b,c,d){var e,f,g,h,i=this._recordBufferOffset,j=this._recordBuffers;for(e=0;eg;g++)h=g-b,j[e][h+i]=f[g];this._recordBufferOffset+=c},a.Recorder.prototype.record=function(a,c,d){if(this.state===b.STOPPED){this.clear(),this._recordBufferOffset=0,c=this.defaultArg(c,0),this._recordDuration=this.toSamples(a),this._recordStartSample=this.toSamples("+"+c),this._recordEndSample=this._recordStartSample+this._recordDuration;for(var e=0;e0){if(a.Buffer._currentDownloads.length0){for(b=0;g>b;b++)c=a.Buffer._currentDownloads[b],f+=c.progress;h=f}d=g-h,e=a.Buffer._totalDownloads-a.Buffer._queue.length-d,a.Buffer.onprogress(e/a.Buffer._totalDownloads)},a.Buffer.load=function(b,c){var d=new XMLHttpRequest;return d.open("GET",b,!0),d.responseType="arraybuffer",d.onload=function(){a.context.decodeAudioData(d.response,function(a){if(!a)throw new Error("could not decode audio data:"+b);c(a)})},d.send(),d},a.Buffer.onload=function(){},a.Buffer.onprogress=function(){},a.Buffer.onerror=function(){},a.Buffer}),ToneModule(function(a){var b={};return a.prototype.send=function(a,c){b.hasOwnProperty(a)||(b[a]=this.context.createGain());var d=this.context.createGain();return d.gain.value=this.defaultArg(c,1),this.output.chain(d,b[a]),d},a.prototype.receive=function(a,c){return b.hasOwnProperty(a)||(b[a]=this.context.createGain()),this.isUndef(c)&&(c=this.input),b[a].connect(c),this},a}),ToneModule(function(a){function b(a,b,d){var e,f,g,h;if(c.hasOwnProperty(a))for(e=c[a],f=0,g=e.length;g>f;f++)h=e[f],Array.isArray(d)?h.apply(window,[b].concat(d)):h(b,d)}var c,d,e,f;return a.Note=function(b,c,d){this.value=d,this._channel=b,this._timelineID=a.Transport.setTimeline(this._trigger.bind(this),c)},a.Note.prototype._trigger=function(a){b(this._channel,a,this.value)},a.Note.prototype.dispose=function(){return a.Tranport.clearTimeline(this._timelineID),this.value=null,this},c={},a.Note.route=function(a,b){c.hasOwnProperty(a)?c[a].push(b):c[a]=[b]},a.Note.unroute=function(a,b){var d,e;c.hasOwnProperty(a)&&(d=c[a],e=d.indexOf(b),-1!==e&&c[a].splice(e,1))},a.Note.parseScore=function(b){var c,d,e,f,g,h,i,j=[];for(c in b)if(d=b[c],"tempo"===c)a.Transport.bpm.value=d;else if("timeSignature"===c)a.Transport.timeSignature=d[0]/(d[1]/4);else{if(!Array.isArray(d))throw new TypeError("score parts must be Arrays");for(e=0;ed;++d)e=2*d/c-1,b[d]=0===e?0:this._getCoefficient(e,a,{});this._shaper.curve=b}}),Object.defineProperty(a.Chebyshev.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.Chebyshev.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},a.Chebyshev}),ToneModule(function(a){return a.StereoEffect=function(){a.call(this);var b=this.optionsObject(arguments,["wet"],a.Effect.defaults);this._dryWet=new a.CrossFade(b.wet),this.wet=this._dryWet.fade,this._split=new a.Split,this.effectSendL=this._split.left,this.effectSendR=this._split.right,this._merge=new a.Merge,this.effectReturnL=this._merge.left,this.effectReturnR=this._merge.right,this.input.connect(this._split),this.input.connect(this._dryWet,0,0),this._merge.connect(this._dryWet,0,1),this._dryWet.connect(this.output)},a.extend(a.StereoEffect,a.Effect),a.StereoEffect.prototype.dispose=function(){return a.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.wet=null,this},a.StereoEffect}),ToneModule(function(a){return a.FeedbackEffect=function(){var b=this.optionsObject(arguments,["feedback"]);b=this.defaultArg(b,a.FeedbackEffect.defaults),a.Effect.call(this,b),this.feedback=new a.Signal(b.feedback,a.Signal.Units.Normal),this._feedbackGain=this.context.createGain(),this.effectReturn.chain(this._feedbackGain,this.effectSend),this.feedback.connect(this._feedbackGain.gain)},a.extend(a.FeedbackEffect,a.Effect),a.FeedbackEffect.defaults={feedback:.125},a.FeedbackEffect.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this.feedback.dispose(),this.feedback=null,this._feedbackGain.disconnect(),this._feedbackGain=null,this},a.FeedbackEffect}),ToneModule(function(a){return a.StereoXFeedbackEffect=function(){var b=this.optionsObject(arguments,["feedback"],a.FeedbackEffect.defaults);a.StereoEffect.call(this,b),this.feedback=new a.Signal(b.feedback),this._feedbackLR=this.context.createGain(),this._feedbackRL=this.context.createGain(),this.effectReturnL.chain(this._feedbackLR,this.effectSendR),this.effectReturnR.chain(this._feedbackRL,this.effectSendL),this.feedback.fan(this._feedbackLR.gain,this._feedbackRL.gain)},a.extend(a.StereoXFeedbackEffect,a.FeedbackEffect),a.StereoXFeedbackEffect.prototype.dispose=function(){return a.StereoEffect.prototype.dispose.call(this),this.feedback.dispose(),this.feedback=null,this._feedbackLR.disconnect(),this._feedbackLR=null,this._feedbackRL.disconnect(),this._feedbackRL=null,this},a.StereoXFeedbackEffect}),ToneModule(function(a){return a.Chorus=function(){var b=this.optionsObject(arguments,["frequency","delayTime","depth"],a.Chorus.defaults);a.StereoXFeedbackEffect.call(this,b),this._depth=b.depth,this._delayTime=b.delayTime/1e3,this._lfoL=new a.LFO(b.rate,0,1),this._lfoR=new a.LFO(b.rate,0,1),this._lfoR.phase=180,this._delayNodeL=this.context.createDelay(),this._delayNodeR=this.context.createDelay(),this.frequency=this._lfoL.frequency,this.connectSeries(this.effectSendL,this._delayNodeL,this.effectReturnL),this.connectSeries(this.effectSendR,this._delayNodeR,this.effectReturnR),this.effectSendL.connect(this.effectReturnL),this.effectSendR.connect(this.effectReturnR),this._lfoL.connect(this._delayNodeL.delayTime),this._lfoR.connect(this._delayNodeR.delayTime),this._lfoL.start(),this._lfoR.start(),this._lfoL.frequency.connect(this._lfoR.frequency),this.depth=this._depth,this.frequency.value=b.frequency,this.type=b.type},a.extend(a.Chorus,a.StereoXFeedbackEffect),a.Chorus.defaults={frequency:1.5,delayTime:3.5,depth:.7,feedback:.1,type:"sine"},Object.defineProperty(a.Chorus.prototype,"depth",{get:function(){return this._depth},set:function(a){this._depth=a;var b=this._delayTime*a;this._lfoL.min=this._delayTime-b,this._lfoL.max=this._delayTime+b,this._lfoR.min=this._delayTime-b,this._lfoR.max=this._delayTime+b}}),Object.defineProperty(a.Chorus.prototype,"delayTime",{get:function(){return 1e3*this._delayTime},set:function(a){this._delayTime=a/1e3,this.depth=this._depth}}),Object.defineProperty(a.Chorus.prototype,"type",{get:function(){return this._lfoL.type},set:function(a){this._lfoL.type=a,this._lfoR.type=a}}),a.Chorus.prototype.dispose=function(){return a.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.frequency=null,this},a.Chorus}),ToneModule(function(a){return a.Convolver=function(b){a.Effect.apply(this,arguments),this._convolver=this.context.createConvolver(),this._buffer=new a.Buffer(b,function(a){this.buffer=a}.bind(this)),this.connectEffect(this._convolver)},a.extend(a.Convolver,a.Effect),Object.defineProperty(a.Convolver.prototype,"buffer",{get:function(){return this._buffer.get()},set:function(a){this._buffer.set(a),this._convolver.buffer=a}}),a.Convolver.prototype.load=function(a,b){return this._buffer.load(a,function(a){this.buffer=a,b&&b()}.bind(this)),this},a.Convolver.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._convolver.disconnect(),this._convolver=null,this._buffer.dispose(),this._buffer=null,this},a.Convolver}),ToneModule(function(a){return a.Distortion=function(){var b=this.optionsObject(arguments,["distortion"],a.Distortion.defaults);a.Effect.call(this),this._shaper=new a.WaveShaper(4096),this._distortion=b.distortion,this.connectEffect(this._shaper),this.distortion=b.distortion,this.oversample=b.oversample},a.extend(a.Distortion,a.Effect),a.Distortion.defaults={distortion:.4,oversample:"none"},Object.defineProperty(a.Distortion.prototype,"distortion",{get:function(){return this._distortion},set:function(a){var b,c;this._distortion=a,b=100*a,c=Math.PI/180,this._shaper.setMap(function(a){return Math.abs(a)<.001?0:(3+b)*a*20*c/(Math.PI+b*Math.abs(a))})}}),Object.defineProperty(a.Distortion.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.Distortion.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},a.Distortion}),ToneModule(function(a){return a.FeedbackDelay=function(){var b=this.optionsObject(arguments,["delayTime","feedback"],a.FeedbackDelay.defaults);a.FeedbackEffect.call(this,b),this.delayTime=new a.Signal(b.delayTime,a.Signal.Units.Time),this._delayNode=this.context.createDelay(4),this.connectEffect(this._delayNode),this.delayTime.connect(this._delayNode.delayTime)},a.extend(a.FeedbackDelay,a.FeedbackEffect),a.FeedbackDelay.defaults={delayTime:.25},a.FeedbackDelay.prototype.dispose=function(){return a.FeedbackEffect.prototype.dispose.call(this),this.delayTime.dispose(),this._delayNode.disconnect(),this._delayNode=null,this.delayTime=null,this},a.FeedbackDelay}),ToneModule(function(a){var b=[1557/44100,1617/44100,1491/44100,1422/44100,1277/44100,1356/44100,1188/44100,1116/44100],c=[225,556,441,341];return a.Freeverb=function(){var d,e,f,g,h,i,j=this.optionsObject(arguments,["roomSize","dampening"],a.Freeverb.defaults);for(a.StereoEffect.call(this,j),this.roomSize=new a.Signal(j.roomSize),this.dampening=new a.Signal(j.dampening),this._dampeningScale=new a.ScaleExp(100,8e3,.5),this._combFilters=[],this._allpassFiltersL=[],this._allpassFiltersR=[],d=0;dd;d++)e=this.context.createBiquadFilter(),e.type="allpass",e.Q.value=c,b.connect(e.frequency),f[d]=e;return this.connectSeries.apply(this,f),f},Object.defineProperty(a.Phaser.prototype,"depth",{get:function(){return this._depth},set:function(a){this._depth=a;var b=this._baseFrequency+this._baseFrequency*a;this._lfoL.max=b,this._lfoR.max=b}}),Object.defineProperty(a.Phaser.prototype,"baseFrequency",{get:function(){return this._baseFrequency},set:function(a){this._baseFrequency=a,this._lfoL.min=a,this._lfoR.min=a,this.depth=this._depth}}),a.Phaser.prototype.dispose=function(){var b,c;for(a.StereoEffect.prototype.dispose.call(this),this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,b=0;ba?-1:1}),this._sawtooth.chain(this._thresh,this.output),this.width.chain(this._widthGate,this._thresh)},a.extend(a.PulseOscillator,a.Oscillator),a.PulseOscillator.defaults={frequency:440,detune:0,phase:0,width:.2},a.PulseOscillator.prototype._start=function(a){a=this.toSeconds(a),this._sawtooth.start(a),this._widthGate.gain.setValueAtTime(1,a)},a.PulseOscillator.prototype._stop=function(a){a=this.toSeconds(a),this._sawtooth.stop(a),this._widthGate.gain.setValueAtTime(0,a)},Object.defineProperty(a.PulseOscillator.prototype,"phase",{get:function(){return this._sawtooth.phase},set:function(a){this._sawtooth.phase=a}}),Object.defineProperty(a.PulseOscillator.prototype,"type",{get:function(){return"pulse"}}),a.PulseOscillator.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),this._sawtooth.dispose(),this._sawtooth=null,this.width.dispose(),this.width=null,this._widthGate.disconnect(),this._widthGate=null,this._thresh.disconnect(),this._thresh=null,this.frequency=null,this.detune=null,this},a.PulseOscillator}),ToneModule(function(a){return a.PWMOscillator=function(){var b=this.optionsObject(arguments,["frequency","modulationFrequency"],a.PWMOscillator.defaults);a.Source.call(this,b),this._pulse=new a.PulseOscillator(b.modulationFrequency),this._pulse._sawtooth.type="sine",this._modulator=new a.Oscillator({frequency:b.frequency,detune:b.detune}),this._scale=new a.Multiply(1.01),this.frequency=this._modulator.frequency,this.detune=this._modulator.detune,this.modulationFrequency=this._pulse.frequency,this._modulator.chain(this._scale,this._pulse.width),this._pulse.connect(this.output)},a.extend(a.PWMOscillator,a.Oscillator),a.PWMOscillator.defaults={frequency:440,detune:0,modulationFrequency:.4},a.PWMOscillator.prototype._start=function(a){a=this.toSeconds(a),this._modulator.start(a),this._pulse.start(a)},a.PWMOscillator.prototype._stop=function(a){a=this.toSeconds(a),this._modulator.stop(a),this._pulse.stop(a)},Object.defineProperty(a.PWMOscillator.prototype,"type",{get:function(){return"pwm"}}),Object.defineProperty(a.PWMOscillator.prototype,"phase",{get:function(){return this._modulator.phase},set:function(a){this._modulator.phase=a}}),a.PWMOscillator.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),this._pulse.dispose(),this._pulse=null,this._scale.dispose(),this._scale=null,this._modulator.dispose(),this._modulator=null,this.frequency=null,this.detune=null,this.modulationFrequency=null,this},a.PWMOscillator}),ToneModule(function(a){a.OmniOscillator=function(){var b=this.optionsObject(arguments,["frequency","type"],a.OmniOscillator.defaults);a.Source.call(this,b),this.frequency=new a.Signal(b.frequency,a.Signal.Units.Frequency),this.detune=new a.Signal(b.detune),this._sourceType=void 0,this._oscillator=null,this.type=b.type},a.extend(a.OmniOscillator,a.Oscillator),a.OmniOscillator.defaults={frequency:440,detune:0,type:"sine",width:.4,modulationFrequency:.4};var b={PulseOscillator:"PulseOscillator",PWMOscillator:"PWMOscillator",Oscillator:"Oscillator"};return a.OmniOscillator.prototype._start=function(a){this._oscillator.start(a)},a.OmniOscillator.prototype._stop=function(a){this._oscillator.stop(a)},Object.defineProperty(a.OmniOscillator.prototype,"type",{get:function(){return this._oscillator.type},set:function(c){if("sine"===c||"square"===c||"triangle"===c||"sawtooth"===c)this._sourceType!==b.Oscillator&&(this._sourceType=b.Oscillator,this._createNewOscillator(a.Oscillator)),this._oscillator.type=c;else if("pwm"===c)this._sourceType!==b.PWMOscillator&&(this._sourceType=b.PWMOscillator,this._createNewOscillator(a.PWMOscillator));else{if("pulse"!==c)throw new TypeError("Tone.OmniOscillator does not support type "+c);this._sourceType!==b.PulseOscillator&&(this._sourceType=b.PulseOscillator,this._createNewOscillator(a.PulseOscillator))}}}),a.OmniOscillator.prototype._createNewOscillator=function(b){var c,d=this.now()+this.bufferTime;null!==this._oscillator&&(c=this._oscillator,c.stop(d),c.onended=function(){c.dispose(),c=null}),this._oscillator=new b,this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.connect(this.output),this.state===a.Source.State.STARTED&&this._oscillator.start(d)},Object.defineProperty(a.OmniOscillator.prototype,"phase",{get:function(){return this._oscillator.phase},set:function(a){this._oscillator.phase=a}}),Object.defineProperty(a.OmniOscillator.prototype,"width",{get:function(){return this._sourceType===b.PulseOscillator?this._oscillator.width:void 0}}),Object.defineProperty(a.OmniOscillator.prototype,"modulationFrequency",{get:function(){return this._sourceType===b.PWMOscillator?this._oscillator.modulationFrequency:void 0}}),a.OmniOscillator.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),this.detune.dispose(),this.detune=null,this.frequency.dispose(),this.frequency=null,this._oscillator.dispose(),this._oscillator=null,this._sourceType=null,this},a.OmniOscillator}),ToneModule(function(a){return a.Instrument=function(){this.output=this.context.createGain(),this.volume=new a.Signal(this.output.gain,a.Signal.Units.Decibels)},a.extend(a.Instrument),a.Instrument.prototype.triggerAttack=function(){},a.Instrument.prototype.triggerRelease=function(){},a.Instrument.prototype.triggerAttackRelease=function(a,b,c,d){return c=this.toSeconds(c),b=this.toSeconds(b),this.triggerAttack(a,c,d),this.triggerRelease(c+b),this},a.Instrument.prototype.dispose=function(){return a.prototype.dispose.call(this),this.volume.dispose(),this.volume=null,this},a.Instrument}),ToneModule(function(a){return a.Monophonic=function(b){a.Instrument.call(this),b=this.defaultArg(b,a.Monophonic.defaults),this.portamento=b.portamento},a.extend(a.Monophonic,a.Instrument),a.Monophonic.defaults={portamento:0},a.Monophonic.prototype.triggerAttack=function(a,b,c){return b=this.toSeconds(b),this.triggerEnvelopeAttack(b,c),this.setNote(a,b),this},a.Monophonic.prototype.triggerRelease=function(a){return this.triggerEnvelopeRelease(a),this},a.Monophonic.prototype.triggerEnvelopeAttack=function(){},a.Monophonic.prototype.triggerEnvelopeRelease=function(){},a.Monophonic.prototype.setNote=function(a,b){var c,d;return b=this.toSeconds(b),this.portamento>0?(c=this.frequency.value,this.frequency.setValueAtTime(c,b),d=this.toSeconds(this.portamento),this.frequency.exponentialRampToValueAtTime(a,b+d)):this.frequency.setValueAtTime(a,b),this},a.Monophonic}),ToneModule(function(a){return a.MonoSynth=function(b){b=this.defaultArg(b,a.MonoSynth.defaults),a.Monophonic.call(this,b),this.oscillator=new a.OmniOscillator(b.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.filter=new a.Filter(b.filter),this.filterEnvelope=new a.ScaledEnvelope(b.filterEnvelope),this.envelope=new a.AmplitudeEnvelope(b.envelope),this.oscillator.chain(this.filter,this.envelope,this.output),this.oscillator.start(),this.filterEnvelope.connect(this.filter.frequency)},a.extend(a.MonoSynth,a.Monophonic),a.MonoSynth.defaults={oscillator:{type:"square"},filter:{Q:6,type:"lowpass",rolloff:-24},envelope:{attack:.005,decay:.1,sustain:.9,release:1},filterEnvelope:{attack:.06,decay:.2,sustain:.5,release:2,min:20,max:4e3,exponent:2}},a.MonoSynth.prototype.triggerEnvelopeAttack=function(a,b){return this.envelope.triggerAttack(a,b),this.filterEnvelope.triggerAttack(a),this},a.MonoSynth.prototype.triggerEnvelopeRelease=function(a){return this.envelope.triggerRelease(a),this.filterEnvelope.triggerRelease(a),this},a.MonoSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),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,this},a.MonoSynth}),ToneModule(function(a){return a.AMSynth=function(b){b=this.defaultArg(b,a.AMSynth.defaults),a.Monophonic.call(this,b),this.carrier=new a.MonoSynth(b.carrier),this.carrier.volume.value=-10,this.modulator=new a.MonoSynth(b.modulator),this.modulator.volume.value=-10,this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),this._modulationScale=new a.AudioToGain,this._modulationNode=this.context.createGain(),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)},a.extend(a.AMSynth,a.Monophonic),a.AMSynth.defaults={harmonicity:3,carrier:{volume:-10,portamento:0,oscillator:{type:"sine"},envelope:{attack:.01,decay:.01,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}},modulator:{volume:-10,portamento:0,oscillator:{type:"square"},envelope:{attack:2,decay:0,sustain:1,release:.5},filterEnvelope:{attack:4,decay:.2,sustain:.5,release:.5,min:20,max:1500}}},a.AMSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.carrier.envelope.triggerAttack(a,b),this.modulator.envelope.triggerAttack(a),this.carrier.filterEnvelope.triggerAttack(a),this.modulator.filterEnvelope.triggerAttack(a),this},a.AMSynth.prototype.triggerEnvelopeRelease=function(a){return this.carrier.triggerRelease(a),this.modulator.triggerRelease(a),this},Object.defineProperty(a.AMSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),a.AMSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),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,this},a.AMSynth}),ToneModule(function(a){return a.DuoSynth=function(b){b=this.defaultArg(b,a.DuoSynth.defaults),a.Monophonic.call(this,b),this.voice0=new a.MonoSynth(b.voice0),this.voice0.volume.value=-10,this.voice1=new a.MonoSynth(b.voice1),this.voice1.volume.value=-10,this._vibrato=new a.LFO(b.vibratoRate,-50,50),this._vibrato.start(),this.vibratoRate=this._vibrato.frequency,this._vibratoGain=this.context.createGain(),this.vibratoAmount=new a.Signal(this._vibratoGain.gain,a.Signal.Units.Gain),this.vibratoAmount.value=b.vibratoAmount,this._vibratoDelay=this.toSeconds(b.vibratoDelay),this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),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)},a.extend(a.DuoSynth,a.Monophonic),a.DuoSynth.defaults={vibratoAmount:.5,vibratoRate:5,vibratoDelay:1,harmonicity:1.5,voice0:{volume:-10,portamento:0,oscillator:{type:"sine"},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5},envelope:{attack:.01,decay:0,sustain:1,release:.5}},voice1:{volume:-10,portamento:0,oscillator:{type:"sine"},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5},envelope:{attack:.01,decay:0,sustain:1,release:.5}}},a.DuoSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.voice0.envelope.triggerAttack(a,b),this.voice1.envelope.triggerAttack(a,b),this.voice0.filterEnvelope.triggerAttack(a),this.voice1.filterEnvelope.triggerAttack(a),this},a.DuoSynth.prototype.triggerEnvelopeRelease=function(a){return this.voice0.triggerRelease(a),this.voice1.triggerRelease(a),this},Object.defineProperty(a.DuoSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),a.DuoSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),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,this},a.DuoSynth}),ToneModule(function(a){return a.FMSynth=function(b){b=this.defaultArg(b,a.FMSynth.defaults),a.Monophonic.call(this,b),this.carrier=new a.MonoSynth(b.carrier),this.carrier.volume.value=-10,this.modulator=new a.MonoSynth(b.modulator),this.modulator.volume.value=-10,this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),this._modulationIndex=new a.Multiply(b.modulationIndex),this._modulationNode=this.context.createGain(),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)},a.extend(a.FMSynth,a.Monophonic),a.FMSynth.defaults={harmonicity:3,modulationIndex:10,carrier:{volume:-10,portamento:0,oscillator:{type:"sine"},envelope:{attack:.01,decay:0,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}},modulator:{volume:-10,portamento:0,oscillator:{type:"triangle"},envelope:{attack:.01,decay:0,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}}},a.FMSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.carrier.envelope.triggerAttack(a,b),this.modulator.envelope.triggerAttack(a),this.carrier.filterEnvelope.triggerAttack(a),this.modulator.filterEnvelope.triggerAttack(a),this},a.FMSynth.prototype.triggerEnvelopeRelease=function(a){return this.carrier.triggerRelease(a),this.modulator.triggerRelease(a),this},Object.defineProperty(a.FMSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),Object.defineProperty(a.FMSynth.prototype,"modulationIndex",{get:function(){return this._modulationIndex.value},set:function(a){this._modulationIndex.value=a}}),a.FMSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),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,this},a.FMSynth}),ToneModule(function(a){return a.Player=function(){var b=this.optionsObject(arguments,["url","onload"],a.Player.defaults);a.Source.call(this,b),this._source=null,this._buffer=new a.Buffer(b.url,b.onload.bind(null,this)),this._loop=b.loop,this._loopStart=b.loopStart,this._loopEnd=b.loopEnd,this._playbackRate=b.playbackRate,this.retrigger=b.retrigger},a.extend(a.Player,a.Source),a.Player.defaults={onload:function(){},playbackRate:1,loop:!1,loopStart:0,loopEnd:0,retrigger:!1},a.Player.prototype.load=function(a,b){return this._buffer.load(a,b.bind(this,this)),this},a.Player.prototype._start=function(a,b,c){if(!this._buffer.loaded)throw Error("tried to start Player before the buffer was loaded");return this._loop?(b=this.defaultArg(b,this._loopStart),b=this.toSeconds(b)):b=this.defaultArg(b,0),c=this.defaultArg(c,this._buffer.duration-b),a=this.toSeconds(a),c=this.toSeconds(c),this._source=this.context.createBufferSource(),this._source.buffer=this._buffer.get(),this._loop?(this._source.loop=this._loop,this._source.loopStart=this.toSeconds(this._loopStart),this._source.loopEnd=this.toSeconds(this._loopEnd)):this._nextStop=a+c,this._source.playbackRate.value=this._playbackRate,this._source.onended=this.onended,this._source.connect(this.output),this._source.start(a,b,c),this},a.Player.prototype._stop=function(a){return this._source&&(this._source.stop(this.toSeconds(a)),this._source=null),this},a.Player.prototype.setLoopPoints=function(a,b){return this.loopStart=a,this.loopEnd=b,this},Object.defineProperty(a.Player.prototype,"loopStart",{get:function(){return this._loopStart},set:function(a){this._loopStart=a,this._source&&(this._source.loopStart=this.toSeconds(a))}}),Object.defineProperty(a.Player.prototype,"loopEnd",{get:function(){return this._loopEnd},set:function(a){this._loopEnd=a,this._source&&(this._source.loopEnd=this.toSeconds(a))}}),Object.defineProperty(a.Player.prototype,"buffer",{get:function(){return this._buffer},set:function(a){this._buffer.set(a)}}),Object.defineProperty(a.Player.prototype,"loop",{get:function(){return this._loop},set:function(a){this._loop=a,this._source&&(this._source.loop=a)}}),Object.defineProperty(a.Player.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(a){this._playbackRate=a,this._source&&(this._source.playbackRate.value=a)}}),a.Player.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),null!==this._source&&(this._source.disconnect(),this._source=null),this._buffer.dispose(),this._buffer=null,this},a.Player}),ToneModule(function(a){return a.Sampler=function(b,c){a.Instrument.call(this),c=this.defaultArg(c,a.Sampler.defaults),this.player=new a.Player(c.player),this.player.retrigger=!0,this._buffers={},this.envelope=new a.AmplitudeEnvelope(c.envelope),this.filterEnvelope=new a.ScaledEnvelope(c.filterEnvelope),this._sample=c.sample,this._pitch=c.pitch,this.filter=new a.Filter(c.filter),this._loadBuffers(b),this.pitch=c.pitch,this.player.chain(this.filter,this.envelope,this.output),this.filterEnvelope.connect(this.filter.frequency)},a.extend(a.Sampler,a.Instrument),a.Sampler.defaults={sample:0,pitch:0,player:{loop:!1},envelope:{attack:.001,decay:0,sustain:1,release:.1},filterEnvelope:{attack:.001,decay:.001,sustain:1,release:.5,min:20,max:2e4,exponent:2},filter:{type:"lowpass"}},a.Sampler.prototype._loadBuffers=function(b){var c,d;if("string"==typeof b)this._buffers[0]=new a.Buffer(b,function(){this.sample="0"}.bind(this));else{b=this._flattenUrls(b);for(c in b)this._sample=c,d=b[c],this._buffers[c]=new a.Buffer(d)}},a.Sampler.prototype._flattenUrls=function(a){var b,c,d,e={};for(b in a)if(a.hasOwnProperty(b))if("object"==typeof a[b]){c=this._flattenUrls(a[b]);for(d in c)c.hasOwnProperty(d)&&(e[b+"."+d]=c[d])}else e[b]=a[b];return e},a.Sampler.prototype.triggerAttack=function(a,b,c){return b=this.toSeconds(b),a&&(this.sample=a),this.player.start(b,0),this.envelope.triggerAttack(b,c),this.filterEnvelope.triggerAttack(b),this},a.Sampler.prototype.triggerRelease=function(a){return a=this.toSeconds(a),this.filterEnvelope.triggerRelease(a),this.envelope.triggerRelease(a),this.player.stop(this.toSeconds(this.envelope.release)+a),this},Object.defineProperty(a.Sampler.prototype,"sample",{get:function(){return this._sample},set:function(a){if(!this._buffers.hasOwnProperty(a))throw new Error("Sampler does not have a sample named "+a);this._sample=a,this.player.buffer=this._buffers[a]}}),Object.defineProperty(a.Sampler.prototype,"pitch",{get:function(){return this._pitch},set:function(a){this._pitch=a,this.player.playbackRate=this.intervalToFrequencyRatio(a)}}),a.Sampler.prototype.dispose=function(){a.Instrument.prototype.dispose.call(this),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 b in this._buffers)this._buffers[b].dispose(),this._buffers[b]=null;return this._buffers=null,this},a.Sampler}),ToneModule(function(a){return a.MultiSampler=function(b,c){console.warn("Tone.MultiSampler is deprecated - use Tone.PolySynth with Tone.Sampler as the voice"),a.Instrument.call(this),this.samples={},this._createSamples(b,c)},a.extend(a.MultiSampler,a.Instrument),a.MultiSampler.prototype._createSamples=function(b,c){var d,e,f,g,h={total:0,loaded:0};for(i in b)h.total++;d=function(){h.loaded++,h.loaded===h.total&&c&&c()};for(e in b)f=b[e],g=new a.Sampler(f,d),g.connect(this.output),this.samples[e]=g},a.MultiSampler.prototype.triggerAttack=function(a,b,c){this.samples.hasOwnProperty(a)&&this.samples[a].triggerAttack(0,b,c)},a.MultiSampler.prototype.triggerRelease=function(a,b){this.samples.hasOwnProperty(a)&&this.samples[a].triggerRelease(b)},a.MultiSampler.prototype.triggerAttackRelease=function(a,b,c,d){if(this.samples.hasOwnProperty(a)){c=this.toSeconds(c),b=this.toSeconds(b);var e=this.samples[a];e.triggerAttack(0,c,d),e.triggerRelease(c+b)}},a.MultiSampler.prototype.set=function(a){for(var b in this.samples)this.samples[b].set(a)},a.MultiSampler.prototype.dispose=function(){a.Instrument.prototype.dispose.call(this);for(var b in this.samples)this.samples[b].dispose(),this.samples[b]=null;this.samples=null},a.MultiSampler}),ToneModule(function(a){a.Noise=function(){var b=this.optionsObject(arguments,["type"],a.Noise.defaults);a.Source.call(this,b),this._source=null,this._buffer=null,this.type=b.type},a.extend(a.Noise,a.Source),a.Noise.defaults={type:"white"},Object.defineProperty(a.Noise.prototype,"type",{get:function(){return this._buffer===d?"white":this._buffer===c?"brown":this._buffer===b?"pink":void 0},set:function(e){if(this.type!==e){switch(e){case"white":this._buffer=d;break;case"pink":this._buffer=b;break;case"brown":this._buffer=c;break;default:this._buffer=d}if(this.state===a.Source.State.STARTED){var f=this.now()+this.bufferTime;this._source.onended=void 0,this._stop(f),this._start(f)}}}}),a.Noise.prototype._start=function(a){this._source=this.context.createBufferSource(),this._source.buffer=this._buffer,this._source.loop=!0,this.connectSeries(this._source,this.output),this._source.start(this.toSeconds(a)),this._source.onended=this.onended},a.Noise.prototype._stop=function(a){this._source&&this._source.stop(this.toSeconds(a))},a.Noise.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),null!==this._source&&(this._source.disconnect(),this._source=null),this._buffer=null,this};var b=null,c=null,d=null;return a._initAudioContext(function(a){var e=a.sampleRate,f=4*e;b=function(){var b,c,d,g,h,i,j,k,l,m,n,o=a.createBuffer(2,f,e);for(b=0;bm;m++)n=2*Math.random()-1,d=.99886*d+.0555179*n,g=.99332*g+.0750759*n,h=.969*h+.153852*n,i=.8665*i+.3104856*n,j=.55*j+.5329522*n,k=-.7616*k-.016898*n,c[m]=d+g+h+i+j+k+l+.5362*n,c[m]*=.11,l=.115926*n;return o}(),c=function(){var b,c,d,g,h,i=a.createBuffer(2,f,e);for(b=0;bg;g++)h=2*Math.random()-1,c[g]=(d+.02*h)/1.02,d=c[g],c[g]*=3.5;return i}(),d=function(){var b,c,d,g=a.createBuffer(2,f,e);for(b=0;bd;d++)c[d]=2*Math.random()-1;return g}()}),a.Noise}),ToneModule(function(a){return a.NoiseSynth=function(b){b=this.defaultArg(b,a.NoiseSynth.defaults),a.Instrument.call(this),this.noise=new a.Noise,this.filter=new a.Filter(b.filter),this.filterEnvelope=new a.ScaledEnvelope(b.filterEnvelope),this.envelope=new a.AmplitudeEnvelope(b.envelope),this.noise.chain(this.filter,this.envelope,this.output),this.noise.start(),this.filterEnvelope.connect(this.filter.frequency)},a.extend(a.NoiseSynth,a.Instrument),a.NoiseSynth.defaults={noise:{type:"white"},filter:{Q:6,type:"highpass",rolloff:-24},envelope:{attack:.005,decay:.1,sustain:0},filterEnvelope:{attack:.06,decay:.2,sustain:0,release:2,min:20,max:4e3,exponent:2}},a.NoiseSynth.prototype.triggerAttack=function(a,b){return this.envelope.triggerAttack(a,b),this.filterEnvelope.triggerAttack(a),this},a.NoiseSynth.prototype.triggerRelease=function(a){return this.envelope.triggerRelease(a),this.filterEnvelope.triggerRelease(a),this},a.NoiseSynth.prototype.triggerAttackRelease=function(a,b,c){return b=this.toSeconds(b),a=this.toSeconds(a),this.triggerAttack(b,c),console.log(b+a),this.triggerRelease(b+a),this},a.NoiseSynth.prototype.dispose=function(){return a.Instrument.prototype.dispose.call(this),this.noise.dispose(),this.noise=null,this.envelope.dispose(),this.envelope=null,this.filterEnvelope.dispose(),this.filterEnvelope=null,this.filter.dispose(),this.filter=null,this},a.NoiseSynth}),ToneModule(function(a){return a.PluckSynth=function(b){b=this.defaultArg(b,a.PluckSynth.defaults),a.Instrument.call(this),this._noise=new a.Noise("pink"),this.attackNoise=1,this._lfcf=new a.LowpassCombFilter(1/440),this.resonance=this._lfcf.resonance,this.dampening=this._lfcf.dampening,this._noise.connect(this._lfcf),this._lfcf.connect(this.output)},a.extend(a.PluckSynth,a.Instrument),a.PluckSynth.defaults={attackNoise:1,dampening:4e3,resonance:.5},a.PluckSynth.prototype.triggerAttack=function(a,b){a=this.toFrequency(a),b=this.toSeconds(b);var c=1/a;return this._lfcf.setDelayTimeAtTime(c,b),this._noise.start(b),this._noise.stop(b+c*this.attackNoise),this},a.PluckSynth.prototype.dispose=function(){return a.Instrument.prototype.dispose.call(this),this._noise.dispose(),this._lfcf.dispose(),this._noise=null,this._lfcf=null,this.dampening=null,this.resonance=null,this},a.PluckSynth}),ToneModule(function(a){return a.PolySynth=function(){var b,c,d;for(a.Instrument.call(this),b=this.optionsObject(arguments,["polyphony","voice"],a.PolySynth.defaults),this.voices=new Array(b.polyphony),this._freeVoices=[],this._activeVoices={},c=0;c0&&(g=this._freeVoices.shift(),g.triggerAttack(e,b,c),this._activeVoices[f]=g);return this},a.PolySynth.prototype.triggerAttackRelease=function(a,b,c,d){return c=this.toSeconds(c),this.triggerAttack(a,c,d),this.triggerRelease(a,c+this.toSeconds(b)),this},a.PolySynth.prototype.triggerRelease=function(a,b){var c,d,e;for(Array.isArray(a)||(a=[a]),c=0;cc){var d=b;b=c,c=d}this.min=this.input=new a.Min(c),this.max=this.output=new a.Max(b),this.min.connect(this.max)},a.extend(a.Clip,a.SignalBase),a.Clip.prototype.dispose=function(){return a.prototype.dispose.call(this),this.min.dispose(),this.min=null,this.max.dispose(),this.max=null,this},a.Clip}),ToneModule(function(a){var b,c,d,e=Math.pow(2,13),f=new Array(e);for(b=0;bc;c++)g=0===c?this._guess:this._inverses[c-1],h=new d(g,this._two),this.input.connect(h),this._inverses[c]=h;this._inverses[b-1].connect(this.output)},a.extend(a.Inverse,a.SignalBase),a.Inverse.prototype.dispose=function(){a.prototype.dispose.call(this);for(var b=0;bd;d++)e=new b(d),this.output[d]=e,this.gate.connect(e.selecter),this.input.connect(e)},a.extend(a.Route,a.SignalBase),a.Route.prototype.select=function(a,b){return a=Math.floor(a),this.gate.setValueAtTime(a,this.toSeconds(b)),this},a.Route.prototype.dispose=function(){this.gate.dispose();for(var b=0;b1&&(this.input=new Array(b)),a(c)||1===c?this.output=this.context.createGain():c>1&&(this.output=new Array(b))},d.prototype.set=function(b,c,e){var f,g,h;"object"==typeof b?e=c:"string"==typeof b&&(f={},f[b]=c,b=f);for(g in b)h=this[g],a(h)||(c=b[g],h instanceof d.Signal?h.value!==c&&(a(e)?h.value=c:h.rampTo(c,e)):h instanceof AudioParam?h.value!==c&&(h.value=c):h instanceof d?h.set(c):h!==c&&(this[g]=c));return this},d.prototype.get=function(c){var e,f,g,h,i,j;if(a(c))c=this._collectDefaults(this.constructor);else if("string"==typeof c)e={},e[c]=0,c=e;else if(Array.isArray(c)){for(f={},g=0;g1)for(a=arguments[0],b=1;b1)for(a=1;a0)for(a=this,b=0;b0)for(var a=0;ab;b++)d=b/c*2-1,this._curve[b]=a(d,b);return this._shaper.curve=this._curve,this},Object.defineProperty(a.WaveShaper.prototype,"curve",{get:function(){return this._shaper.curve},set:function(a){if(this._isSafari()){var b=a[0];a.unshift(b)}this._curve=new Float32Array(a),this._shaper.curve=this._curve}}),Object.defineProperty(a.WaveShaper.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.WaveShaper.prototype._isSafari=function(){var a=navigator.userAgent.toLowerCase();return-1!==a.indexOf("safari")&&-1===a.indexOf("chrome")},a.WaveShaper.prototype.dispose=function(){return a.prototype.dispose.call(this),this._shaper.disconnect(),this._shaper=null,this._curve=null,this},a.WaveShaper}),ToneModule(function(a){return a.Signal=function(b,c){this.units=this.defaultArg(c,a.Signal.Units.Number),this.convert=!0,this.overridden=!1,this.output=this._scaler=this.context.createGain(),this.input=this._value=this._scaler.gain,b instanceof AudioParam?(this._scaler.connect(b),b.value=0):this.value=this.defaultArg(b,a.Signal.defaults.value),a.Signal._constant.chain(this._scaler)},a.extend(a.Signal,a.SignalBase),a.Signal.defaults={value:0},Object.defineProperty(a.Signal.prototype,"value",{get:function(){return this._toUnits(this._value.value)},set:function(a){var b=this._fromUnits(a);this.cancelScheduledValues(0),this._value.value=b}}),a.Signal.prototype._fromUnits=function(b){if(!this.convert)return b;switch(this.units){case a.Signal.Units.Time:return this.toSeconds(b);case a.Signal.Units.Frequency:return this.toFrequency(b);case a.Signal.Units.Decibels:return this.dbToGain(b);case a.Signal.Units.Normal:return Math.min(Math.max(b,0),1);case a.Signal.Units.Audio:return Math.min(Math.max(b,-1),1);default:return b}},a.Signal.prototype._toUnits=function(b){if(!this.convert)return b;switch(this.units){case a.Signal.Units.Decibels:return this.gainToDb(b);default:return b}},a.Signal.prototype.setValueAtTime=function(a,b){return a=this._fromUnits(a),this._value.setValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.setCurrentValueNow=function(a){a=this.defaultArg(a,this.now());var b=this._value.value;return this.cancelScheduledValues(a),this._value.setValueAtTime(b,a),this},a.Signal.prototype.linearRampToValueAtTime=function(a,b){return a=this._fromUnits(a),this._value.linearRampToValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.exponentialRampToValueAtTime=function(a,b){return a=this._fromUnits(a),a=Math.max(1e-5,a),this._value.exponentialRampToValueAtTime(a,this.toSeconds(b)),this},a.Signal.prototype.exponentialRampToValueNow=function(a,b){var c=this.now(),d=this.value;return this.setValueAtTime(Math.max(d,1e-4),c),this.exponentialRampToValueAtTime(a,c+this.toSeconds(b)),this},a.Signal.prototype.linearRampToValueNow=function(a,b){var c=this.now();return this.setCurrentValueNow(c),this.linearRampToValueAtTime(a,c+this.toSeconds(b)),this},a.Signal.prototype.setTargetAtTime=function(a,b,c){return a=this._fromUnits(a),c=Math.max(1e-5,c),this._value.setTargetAtTime(a,this.toSeconds(b),c),this},a.Signal.prototype.setValueCurveAtTime=function(a,b,c){for(var d=0;db?this._nextAttack<=b&&this._nextDecay>b?a.Envelope.Phase.ATTACK:this._nextDecay<=b&&this._nextSustain>b?a.Envelope.Phase.DECAY:this._nextSustain<=b&&this._nextRelease>b?a.Envelope.Phase.SUSTAIN:a.Envelope.Phase.STANDBY:this._nextReleaseb?a.Envelope.Phase.RELEASE:a.Envelope.Phase.STANDBY},a.Envelope.prototype._exponentialApproach=function(a,b,c,d,e){return c+(b-c)*Math.exp(-(e-a)/d)},a.Envelope.prototype._linearInterpolate=function(a,b,c,d,e){return b+(d-b)*((e-a)/(c-a))},a.Envelope.prototype._exponentialInterpolate=function(a,b,c,d,e){return b*Math.pow(d/b,(e-a)/(c-a))},a.Envelope.prototype._valueAtTime=function(b){var c=this.toSeconds(this.attack),d=this.toSeconds(this.decay),e=this.toSeconds(this.release);switch(this._phaseAtTime(b)){case a.Envelope.Phase.ATTACK:return this._attackCurve===a.Envelope.Type.LINEAR?this._linearInterpolate(this._nextAttack,this._minOutput,this._nextAttack+c,this._peakValue,b):this._exponentialInterpolate(this._nextAttack,this._minOutput,this._nextAttack+c,this._peakValue,b);case a.Envelope.Phase.DECAY:return this._exponentialApproach(this._nextDecay,this._peakValue,this.sustain*this._peakValue,d*this._timeMult,b);case a.Envelope.Phase.RELEASE:return this._exponentialApproach(this._nextRelease,this._peakValue,this._minOutput,e*this._timeMult,b);case a.Envelope.Phase.SUSTAIN:return this.sustain*this._peakValue;case a.Envelope.Phase.STANDBY:return this._minOutput}},a.Envelope.prototype.triggerAttack=function(b,c){var d,e,f,g,h,i;return b=this.toSeconds(b),d=this.toSeconds(this.attack),e=this.toSeconds(this.decay),f=this._valueAtTime(b),g=f*d,this._nextAttack=b-g,this._nextDecay=this._nextAttack+d,this._nextSustain=this._nextDecay+e,this._nextRelease=1/0,this._peakValue=this.defaultArg(c,1),h=this._peakValue,i=this.sustain*h,this._sig.cancelScheduledValues(b),this._sig.setValueAtTime(f,b),this._attackCurve===a.Envelope.Type.LINEAR?this._sig.linearRampToValueAtTime(h,this._nextDecay):this._sig.exponentialRampToValueAtTime(h,this._nextDecay),this._sig.setTargetAtTime(i,this._nextDecay,e*this._timeMult),this},a.Envelope.prototype.triggerRelease=function(b){var c,d,e;return b=this.toSeconds(b),c=this._phaseAtTime(b),d=this.toSeconds(this.release),e=this._valueAtTime(b),this._peakValue=e,this._nextRelease=b,this._nextStandby=this._nextRelease+d,this._sig.cancelScheduledValues(this._nextRelease),c===a.Envelope.Phase.ATTACK?(this._sig.setCurrentValueNow(),this.attackCurve===a.Envelope.Type.LINEAR?this._sig.linearRampToValueAtTime(this._peakValue,this._nextRelease):this._sig.exponentialRampToValueAtTime(this._peakValue,this._nextRelease)):this._sig.setValueAtTime(this._peakValue,this._nextRelease),this._sig.setTargetAtTime(this._minOutput,this._nextRelease,d*this._timeMult),this},a.Envelope.prototype.triggerAttackRelease=function(a,b,c){return b=this.toSeconds(b),this.triggerAttack(b,c),this.triggerRelease(b+this.toSeconds(a)),this},a.Envelope.prototype.connect=a.Signal.prototype.connect,a.Envelope.prototype.dispose=function(){return a.prototype.dispose.call(this),this._sig.dispose(),this._sig=null,this},a.Envelope.Phase={ATTACK:"attack",DECAY:"decay",SUSTAIN:"sustain",RELEASE:"release",STANDBY:"standby"},a.Envelope.Type={LINEAR:"linear",EXPONENTIAL:"exponential"},a.Envelope}),ToneModule(function(a){return a.AmplitudeEnvelope=function(){a.Envelope.apply(this,arguments),this.input=this.output=this.context.createGain(),this._sig.connect(this.output.gain)},a.extend(a.AmplitudeEnvelope,a.Envelope),a.AmplitudeEnvelope}),ToneModule(function(a){return a.Compressor=function(){var b=this.optionsObject(arguments,["threshold","ratio"],a.Compressor.defaults);this._compressor=this.input=this.output=this.context.createDynamicsCompressor(),this.threshold=this._compressor.threshold,this.attack=new a.Signal(this._compressor.attack,a.Signal.Units.Time),this.release=new a.Signal(this._compressor.release,a.Signal.Units.Time),this.knee=this._compressor.knee,this.ratio=this._compressor.ratio,this.attack.connect(this._compressor.attack),this.release.connect(this._compressor.release),this._readOnly(["knee","release","attack","ratio","threshold"]),this.set(b)},a.extend(a.Compressor),a.Compressor.defaults={ratio:12,threshold:-24,release:.25,attack:.003,knee:30},a.Compressor.prototype.dispose=function(){return a.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,this},a.Compressor}),ToneModule(function(a){return a.Add=function(b){a.call(this,2,0),this._sum=this.input[0]=this.input[1]=this.output=this.context.createGain(),this._value=this.input[1]=new a.Signal(b),this._value.connect(this._sum)},a.extend(a.Add,a.Signal),a.Add.prototype.dispose=function(){return a.prototype.dispose.call(this),this._sum.disconnect(),this._sum=null,this._value.dispose(),this._value=null,this},a.Add}),ToneModule(function(a){return a.Multiply=function(b){a.call(this,2,0),this._mult=this.input[0]=this.output=this.context.createGain(),this._value=this.input[1]=this.output.gain,this._value.value=this.defaultArg(b,0)},a.extend(a.Multiply,a.Signal),a.Multiply.prototype.dispose=function(){return a.prototype.dispose.call(this),this._mult.disconnect(),this._mult=null,this._value=null,this},a.Multiply}),ToneModule(function(a){return a.Negate=function(){this._multiply=this.input=this.output=new a.Multiply(-1)},a.extend(a.Negate,a.SignalBase),a.Negate.prototype.dispose=function(){return a.prototype.dispose.call(this),this._multiply.dispose(),this._multiply=null,this},a.Negate}),ToneModule(function(a){return a.Subtract=function(b){a.call(this,2,0),this._sum=this.input[0]=this.output=this.context.createGain(),this._neg=new a.Negate,this._value=this.input[1]=new a.Signal(b),this._value.chain(this._neg,this._sum)},a.extend(a.Subtract,a.Signal),a.Subtract.prototype.dispose=function(){return a.prototype.dispose.call(this),this._neg.dispose(),this._neg=null,this._sum.disconnect(),this._sum=null,this._value.dispose(),this._value=null,this},a.Subtract}),ToneModule(function(a){return a.GreaterThanZero=function(){this._thresh=this.output=new a.WaveShaper(function(a){return 0>=a?0:1}),this._scale=this.input=new a.Multiply(1e4),this._scale.connect(this._thresh)},a.extend(a.GreaterThanZero,a.SignalBase),a.GreaterThanZero.prototype.dispose=function(){return a.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},a.GreaterThanZero}),ToneModule(function(a){return a.EqualZero=function(){this._scale=this.input=new a.Multiply(1e4),this._thresh=new a.WaveShaper(function(a){return 0===a?1:0},128),this._gtz=this.output=new a.GreaterThanZero,this._scale.chain(this._thresh,this._gtz)},a.extend(a.EqualZero,a.SignalBase),a.EqualZero.prototype.dispose=function(){return a.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},a.EqualZero}),ToneModule(function(a){return a.Equal=function(b){a.call(this,2,0),this._sub=this.input[0]=new a.Subtract(b),this._equals=this.output=new a.EqualZero,this._sub.connect(this._equals),this.input[1]=this._sub.input[1]},a.extend(a.Equal,a.SignalBase),Object.defineProperty(a.Equal.prototype,"value",{get:function(){return this._sub.value},set:function(a){this._sub.value=a}}),a.Equal.prototype.dispose=function(){return a.prototype.dispose.call(this),this._equals.dispose(),this._equals=null,this._sub.dispose(),this._sub=null,this},a.Equal}),ToneModule(function(a){a.Select=function(c){var d,e;for(c=this.defaultArg(c,2),a.call(this,c,1),this.gate=new a.Signal(0),this._readOnly("gate"),d=0;c>d;d++)e=new b(d),this.input[d]=e,this.gate.connect(e.selecter),e.connect(this.output)},a.extend(a.Select,a.SignalBase),a.Select.prototype.select=function(a,b){return a=Math.floor(a),this.gate.setValueAtTime(a,this.toSeconds(b)),this},a.Select.prototype.dispose=function(){this._writable("gate"),this.gate.dispose(),this.gate=null;for(var b=0;bc;c++)this.input[c]=this._sum;this._sum.connect(this._gtz)},a.extend(a.OR,a.SignalBase),a.OR.prototype.dispose=function(){return a.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._sum.disconnect(),this._sum=null,this},a.OR}),ToneModule(function(a){return a.AND=function(b){b=this.defaultArg(b,2),a.call(this,b,0),this._equals=this.output=new a.Equal(b);for(var c=0;b>c;c++)this.input[c]=this._equals},a.extend(a.AND,a.SignalBase),a.AND.prototype.dispose=function(){return a.prototype.dispose.call(this),this._equals.dispose(),this._equals=null,this},a.AND}),ToneModule(function(a){return a.NOT=a.EqualZero,a.NOT}),ToneModule(function(a){return a.GreaterThan=function(b){a.call(this,2,0),this._value=this.input[0]=new a.Subtract(b),this.input[1]=this._value.input[1],this._gtz=this.output=new a.GreaterThanZero,this._value.connect(this._gtz)},a.extend(a.GreaterThan,a.Signal),a.GreaterThan.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._value=null,this._gtz.dispose(),this._gtz=null,this},a.GreaterThan}),ToneModule(function(a){return a.LessThan=function(b){a.call(this,2,0),this._neg=this.input[0]=new a.Negate,this._gt=this.output=new a.GreaterThan,this._rhNeg=new a.Negate,this._value=this.input[1]=new a.Signal(b),this._neg.connect(this._gt),this._value.connect(this._rhNeg),this._rhNeg.connect(this._gt,0,1)},a.extend(a.LessThan,a.Signal),a.LessThan.prototype.dispose=function(){return a.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,this},a.LessThan}),ToneModule(function(a){return a.Abs=function(){a.call(this,1,0),this._ltz=new a.LessThan(0),this._switch=this.output=new a.Select(2),this._negate=new a.Negate,this.input.connect(this._switch,0,0),this.input.connect(this._negate),this._negate.connect(this._switch,0,1),this.input.chain(this._ltz,this._switch.gate)},a.extend(a.Abs,a.SignalBase),a.Abs.prototype.dispose=function(){return a.prototype.dispose.call(this),this._switch.dispose(),this._switch=null,this._ltz.dispose(),this._ltz=null,this._negate.dispose(),this._negate=null,this},a.Abs}),ToneModule(function(a){return a.Max=function(b){a.call(this,2,0),this.input[0]=this.context.createGain(),this._value=this.input[1]=new a.Signal(b),this._ifThenElse=this.output=new a.IfThenElse,this._gt=new a.GreaterThan,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)},a.extend(a.Max,a.Signal),a.Max.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._gt.dispose(),this._value=null,this._ifThenElse=null,this._gt=null,this},a.Max}),ToneModule(function(a){return a.Min=function(b){a.call(this,2,0),this.input[0]=this.context.createGain(),this._ifThenElse=this.output=new a.IfThenElse,this._lt=new a.LessThan,this._value=this.input[1]=new a.Signal(b),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)},a.extend(a.Min,a.Signal),a.Min.prototype.dispose=function(){return a.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._lt.dispose(),this._value=null,this._ifThenElse=null,this._lt=null,this},a.Min}),ToneModule(function(a){return a.Modulo=function(b){a.call(this,1,1),this._shaper=new a.WaveShaper(Math.pow(2,16)),this._multiply=new a.Multiply,this._subtract=this.output=new a.Subtract,this._modSignal=new a.Signal(b),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(b)},a.extend(a.Modulo,a.SignalBase),a.Modulo.prototype._setWaveShaper=function(a){this._shaper.setMap(function(b){var c=Math.floor((b+1e-4)/a);return c})},Object.defineProperty(a.Modulo.prototype,"value",{get:function(){return this._modSignal.value},set:function(a){this._modSignal.value=a,this._setWaveShaper(a)}}),a.Modulo.prototype.dispose=function(){return a.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,this},a.Modulo}),ToneModule(function(a){function b(a,b,c){var d=new a;return c._eval(b[0]).connect(d,0,0),c._eval(b[1]).connect(d,0,1),d}function c(a,b,c){var d=new a;return c._eval(b[0]).connect(d,0,0),d}function d(a){return a?parseFloat(a):void 0}function e(a){return a&&a.args?parseFloat(a.args):void 0}return a.Expr=function(){var a,b,c,d=this._replacements(Array.prototype.slice.call(arguments)),e=this._parseInputs(d);for(this._nodes=[],this.input=new Array(e),a=0;e>a;a++)this.input[a]=this.context.createGain();b=this._parseTree(d);try{c=this._eval(b)}catch(f){throw this._disposeNodes(),new Error("Could evaluate expression: "+d)}this.output=c},a.extend(a.Expr,a.SignalBase),a.Expr._Expressions={value:{signal:{regexp:/^\d+\.\d+|^\d+/,method:function(b){var c=new a.Signal(d(b));return c}},input:{regexp:/^\$\d/,method:function(a,b){return b.input[d(a.substr(1))]}}},glue:{"(":{regexp:/^\(/},")":{regexp:/^\)/},",":{regexp:/^,/}},func:{abs:{regexp:/^abs/,method:c.bind(this,a.Abs)},min:{regexp:/^min/,method:b.bind(this,a.Min)},max:{regexp:/^max/,method:b.bind(this,a.Max)},"if":{regexp:/^if/,method:function(b,c){var d=new a.IfThenElse;return c._eval(b[0]).connect(d.if),c._eval(b[1]).connect(d.then),c._eval(b[2]).connect(d.else),d}},gt0:{regexp:/^gt0/,method:c.bind(this,a.GreaterThanZero)},eq0:{regexp:/^eq0/,method:c.bind(this,a.EqualZero)},mod:{regexp:/^mod/,method:function(b,c){var d=e(b[1]),f=new a.Modulo(d);return c._eval(b[0]).connect(f),f}},pow:{regexp:/^pow/,method:function(b,c){var d=e(b[1]),f=new a.Pow(d);return c._eval(b[0]).connect(f),f}}},binary:{"+":{regexp:/^\+/,precedence:1,method:b.bind(this,a.Add)},"-":{regexp:/^\-/,precedence:1,method:function(d,e){return 1===d.length?c(a.Negate,d,e):b(a.Subtract,d,e)}},"*":{regexp:/^\*/,precedence:0,method:b.bind(this,a.Multiply)},">":{regexp:/^\>/,precedence:2,method:b.bind(this,a.GreaterThan)},"<":{regexp:/^0;)b=b.trim(),d=c(b),f.push(d),b=b.substr(d.value.length);return{next:function(){return f[++e]},peek:function(){return f[e+1]}}},a.Expr.prototype._parseTree=function(b){function c(a,b){return!k(a)&&"glue"===a.type&&a.value===b}function d(b,c,d){var e,f,g=!1,h=a.Expr._Expressions[c];if(!k(b))for(e in h)if(f=h[e],f.regexp.test(b.value)){if(k(d))return!0;if(f.precedence===d)return!0}return g}function e(a){var b,c;for(k(a)&&(a=5),b=0>a?f():e(a-1),c=j.peek();d(c,"binary",a);)c=j.next(),b={operator:c.value,method:c.method,args:[b,e(a)]},c=j.peek();return b}function f(){var a,b;return a=j.peek(),d(a,"unary")?(a=j.next(),b=f(),{operator:a.value,method:a.method,args:[b]}):g()}function g(){var a,b;if(a=j.peek(),k(a))throw new SyntaxError("Unexpected termination of expression");if("func"===a.type)return a=j.next(),h(a);if("value"===a.type)return a=j.next(),{method:a.method,args:a.value};if(c(a,"(")){if(j.next(),b=e(),a=j.next(),!c(a,")"))throw new SyntaxError("Expected )");return b}throw new SyntaxError("Parse error, cannot process token "+a.value)}function h(a){var b,d=[];if(b=j.next(),!c(b,"("))throw new SyntaxError('Expected ( in a function call "'+a.value+'"');if(b=j.peek(),c(b,")")||(d=i()),b=j.next(),!c(b,")"))throw new SyntaxError('Expected ) in a function call "'+a.value+'"');return{method:a.method,args:d,name:name}}function i(){for(var a,b,d=[];;){if(b=e(),k(b))break;if(d.push(b),a=j.peek(),!c(a,","))break;j.next()}return d}var j=this._tokenize(b),k=this.isUndef.bind(this);return e()},a.Expr.prototype._eval=function(a){if(!this.isUndef(a)){var b=a.method(a.args,this);return this._nodes.push(b),b}},a.Expr.prototype._disposeNodes=function(){var a,b;for(a=0;ac;c++)d=this.context.createBiquadFilter(),d.type=this._type,this.frequency.connect(d.frequency),this.detune.connect(d.detune),this.Q.connect(d.Q),this.gain.connect(d.gain),this._filters[c]=d;e=[this.input].concat(this._filters).concat([this.output]),this.connectSeries.apply(this,e)}}),a.Filter.prototype.dispose=function(){a.prototype.dispose.call(this);for(var b=0;b=c?a:b})},Object.defineProperty(a.Follower.prototype,"attack",{get:function(){return this._attack},set:function(a){this._attack=a,this._setAttackRelease(this._attack,this._release)}}),Object.defineProperty(a.Follower.prototype,"release",{get:function(){return this._release},set:function(a){this._release=a,this._setAttackRelease(this._attack,this._release)}}),a.Follower.prototype.connect=a.Signal.prototype.connect,a.Follower.prototype.dispose=function(){return a.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,this},a.Follower}),ToneModule(function(a){return a.Gate=function(){a.call(this);var b=this.optionsObject(arguments,["threshold","attack","release"],a.Gate.defaults);this._follower=new a.Follower(b.attack,b.release),this._gt=new a.GreaterThan(this.dbToGain(b.threshold)),this.input.connect(this.output),this.input.chain(this._gt,this._follower,this.output.gain)},a.extend(a.Gate),a.Gate.defaults={attack:.1,release:.1,threshold:-40},Object.defineProperty(a.Gate.prototype,"threshold",{get:function(){return this.gainToDb(this._gt.value)},set:function(a){this._gt.value=this.dbToGain(a)}}),Object.defineProperty(a.Gate.prototype,"attack",{get:function(){return this._follower.attack},set:function(a){this._follower.attack=a}}),Object.defineProperty(a.Gate.prototype,"release",{get:function(){return this._follower.release},set:function(a){this._follower.release=a}}),a.Gate.prototype.dispose=function(){return a.prototype.dispose.call(this),this._follower.dispose(),this._gt.dispose(),this._follower=null,this._gt=null,this},a.Gate}),ToneModule(function(a){return a.Clock=function(b,c){this._oscillator=null,this._jsNode=this.context.createScriptProcessor(this.bufferSize,1,1),this._jsNode.onaudioprocess=this._processBuffer.bind(this),this.frequency=new a.Signal(b,a.Signal.Units.Frequency),this._upTick=!1,this.tick=c,this.onended=function(){},this._jsNode.noGC()},a.extend(a.Clock),a.Clock.prototype.start=function(a){if(!this._oscillator){this._oscillator=this.context.createOscillator(),this._oscillator.type="square",this._oscillator.connect(this._jsNode),this.frequency.connect(this._oscillator.frequency),this._upTick=!1;var b=this.toSeconds(a);this._oscillator.start(b)}return this},a.Clock.prototype.stop=function(a){var b,c;return this._oscillator&&(b=this.now(),c=this.toSeconds(a,b),this._oscillator.stop(c),this._oscillator=null,a?setTimeout(this.onended.bind(this),1e3*(c-b)):this.onended()),this},a.Clock.prototype._processBuffer=function(a){var b,c,d=this.defaultArg(a.playbackTime,this.now()),e=this._jsNode.bufferSize,f=a.inputBuffer.getChannelData(0),g=this._upTick,h=this;for(b=0;e>b;b++)c=f[b],c>0&&!g?(g=!0,setTimeout(function(){var a=d+h.samplesToSeconds(b+2*e);return function(){h.tick&&h.tick(a)}}(),0)):0>c&&g&&(g=!1);this._upTick=g},a.Clock.prototype.dispose=function(){return this._jsNode.disconnect(),this.frequency.dispose(),this.frequency=null,this._oscillator&&(this._oscillator.disconnect(),this._oscillator=null),this._jsNode.onaudioprocess=function(){},this._jsNode=null,this.tick=null,this.onended=function(){},this},a.Clock}),ToneModule(function(Tone){var tatum,timelineTicks,transportTicks,swingSubdivision,swingTatum,swingAmount,transportTimeSignature,loopStart,loopEnd,intervals,timeouts,transportTimeline,timelineProgress,SyncedSources,SyncedSignals,TransportState,processIntervals,processTimeouts,processTimeline,TimelineEventIDCounter,TimelineEvent,TransportConstructor;return Tone.Transport=function(){this._clock=new Tone.Clock(0,this._processTick.bind(this)),this._clock.onended=this._onended.bind(this),this.loop=!1,this.bpm=new Tone.Signal(120,Tone.Signal.Units.BPM),this._bpmMult=new Tone.Multiply(1/60*tatum),this.state=TransportState.STOPPED,this.bpm.chain(this._bpmMult,this._clock.frequency)},Tone.extend(Tone.Transport),Tone.Transport.defaults={bpm:120,swing:0,swingSubdivision:"16n",timeSignature:4,loopStart:0,loopEnd:"4m"},tatum=12,timelineTicks=0,transportTicks=0,swingSubdivision="16n",swingTatum=3,swingAmount=0,transportTimeSignature=4,loopStart=0,loopEnd=4*tatum,intervals=[],timeouts=[],transportTimeline=[],timelineProgress=0,SyncedSources=[],SyncedSignals=[],TransportState={STARTED:"started",PAUSED:"paused",STOPPED:"stopped"},Tone.Transport.prototype._processTick=function(a){this.state===TransportState.STARTED&&(swingAmount>0&&timelineTicks%tatum!==0&&timelineTicks%swingTatum===0&&(a+=this._ticksToSeconds(swingTatum)*swingAmount),processIntervals(a),processTimeouts(a),processTimeline(a),transportTicks+=1,timelineTicks+=1,this.loop&&timelineTicks===loopEnd&&this._setTicks(loopStart))},Tone.Transport.prototype._setTicks=function(a){var b,c;for(timelineTicks=a,b=0;b=a){timelineProgress=b;break}},processIntervals=function(a){var b,c,d;for(b=0,c=intervals.length;c>b;b++)d=intervals[b],d.testInterval(transportTicks)&&d.doCallback(a)},processTimeouts=function(a){var b,c,d,e,f=0;for(b=0,c=timeouts.length;c>b;b++)if(d=timeouts[b],e=d.callbackTick(),transportTicks>=e)d.doCallback(a),f++;else if(e>transportTicks)break;timeouts.splice(0,f)},processTimeline=function(a){var b,c,d,e;for(b=timelineProgress,c=transportTimeline.length;c>b;b++)if(d=transportTimeline[b],e=d.callbackTick(),e===timelineTicks)timelineProgress=b,d.doCallback(a);else if(e>timelineTicks)break},Tone.Transport.prototype.setInterval=function(a,b,c){var d=this._toTicks(b),e=new TimelineEvent(a,c,d,transportTicks);return intervals.push(e),e.id},Tone.Transport.prototype.clearInterval=function(a){var b,c;for(b=0;b0;return intervals=[],a},Tone.Transport.prototype.setTimeout=function(a,b,c){var d,e,f,g=this._toTicks(b),h=new TimelineEvent(a,c,g+transportTicks,0);for(d=0,e=timeouts.length;e>d;d++)if(f=timeouts[d],f.callbackTick()>h.callbackTick())return timeouts.splice(d,0,h),h.id;return timeouts.push(h),h.id},Tone.Transport.prototype.clearTimeout=function(a){var b,c;for(b=0;b0;return timeouts=[],a},Tone.Transport.prototype.setTimeline=function(a,b,c){var d,e,f,g=this._toTicks(b),h=new TimelineEvent(a,c,g,0);for(d=timelineProgress,e=transportTimeline.length;e>d;d++)if(f=transportTimeline[d],f.callbackTick()>h.callbackTick())return transportTimeline.splice(d,0,h),h.id;return transportTimeline.push(h),h.id},Tone.Transport.prototype.clearTimeline=function(a){var b,c;for(b=0;b0;return transportTimeline=[],a},Tone.Transport.prototype._toTicks=function(a){var b=this.toSeconds(a),c=this.notationToSeconds("4n"),d=b/c,e=d*tatum;return Math.round(e)},Tone.Transport.prototype._ticksToSeconds=function(a,b,c){a=Math.floor(a);var d=this.notationToSeconds("4n",b,c);return d*a/tatum},Tone.Transport.prototype.nextBeat=function(a){var b,c,d;return a=this.defaultArg(a,"4n"),b=this._toTicks(a),c=transportTicks%b,d=c,c>0&&(d=b-c),this._ticksToSeconds(d)},Tone.Transport.prototype.start=function(a,b){var c,d,e,f;if(this.state===TransportState.STOPPED||this.state===TransportState.PAUSED)for(this.isUndef(b)||this._setTicks(this._toTicks(b)),this.state=TransportState.STARTED,c=this.toSeconds(a),this._clock.start(c),d=0;d1){for(originalTime=time,i=0;ib?a.Source.State.STARTED:this._nextStop<=b?a.Source.State.STOPPED:a.Source.State.STOPPED},a.Source.prototype.start=function(b){return b=this.toSeconds(b),(this._stateAtTime(b)!==a.Source.State.STARTED||this.retrigger)&&(this._nextStart=b,this._nextStop=1/0,this._start.apply(this,arguments)),this},a.Source.prototype.stop=function(b){var c,d=this.now();return b=this.toSeconds(b,d),this._stateAtTime(b)===a.Source.State.STARTED&&(this._nextStop=this.toSeconds(b),clearTimeout(this._timeout),c=b-d,c>0?this._timeout=setTimeout(this.onended,1e3*c+20):this.onended(),this._stop.apply(this,arguments)),this},a.Source.prototype.pause=function(a){return this.stop(a),this},a.Source.prototype.sync=function(b){return a.Transport.syncSource(this,b),this},a.Source.prototype.unsync=function(){return a.Transport.unsyncSource(this),this},a.Source.prototype.dispose=function(){a.prototype.dispose.call(this),this.stop(),clearTimeout(this._timeout),this.onended=function(){},this._writable("volume"),this.volume.dispose(),this.volume=null},a.Source}),ToneModule(function(a){return a.Oscillator=function(){var b=this.optionsObject(arguments,["frequency","type"],a.Oscillator.defaults);a.Source.call(this,b),this._oscillator=null,this.frequency=new a.Signal(b.frequency,a.Signal.Units.Frequency),this.detune=new a.Signal(b.detune),this._wave=null,this._phase=b.phase,this._type=null,this.type=b.type,this.phase=this._phase,this._readOnly(["frequency","detune"])},a.extend(a.Oscillator,a.Source),a.Oscillator.defaults={type:"sine",frequency:440,detune:0,phase:0},a.Oscillator.prototype._start=function(a){this._oscillator=this.context.createOscillator(),this._oscillator.setPeriodicWave(this._wave),this._oscillator.connect(this.output),this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.start(this.toSeconds(a))},a.Oscillator.prototype._stop=function(a){return this._oscillator&&(this._oscillator.stop(this.toSeconds(a)),this._oscillator=null),this},a.Oscillator.prototype.syncFrequency=function(){return a.Transport.syncSignal(this.frequency),this},a.Oscillator.prototype.unsyncFrequency=function(){return a.Transport.unsyncSignal(this.frequency),this},Object.defineProperty(a.Oscillator.prototype,"type",{get:function(){return this._type},set:function(a){var b,c,d,e,f,g=a,h=4096,i=h/2,j=new Float32Array(i),k=new Float32Array(i),l=1,m=/(sine|triangle|square|sawtooth)(\d+)$/.exec(a);for(m&&(l=parseInt(m[2]),a=m[1],l=Math.max(l,2),i=l),b=this._phase,c=1;i>c;++c){switch(d=2/(c*Math.PI),a){case"sine":e=l>=c?1:0;break;case"square":e=1&c?2*d:0;break;case"sawtooth":e=d*(1&c?1:-1);break;case"triangle":e=1&c?2*d*d*(c-1>>1&1?-1:1):0;break;default:throw new TypeError("invalid oscillator type: "+a)}0!==e?(j[c]=-e*Math.sin(b*c),k[c]=e*Math.cos(b*c)):(j[c]=0,k[c]=0)}f=this.context.createPeriodicWave(j,k),this._wave=f,null!==this._oscillator&&this._oscillator.setPeriodicWave(this._wave),this._type=g}}),Object.defineProperty(a.Oscillator.prototype,"phase",{get:function(){return this._phase*(180/Math.PI)},set:function(a){this._phase=a*Math.PI/180,this.type=this._type}}),a.Oscillator.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),null!==this._oscillator&&(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,this},a.Oscillator}),ToneModule(function(a){return a.AudioToGain=function(){this._norm=this.input=this.output=new a.WaveShaper(function(a){return(a+1)/2})},a.extend(a.AudioToGain,a.SignalBase),a.AudioToGain.prototype.dispose=function(){return a.prototype.dispose.call(this),this._norm.dispose(),this._norm=null,this},a.AudioToGain}),ToneModule(function(a){return a.LFO=function(){var b=this.optionsObject(arguments,["frequency","min","max"],a.LFO.defaults);this.oscillator=new a.Oscillator({frequency:b.frequency,type:b.type,phase:b.phase}),this.frequency=this.oscillator.frequency,this.amplitude=this.oscillator.volume,this.amplitude.units=a.Signal.Units.Normal,this.amplitude.value=b.amplitude,this._a2g=new a.AudioToGain,this._scaler=this.output=new a.Scale(b.min,b.max),this.oscillator.chain(this._a2g,this._scaler),this._readOnly(["amplitude","frequency","oscillator"])},a.extend(a.LFO,a.Oscillator),a.LFO.defaults={type:"sine",min:0,max:1,phase:0,frequency:"4n",amplitude:1},a.LFO.prototype.start=function(a){return this.oscillator.start(a),this},a.LFO.prototype.stop=function(a){return this.oscillator.stop(a),this},a.LFO.prototype.sync=function(a){return this.oscillator.sync(a),this.oscillator.syncFrequency(),this},a.LFO.prototype.unsync=function(){return this.oscillator.unsync(),this.oscillator.unsyncFrequency(),this},Object.defineProperty(a.LFO.prototype,"min",{get:function(){return this._scaler.min},set:function(a){this._scaler.min=a}}),Object.defineProperty(a.LFO.prototype,"max",{get:function(){return this._scaler.max},set:function(a){this._scaler.max=a}}),Object.defineProperty(a.LFO.prototype,"type",{get:function(){return this.oscillator.type},set:function(a){this.oscillator.type=a}}),Object.defineProperty(a.LFO.prototype,"phase",{get:function(){return this.oscillator.phase},set:function(a){this.oscillator.phase=a}}),a.LFO.prototype.connect=a.Signal.prototype.connect,a.LFO.prototype.dispose=function(){return a.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,this},a.LFO}),ToneModule(function(a){return a.Limiter=function(b){this._compressor=this.input=this.output=new a.Compressor({attack:1e-4,decay:1e-4,threshold:b}),this.threshold=this._compressor.threshold,this._readOnly("threshold")},a.extend(a.Limiter),a.Limiter.prototype.dispose=function(){return a.prototype.dispose.call(this),this._compressor.dispose(),this._compressor=null,this._writable("threshold"),this.threshold=null,this},a.Limiter}),ToneModule(function(a){return a.LowpassCombFilter=function(){a.call(this);var b=this.optionsObject(arguments,["delayTime","resonance","dampening"],a.LowpassCombFilter.defaults);this._delay=this.input=this.context.createDelay(1),this.delayTime=new a.Signal(b.delayTime,a.Signal.Units.Time),this._lowpass=this.output=this.context.createBiquadFilter(),this._lowpass.Q.value=0,this._lowpass.type="lowpass",this._lowpass.frequency.value=b.dampening,this.dampening=new a.Signal(this._lowpass.frequency,a.Signal.Units.Frequency),this._feedback=this.context.createGain(),this.resonance=new a.Signal(b.resonance,a.Signal.Units.Normal),this._delay.chain(this._lowpass,this._feedback,this._delay),this.delayTime.connect(this._delay.delayTime),this.resonance.connect(this._feedback.gain),this._readOnly(["dampening","resonance","delayTime"])},a.extend(a.LowpassCombFilter),a.LowpassCombFilter.defaults={delayTime:.1,resonance:.5,dampening:3e3},a.LowpassCombFilter.prototype.dispose=function(){return a.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,this +},a.LowpassCombFilter}),ToneModule(function(a){return a.Merge=function(){a.call(this,2,0),this.left=this.input[0]=this.context.createGain(),this.right=this.input[1]=this.context.createGain(),this._merger=this.output=this.context.createChannelMerger(2),this.left.connect(this._merger,0,0),this.right.connect(this._merger,0,1)},a.extend(a.Merge),a.Merge.prototype.dispose=function(){return a.prototype.dispose.call(this),this.left.disconnect(),this.left=null,this.right.disconnect(),this.right=null,this._merger.disconnect(),this._merger=null,this},a.Merge}),ToneModule(function(a){return a.Meter=function(b,c,d){a.call(this),this._channels=this.defaultArg(b,1),this._smoothing=this.defaultArg(c,.8),this._clipMemory=1e3*this.defaultArg(d,.5),this._volume=new Array(this._channels),this._values=new Array(this._channels);for(var e=0;eh;h++)f=c[h],!g&&f>.95&&(g=!0,this._lastClip=Date.now()),e+=f,d+=f*f;i=e/k,j=Math.sqrt(d/k),this._volume[b]=Math.max(j,this._volume[b]*l),this._values[b]=i}},a.Meter.prototype.getLevel=function(a){a=this.defaultArg(a,0);var b=this._volume[a];return 1e-5>b?0:b},a.Meter.prototype.getValue=function(a){return a=this.defaultArg(a,0),this._values[a]},a.Meter.prototype.getDb=function(a){return this.gainToDb(this.getLevel(a))},a.Meter.prototype.isClipped=function(){return Date.now()-this._lastClip0){if(a.Buffer._currentDownloads.length0){for(b=0;g>b;b++)c=a.Buffer._currentDownloads[b],f+=c.progress;h=f}d=g-h,e=a.Buffer._totalDownloads-a.Buffer._queue.length-d,a.Buffer.onprogress(e/a.Buffer._totalDownloads)},a.Buffer.load=function(b,c){var d=new XMLHttpRequest;return d.open("GET",b,!0),d.responseType="arraybuffer",d.onload=function(){a.context.decodeAudioData(d.response,function(a){if(!a)throw new Error("could not decode audio data:"+b);c(a)})},d.send(),d},a.Buffer.onload=function(){},a.Buffer.onprogress=function(){},a.Buffer.onerror=function(){},a.Buffer}),ToneModule(function(a){var b={};return a.prototype.send=function(a,c){b.hasOwnProperty(a)||(b[a]=this.context.createGain());var d=this.context.createGain();return d.gain.value=this.dbToGain(this.defaultArg(c,1)),this.output.chain(d,b[a]),d},a.prototype.receive=function(a,c){return b.hasOwnProperty(a)||(b[a]=this.context.createGain()),this.isUndef(c)&&(c=this.input),b[a].connect(c),this},a}),ToneModule(function(a){function b(a,b,d){var e,f,g,h;if(c.hasOwnProperty(a))for(e=c[a],f=0,g=e.length;g>f;f++)h=e[f],Array.isArray(d)?h.apply(window,[b].concat(d)):h(b,d)}var c,d,e,f;return a.Note=function(b,c,d){this.value=d,this._channel=b,this._timelineID=a.Transport.setTimeline(this._trigger.bind(this),c)},a.Note.prototype._trigger=function(a){b(this._channel,a,this.value)},a.Note.prototype.dispose=function(){return a.Tranport.clearTimeline(this._timelineID),this.value=null,this},c={},a.Note.route=function(a,b){c.hasOwnProperty(a)?c[a].push(b):c[a]=[b]},a.Note.unroute=function(a,b){var d,e;c.hasOwnProperty(a)&&(d=c[a],e=d.indexOf(b),-1!==e&&c[a].splice(e,1))},a.Note.parseScore=function(b){var c,d,e,f,g,h,i,j=[];for(c in b)if(d=b[c],"tempo"===c)a.Transport.bpm.value=d;else if("timeSignature"===c)a.Transport.timeSignature=d[0]/(d[1]/4);else{if(!Array.isArray(d))throw new TypeError("score parts must be Arrays");for(e=0;ed;++d)e=2*d/c-1,b[d]=0===e?0:this._getCoefficient(e,a,{});this._shaper.curve=b}}),Object.defineProperty(a.Chebyshev.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.Chebyshev.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},a.Chebyshev}),ToneModule(function(a){return a.StereoEffect=function(){a.call(this);var b=this.optionsObject(arguments,["wet"],a.Effect.defaults);this._dryWet=new a.CrossFade(b.wet),this.wet=this._dryWet.fade,this._split=new a.Split,this.effectSendL=this._split.left,this.effectSendR=this._split.right,this._merge=new a.Merge,this.effectReturnL=this._merge.left,this.effectReturnR=this._merge.right,this.input.connect(this._split),this.input.connect(this._dryWet,0,0),this._merge.connect(this._dryWet,0,1),this._dryWet.connect(this.output),this._readOnly(["wet"])},a.extend(a.StereoEffect,a.Effect),a.StereoEffect.prototype.dispose=function(){return a.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,this},a.StereoEffect}),ToneModule(function(a){return a.FeedbackEffect=function(){var b=this.optionsObject(arguments,["feedback"]);b=this.defaultArg(b,a.FeedbackEffect.defaults),a.Effect.call(this,b),this.feedback=new a.Signal(b.feedback,a.Signal.Units.Normal),this._feedbackGain=this.context.createGain(),this.effectReturn.chain(this._feedbackGain,this.effectSend),this.feedback.connect(this._feedbackGain.gain),this._readOnly(["feedback"])},a.extend(a.FeedbackEffect,a.Effect),a.FeedbackEffect.defaults={feedback:.125},a.FeedbackEffect.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._writable(["feedback"]),this.feedback.dispose(),this.feedback=null,this._feedbackGain.disconnect(),this._feedbackGain=null,this},a.FeedbackEffect}),ToneModule(function(a){return a.StereoXFeedbackEffect=function(){var b=this.optionsObject(arguments,["feedback"],a.FeedbackEffect.defaults);a.StereoEffect.call(this,b),this.feedback=new a.Signal(b.feedback),this._feedbackLR=this.context.createGain(),this._feedbackRL=this.context.createGain(),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"])},a.extend(a.StereoXFeedbackEffect,a.FeedbackEffect),a.StereoXFeedbackEffect.prototype.dispose=function(){return a.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,this},a.StereoXFeedbackEffect}),ToneModule(function(a){return a.Chorus=function(){var b=this.optionsObject(arguments,["frequency","delayTime","depth"],a.Chorus.defaults);a.StereoXFeedbackEffect.call(this,b),this._depth=b.depth,this._delayTime=b.delayTime/1e3,this._lfoL=new a.LFO(b.rate,0,1),this._lfoR=new a.LFO(b.rate,0,1),this._lfoR.phase=180,this._delayNodeL=this.context.createDelay(),this._delayNodeR=this.context.createDelay(),this.frequency=this._lfoL.frequency,this.connectSeries(this.effectSendL,this._delayNodeL,this.effectReturnL),this.connectSeries(this.effectSendR,this._delayNodeR,this.effectReturnR),this.effectSendL.connect(this.effectReturnL),this.effectSendR.connect(this.effectReturnR),this._lfoL.connect(this._delayNodeL.delayTime),this._lfoR.connect(this._delayNodeR.delayTime),this._lfoL.start(),this._lfoR.start(),this._lfoL.frequency.connect(this._lfoR.frequency),this.depth=this._depth,this.frequency.value=b.frequency,this.type=b.type,this._readOnly(["frequency"])},a.extend(a.Chorus,a.StereoXFeedbackEffect),a.Chorus.defaults={frequency:1.5,delayTime:3.5,depth:.7,feedback:.1,type:"sine"},Object.defineProperty(a.Chorus.prototype,"depth",{get:function(){return this._depth},set:function(a){this._depth=a;var b=this._delayTime*a;this._lfoL.min=Math.max(this._delayTime-b,0),this._lfoL.max=this._delayTime+b,this._lfoR.min=Math.max(this._delayTime-b,0),this._lfoR.max=this._delayTime+b}}),Object.defineProperty(a.Chorus.prototype,"delayTime",{get:function(){return 1e3*this._delayTime},set:function(a){this._delayTime=a/1e3,this.depth=this._depth}}),Object.defineProperty(a.Chorus.prototype,"type",{get:function(){return this._lfoL.type},set:function(a){this._lfoL.type=a,this._lfoR.type=a}}),a.Chorus.prototype.dispose=function(){return a.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,this},a.Chorus}),ToneModule(function(a){return a.Convolver=function(){var b=this.optionsObject(arguments,["url"],a.Convolver.defaults);a.Effect.call(this,b),this._convolver=this.context.createConvolver(),this._buffer=new a.Buffer(b.url,function(a){this.buffer=a,b.onload()}.bind(this)),this.connectEffect(this._convolver)},a.extend(a.Convolver,a.Effect),a.Convolver.defaults={url:"",onload:function(){}},Object.defineProperty(a.Convolver.prototype,"buffer",{get:function(){return this._buffer.get()},set:function(a){this._buffer.set(a),this._convolver.buffer=this._buffer.get()}}),a.Convolver.prototype.load=function(a,b){return this._buffer.load(a,function(a){this.buffer=a,b&&b()}.bind(this)),this},a.Convolver.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._convolver.disconnect(),this._convolver=null,this._buffer.dispose(),this._buffer=null,this},a.Convolver}),ToneModule(function(a){return a.Distortion=function(){var b=this.optionsObject(arguments,["distortion"],a.Distortion.defaults);a.Effect.call(this),this._shaper=new a.WaveShaper(4096),this._distortion=b.distortion,this.connectEffect(this._shaper),this.distortion=b.distortion,this.oversample=b.oversample},a.extend(a.Distortion,a.Effect),a.Distortion.defaults={distortion:.4,oversample:"none"},Object.defineProperty(a.Distortion.prototype,"distortion",{get:function(){return this._distortion},set:function(a){var b,c;this._distortion=a,b=100*a,c=Math.PI/180,this._shaper.setMap(function(a){return Math.abs(a)<.001?0:(3+b)*a*20*c/(Math.PI+b*Math.abs(a))})}}),Object.defineProperty(a.Distortion.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(a){this._shaper.oversample=a}}),a.Distortion.prototype.dispose=function(){return a.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},a.Distortion}),ToneModule(function(a){return a.FeedbackDelay=function(){var b=this.optionsObject(arguments,["delayTime","feedback"],a.FeedbackDelay.defaults);a.FeedbackEffect.call(this,b),this.delayTime=new a.Signal(b.delayTime,a.Signal.Units.Time),this._delayNode=this.context.createDelay(4),this.connectEffect(this._delayNode),this.delayTime.connect(this._delayNode.delayTime),this._readOnly(["delayTime"])},a.extend(a.FeedbackDelay,a.FeedbackEffect),a.FeedbackDelay.defaults={delayTime:.25},a.FeedbackDelay.prototype.dispose=function(){return a.FeedbackEffect.prototype.dispose.call(this),this.delayTime.dispose(),this._delayNode.disconnect(),this._delayNode=null,this._writable(["delayTime"]),this.delayTime=null,this},a.FeedbackDelay}),ToneModule(function(a){var b=[1557/44100,1617/44100,1491/44100,1422/44100,1277/44100,1356/44100,1188/44100,1116/44100],c=[225,556,441,341];return a.Freeverb=function(){var d,e,f,g,h,i,j=this.optionsObject(arguments,["roomSize","dampening"],a.Freeverb.defaults);for(a.StereoEffect.call(this,j),this.roomSize=new a.Signal(j.roomSize,a.Signal.Units.Normal),this.dampening=new a.Signal(j.dampening,a.Signal.Units.Frequency),this._combFilters=[],this._allpassFiltersL=[],this._allpassFiltersR=[],d=0;dd;d++)e=this.context.createBiquadFilter(),e.type="allpass",e.Q.value=c,b.connect(e.frequency),f[d]=e;return this.connectSeries.apply(this,f),f},Object.defineProperty(a.Phaser.prototype,"depth",{get:function(){return this._depth},set:function(a){this._depth=a;var b=this._baseFrequency+this._baseFrequency*a;this._lfoL.max=b,this._lfoR.max=b}}),Object.defineProperty(a.Phaser.prototype,"baseFrequency",{get:function(){return this._baseFrequency},set:function(a){this._baseFrequency=a,this._lfoL.min=a,this._lfoR.min=a,this.depth=this._depth}}),a.Phaser.prototype.dispose=function(){var b,c;for(a.StereoEffect.prototype.dispose.call(this),this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,b=0;ba?-1:1}),this._sawtooth.chain(this._thresh,this.output),this.width.chain(this._widthGate,this._thresh),this._readOnly(["width","frequency","detune"])},a.extend(a.PulseOscillator,a.Oscillator),a.PulseOscillator.defaults={frequency:440,detune:0,phase:0,width:.2},a.PulseOscillator.prototype._start=function(a){a=this.toSeconds(a),this._sawtooth.start(a),this._widthGate.gain.setValueAtTime(1,a)},a.PulseOscillator.prototype._stop=function(a){a=this.toSeconds(a),this._sawtooth.stop(a),this._widthGate.gain.setValueAtTime(0,a)},Object.defineProperty(a.PulseOscillator.prototype,"phase",{get:function(){return this._sawtooth.phase},set:function(a){this._sawtooth.phase=a}}),Object.defineProperty(a.PulseOscillator.prototype,"type",{get:function(){return"pulse"}}),a.PulseOscillator.prototype.dispose=function(){return a.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,this},a.PulseOscillator}),ToneModule(function(a){return a.PWMOscillator=function(){var b=this.optionsObject(arguments,["frequency","modulationFrequency"],a.PWMOscillator.defaults);a.Source.call(this,b),this._pulse=new a.PulseOscillator(b.modulationFrequency),this._pulse._sawtooth.type="sine",this._modulator=new a.Oscillator({frequency:b.frequency,detune:b.detune}),this._scale=new a.Multiply(1.01),this.frequency=this._modulator.frequency,this.detune=this._modulator.detune,this.modulationFrequency=this._pulse.frequency,this._modulator.chain(this._scale,this._pulse.width),this._pulse.connect(this.output),this._readOnly(["modulationFrequency","frequency","detune"])},a.extend(a.PWMOscillator,a.Oscillator),a.PWMOscillator.defaults={frequency:440,detune:0,modulationFrequency:.4},a.PWMOscillator.prototype._start=function(a){a=this.toSeconds(a),this._modulator.start(a),this._pulse.start(a)},a.PWMOscillator.prototype._stop=function(a){a=this.toSeconds(a),this._modulator.stop(a),this._pulse.stop(a)},Object.defineProperty(a.PWMOscillator.prototype,"type",{get:function(){return"pwm"}}),Object.defineProperty(a.PWMOscillator.prototype,"phase",{get:function(){return this._modulator.phase},set:function(a){this._modulator.phase=a}}),a.PWMOscillator.prototype.dispose=function(){return a.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,this},a.PWMOscillator}),ToneModule(function(a){a.OmniOscillator=function(){var b=this.optionsObject(arguments,["frequency","type"],a.OmniOscillator.defaults);a.Source.call(this,b),this.frequency=new a.Signal(b.frequency,a.Signal.Units.Frequency),this.detune=new a.Signal(b.detune),this._sourceType=void 0,this._oscillator=null,this.type=b.type,this._readOnly(["frequency","detune"])},a.extend(a.OmniOscillator,a.Oscillator),a.OmniOscillator.defaults={frequency:440,detune:0,type:"sine",width:.4,modulationFrequency:.4};var b={PulseOscillator:"PulseOscillator",PWMOscillator:"PWMOscillator",Oscillator:"Oscillator"};return a.OmniOscillator.prototype._start=function(a){this._oscillator.start(a)},a.OmniOscillator.prototype._stop=function(a){this._oscillator.stop(a)},Object.defineProperty(a.OmniOscillator.prototype,"type",{get:function(){return this._oscillator.type},set:function(c){if("sine"===c||"square"===c||"triangle"===c||"sawtooth"===c)this._sourceType!==b.Oscillator&&(this._sourceType=b.Oscillator,this._createNewOscillator(a.Oscillator)),this._oscillator.type=c;else if("pwm"===c)this._sourceType!==b.PWMOscillator&&(this._sourceType=b.PWMOscillator,this._createNewOscillator(a.PWMOscillator));else{if("pulse"!==c)throw new TypeError("Tone.OmniOscillator does not support type "+c);this._sourceType!==b.PulseOscillator&&(this._sourceType=b.PulseOscillator,this._createNewOscillator(a.PulseOscillator))}}}),a.OmniOscillator.prototype._createNewOscillator=function(b){var c,d=this.now()+this.bufferTime;null!==this._oscillator&&(c=this._oscillator,c.stop(d),c.onended=function(){c.dispose(),c=null}),this._oscillator=new b,this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.connect(this.output),this.state===a.Source.State.STARTED&&this._oscillator.start(d)},Object.defineProperty(a.OmniOscillator.prototype,"phase",{get:function(){return this._oscillator.phase},set:function(a){this._oscillator.phase=a}}),Object.defineProperty(a.OmniOscillator.prototype,"width",{get:function(){return this._sourceType===b.PulseOscillator?this._oscillator.width:void 0}}),Object.defineProperty(a.OmniOscillator.prototype,"modulationFrequency",{get:function(){return this._sourceType===b.PWMOscillator?this._oscillator.modulationFrequency:void 0}}),a.OmniOscillator.prototype.dispose=function(){return a.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,this},a.OmniOscillator}),ToneModule(function(a){return a.Instrument=function(){this.output=this.context.createGain(),this.volume=new a.Signal(this.output.gain,a.Signal.Units.Decibels),this._readOnly(["volume"])},a.extend(a.Instrument),a.Instrument.prototype.triggerAttack=function(){},a.Instrument.prototype.triggerRelease=function(){},a.Instrument.prototype.triggerAttackRelease=function(a,b,c,d){return c=this.toSeconds(c),b=this.toSeconds(b),this.triggerAttack(a,c,d),this.triggerRelease(c+b),this},a.Instrument.prototype.dispose=function(){return a.prototype.dispose.call(this),this._writable(["volume"]),this.volume.dispose(),this.volume=null,this},a.Instrument}),ToneModule(function(a){return a.Monophonic=function(b){a.Instrument.call(this),b=this.defaultArg(b,a.Monophonic.defaults),this.portamento=b.portamento},a.extend(a.Monophonic,a.Instrument),a.Monophonic.defaults={portamento:0},a.Monophonic.prototype.triggerAttack=function(a,b,c){return b=this.toSeconds(b),this.triggerEnvelopeAttack(b,c),this.setNote(a,b),this},a.Monophonic.prototype.triggerRelease=function(a){return this.triggerEnvelopeRelease(a),this},a.Monophonic.prototype.triggerEnvelopeAttack=function(){},a.Monophonic.prototype.triggerEnvelopeRelease=function(){},a.Monophonic.prototype.setNote=function(a,b){var c,d;return b=this.toSeconds(b),this.portamento>0?(c=this.frequency.value,this.frequency.setValueAtTime(c,b),d=this.toSeconds(this.portamento),this.frequency.exponentialRampToValueAtTime(a,b+d)):this.frequency.setValueAtTime(a,b),this},a.Monophonic}),ToneModule(function(a){return a.MonoSynth=function(b){b=this.defaultArg(b,a.MonoSynth.defaults),a.Monophonic.call(this,b),this.oscillator=new a.OmniOscillator(b.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.filter=new a.Filter(b.filter),this.filterEnvelope=new a.ScaledEnvelope(b.filterEnvelope),this.envelope=new a.AmplitudeEnvelope(b.envelope),this.oscillator.chain(this.filter,this.envelope,this.output),this.oscillator.start(),this.filterEnvelope.connect(this.filter.frequency),this._readOnly(["oscillator","frequency","detune","filter","filterEnvelope","envelope"])},a.extend(a.MonoSynth,a.Monophonic),a.MonoSynth.defaults={frequency:"C4",detune:0,oscillator:{type:"square"},filter:{Q:6,type:"lowpass",rolloff:-24},envelope:{attack:.005,decay:.1,sustain:.9,release:1},filterEnvelope:{attack:.06,decay:.2,sustain:.5,release:2,min:20,max:4e3,exponent:2}},a.MonoSynth.prototype.triggerEnvelopeAttack=function(a,b){return this.envelope.triggerAttack(a,b),this.filterEnvelope.triggerAttack(a),this},a.MonoSynth.prototype.triggerEnvelopeRelease=function(a){return this.envelope.triggerRelease(a),this.filterEnvelope.triggerRelease(a),this},a.MonoSynth.prototype.dispose=function(){return a.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,this},a.MonoSynth}),ToneModule(function(a){return a.AMSynth=function(b){b=this.defaultArg(b,a.AMSynth.defaults),a.Monophonic.call(this,b),this.carrier=new a.MonoSynth(b.carrier),this.carrier.volume.value=-10,this.modulator=new a.MonoSynth(b.modulator),this.modulator.volume.value=-10,this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),this._modulationScale=new a.AudioToGain,this._modulationNode=this.context.createGain(),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"])},a.extend(a.AMSynth,a.Monophonic),a.AMSynth.defaults={harmonicity:3,carrier:{volume:-10,oscillator:{type:"sine"},envelope:{attack:.01,decay:.01,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4},filter:{Q:6,type:"lowpass",rolloff:-24}},modulator:{volume:-10,oscillator:{type:"square"},envelope:{attack:2,decay:0,sustain:1,release:.5},filterEnvelope:{attack:4,decay:.2,sustain:.5,release:.5,min:20,max:1500},filter:{Q:6,type:"lowpass",rolloff:-24}}},a.AMSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.carrier.envelope.triggerAttack(a,b),this.modulator.envelope.triggerAttack(a),this.carrier.filterEnvelope.triggerAttack(a),this.modulator.filterEnvelope.triggerAttack(a),this},a.AMSynth.prototype.triggerEnvelopeRelease=function(a){return this.carrier.triggerRelease(a),this.modulator.triggerRelease(a),this},Object.defineProperty(a.AMSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),a.AMSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),this._writable(["carrier","modulator","frequency"]),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,this},a.AMSynth}),ToneModule(function(a){return a.DroneSynth=function(b){b=this.defaultArg(b,a.DroneSynth.defaults),a.Monophonic.call(this,b),this.oscillator=new a.OmniOscillator(b.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.envelope=new a.AmplitudeEnvelope(b.envelope),this.oscillator.chain(this.envelope,this.output),this.oscillator.start(),this._readOnly(["oscillator","frequency","detune","envelope"])},a.extend(a.DroneSynth,a.Monophonic),a.DroneSynth.defaults={oscillator:{type:"triangle"},envelope:{attack:.005,decay:.1,sustain:.3,release:1}},a.DroneSynth.prototype.triggerEnvelopeAttack=function(a,b){return this.envelope.triggerAttack(a,b),this},a.DroneSynth.prototype.triggerEnvelopeRelease=function(a){return this.envelope.triggerRelease(a),this},a.DroneSynth.prototype.dispose=function(){return a.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,this},a.DroneSynth}),ToneModule(function(a){return a.DrumSynth=function(b){b=this.defaultArg(b,a.DrumSynth.defaults),a.Instrument.call(this,b),this.oscillator=new a.Oscillator(b.oscillator).start(),this.envelope=new a.AmplitudeEnvelope(b.envelope),this.octaves=b.octaves,this.pitchDecay=b.pitchDecay,this.oscillator.chain(this.envelope,this.output),this._readOnly(["oscillator","envelope"])},a.extend(a.DrumSynth,a.Instrument),a.DrumSynth.defaults={pitchDecay:.05,octaves:10,oscillator:{type:"sine"},envelope:{attack:.001,decay:.4,sustain:.01,release:1.4,attackCurve:"exponential"}},a.DrumSynth.prototype.triggerAttack=function(a,b,c){b=this.toSeconds(b),a=this.toFrequency(a);var d=a*this.octaves;return this.oscillator.frequency.setValueAtTime(d,b),this.oscillator.frequency.exponentialRampToValueAtTime(a,b+this.toSeconds(this.pitchDecay)),this.envelope.triggerAttack(b,c),this},a.DrumSynth.prototype.triggerRelease=function(a){return this.envelope.triggerRelease(a),this},a.DrumSynth.prototype.dispose=function(){return a.Instrument.prototype.dispose.call(this),this._writable(["oscillator","envelope"]),this.oscillator.dispose(),this.oscillator=null,this.envelope.dispose(),this.envelope=null,this},a.DrumSynth}),ToneModule(function(a){return a.DuoSynth=function(b){b=this.defaultArg(b,a.DuoSynth.defaults),a.Monophonic.call(this,b),this.voice0=new a.MonoSynth(b.voice0),this.voice0.volume.value=-10,this.voice1=new a.MonoSynth(b.voice1),this.voice1.volume.value=-10,this._vibrato=new a.LFO(b.vibratoRate,-50,50),this._vibrato.start(),this.vibratoRate=this._vibrato.frequency,this._vibratoGain=this.context.createGain(),this.vibratoAmount=new a.Signal(this._vibratoGain.gain,a.Signal.Units.Gain),this.vibratoAmount.value=b.vibratoAmount,this._vibratoDelay=this.toSeconds(b.vibratoDelay),this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),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"])},a.extend(a.DuoSynth,a.Monophonic),a.DuoSynth.defaults={vibratoAmount:.5,vibratoRate:5,vibratoDelay:1,harmonicity:1.5,voice0:{volume:-10,portamento:0,oscillator:{type:"sine"},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5},envelope:{attack:.01,decay:0,sustain:1,release:.5}},voice1:{volume:-10,portamento:0,oscillator:{type:"sine"},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5},envelope:{attack:.01,decay:0,sustain:1,release:.5}}},a.DuoSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.voice0.envelope.triggerAttack(a,b),this.voice1.envelope.triggerAttack(a,b),this.voice0.filterEnvelope.triggerAttack(a),this.voice1.filterEnvelope.triggerAttack(a),this},a.DuoSynth.prototype.triggerEnvelopeRelease=function(a){return this.voice0.triggerRelease(a),this.voice1.triggerRelease(a),this},Object.defineProperty(a.DuoSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),a.DuoSynth.prototype.dispose=function(){return a.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,this},a.DuoSynth}),ToneModule(function(a){return a.FMSynth=function(b){b=this.defaultArg(b,a.FMSynth.defaults),a.Monophonic.call(this,b),this.carrier=new a.MonoSynth(b.carrier),this.carrier.volume.value=-10,this.modulator=new a.MonoSynth(b.modulator),this.modulator.volume.value=-10,this.frequency=new a.Signal(440,a.Signal.Units.Frequency),this._harmonicity=new a.Multiply(b.harmonicity),this._modulationIndex=new a.Multiply(b.modulationIndex),this._modulationNode=this.context.createGain(),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"])},a.extend(a.FMSynth,a.Monophonic),a.FMSynth.defaults={harmonicity:3,modulationIndex:10,carrier:{volume:-10,portamento:0,oscillator:{type:"sine"},envelope:{attack:.01,decay:0,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}},modulator:{volume:-10,portamento:0,oscillator:{type:"triangle"},envelope:{attack:.01,decay:0,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}}},a.FMSynth.prototype.triggerEnvelopeAttack=function(a,b){return a=this.toSeconds(a),this.carrier.envelope.triggerAttack(a,b),this.modulator.envelope.triggerAttack(a),this.carrier.filterEnvelope.triggerAttack(a),this.modulator.filterEnvelope.triggerAttack(a),this},a.FMSynth.prototype.triggerEnvelopeRelease=function(a){return this.carrier.triggerRelease(a),this.modulator.triggerRelease(a),this},Object.defineProperty(a.FMSynth.prototype,"harmonicity",{get:function(){return this._harmonicity.value},set:function(a){this._harmonicity.value=a}}),Object.defineProperty(a.FMSynth.prototype,"modulationIndex",{get:function(){return this._modulationIndex.value},set:function(a){this._modulationIndex.value=a}}),a.FMSynth.prototype.dispose=function(){return a.Monophonic.prototype.dispose.call(this),this._writable(["carrier","modulator","frequency"]),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,this},a.FMSynth}),ToneModule(function(a){a.Noise=function(){var b=this.optionsObject(arguments,["type"],a.Noise.defaults);a.Source.call(this,b),this._source=null,this._buffer=null,this.type=b.type},a.extend(a.Noise,a.Source),a.Noise.defaults={type:"white"},Object.defineProperty(a.Noise.prototype,"type",{get:function(){return this._buffer===d?"white":this._buffer===c?"brown":this._buffer===b?"pink":void 0},set:function(e){if(this.type!==e){switch(e){case"white":this._buffer=d;break;case"pink":this._buffer=b;break;case"brown":this._buffer=c;break;default:this._buffer=d}if(this.state===a.Source.State.STARTED){var f=this.now()+this.bufferTime;this._source.onended=void 0,this._stop(f),this._start(f)}}}}),a.Noise.prototype._start=function(a){this._source=this.context.createBufferSource(),this._source.buffer=this._buffer,this._source.loop=!0,this.connectSeries(this._source,this.output),this._source.start(this.toSeconds(a)),this._source.onended=this.onended},a.Noise.prototype._stop=function(a){this._source&&this._source.stop(this.toSeconds(a))},a.Noise.prototype.dispose=function(){return a.Source.prototype.dispose.call(this),null!==this._source&&(this._source.disconnect(),this._source=null),this._buffer=null,this};var b=null,c=null,d=null;return a._initAudioContext(function(a){var e=a.sampleRate,f=4*e;b=function(){var b,c,d,g,h,i,j,k,l,m,n,o=a.createBuffer(2,f,e);for(b=0;bm;m++)n=2*Math.random()-1,d=.99886*d+.0555179*n,g=.99332*g+.0750759*n,h=.969*h+.153852*n,i=.8665*i+.3104856*n,j=.55*j+.5329522*n,k=-.7616*k-.016898*n,c[m]=d+g+h+i+j+k+l+.5362*n,c[m]*=.11,l=.115926*n;return o}(),c=function(){var b,c,d,g,h,i=a.createBuffer(2,f,e);for(b=0;bg;g++)h=2*Math.random()-1,c[g]=(d+.02*h)/1.02,d=c[g],c[g]*=3.5;return i}(),d=function(){var b,c,d,g=a.createBuffer(2,f,e);for(b=0;bd;d++)c[d]=2*Math.random()-1;return g}()}),a.Noise}),ToneModule(function(a){return a.NoiseSynth=function(b){b=this.defaultArg(b,a.NoiseSynth.defaults),a.Instrument.call(this),this.noise=new a.Noise,this.filter=new a.Filter(b.filter),this.filterEnvelope=new a.ScaledEnvelope(b.filterEnvelope),this.envelope=new a.AmplitudeEnvelope(b.envelope),this.noise.chain(this.filter,this.envelope,this.output),this.noise.start(),this.filterEnvelope.connect(this.filter.frequency),this._readOnly(["noise","filter","filterEnvelope","envelope"])},a.extend(a.NoiseSynth,a.Instrument),a.NoiseSynth.defaults={noise:{type:"white"},filter:{Q:6,type:"highpass",rolloff:-24},envelope:{attack:.005,decay:.1,sustain:0},filterEnvelope:{attack:.06,decay:.2,sustain:0,release:2,min:20,max:4e3,exponent:2}},a.NoiseSynth.prototype.triggerAttack=function(a,b){return this.envelope.triggerAttack(a,b),this.filterEnvelope.triggerAttack(a),this},a.NoiseSynth.prototype.triggerRelease=function(a){return this.envelope.triggerRelease(a),this.filterEnvelope.triggerRelease(a),this},a.NoiseSynth.prototype.triggerAttackRelease=function(a,b,c){return b=this.toSeconds(b),a=this.toSeconds(a),this.triggerAttack(b,c),this.triggerRelease(b+a),this},a.NoiseSynth.prototype.dispose=function(){return a.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,this},a.NoiseSynth}),ToneModule(function(a){return a.PluckSynth=function(b){b=this.defaultArg(b,a.PluckSynth.defaults),a.Instrument.call(this),this._noise=new a.Noise("pink"),this.attackNoise=1,this._lfcf=new a.LowpassCombFilter(1/440),this.resonance=this._lfcf.resonance,this.dampening=this._lfcf.dampening,this._noise.connect(this._lfcf),this._lfcf.connect(this.output),this._readOnly(["resonance","dampening"])},a.extend(a.PluckSynth,a.Instrument),a.PluckSynth.defaults={attackNoise:1,dampening:4e3,resonance:.5},a.PluckSynth.prototype.triggerAttack=function(a,b){a=this.toFrequency(a),b=this.toSeconds(b); +var c=1/a;return this._lfcf.delayTime.setValueAtTime(c,b),this._noise.start(b),this._noise.stop(b+c*this.attackNoise),this},a.PluckSynth.prototype.dispose=function(){return a.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,this},a.PluckSynth}),ToneModule(function(a){return a.PolySynth=function(){var b,c,d;for(a.Instrument.call(this),b=this.optionsObject(arguments,["polyphony","voice"],a.PolySynth.defaults),this.voices=new Array(b.polyphony),this._freeVoices=[],this._activeVoices={},c=0;c0&&(g=this._freeVoices.shift(),g.triggerAttack(e,b,c),this._activeVoices[f]=g);return this},a.PolySynth.prototype.triggerAttackRelease=function(a,b,c,d){return c=this.toSeconds(c),this.triggerAttack(a,c,d),this.triggerRelease(a,c+this.toSeconds(b)),this},a.PolySynth.prototype.triggerRelease=function(a,b){var c,d,e;for(Array.isArray(a)||(a=[a]),c=0;cc){var d=b;b=c,c=d}this.min=this.input=new a.Min(c),this._readOnly("min"),this.max=this.output=new a.Max(b),this._readOnly("max"),this.min.connect(this.max)},a.extend(a.Clip,a.SignalBase),a.Clip.prototype.dispose=function(){return a.prototype.dispose.call(this),this._writable("min"),this.min.dispose(),this.min=null,this._writable("max"),this.max.dispose(),this.max=null,this},a.Clip}),ToneModule(function(a){return a.Normalize=function(b,c){this._inputMin=this.defaultArg(b,0),this._inputMax=this.defaultArg(c,1),this._sub=this.input=new a.Add(0),this._div=this.output=new a.Multiply(1),this._sub.connect(this._div),this._setRange()},a.extend(a.Normalize,a.SignalBase),Object.defineProperty(a.Normalize.prototype,"min",{get:function(){return this._inputMin},set:function(a){this._inputMin=a,this._setRange()}}),Object.defineProperty(a.Normalize.prototype,"max",{get:function(){return this._inputMax},set:function(a){this._inputMax=a,this._setRange()}}),a.Normalize.prototype._setRange=function(){this._sub.value=-this._inputMin,this._div.value=1/(this._inputMax-this._inputMin)},a.Normalize.prototype.dispose=function(){return a.prototype.dispose.call(this),this._sub.dispose(),this._sub=null,this._div.dispose(),this._div=null,this},a.Normalize}),ToneModule(function(a){a.Route=function(c){var d,e;for(c=this.defaultArg(c,2),a.call(this,1,c),this.gate=new a.Signal(0),this._readOnly("gate"),d=0;c>d;d++)e=new b(d),this.output[d]=e,this.gate.connect(e.selecter),this.input.connect(e)},a.extend(a.Route,a.SignalBase),a.Route.prototype.select=function(a,b){return a=Math.floor(a),this.gate.setValueAtTime(a,this.toSeconds(b)),this},a.Route.prototype.dispose=function(){this._writable("gate"),this.gate.dispose(),this.gate=null;for(var b=0;bany): Tone.Player; setLoopPoints(loopStart:Tone.Time, loopEnd:Tone.Time): Tone.Player; @@ -1099,6 +1158,14 @@ declare module Tone { interface TransportState {} + var Volume: { + new(volume: number): Tone.Volume; + }; + + interface Volume extends Tone { + volume: Tone.Signal; + } + var WaveShaper: { new(mapping: any, bufferLen?: number): Tone.WaveShaper; //TODO: change 'any' to 'Function | Array | number' }; @@ -1108,23 +1175,3 @@ declare module Tone { oversample: string; } } - - -/*** - * NOTES - * LFO.phase should be type number - OmniOscillator frequency type should be Tone.Frequency - PWMOscillator frequency type should be Tone.Frequency - - WaveShaper oversample @name is wrong - AudioGain returns this (Tone.AudioGain) - LowpassCombFilter.setDelayTimeAtTime() should return this - Master has two mute methods. One of them should be unmute. - Normalize is missing constructor in docs: new(min?: number, max?: number) - OR is missing constructor in docs: new(inputCount?:number) - PanVol is missing constructor in docs: new(pan: number, volume: number) - Phaser.baseFrequency should be type number not string. (Or is it meant to be Tone.Frequency?) - AmplitudeEnvelope should have its own dispose. - Convolution Reverb not working - * - */ \ No newline at end of file