diff --git a/.gitignore b/.gitignore index 828b7af5a..328e066d9 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,5 @@ test/Main.js build/p5.Tone.min.js build/p5.Tone.js -.DS_Store \ No newline at end of file +.DS_Store +examples/graph.html diff --git a/.jshintrc b/.jshintrc index 7561cf848..dc346d474 100644 --- a/.jshintrc +++ b/.jshintrc @@ -18,6 +18,7 @@ "GainNode" : false, "AudioNode" : false, "AudioParam" : false, + "AnalyserNode" : false, "WaveShaperNode" : false, "DynamicsCompressorNode" : false, "MediaStreamTrack" : false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..f42e14104 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: node_js +node_js: + - "4.1" +before_install: + - export CHROME_BIN=chromium-browser + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +before_script: + - cd gulp + - npm install -g karma + - npm install -g gulp + - npm install +script: gulp karma-test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 18aa1be61..cb79073e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +### r7 + +* MetalSynth creates metalic, cymbal sounds +* DrumSynth -> MembraneSynth +* FMOscillator, AMOscillator types +* FatOscillator creates multiple oscillators and detunes them slightly +* FM, AM, Fat Oscillators incorporated into OmniOscillator +* Simplified FM and AM Synths and APIs +* Panner.pan is between -1,1 like the StereoPannerNode +* Pruned away unused (or little used) Signal classes. + * All this functionality will be available when the AudioWorkerNode is introduced. +* Clock uses Web Workers instead of requestAnimationFrame which allows it to run in the background. +* Removed `startMobile`. Using [StartAudioContext](https://github.com/tambien/StartAudioContext) in examples. +* Automated test runner using [Travis CI](https://travis-ci.org/Tonejs/Tone.js/) +* Simplified NoiseSynth by removing filter and filter envelope. +* Added new timing primitive types: Time, Frequency, TransportTime. +* Switching parameter position of type and size in Tone.Analyser +* Tone.Meter uses Tone.Analyser instead of ScriptProcessorNode. +* Tone.Envelope has 5 new attack/release curves: "sine", "cosine", "bounce", "ripple", "step" +* Renamed Tone.SimpleSynth -> Tone.Synth +* Tone.Buffers combines multiple buffers +* Tone.BufferSource a low-level wrapper, and Tone.MultiPlayer which is good for multisampled instruments. +* Tone.GrainPlayer: granular synthesis buffer player. +* Simplified Sampler + +DEPRECATED: +* Removed SimpleFM and SimpleAM + ### r6 * Added PitchShift and Vibrato Effect. diff --git a/README.md b/README.md index 90f9b9b3d..3b6040808 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,43 @@ 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 for scheduling and timing events and prebuilt 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. +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 for scheduling events and prebuilt 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. -[API](http://tonejs.org/docs/) +[API](https://tonejs.github.io/docs/) -[Examples](http://tonejs.org/examples/) +[Examples](https://tonejs.github.io/examples/) # Demos +* [Chrome Music Lab - Google Creative Lab](https://musiclab.chromeexperiments.com) +* [Groove Pizza - NYU Music Experience Design Lab](https://apps.musedlab.org/groovepizza/) * [Jazz.Computer - Yotam Mann](http://jazz.computer/) * [motionEmotion - Karen Peng, Jason Sigal](http://motionemotion.herokuapp.com/) * [p5.sound - build with Tone.js](https://github.com/processing/p5.js-sound) * [Hypercube - @eddietree](http://eddietree.github.io/hypercube/) -* [Random Commander - Jake Albaugh](http://randomcommander.io/) +* [Musical Chord Progression Arpeggiator - Jake Albaugh](http://codepen.io/jakealbaugh/full/qNrZyw/) * [Tone.js + NexusUI - Ben Taylor](http://taylorbf.github.io/Tone-Rack/) * [Solarbeat - Luke Twyman](http://www.whitevinyldesign.com/solarbeat/) -* [Wind - João Costa](http://wind.joaocosta.co) * [Block Chords - Abe Rubenstein](http://dev.abe.sh/block-chords/) * [This is Not a Machine Learning - David Karam](http://posttool.github.io/) -* [Airjam - Seth Kranzler, Abe Rubenstein, and Teresa Lamb](http://airjam.band/) * [Calculaural - Matthew Hasbach](https://github.com/mjhasbach/calculaural) * [Scratch + Tone.js - Eric Rosenbaum](http://ericrosenbaum.github.io/tone-synth-extension/) * [Game of Reich - Ben Taylor](http://nexusosc.com/gameofreich/) * [Yume - Helios + Luke Twyman](http://www.unseen-music.com/yume/) +* [TR-808 - Gregor Adams](http://codepen.io/pixelass/full/adyLPR) +* [Tweet FM - Mike Mitchell](https://tweet-fm.herokuapp.com/) +* [TextXoX - Damon Holzborn](http://rustleworks.com/textxox/) +* [Stepping - John Hussey](http://stepping.audio/) +* [Limp Body Beat](http://www.adultswim.com/etcetera/limp-body-beat/) +* [MsCompose 95 - Autotel](http://autotel.co/mscompose95/) +* [Pedalboard - Micha Hanselmann](https://deermichel.github.io/pedalboard/) +* [Keyboard Boogie - Douglas Tarr](http://douglastarr.com/keyboard-boogie) -Using Tone.js? I'd love to hear it: yotammann@gmail.com +Using Tone.js? I'd love to hear it: yotam@tonejs.org # Installation -* CDN - [full](http://cdn.tonejs.org/latest/Tone.js) | [min](http://cdn.tonejs.org/latest/Tone.min.js) +* CDN - [full](https://tonejs.github.io/CDN/latest/Tone.js) | [min](https://tonejs.github.io/CDN/latest/Tone.min.js) * [bower](http://bower.io/) - `bower install tone` * [npm](https://www.npmjs.org/) - `npm install tone` @@ -38,75 +46,73 @@ Using Tone.js? I'd love to hear it: yotammann@gmail.com # Hello Tone ```javascript -//create one of Tone's built-in synthesizers and connect it to the master output -var synth = new Tone.SimpleSynth().toMaster(); +//create a synth and connect it to the master output (your speakers) +var synth = new Tone.Synth().toMaster(); -//play a middle c for the duration of an 8th note +//play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n"); ``` -[SimpleSynth](http://tonejs.org/docs/#SimpleSynth) is a single oscillator, single envelope synthesizer. It's [ADSR envelope](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope) has two phases: the attack and the release. These can be triggered by calling `triggerAttack` and `triggerRelease` separately, or combined as shown above. The first argument of `triggerAttackRelease` is the frequency, which can be given either a number (like `440`) or as "pitch-octave" notation (like `"D#2"`). The second argument is the duration of the envelope's sustain (i.e. how long the note is held for). The third (optional) argument of `triggerAttackRelease` is the time the attack should start. With no argument, the time will evaluate to "now" and play immediately. Passing in a time value let's you schedule the event in the future. +#### Tone.Synth -### Time +[Tone.Synth](https://tonejs.github.io/docs/#Synth) is a basic synthesizer with a single [oscillator](https://tonejs.github.io/docs/#OmniOscillator) and an [ADSR envelope](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope). -Any method which takes a time as a parameter will accept either a number or a string. Numbers will be taken literally as the time in seconds and strings can encode time expressions in terms of the current tempo. For example `"4n"` is a quarter-note, `"8t"` is an eighth-note triplet, and `"1m"` is one measure. Any value prefixed with `"+"` will be added to the current time. To trigger the same note one measure from now: +#### triggerAttackRelease -```javascript -synth.triggerAttackRelease("C4", "8n", "+1m"); -``` +The "attack" of an envelope is the period when the amplitude is rising, and the "release" is when it is falling back to 0. These two methods can be invoked separately as `triggerAttack` and `triggerRelease`, or combined as shown above. The first argument is the frequency which can either be a number (like `440`) or as "pitch-octave" notation (like `"D#2"`). The second argument is how long the note should be held before triggering the release phases. An optional third argument schedules the event for some time in the future. With no third argument, the note will play immediately. + +#### Time + +In the examples above, instead of using the time in seconds (for an 8th note at 120 BPM it would be 0.25 seconds), any method which takes time as an argument can accept a number or a string. Numbers will be taken literally as the time in seconds and strings can encode time expressions in terms of the current tempo. For example `"4n"` is a quarter-note, `"8t"` is an eighth-note triplet, and `"1m"` is one measure. [Read about Time encodings.](https://github.com/Tonejs/Tone.js/wiki/Time) +# Scheduling + ### Transport -Time expressions are evaluated against the Transport's BPM. [Tone.Transport](http://tonejs.org/docs/#Transport) is the master timekeeper, allowing for application-wide synchronization of sources, signals and events along a shared timeline. Callbacks scheduled with Tone.Transport will be invoked right before the scheduled time with the exact time of the event is passed in as the first parameter to the callback. - -```javascript -//schedule a callback on the second beat of the first measure -Tone.Transport.schedule(function(time){ - //schedule the synth's attackRelease using the passed-in time - synth.triggerAttackRelease("C4", "8n", time); -}, "1:2:0"); - -//start the transport -Tone.Transport.start(); -``` -[Read more about scheduling events with the Transport.](https://github.com/Tonejs/Tone.js/wiki/Transport) +[Tone.Transport](https://tonejs.github.io/docs/#Transport) is the master timekeeper, allowing for application-wide synchronization of sources, signals and events along a shared timeline. Time expressions (like the ones above) are evaluated against the Transport's BPM which can be set like this: `Tone.Transport.bpm.value = 120`. ### Loops -Instead of scheduling events directly on the Transport, Tone.js provides a few higher-level classes for working with events. [Tone.Loop](http://tonejs.org/docs/#Loop) is a simple way to create a looped callback that can be scheduled to start and stop. +Tone.js provides higher-level abstractions for scheduling events. [Tone.Loop](https://tonejs.github.io/docs/#Loop) is a simple way to create a looped callback that can be scheduled to start and stop. ```javascript //play a note every quarter-note var loop = new Tone.Loop(function(time){ synth.triggerAttackRelease("C2", "8n", time); }, "4n"); +``` + +Since Javascript timing is not sample-accurate, the precise time of the event is passed into the callback function. This time should be used to schedule events within the loop. +You can then start and stop the loop along the Transport's timeline. + +```javascript //loop between the first and fourth measures of the Transport's timeline loop.start("1m").stop("4m"); ``` -Start the Transport to hear the looped notes: +Then start the Transport to hear the loop: ```javascript Transport.start(); ``` -[Read about Tone.js' Event classes.](https://github.com/Tonejs/Tone.js/wiki/Events) +[Read about Tone.js' Event classes](https://github.com/Tonejs/Tone.js/wiki/Events) and [scheduling events with the Transport.](https://github.com/Tonejs/Tone.js/wiki/Transport) # Instruments -Tone has a number of instruments which all inherit from the same [Instrument base class](http://tonejs.org/docs/#Instrument), giving them a common API for playing notes. [Tone.MonoSynth](http://tonejs.org/docs/#MonoSynth) is composed of one oscillator, one filter, and two envelopes connected to the amplitude and the filter frequency. +Tone has a number of instruments which all inherit from the same [Instrument base class](https://tonejs.github.io/docs/#Instrument), giving them a common API for playing notes. [Tone.Synth](https://tonejs.github.io/docs/#Synth) is composed of one oscillator and an amplitude envelope. ```javascript //pass in some initial values for the filter and filter envelope -var monoSynth = new Tone.MonoSynth({ - "filter" : { - "type" : "lowpass", - "Q" : 7 +var synth = new Tone.Synth({ + "oscillator" : { + "type" : "pwm", + "modulationFrequency" : 0.2 }, - "filterEnvelope" : { + "envelope" : { "attack" : 0.02, "decay" : 0.1, "sustain" : 0.2, @@ -115,14 +121,14 @@ var monoSynth = new Tone.MonoSynth({ }).toMaster(); //start the note "D3" one second from now -monoSynth.triggerAttack("D3", "+1"); +synth.triggerAttack("D3", "+1"); ``` -All instruments are monophonic (one voice) but can be made polyphonic when the constructor is passed in as the second argument to [Tone.PolySynth](http://tonejs.org/docs/#PolySynth). +All instruments are monophonic (one voice) but can be made polyphonic when the constructor is passed in as the second argument to [Tone.PolySynth](https://tonejs.github.io/docs/#PolySynth). ```javascript -//a 4 voice MonoSynth -var polySynth = new Tone.PolySynth(4, Tone.MonoSynth).toMaster(); +//a 4 voice Synth +var polySynth = new Tone.PolySynth(4, Tone.Synth).toMaster(); //play a chord polySynth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "2n"); ``` @@ -131,7 +137,7 @@ polySynth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "2n"); # Effects -In the above examples, the synthesizer was always connected directly to the [master output](http://tonejs.org/docs/#Master), but the output of the synth could also be routed through one (or more) effects before going to the speakers. +In the above examples, the synthesizer was always connected directly to the [master output](https://tonejs.github.io/docs/#Master), but the output of the synth could also be routed through one (or more) effects before going to the speakers. ```javascript //create a distortion effect @@ -144,7 +150,7 @@ synth.connect(distortion); # Sources -Tone has a few basic audio sources like [Tone.Oscillator](http://tonejs.org/docs/#Oscillator) which has sine, square, triangle, and sawtooth waveforms, a buffer player ([Tone.Player](http://tonejs.org/docs/#Player)), a noise generator ([Tone.Noise]((http://tonejs.org/docs/#Noise))), two additional oscillator types ([pwm](http://tonejs.org/docs/#PWMOscillator), [pulse](http://tonejs.org/docs/#PulseOscillator)) and [external audio input](http://tonejs.org/docs/#Microphone) (when [WebRTC is supported](http://caniuse.com/#feat=stream)). +Tone has a few basic audio sources like [Tone.Oscillator](https://tonejs.github.io/docs/#Oscillator) which has sine, square, triangle, and sawtooth waveforms, a buffer player ([Tone.Player](https://tonejs.github.io/docs/#Player)), a noise generator ([Tone.Noise]((https://tonejs.github.io/docs/#Noise))), two additional oscillator types ([pwm](https://tonejs.github.io/docs/#PWMOscillator), [pulse](https://tonejs.github.io/docs/#PulseOscillator)) and [external audio input](https://tonejs.github.io/docs/#Microphone) (when [WebRTC is supported](http://caniuse.com/#feat=stream)). ```javascript //a pwm oscillator which is connected to the speaker and started right away @@ -165,16 +171,18 @@ Tone.js creates an AudioContext when it loads and shims it for maximum browser c # MIDI -To use MIDI files, you'll first need to convert them into a JSON format which Tone.js can understand using [MidiConvert](http://tonejs.github.io/MidiConvert/). +To use MIDI files, you'll first need to convert them into a JSON format which Tone.js can understand using [MidiConvert](https://tonejs.github.io/MidiConvert/). # Performance -Tone.js uses only one ScriptProcessorNode (in Tone.Meter). The rest of Tone's 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. +Tone.js makes extensive use of the native Web Audio Nodes such as the GainNode and WaveShaperNode for all signal processing, which enables Tone.js to work well on both desktop and mobile browsers. It uses no ScriptProcessorNodes. # Contributing There are many ways to contribute to Tone.js. Check out [this wiki](https://github.com/Tonejs/Tone.js/wiki/Contributing) if you're interested. +If you have questions (or answers) that are not necessarily bugs/issues, please post them to the [forum](https://groups.google.com/forum/#!forum/tonejs). + # References and Inspiration * [Tuna.js](https://github.com/Dinahmoe/tuna) diff --git a/Tone/component/Analyser.js b/Tone/component/Analyser.js index 38eb08e32..dda49d35d 100644 --- a/Tone/component/Analyser.js +++ b/Tone/component/Analyser.js @@ -7,20 +7,20 @@ define(["Tone/core/Tone"], function (Tone) { * [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode). * Extracts FFT or Waveform data from the incoming signal. * @extends {Tone} + * @param {String=} type The return type of the analysis, either "fft", or "waveform". * @param {Number=} size The size of the FFT. Value must be a power of * two in the range 32 to 32768. - * @param {String=} type The return type of the analysis, either "fft", or "waveform". */ Tone.Analyser = function(){ - var options = this.optionsObject(arguments, ["size", "type"], Tone.Analyser.defaults); + var options = this.optionsObject(arguments, ["type", "size"], Tone.Analyser.defaults); /** * The analyser node. * @private * @type {AnalyserNode} */ - this._analyser = this.input = this.context.createAnalyser(); + this._analyser = this.input = this.output = this.context.createAnalyser(); /** * The analysis type @@ -59,7 +59,7 @@ define(["Tone/core/Tone"], function (Tone) { * @const */ Tone.Analyser.defaults = { - "size" : 2048, + "size" : 1024, "returnType" : "byte", "type" : "fft", "smoothing" : 0.8, @@ -68,7 +68,7 @@ define(["Tone/core/Tone"], function (Tone) { }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse() * @enum {String} */ Tone.Analyser.Type = { @@ -77,7 +77,10 @@ define(["Tone/core/Tone"], function (Tone) { }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse(). + * byte values are between [0,255]. float values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @enum {String} */ Tone.Analyser.ReturnType = { @@ -101,7 +104,17 @@ define(["Tone/core/Tone"], function (Tone) { if (this._returnType === Tone.Analyser.ReturnType.Byte){ this._analyser.getByteTimeDomainData(this._buffer); } else { - this._analyser.getFloatTimeDomainData(this._buffer); + if (this.isFunction(AnalyserNode.prototype.getFloatTimeDomainData)){ + this._analyser.getFloatTimeDomainData(this._buffer); + } else { + var uint8 = new Uint8Array(this._buffer.length); + this._analyser.getByteTimeDomainData(uint8); + //referenced https://github.com/mohayonao/get-float-time-domain-data + // POLYFILL + for (var i = 0; i < uint8.length; i++){ + this._buffer[i] = (uint8[i] - 128) * 0.0078125; + } + } } } return this._buffer; @@ -124,9 +137,11 @@ define(["Tone/core/Tone"], function (Tone) { }); /** - * The return type of Tone.Analyser.value, either "byte" or "float". + * The return type of Tone.Analyser.analyse(), either "byte" or "float". * When the type is set to "byte" the range of values returned in the array - * are between 0-255, when set to "float" the values are between 0-1. + * are between 0-255. "float" values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -141,14 +156,14 @@ define(["Tone/core/Tone"], function (Tone) { } else if (type === Tone.Analyser.ReturnType.Float){ this._buffer = new Float32Array(this._analyser.frequencyBinCount); } else { - throw new Error("Invalid Return Type: "+type); + throw new TypeError("Tone.Analayser: invalid return type: "+type); } this._returnType = type; } }); /** - * The analysis function returned by Tone.Analyser.value, either "fft" or "waveform". + * The analysis function returned by Tone.Analyser.analyse(), either "fft" or "waveform". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -159,7 +174,7 @@ define(["Tone/core/Tone"], function (Tone) { }, set : function(type){ if (type !== Tone.Analyser.Type.Waveform && type !== Tone.Analyser.Type.FFT){ - throw new Error("Invalid Type: "+type); + throw new TypeError("Tone.Analyser: invalid type: "+type); } this._type = type; } diff --git a/Tone/component/Envelope.js b/Tone/component/Envelope.js index 21726456f..88c12eb0c 100644 --- a/Tone/component/Envelope.js +++ b/Tone/component/Envelope.js @@ -1,5 +1,5 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", - "Tone/signal/Pow", "Tone/core/Type"], function(Tone){ + "Tone/signal/Pow", "Tone/type/Type"], function(Tone){ "use strict"; @@ -69,21 +69,14 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", * @type {number} * @private */ - this._attackCurve = Tone.Envelope.Type.Linear; + this._attackCurve = "linear"; /** * the next time the envelope is at standby * @type {number} * @private */ - this._releaseCurve = Tone.Envelope.Type.Exponential; - - /** - * the minimum output value - * @type {number} - * @private - */ - this._minOutput = 0.00001; + this._releaseCurve = "exponential"; /** * the signal @@ -91,7 +84,7 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", * @private */ this._sig = this.output = new Tone.TimelineSignal(); - this._sig.setValueAtTime(this._minOutput, 0); + this._sig.setValueAtTime(0, 0); //set the attackCurve initially this.attackCurve = options.attackCurve; @@ -114,13 +107,6 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "releaseCurve" : "exponential", }; - /** - * the envelope time multipler - * @type {number} - * @private - */ - Tone.Envelope.prototype._timeMult = 0.25; - /** * Read the current value of the envelope. Useful for * syncronizing visual output to the envelope. @@ -131,50 +117,102 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", */ Object.defineProperty(Tone.Envelope.prototype, "value", { get : function(){ - return this._sig.value; + return this.getValueAtTime(this.now()); } }); /** - * The slope of the attack. Either "linear" or "exponential". + * The shape of the attack. + * Can be any of these strings: + * + * Can also be an array which describes the curve. Values + * in the array are evenly subdivided and linearly + * interpolated over the duration of the attack. * @memberOf Tone.Envelope# - * @type {string} + * @type {String|Array} * @name attackCurve * @example * env.attackCurve = "linear"; + * @example + * //can also be an array + * env.attackCurve = [0, 0.2, 0.3, 0.4, 1] */ Object.defineProperty(Tone.Envelope.prototype, "attackCurve", { get : function(){ - return this._attackCurve; + if (this.isString(this._attackCurve)){ + return this._attackCurve; + } else if (this.isArray(this._attackCurve)){ + //look up the name in the curves array + for (var type in Tone.Envelope.Type){ + if (Tone.Envelope.Type[type].In === this._attackCurve){ + return type; + } + } + //otherwise just return the array + return this._attackCurve; + } }, - set : function(type){ - if (type === Tone.Envelope.Type.Linear || - type === Tone.Envelope.Type.Exponential){ - this._attackCurve = type; + set : function(curve){ + //check if it's a valid type + if (Tone.Envelope.Type.hasOwnProperty(curve)){ + var curveDef = Tone.Envelope.Type[curve]; + if (this.isObject(curveDef)){ + this._attackCurve = curveDef.In; + } else { + this._attackCurve = curveDef; + } + } else if (this.isArray(curve)){ + this._attackCurve = curve; } else { - throw Error("attackCurve must be either \"linear\" or \"exponential\". Invalid type: ", type); + throw new Error("Tone.Envelope: invalid curve: " + curve); } } }); /** - * The slope of the Release. Either "linear" or "exponential". + * The shape of the release. See the attack curve types. * @memberOf Tone.Envelope# - * @type {string} + * @type {String|Array} * @name releaseCurve * @example * env.releaseCurve = "linear"; */ Object.defineProperty(Tone.Envelope.prototype, "releaseCurve", { get : function(){ - return this._releaseCurve; + if (this.isString(this._releaseCurve)){ + return this._releaseCurve; + } else if (this.isArray(this._releaseCurve)){ + //look up the name in the curves array + for (var type in Tone.Envelope.Type){ + if (Tone.Envelope.Type[type].Out === this._releaseCurve){ + return type; + } + } + //otherwise just return the array + return this._releaseCurve; + } }, - set : function(type){ - if (type === Tone.Envelope.Type.Linear || - type === Tone.Envelope.Type.Exponential){ - this._releaseCurve = type; + set : function(curve){ + //check if it's a valid type + if (Tone.Envelope.Type.hasOwnProperty(curve)){ + var curveDef = Tone.Envelope.Type[curve]; + if (this.isObject(curveDef)){ + this._releaseCurve = curveDef.Out; + } else { + this._releaseCurve = curveDef; + } + } else if (this.isArray(curve)){ + this._releaseCurve = curve; } else { - throw Error("releaseCurve must be either \"linear\" or \"exponential\". Invalid type: ", type); + throw new Error("Tone.Envelope: invalid curve: " + curve); } } }); @@ -193,18 +231,39 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", //to seconds var now = this.now() + this.blockTime; time = this.toSeconds(time, now); - var attack = this.toSeconds(this.attack) + time; + var originalAttack = this.toSeconds(this.attack); + var attack = originalAttack; var decay = this.toSeconds(this.decay); velocity = this.defaultArg(velocity, 1); + //check if it's not a complete attack + var currentValue = this.getValueAtTime(time); + if (currentValue > 0){ + //subtract the current value from the attack time + var attackRate = 1 / attack; + var remainingDistance = 1 - currentValue; + //the attack is now the remaining time + attack = remainingDistance / attackRate; + } //attack - if (this._attackCurve === Tone.Envelope.Type.Linear){ - this._sig.linearRampToValueBetween(velocity, time, attack); - } else { - this._sig.exponentialRampToValueBetween(velocity, time, attack); + if (this._attackCurve === "linear"){ + this._sig.linearRampToValue(velocity, attack, time); + } else if (this._attackCurve === "exponential"){ + this._sig.exponentialRampToValue(velocity, attack, time); + } else if (attack > 0){ + this._sig.setRampPoint(time); + var curve = this._attackCurve; + //take only a portion of the curve + if (attack < originalAttack){ + var percentComplete = 1 - attack / originalAttack; + var sliceIndex = Math.floor(percentComplete * this._attackCurve.length); + curve = this._attackCurve.slice(sliceIndex); + //the first index is the current value + curve[0] = currentValue; + } + this._sig.setValueCurveAtTime(curve, time, attack, velocity); } //decay - this._sig.setValueAtTime(velocity, attack); - this._sig.exponentialRampToValueAtTime(this.sustain * velocity, attack + decay); + this._sig.exponentialRampToValue(velocity * this.sustain, decay, attack + time); return this; }; @@ -219,15 +278,34 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", Tone.Envelope.prototype.triggerRelease = function(time){ var now = this.now() + this.blockTime; time = this.toSeconds(time, now); - var release = this.toSeconds(this.release); - if (this._releaseCurve === Tone.Envelope.Type.Linear){ - this._sig.linearRampToValueBetween(this._minOutput, time, time + release); - } else { - this._sig.exponentialRampToValueBetween(this._minOutput, time, release + time); + var currentValue = this.getValueAtTime(time); + if (currentValue > 0){ + var release = this.toSeconds(this.release); + if (this._releaseCurve === "linear"){ + this._sig.linearRampToValue(0, release, time); + } else if (this._releaseCurve === "exponential"){ + this._sig.exponentialRampToValue(0, release, time); + } else{ + var curve = this._releaseCurve; + if (this.isArray(curve)){ + this._sig.setRampPoint(time); + this._sig.setValueCurveAtTime(curve, time, release, currentValue); + } + } } return this; }; + /** + * Get the scheduled value at the given time. This will + * return the unconverted (raw) value. + * @param {Number} time The time in seconds. + * @return {Number} The scheduled value at the given time. + */ + Tone.Envelope.prototype.getValueAtTime = function(time){ + return this._sig.getValueAtTime(time); + }; + /** * triggerAttackRelease is shorthand for triggerAttack, then waiting * some duration, then triggerRelease. @@ -246,6 +324,16 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", return this; }; + /** + * Cancels all scheduled envelope changes after the given time. + * @param {Time} after + * @returns {Tone.Envelope} this + */ + Tone.Envelope.prototype.cancel = function (after) { + this._sig.cancelScheduledValues(after); + return this; + }; + /** * Borrows the connect method from Tone.Signal. * @function @@ -253,6 +341,106 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", */ Tone.Envelope.prototype.connect = Tone.Signal.prototype.connect; + /** + * Generate some complex envelope curves. + */ + (function _createCurves(){ + + var curveLen = 128; + + var i, k; + + //cosine curve + var cosineCurve = []; + for (i = 0; i < curveLen; i++){ + cosineCurve[i] = Math.sin((i / (curveLen - 1)) * (Math.PI / 2)); + } + + //ripple curve + var rippleCurve = []; + var rippleCurveFreq = 6.4; + for (i = 0; i < curveLen - 1; i++){ + k = (i / (curveLen - 1)); + var sineWave = Math.sin(k * (Math.PI * 2) * rippleCurveFreq - Math.PI / 2) + 1; + rippleCurve[i] = sineWave/10 + k * 0.83; + } + rippleCurve[curveLen - 1] = 1; + + //stairs curve + var stairsCurve = []; + var steps = 5; + for (i = 0; i < curveLen; i++){ + stairsCurve[i] = Math.ceil((i / (curveLen - 1)) * steps) / steps; + } + + //in-out easing curve + var sineCurve = []; + for (i = 0; i < curveLen; i++){ + k = i / (curveLen - 1); + sineCurve[i] = 0.5 * (1 - Math.cos(Math.PI * k)); + } + + //a bounce curve + var bounceCurve = []; + for (i = 0; i < curveLen; i++){ + k = i / (curveLen - 1); + var freq = Math.pow(k, 3) * 4 + 0.2; + var val = Math.cos(freq * Math.PI * 2 * k); + bounceCurve[i] = Math.abs(val * (1 - k)); + } + + /** + * Invert a value curve to make it work for the release + * @private + */ + function invertCurve(curve){ + var out = new Array(curve.length); + for (var j = 0; j < curve.length; j++){ + out[j] = 1 - curve[j]; + } + return out; + } + + /** + * reverse the curve + * @private + */ + function reverseCurve(curve){ + return curve.slice(0).reverse(); + } + + /** + * attack and release curve arrays + * @type {Object} + * @private + */ + Tone.Envelope.Type = { + "linear" : "linear", + "exponential" : "exponential", + "bounce" : { + In : invertCurve(bounceCurve), + Out : bounceCurve + }, + "cosine" : { + In : cosineCurve, + Out : reverseCurve(cosineCurve) + }, + "step" : { + In : stairsCurve, + Out : invertCurve(stairsCurve) + }, + "ripple" : { + In : rippleCurve, + Out : invertCurve(rippleCurve) + }, + "sine" : { + In : sineCurve, + Out : invertCurve(sineCurve) + } + }; + + })(); + /** * Disconnect and dispose. * @returns {Tone.Envelope} this @@ -261,29 +449,11 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", Tone.prototype.dispose.call(this); this._sig.dispose(); this._sig = null; + this._attackCurve = null; + this._releaseCurve = null; return this; }; - /** - * The phase of the envelope. - * @enum {string} - */ - Tone.Envelope.Phase = { - Attack : "attack", - Decay : "decay", - Sustain : "sustain", - Release : "release", - Standby : "standby", - }; - - /** - * The phase of the envelope. - * @enum {string} - */ - Tone.Envelope.Type = { - Linear : "linear", - Exponential : "exponential", - }; return Tone.Envelope; }); diff --git a/Tone/component/Filter.js b/Tone/component/Filter.js index 5783f8fa2..f1c5e8768 100644 --- a/Tone/component/Filter.js +++ b/Tone/component/Filter.js @@ -109,7 +109,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ set : function(type){ var types = ["lowpass", "highpass", "bandpass", "lowshelf", "highshelf", "notch", "allpass", "peaking"]; if (types.indexOf(type)=== -1){ - throw new Error("Tone.Filter does not have filter type "+type); + throw new TypeError("Tone.Filter: invalid type "+type); } this._type = type; for (var i = 0; i < this._filters.length; i++){ @@ -136,7 +136,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ var cascadingCount = possibilities.indexOf(rolloff); //check the rolloff is valid if (cascadingCount === -1){ - throw new Error("Filter rolloff can only be -12, -24, -48 or -96"); + throw new RangeError("Tone.Filter: rolloff can only be -12, -24, -48 or -96"); } cascadingCount += 1; this._rolloff = rolloff; diff --git a/Tone/component/Follower.js b/Tone/component/Follower.js index 1c02c45d7..c6973d01f 100644 --- a/Tone/component/Follower.js +++ b/Tone/component/Follower.js @@ -1,5 +1,5 @@ define(["Tone/core/Tone", "Tone/signal/Abs", "Tone/signal/Subtract", - "Tone/signal/Multiply", "Tone/signal/Signal", "Tone/signal/WaveShaper", "Tone/core/Type"], + "Tone/signal/Multiply", "Tone/signal/Signal", "Tone/signal/WaveShaper", "Tone/type/Type"], function(Tone){ "use strict"; @@ -108,8 +108,8 @@ function(Tone){ */ Tone.Follower.prototype._setAttackRelease = function(attack, release){ var minTime = this.blockTime; - attack = this.secondsToFrequency(this.toSeconds(attack)); - release = this.secondsToFrequency(this.toSeconds(release)); + attack = Tone.Time(attack).toFrequency(); + release = Tone.Time(release).toFrequency(); attack = Math.max(attack, minTime); release = Math.max(release, minTime); this._frequencyValues.setMap(function(val){ diff --git a/Tone/component/LFO.js b/Tone/component/LFO.js index c3f90f82b..d5cc933b9 100644 --- a/Tone/component/LFO.js +++ b/Tone/component/LFO.js @@ -1,5 +1,5 @@ define(["Tone/core/Tone", "Tone/source/Oscillator", "Tone/signal/Scale", - "Tone/signal/Signal", "Tone/signal/AudioToGain", "Tone/core/Type"], + "Tone/signal/Signal", "Tone/signal/AudioToGain", "Tone/type/Type", "Tone/signal/Zero"], function(Tone){ "use strict"; @@ -60,6 +60,13 @@ function(Tone){ */ this._stoppedSignal = new Tone.Signal(0, Tone.Type.AudioRange); + /** + * Just outputs zeros. + * @type {Tone.Zero} + * @private + */ + this._zeros = new Tone.Zero(); + /** * The value that the LFO outputs when it's stopped * @type {AudioRange} @@ -89,6 +96,7 @@ function(Tone){ //connect it up this._oscillator.chain(this._a2g, this._scaler); + this._zeros.connect(this._a2g); this._stoppedSignal.connect(this._a2g); this._readOnly(["amplitude", "frequency"]); this.phase = options.phase; @@ -252,6 +260,21 @@ function(Tone){ } }); + /** + * Mute the output. + * @memberOf Tone.LFO# + * @type {Boolean} + * @name mute + */ + Object.defineProperty(Tone.LFO.prototype, "mute", { + get : function(){ + return this._oscillator.mute; + }, + set : function(mute){ + this._oscillator.mute = mute; + } + }); + /** * Returns the playback state of the source, either "started" or "stopped". * @type {Tone.State} @@ -311,6 +334,8 @@ function(Tone){ this._oscillator = null; this._stoppedSignal.dispose(); this._stoppedSignal = null; + this._zeros.dispose(); + this._zeros = null; this._scaler.dispose(); this._scaler = null; this._a2g.dispose(); diff --git a/Tone/component/Meter.js b/Tone/component/Meter.js index 85a4e1bed..e435d30ef 100644 --- a/Tone/component/Meter.js +++ b/Tone/component/Meter.js @@ -1,110 +1,71 @@ -define(["Tone/core/Tone"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/Analyser"], function(Tone){ "use strict"; /** * @class Tone.Meter gets the [RMS](https://en.wikipedia.org/wiki/Root_mean_square) - * of an input signal with some averaging applied. - * It can also get the raw value of the signal or the value in dB. For signal - * processing, it's better to use Tone.Follower which will produce an audio-rate - * envelope follower instead of needing to poll the Meter to get the output. - *

- * Meter was inspired by [Chris Wilsons Volume Meter](https://github.com/cwilso/volume-meter/blob/master/volume-meter.js). + * of an input signal with some averaging applied. It can also get the raw + * value of the input signal. * * @constructor * @extends {Tone} - * @param {number} [channels=1] number of channels being metered - * @param {number} [smoothing=0.8] amount of smoothing applied to the volume - * @param {number} [clipMemory=0.5] number in seconds that a "clip" should be remembered + * @param {String} type Either "level" or "signal". + * @param {Number} smoothing The amount of smoothing applied between frames. * @example * var meter = new Tone.Meter(); * var mic = new Tone.Microphone().start(); * //connect mic to the meter * mic.connect(meter); - * //use getLevel or getDb - * //to access meter level - * meter.getLevel(); + * //the current level of the mic input + * var level = meter.value; */ Tone.Meter = function(){ - var options = this.optionsObject(arguments, ["channels", "smoothing"], Tone.Meter.defaults); - //extends Unit - Tone.call(this); - - /** - * The channel count - * @type {number} - * @private - */ - this._channels = options.channels; - + var options = this.optionsObject(arguments, ["type", "smoothing"], Tone.Meter.defaults); + /** - * The amount which the decays of the meter are smoothed. Small values - * will follow the contours of the incoming envelope more closely than large values. - * @type {NormalRange} - */ - this.smoothing = options.smoothing; - - /** - * The amount of time a clip is remember for. - * @type {Time} - */ - this.clipMemory = options.clipMemory; - - /** - * The value above which the signal is considered clipped. - * @type {Number} - */ - this.clipLevel = options.clipLevel; - - /** - * the rms for each of the channels - * @private - * @type {Array} + * The type of the meter, either "level" or "signal". + * A "level" meter will return the volume level (rms) of the + * input signal and a "signal" meter will return + * the signal value of the input. + * @type {String} */ - this._volume = new Array(this._channels); + this.type = options.type; - /** - * the raw values for each of the channels + /** + * The analyser node which computes the levels. * @private - * @type {Array} + * @type {Tone.Analyser} */ - this._values = new Array(this._channels); + this.input = this.output = this._analyser = new Tone.Analyser("waveform", 512); + this._analyser.returnType = "float"; - //zero out the volume array - for (var i = 0; i < this._channels; i++){ - this._volume[i] = 0; - this._values[i] = 0; - } - - /** - * last time the values clipped - * @private - * @type {Array} + /** + * The amount of carryover between the current and last frame. + * Only applied meter for "level" type. + * @type {Number} */ - this._lastClip = new Array(this._channels); + this.smoothing = options.smoothing; - //zero out the clip array - for (var j = 0; j < this._lastClip.length; j++){ - this._lastClip[j] = 0; - } - - /** + /** + * The last computed value + * @type {Number} * @private - * @type {ScriptProcessorNode} */ - this._jsNode = this.context.createScriptProcessor(options.bufferSize, this._channels, 1); - this._jsNode.onaudioprocess = this._onprocess.bind(this); - //so it doesn't get garbage collected - this._jsNode.noGC(); - - //signal just passes - this.input.connect(this.output); - this.input.connect(this._jsNode); + this._lastValue = 0; }; Tone.extend(Tone.Meter); + /** + * @private + * @enum {String} + */ + Tone.Meter.Type = { + Level : "level", + Signal : "signal" + }; + /** * The defaults * @type {Object} @@ -113,82 +74,40 @@ define(["Tone/core/Tone"], function(Tone){ */ Tone.Meter.defaults = { "smoothing" : 0.8, - "bufferSize" : 1024, - "clipMemory" : 0.5, - "clipLevel" : 0.9, - "channels" : 1 + "type" : Tone.Meter.Type.Level }; /** - * called on each processing frame - * @private - * @param {AudioProcessingEvent} event + * The current value of the meter. A value of 1 is + * "unity". + * @memberOf Tone.Meter# + * @type {Number} + * @name value + * @readOnly */ - Tone.Meter.prototype._onprocess = function(event){ - var bufferSize = this._jsNode.bufferSize; - var smoothing = this.smoothing; - for (var channel = 0; channel < this._channels; channel++){ - var input = event.inputBuffer.getChannelData(channel); - var sum = 0; - var total = 0; - var x; - for (var i = 0; i < bufferSize; i++){ - x = input[i]; - total += x; - sum += x * x; - } - var average = total / bufferSize; - var rms = Math.sqrt(sum / bufferSize); - if (rms > 0.9){ - this._lastClip[channel] = Date.now(); + Object.defineProperty(Tone.Meter.prototype, "value", { + get : function(){ + var signal = this._analyser.analyse(); + if (this.type === Tone.Meter.Type.Level){ + //rms + var sum = 0; + for (var i = 0; i < signal.length; i++){ + sum += Math.pow(signal[i], 2); + } + var rms = Math.sqrt(sum / signal.length); + //smooth it + rms = Math.max(rms, this._lastValue * this.smoothing); + this._lastValue = rms; + //scale it + var unity = 0.35; + var val = rms / unity; + //scale the output curve + return Math.sqrt(val); + } else { + return signal[0]; } - this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing); - this._values[channel] = average; - } - }; - - /** - * Get the rms of the signal. - * @param {number} [channel=0] which channel - * @return {number} the value - */ - Tone.Meter.prototype.getLevel = function(channel){ - channel = this.defaultArg(channel, 0); - var vol = this._volume[channel]; - if (vol < 0.00001){ - return 0; - } else { - return vol; - } - }; - - /** - * Get the raw value of the signal. - * @param {number=} channel - * @return {number} - */ - Tone.Meter.prototype.getValue = function(channel){ - channel = this.defaultArg(channel, 0); - return this._values[channel]; - }; - - /** - * Get the volume of the signal in dB - * @param {number=} channel - * @return {Decibels} - */ - Tone.Meter.prototype.getDb = function(channel){ - return this.gainToDb(this.getLevel(channel)); - }; - - /** - * @returns {boolean} if the audio has clipped. The value resets - * based on the clipMemory defined. - */ - Tone.Meter.prototype.isClipped = function(channel){ - channel = this.defaultArg(channel, 0); - return Date.now() - this._lastClip[channel] < this._clipMemory * 1000; - }; + }, + }); /** * Clean up. @@ -196,12 +115,8 @@ define(["Tone/core/Tone"], function(Tone){ */ Tone.Meter.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._jsNode.disconnect(); - this._jsNode.onaudioprocess = null; - this._jsNode = null; - this._volume = null; - this._values = null; - this._lastClip = null; + this._analyser.dispose(); + this._analyser = null; return this; }; diff --git a/Tone/component/Panner.js b/Tone/component/Panner.js index f6d2f52a8..34b838109 100644 --- a/Tone/component/Panner.js +++ b/Tone/component/Panner.js @@ -1,16 +1,16 @@ -define(["Tone/core/Tone", "Tone/component/CrossFade", "Tone/component/Merge", - "Tone/component/Split", "Tone/signal/Signal", "Tone/signal/GainToAudio"], +define(["Tone/core/Tone", "Tone/component/CrossFade", "Tone/component/Merge", "Tone/component/Split", + "Tone/signal/Signal", "Tone/signal/AudioToGain", "Tone/signal/Zero"], function(Tone){ "use strict"; /** * @class Tone.Panner is an equal power Left/Right Panner and does not - * support 3D. Panner uses the StereoPannerNode when available. + * support 3D. Panner uses the StereoPannerNode when available. * * @constructor * @extends {Tone} - * @param {NormalRange} [initialPan=0.5] The initail panner value (defaults to 0.5 = center) + * @param {NormalRange} [initialPan=0] The initail panner value (defaults to 0 = center) * @example * //pan the input signal hard right. * var panner = new Tone.Panner(1); @@ -19,13 +19,6 @@ function(Tone){ Tone.call(this); - /** - * indicates if the panner is using the new StereoPannerNode internally - * @type {boolean} - * @private - */ - this._hasStereoPanner = this.isFunction(this.context.createStereoPanner); - if (this._hasStereoPanner){ /** @@ -36,21 +29,11 @@ function(Tone){ this._panner = this.input = this.output = this.context.createStereoPanner(); /** - * The pan control. 0 = hard left, 1 = hard right. + * The pan control. -1 = hard left, 1 = hard right. * @type {NormalRange} * @signal */ - this.pan = new Tone.Signal(0, Tone.Type.NormalRange); - - /** - * scale the pan signal to between -1 and 1 - * @type {Tone.WaveShaper} - * @private - */ - this._scalePan = new Tone.GainToAudio(); - - //connections - this.pan.chain(this._scalePan, this._panner.pan); + this.pan = this._panner.pan; } else { @@ -74,13 +57,29 @@ function(Tone){ this._splitter = this.input = new Tone.Split(); /** - * The pan control. 0 = hard left, 1 = hard right. - * @type {NormalRange} + * The pan control. -1 = hard left, 1 = hard right. + * @type {AudioRange} * @signal */ - this.pan = this._crossFade.fade; + this.pan = new Tone.Signal(0, Tone.Type.AudioRange); + + /** + * always sends 0 + * @type {Tone.Zero} + * @private + */ + this._zero = new Tone.Zero(); + + /** + * The analog to gain conversion + * @type {Tone.AudioToGain} + * @private + */ + this._a2g = new Tone.AudioToGain(); //CONNECTIONS: + this._zero.connect(this._a2g); + this.pan.chain(this._a2g, this._crossFade.fade); //left channel is a, right channel is b this._splitter.connect(this._crossFade, 0, 0); this._splitter.connect(this._crossFade, 1, 1); @@ -88,14 +87,20 @@ function(Tone){ 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.pan.value = this.defaultArg(initialPan, 0); this._readOnly("pan"); }; Tone.extend(Tone.Panner); + /** + * indicates if the panner is using the new StereoPannerNode internally + * @type {boolean} + * @private + */ + Tone.Panner.prototype._hasStereoPanner = Tone.prototype.isFunction(Tone.context.createStereoPanner); + /** * Clean up. * @returns {Tone.Panner} this @@ -106,18 +111,20 @@ function(Tone){ if (this._hasStereoPanner){ this._panner.disconnect(); this._panner = null; - this.pan.dispose(); this.pan = null; - this._scalePan.dispose(); - this._scalePan = null; } else { + this._zero.dispose(); + this._zero = null; this._crossFade.dispose(); this._crossFade = null; this._splitter.dispose(); this._splitter = null; this._merger.dispose(); this._merger = null; + this.pan.dispose(); this.pan = null; + this._a2g.dispose(); + this._a2g = null; } return this; }; diff --git a/Tone/component/Volume.js b/Tone/component/Volume.js index 886fe583f..3e421c9ed 100644 --- a/Tone/component/Volume.js +++ b/Tone/component/Volume.js @@ -23,6 +23,20 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Gain"], function(Tone */ this.output = this.input = new Tone.Gain(options.volume, Tone.Type.Decibels); + /** + * The unmuted volume + * @type {Decibels} + * @private + */ + this._unmutedVolume = 0; + + /** + * if the volume is muted + * @type {Boolean} + * @private + */ + this._muted = false; + /** * The volume control in decibels. * @type {Decibels} @@ -31,6 +45,9 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Gain"], function(Tone this.volume = this.output.gain; this._readOnly("volume"); + + //set the mute initially + this.mute = options.mute; }; Tone.extend(Tone.Volume); @@ -42,9 +59,35 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Gain"], function(Tone * @static */ Tone.Volume.defaults = { - "volume" : 0 + "volume" : 0, + "mute" : false }; + /** + * Mute the output. + * @memberOf Tone.Volume# + * @type {boolean} + * @name mute + * @example + * //mute the output + * volume.mute = true; + */ + Object.defineProperty(Tone.Volume.prototype, "mute", { + get : function(){ + return this._muted; + }, + set : function(mute){ + if (!this._muted && mute){ + this._unmutedVolume = this.volume.value; + //maybe it should ramp here? + this.volume.value = -Infinity; + } else if (this._muted && !mute){ + this.volume.value = this._unmutedVolume; + } + this._muted = mute; + } + }); + /** * clean up * @returns {Tone.Volume} this diff --git a/Tone/control/CtrlInterpolate.js b/Tone/control/CtrlInterpolate.js index ef2886ff1..cc9adaeec 100644 --- a/Tone/control/CtrlInterpolate.js +++ b/Tone/control/CtrlInterpolate.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/type/Type"], function (Tone) { "use strict"; @@ -15,8 +15,8 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { * * @example * var interp = new Tone.CtrlInterpolate([ - * ["C3", "G4", "E5"], - * ["D4", "F#4", "E5"], + * [2, 4, 5], + * [9, 3, 2], * ]); * @param {Array} values The array of values to interpolate over * @param {Positive} index The initial interpolation index. @@ -113,8 +113,6 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { Tone.CtrlInterpolate.prototype._toNumber = function(val){ if (this.isNumber(val)){ return val; - } else if (this.isNote(val)){ - return this.toFrequency(val); } else { //otherwise assume that it's Time... return this.toSeconds(val); diff --git a/Tone/control/CtrlRandom.js b/Tone/control/CtrlRandom.js index 04a45091c..dd05aac56 100644 --- a/Tone/control/CtrlRandom.js +++ b/Tone/control/CtrlRandom.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/type/Type"], function (Tone) { "use strict"; diff --git a/Tone/core/Buffer.js b/Tone/core/Buffer.js index d27b05cfe..22a26da1c 100644 --- a/Tone/core/Buffer.js +++ b/Tone/core/Buffer.js @@ -317,6 +317,13 @@ define(["Tone/core/Tone", "Tone/core/Emitter"], function(Tone){ Tone.Buffer.trigger("progress", completed / Tone.Buffer._totalDownloads); }; + /** + * A path which is prefixed before every url. + * @type {String} + * @static + */ + Tone.Buffer.baseUrl = ""; + /** * Makes an xhr reqest for the selected url then decodes * the file as an audio buffer. Invokes @@ -329,15 +336,14 @@ define(["Tone/core/Tone", "Tone/core/Emitter"], function(Tone){ */ Tone.Buffer.load = function(url, callback){ var request = new XMLHttpRequest(); - request.open("GET", url, true); + request.open("GET", Tone.Buffer.baseUrl + 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); + }, function(){ + throw new Error("Tone.Buffer: could not decode audio data:" + url); }); }; //send the request @@ -346,28 +352,20 @@ define(["Tone/core/Tone", "Tone/core/Emitter"], function(Tone){ }; /** - * @deprecated us on([event]) instead + * Checks a url's extension to see if the current browser can play that file type. + * @param {String} url The url/extension to test + * @return {Boolean} If the file extension can be played + * @static + * @example + * Tone.Buffer.supportsType("wav"); //returns true + * Tone.Buffer.supportsType("path/to/file.wav"); //returns true */ - Object.defineProperty(Tone.Buffer, "onload", { - set : function(cb){ - console.warn("Tone.Buffer.onload is deprecated, use Tone.Buffer.on('load', callback)"); - Tone.Buffer.on("load", cb); - } - }); - - Object.defineProperty(Tone.Buffer, "onprogress", { - set : function(cb){ - console.warn("Tone.Buffer.onprogress is deprecated, use Tone.Buffer.on('progress', callback)"); - Tone.Buffer.on("progress", cb); - } - }); - - Object.defineProperty(Tone.Buffer, "onerror", { - set : function(cb){ - console.warn("Tone.Buffer.onerror is deprecated, use Tone.Buffer.on('error', callback)"); - Tone.Buffer.on("error", cb); - } - }); + Tone.Buffer.supportsType = function(url){ + var extension = url.split("."); + extension = extension[extension.length - 1]; + var response = document.createElement("audio").canPlayType("audio/"+extension); + return response !== ""; + }; return Tone.Buffer; }); \ No newline at end of file diff --git a/Tone/core/Buffers.js b/Tone/core/Buffers.js new file mode 100644 index 000000000..9f79720f8 --- /dev/null +++ b/Tone/core/Buffers.js @@ -0,0 +1,140 @@ +define(["Tone/core/Tone", "Tone/core/Buffer"], function (Tone) { + + /** + * @class A data structure for holding multiple buffers. + * + * @param {Object|Array} urls An object literal or array + * of urls to load. + * @param {Function=} callback The callback to invoke when + * the buffers are loaded. + * @extends {Tone} + * @example + * //load a whole bank of piano samples + * var pianoSamples = new Tone.Buffers({ + * "C4" : "path/to/C4.mp3" + * "C#4" : "path/to/C#4.mp3" + * "D4" : "path/to/D4.mp3" + * "D#4" : "path/to/D#4.mp3" + * ... + * }, function(){ + * //play one of the samples when they all load + * player.buffer = pianoSamples.get("C4"); + * player.start(); + * }); + * + */ + Tone.Buffers = function(urls, onload, baseUrl){ + + /** + * All of the buffers + * @type {Object} + * @private + */ + this._buffers = {}; + + /** + * A path which is prefixed before every url. + * @type {String} + */ + this.baseUrl = this.defaultArg(baseUrl, ""); + + urls = this._flattenUrls(urls); + this._loadingCount = 0; + //add each one + for (var key in urls){ + this._loadingCount++; + this.add(key, urls[key], this._bufferLoaded.bind(this, onload)); + } + }; + + Tone.extend(Tone.Buffers); + + /** + * Get a buffer by name. If an array was loaded, + * then use the array index. + * @param {String|Number} name The key or index of the + * buffer. + * @return {Tone.Buffer} + */ + Tone.Buffers.prototype.get = function(name){ + if (this._buffers.hasOwnProperty(name)){ + return this._buffers[name]; + } else { + throw new Error("Tone.Buffers: no buffer named"+name); + } + }; + + /** + * A buffer was loaded. decrement the counter. + * @param {Function} callback + * @private + */ + Tone.Buffers.prototype._bufferLoaded = function(callback){ + this._loadingCount--; + if (this._loadingCount === 0 && callback){ + callback(this); + } + }; + + /** + * Add a buffer by name and url to the Buffers + * @param {String} name A unique name to give + * the buffer + * @param {String|Tone.Buffer|Audiobuffer} url Either the url of the bufer, + * or a buffer which will be added + * with the given name. + * @param {Function=} callback The callback to invoke + * when the url is loaded. + */ + Tone.Buffers.prototype.add = function(name, url, callback){ + callback = this.defaultArg(callback, Tone.noOp); + if (url instanceof Tone.Buffer){ + this._buffers[name] = url; + callback(this); + } else if (url instanceof AudioBuffer){ + this._buffers[name] = new Tone.Buffer(url); + callback(this); + } else if (this.isString(url)){ + this._buffers[name] = new Tone.Buffer(this.baseUrl + url, callback); + } + return this; + }; + + /** + * Flatten an object into a single depth object. + * thanks to https://gist.github.com/penguinboy/762197 + * @param {Object} ob + * @return {Object} + * @private + */ + Tone.Buffers.prototype._flattenUrls = function(ob) { + var toReturn = {}; + for (var i in ob) { + if (!ob.hasOwnProperty(i)) continue; + if (this.isObject(ob[i])) { + var flatObject = this._flattenUrls(ob[i]); + for (var x in flatObject) { + if (!flatObject.hasOwnProperty(x)) continue; + toReturn[i + "." + x] = flatObject[x]; + } + } else { + toReturn[i] = ob[i]; + } + } + return toReturn; + }; + + /** + * Clean up. + * @return {Tone.Buffers} this + */ + Tone.Buffers.prototype.dispose = function(){ + for (var name in this._buffers){ + this._buffers[name].dispose(); + } + this._buffers = null; + return this; + }; + + return Tone.Buffers; +}); \ No newline at end of file diff --git a/Tone/core/Clock.js b/Tone/core/Clock.js index 6bcbce4a5..e1f2af9c5 100644 --- a/Tone/core/Clock.js +++ b/Tone/core/Clock.js @@ -49,14 +49,7 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState * @type {Number} * @private */ - this._computedLookAhead = 1/60; - - /** - * The value afterwhich events are thrown out - * @type {Number} - * @private - */ - this._threshold = 0.5; + this._computedLookAhead = UPDATE_RATE/1000; /** * The next time the callback is scheduled. @@ -70,7 +63,7 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState * @type {Number} * @private */ - this._lastUpdate = 0; + this._lastUpdate = -1; /** * The id of the requestAnimationFrame @@ -102,16 +95,17 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * A pre-binded loop function to save a tiny bit of overhead - * of rebinding the function on every frame. - * @type {Function} + * The loop function bound to its context. + * This is necessary to remove the event in the end. + * @type {Function} * @private */ this._boundLoop = this._loop.bind(this); + //bind a callback to the worker thread + Tone.Clock._worker.addEventListener("message", this._boundLoop); + this._readOnly("frequency"); - //start the loop - this._loop(); }; Tone.extend(Tone.Clock); @@ -195,9 +189,8 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState */ Tone.Clock.prototype.stop = function(time){ time = this.toSeconds(time); - if (this._state.getStateAtTime(time) !== Tone.State.Stopped){ - this._state.setStateAtTime(Tone.State.Stopped, time); - } + this._state.cancel(time); + this._state.setStateAtTime(Tone.State.Stopped, time); return this; }; @@ -221,19 +214,18 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState * when the page was loaded. * @private */ - Tone.Clock.prototype._loop = function(time){ - this._loopID = requestAnimationFrame(this._boundLoop); + Tone.Clock.prototype._loop = function(){ //compute the look ahead if (this._lookAhead === "auto"){ - if (!this.isUndef(time)){ - var diff = (time - this._lastUpdate) / 1000; - this._lastUpdate = time; - //throw away large differences - if (diff < this._threshold){ - //averaging - this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; - } + var time = this.now(); + if (this._lastUpdate !== -1){ + var diff = (time - this._lastUpdate); + //max size on the diff + diff = Math.min(10 * UPDATE_RATE/1000, diff); + //averaging + this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; } + this._lastUpdate = time; } else { this._computedLookAhead = this._lookAhead; } @@ -255,10 +247,6 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState } if (state === Tone.State.Started){ while (now + lookAhead > this._nextTick){ - //catch up - if (now > this._nextTick + this._threshold){ - this._nextTick = now; - } var tickTime = this._nextTick; this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick); this.callback(tickTime); @@ -289,15 +277,49 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState Tone.Clock.prototype.dispose = function(){ cancelAnimationFrame(this._loopID); Tone.TimelineState.prototype.dispose.call(this); + Tone.Clock._worker.removeEventListener("message", this._boundLoop); this._writable("frequency"); this.frequency.dispose(); this.frequency = null; - this._boundLoop = Tone.noOp; + this._boundLoop = null; this._nextTick = Infinity; this.callback = null; this._state.dispose(); this._state = null; }; + //URL Shim + window.URL = window.URL || window.webkitURL; + + /** + * The update rate in Milliseconds + * @const + * @type {Number} + * @private + */ + var UPDATE_RATE = 20; + + /** + * The script which runs in a web worker + * @type {Blob} + * @private + */ + var blob = new Blob(["setInterval(function(){self.postMessage('tick')}, "+UPDATE_RATE+")"]); + + /** + * Create a blob url from the Blob + * @type {URL} + * @private + */ + var blobUrl = URL.createObjectURL(blob); + + /** + * The Worker which generates a regular callback + * @type {Worker} + * @private + * @static + */ + Tone.Clock._worker = new Worker(blobUrl); + return Tone.Clock; }); \ No newline at end of file diff --git a/Tone/core/Gain.js b/Tone/core/Gain.js index f5db11a79..31ee1ab72 100644 --- a/Tone/core/Gain.js +++ b/Tone/core/Gain.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Param", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/core/Param", "Tone/type/Type"], function (Tone) { "use strict"; diff --git a/Tone/core/IntervalTimeline.js b/Tone/core/IntervalTimeline.js index 77fe80bb5..0809cd8e4 100644 --- a/Tone/core/IntervalTimeline.js +++ b/Tone/core/IntervalTimeline.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/type/Type"], function (Tone) { "use strict"; @@ -38,7 +38,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { */ Tone.IntervalTimeline.prototype.addEvent = function(event){ if (this.isUndef(event.time) || this.isUndef(event.duration)){ - throw new Error("events must have time and duration parameters"); + throw new Error("Tone.IntervalTimeline: events must have time and duration parameters"); } var node = new IntervalNode(event.time, event.time + event.duration, event); if (this._root === null){ @@ -324,7 +324,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { * @param {Function} callback The callback to invoke with every item * @returns {Tone.IntervalTimeline} this */ - Tone.IntervalTimeline.prototype.forEachOverlap = function(time, callback){ + Tone.IntervalTimeline.prototype.forEachAtTime = function(time, callback){ time = this.toSeconds(time); if (this._root !== null){ var results = []; @@ -451,7 +451,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { this.left.search(point, results); } // Check this node - if (this.low <= point && this.high >= point){ + if (this.low <= point && this.high > point){ results.push(this); } // If p is to the left of the time of this interval, diff --git a/Tone/core/Master.js b/Tone/core/Master.js index b2d372d75..64864692f 100644 --- a/Tone/core/Master.js +++ b/Tone/core/Master.js @@ -25,20 +25,6 @@ define(["Tone/core/Tone", "Tone/component/Volume"], function(Tone){ Tone.Master = function(){ Tone.call(this); - /** - * the unmuted volume - * @type {number} - * @private - */ - this._unmutedVolume = 1; - - /** - * if the master is muted - * @type {boolean} - * @private - */ - this._muted = false; - /** * The private volume node * @type {Tone.Volume} @@ -80,17 +66,10 @@ define(["Tone/core/Tone", "Tone/component/Volume"], function(Tone){ */ Object.defineProperty(Tone.Master.prototype, "mute", { get : function(){ - return this._muted; + return this._volume.mute; }, set : function(mute){ - if (!this._muted && mute){ - this._unmutedVolume = this.volume.value; - //maybe it should ramp here? - this.volume.value = -Infinity; - } else if (this._muted && !mute){ - this.volume.value = this._unmutedVolume; - } - this._muted = mute; + this._volume.mute = mute; } }); diff --git a/Tone/core/Note.js b/Tone/core/Note.js deleted file mode 100644 index fa47b2ebe..000000000 --- a/Tone/core/Note.js +++ /dev/null @@ -1,181 +0,0 @@ -define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){ - - "use strict"; - - /** - * @class A timed note. Creating a note will register a callback - * which will be invoked on the channel at the time with - * whatever value was specified. - * - * @constructor - * @param {number|string} channel the channel name of the note - * @param {Time} time the time when the note will occur - * @param {string|number|Object|Array} value the value of the note - */ - Tone.Note = function(channel, time, value){ - - /** - * the value of the note. This value is returned - * when the channel callback is invoked. - * - * @type {string|number|Object} - */ - this.value = value; - - /** - * the channel name or number - * - * @type {string|number} - * @private - */ - this._channel = channel; - - /** - * an internal reference to the id of the timeline - * callback which is set. - * - * @type {number} - * @private - */ - this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time); - }; - - /** - * invoked by the timeline - * @private - * @param {number} time the time at which the note should play - */ - Tone.Note.prototype._trigger = function(time){ - //invoke the callback - channelCallbacks(this._channel, time, this.value); - }; - - /** - * clean up - * @returns {Tone.Note} this - */ - Tone.Note.prototype.dispose = function(){ - Tone.Transport.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 if (typeof noteDescription === "object"){ - note = new Tone.Note(inst, noteDescription.time, noteDescription); - } else { - note = new Tone.Note(inst, noteDescription); - } - notes.push(note); - } - } else { - throw new TypeError("score parts must be Arrays"); - } - } - return notes; - }; - - return Tone.Note; -}); \ No newline at end of file diff --git a/Tone/core/Param.js b/Tone/core/Param.js index 1beb12784..f53d33bc6 100644 --- a/Tone/core/Param.js +++ b/Tone/core/Param.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ +define(["Tone/core/Tone", "Tone/type/Type"], function(Tone){ "use strict"; @@ -74,6 +74,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ }, set : function(value){ var convertedVal = this._fromUnits(value); + this._param.cancelScheduledValues(0); this._param.value = convertedVal; } }); @@ -160,6 +161,11 @@ define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ Tone.Param.prototype.setRampPoint = function(now){ now = this.defaultArg(now, this.now()); var currentVal = this._param.value; + // exponentialRampToValueAt cannot ever ramp from or to 0 + // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 + if (currentVal === 0){ + currentVal = this._minOutput; + } this._param.setValueAtTime(currentVal, now); return this; }; @@ -201,18 +207,16 @@ define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ * @param {number} value The value to ramp to. * @param {Time} rampTime the time that it takes the * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //exponentially ramp to the value 2 over 4 seconds. * signal.exponentialRampToValue(2, 4); */ - Tone.Param.prototype.exponentialRampToValue = function(value, rampTime){ - var now = this.now(); - // exponentialRampToValueAt cannot ever ramp from 0, apparently. - // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 - var currentVal = this.value; - this.setValueAtTime(Math.max(currentVal, this._minOutput), now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); + Tone.Param.prototype.exponentialRampToValue = function(value, rampTime, startTime){ + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.exponentialRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); return this; }; @@ -224,15 +228,16 @@ define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ * @param {number} value The value to ramp to. * @param {Time} rampTime the time that it takes the * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //linearly ramp to the value 4 over 3 seconds. * signal.linearRampToValue(4, 3); */ - Tone.Param.prototype.linearRampToValue = function(value, rampTime){ - var now = this.now(); - this.setRampPoint(now); - this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); + Tone.Param.prototype.linearRampToValue = function(value, rampTime, startTime){ + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.linearRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); return this; }; @@ -290,20 +295,24 @@ define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ * depending on the `units` of the signal * * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value + * @param {Time} rampTime The time that it takes the + * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //ramp to the value either linearly or exponentially * //depending on the "units" value of the signal * signal.rampTo(0, 10); + * @example + * //schedule it to ramp starting at a specific time + * signal.rampTo(0, 10, 5) */ - Tone.Param.prototype.rampTo = function(value, rampTime){ + Tone.Param.prototype.rampTo = function(value, rampTime, startTime){ rampTime = this.defaultArg(rampTime, 0); - if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM){ - this.exponentialRampToValue(value, rampTime); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM || this.units === Tone.Type.Decibels){ + this.exponentialRampToValue(value, rampTime, startTime); } else { - this.linearRampToValue(value, rampTime); + this.linearRampToValue(value, rampTime, startTime); } return this; }; diff --git a/Tone/core/Timeline.js b/Tone/core/Timeline.js index 3379bbf00..d901619b2 100644 --- a/Tone/core/Timeline.js +++ b/Tone/core/Timeline.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/type/Type"], function (Tone) { "use strict"; @@ -76,7 +76,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { Tone.Timeline.prototype.addEvent = function(event){ //the event needs to have a time attribute if (this.isUndef(event.time)){ - throw new Error("events must have a time attribute"); + throw new Error("Tone.Timeline: events must have a time attribute"); } event.time = this.toSeconds(event.time); if (this._timeline.length){ @@ -111,7 +111,7 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { }; /** - * Get the event whose time is less than or equal to the given time. + * Get the nearest event whose time is less than or equal to the given time. * @param {Number} time The time to query. * @returns {Object} The event object set after that time. */ @@ -147,6 +147,11 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { */ Tone.Timeline.prototype.getEventBefore = function(time){ time = this.toSeconds(time); + var len = this._timeline.length; + //if it's after the last item, return the last item + if (len > 0 && this._timeline[len - 1].time < time){ + return this._timeline[len - 1]; + } var index = this._search(time); if (index - 1 >= 0){ return this._timeline[index - 1]; @@ -165,7 +170,19 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { after = this.toSeconds(after); var index = this._search(after); if (index >= 0){ - this._timeline = this._timeline.slice(0, index); + if (this._timeline[index].time === after){ + //get the first item with that time + for (var i = index; i >= 0; i--){ + if (this._timeline[i].time === after){ + index = i; + } else { + break; + } + } + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = this._timeline.slice(0, index + 1); + } } else { this._timeline = []; } @@ -196,7 +213,9 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { /** * Does a binary serach on the timeline array and returns the - * event which is after or equal to the time. + * nearest event index whose time is after or equal to the given time. + * If a time is searched before the first index in the timeline, -1 is returned. + * If the time is after the end, the index of the last item is returned. * @param {Number} time * @return {Number} the index in the timeline array * @private @@ -205,11 +224,14 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { var beginning = 0; var len = this._timeline.length; var end = len; - // continue searching while [imin,imax] is not empty - while (beginning <= end && beginning < len){ + if (len > 0 && this._timeline[len - 1].time <= time){ + return len - 1; + } + while (beginning < end){ // calculate the midpoint for roughly equal partition var midPoint = Math.floor(beginning + (end - beginning) / 2); var event = this._timeline[midPoint]; + var nextEvent = this._timeline[midPoint + 1]; if (event.time === time){ //choose the last one that has the same time for (var i = midPoint; i < this._timeline.length; i++){ @@ -219,15 +241,17 @@ define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { } } return midPoint; + } else if (event.time < time && nextEvent.time > time){ + return midPoint; } else if (event.time > time){ //search lower - end = midPoint - 1; + end = midPoint; } else if (event.time < time){ //search upper beginning = midPoint + 1; } } - return beginning - 1; + return -1; }; /** diff --git a/Tone/core/TimelineState.js b/Tone/core/TimelineState.js index 079ddaa94..e3a63d107 100644 --- a/Tone/core/TimelineState.js +++ b/Tone/core/TimelineState.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Timeline", "Tone/core/Type"], function (Tone) { +define(["Tone/core/Tone", "Tone/core/Timeline", "Tone/type/Type"], function (Tone) { "use strict"; diff --git a/Tone/core/Tone.js b/Tone/core/Tone.js index 2151b1ddc..60921ea33 100644 --- a/Tone/core/Tone.js +++ b/Tone/core/Tone.js @@ -2,7 +2,7 @@ * Tone.js * @author Yotam Mann * @license http://opensource.org/licenses/MIT MIT License - * @copyright 2014-2015 Yotam Mann + * @copyright 2014-2016 Yotam Mann */ define(function(){ @@ -64,30 +64,33 @@ define(function(){ if (!isFunction(OscillatorNode.prototype.setPeriodicWave)){ OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; } + //extend the connect function to include Tones - AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; - AudioNode.prototype.connect = function(B, outNum, inNum){ - if (B.input){ - if (Array.isArray(B.input)){ - if (isUndef(inNum)){ - inNum = 0; + if (isUndef(AudioNode.prototype._nativeConnect)){ + AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; + AudioNode.prototype.connect = function(B, outNum, inNum){ + if (B.input){ + if (Array.isArray(B.input)){ + if (isUndef(inNum)){ + inNum = 0; + } + this.connect(B.input[inNum]); + } else { + this.connect(B.input, outNum, inNum); } - this.connect(B.input[inNum]); } else { - this.connect(B.input, outNum, inNum); - } - } else { - try { - if (B instanceof AudioNode){ - this._nativeConnect(B, outNum, inNum); - } else { - this._nativeConnect(B, outNum); + try { + if (B instanceof AudioNode){ + this._nativeConnect(B, outNum, inNum); + } else { + this._nativeConnect(B, outNum); + } + } catch (e) { + throw new Error("error connecting to node: "+B); } - } catch (e) { - throw new Error("error connecting to node: "+B); } - } - }; + }; + } /////////////////////////////////////////////////////////////////////////// // TONE @@ -322,6 +325,14 @@ define(function(){ * @const */ Tone.prototype.blockTime = 128 / Tone.context.sampleRate; + + /** + * The time of a single sample + * @type {number} + * @static + * @const + */ + Tone.prototype.sampleTime = 1 / Tone.context.sampleRate; /////////////////////////////////////////////////////////////////////////// // CONNECTIONS @@ -392,12 +403,17 @@ define(function(){ /** * disconnect the output + * @param {Number|AudioNode} output Either the output index to disconnect + * if the output is an array, or the + * node to disconnect from. * @returns {Tone} this */ - Tone.prototype.disconnect = function(outputNum){ + Tone.prototype.disconnect = function(output){ if (Array.isArray(this.output)){ - outputNum = this.defaultArg(outputNum, 0); - this.output[outputNum].disconnect(); + output = this.defaultArg(output, 0); + this.output[output].disconnect(); + } else if (!this.isUndef(output)){ + this.output.disconnect(output); } else { this.output.disconnect(); } @@ -421,22 +437,6 @@ define(function(){ return this; }; - /** - * fan out the connection from the first argument to the rest of the arguments - * @param {...AudioParam|Tone|AudioNode} nodes - * @returns {Tone} this - */ - Tone.prototype.connectParallel = function(){ - var connectFrom = arguments[0]; - if (arguments.length > 1){ - for (var i = 1; i < arguments.length; i++){ - var connectTo = arguments[i]; - connectFrom.connect(connectTo); - } - } - return this; - }; - /** * Connect the output of this node to the rest of the nodes in series. * @example @@ -687,20 +687,40 @@ define(function(){ return 20 * (Math.log(gain) / Math.LN10); }; + /** + * Convert an interval (in semitones) to a frequency ratio. + * @param {Interval} interval the number of semitones above the base note + * @return {number} the frequency ratio + * @example + * tone.intervalToFrequencyRatio(0); // 1 + * tone.intervalToFrequencyRatio(12); // 2 + * tone.intervalToFrequencyRatio(-12); // 0.5 + */ + Tone.prototype.intervalToFrequencyRatio = function(interval){ + return Math.pow(2,(interval/12)); + }; + /////////////////////////////////////////////////////////////////////////// // TIMING /////////////////////////////////////////////////////////////////////////// /** - * Return the current time of the clock + a single buffer frame. - * If this value is used to schedule a value to change, the earliest - * it could be scheduled is the following frame. - * @return {number} the currentTime from the AudioContext + * Return the current time of the AudioContext clock. + * @return {Number} the currentTime from the AudioContext */ Tone.prototype.now = function(){ return this.context.currentTime; }; + /** + * Return the current time of the AudioContext clock. + * @return {Number} the currentTime from the AudioContext + * @static + */ + Tone.now = function(){ + return Tone.context.currentTime; + }; + /////////////////////////////////////////////////////////////////////////// // INHERITANCE /////////////////////////////////////////////////////////////////////////// @@ -774,33 +794,17 @@ define(function(){ } }; - /** - * Bind this to a touchstart event to start the audio on mobile devices. - *
- * http://stackoverflow.com/questions/12517000/no-sound-on-ios-6-web-audio-api/12569290#12569290 - * @static - */ - Tone.startMobile = function(){ - var osc = Tone.context.createOscillator(); - var silent = Tone.context.createGain(); - silent.gain.value = 0; - osc.connect(silent); - silent.connect(Tone.context.destination); - var now = Tone.context.currentTime; - osc.start(now); - osc.stop(now+1); - }; - //setup the context Tone._initAudioContext(function(audioContext){ //set the blockTime Tone.prototype.blockTime = 128 / audioContext.sampleRate; + Tone.prototype.sampleTime = 1 / audioContext.sampleRate; _silentNode = audioContext.createGain(); _silentNode.gain.value = 0; _silentNode.connect(audioContext.destination); }); - Tone.version = "r6"; + Tone.version = "r7"; console.log("%c * Tone.js " + Tone.version + " * ", "background: #000; color: #fff"); diff --git a/Tone/core/Transport.js b/Tone/core/Transport.js index 12ca71deb..259927b78 100644 --- a/Tone/core/Transport.js +++ b/Tone/core/Transport.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Clock", "Tone/core/Type", "Tone/core/Timeline", +define(["Tone/core/Tone", "Tone/core/Clock", "Tone/type/Type", "Tone/core/Timeline", "Tone/core/Emitter", "Tone/core/Gain", "Tone/core/IntervalTimeline"], function(Tone){ @@ -19,17 +19,12 @@ function(Tone){ * @singleton * @example * //repeated event every 8th note - * Tone.Transport.setInterval(function(time){ + * Tone.Transport.scheduleRepeat(function(time){ * //do something with the time * }, "8n"); * @example - * //one time event 1 second in the future - * Tone.Transport.setTimeout(function(time){ - * //do something with the time - * }, 1); - * @example - * //event fixed to the Transports timeline. - * Tone.Transport.setTimeline(function(time){ + * //schedule an event on the 16th measure + * Tone.Transport.schedule(function(time){ * //do something with the time * }, "16:0:0"); */ @@ -156,14 +151,12 @@ function(Tone){ // SWING ////////////////////////////////////////////////////////////////////// - var swingSeconds = this.notationToSeconds(TransportConstructor.defaults.swingSubdivision, TransportConstructor.defaults.bpm, TransportConstructor.defaults.timeSignature); - /** * The subdivision of the swing * @type {Ticks} * @private */ - this._swingTicks = (swingSeconds / (60 / TransportConstructor.defaults.bpm)) * this._ppq; + this._swingTicks = TransportConstructor.defaults.PPQ / 2; //8n /** * The swing amount @@ -185,11 +178,11 @@ function(Tone){ Tone.Transport.defaults = { "bpm" : 120, "swing" : 0, - "swingSubdivision" : "16n", + "swingSubdivision" : "8n", "timeSignature" : 4, "loopStart" : 0, "loopEnd" : "4m", - "PPQ" : 48 + "PPQ" : 192 }; /////////////////////////////////////////////////////////////////////////////// @@ -202,37 +195,40 @@ function(Tone){ * @private */ Tone.Transport.prototype._processTick = function(tickTime){ + var ticks = this._clock.ticks; //handle swing if (this._swingAmount > 0 && - this._clock.ticks % this._ppq !== 0 && //not on a downbeat - this._clock.ticks % this._swingTicks === 0){ + ticks % this._ppq !== 0 && //not on a downbeat + ticks % (this._swingTicks * 2) !== 0){ //add some swing - tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount; - } + var progress = (ticks % (this._swingTicks * 2)) / (this._swingTicks * 2); + var amount = Math.sin((progress) * Math.PI) * this._swingAmount; + tickTime += Tone.Time(this._swingTicks * 2/3, "i").eval() * amount; + } //do the loop test if (this.loop){ - if (this._clock.ticks === this._loopEnd){ + if (ticks === this._loopEnd){ this.ticks = this._loopStart; + ticks = this._loopStart; this.trigger("loop", tickTime); } } - var ticks = this._clock.ticks; + //process the single occurrence events + this._onceEvents.forEachBefore(ticks, function(event){ + event.callback(tickTime); + }); + //and clear the single occurrence timeline + this._onceEvents.cancelBefore(ticks); //fire the next tick events if their time has come this._timeline.forEachAtTime(ticks, function(event){ event.callback(tickTime); }); //process the repeated events - this._repeatedEvents.forEachOverlap(ticks, function(event){ + this._repeatedEvents.forEachAtTime(ticks, function(event){ if ((ticks - event.time) % event.interval === 0){ event.callback(tickTime); } }); - //process the single occurrence events - this._onceEvents.forEachBefore(ticks, function(event){ - event.callback(tickTime); - }); - //and clear the single occurrence timeline - this._onceEvents.cancelBefore(ticks); }; /////////////////////////////////////////////////////////////////////////////// @@ -242,7 +238,7 @@ function(Tone){ /** * Schedule an event along the timeline. * @param {Function} callback The callback to be invoked at the time. - * @param {Time} time The time to invoke the callback at. + * @param {TransportTime} time The time to invoke the callback at. * @return {Number} The id of the event which can be used for canceling the event. * @example * //trigger the callback when the Transport reaches the desired time @@ -271,7 +267,7 @@ function(Tone){ * @param {Function} callback The callback to invoke. * @param {Time} interval The duration between successive * callbacks. - * @param {Time=} startTime When along the timeline the events should + * @param {TimelinePosition=} startTime When along the timeline the events should * start being invoked. * @param {Time} [duration=Infinity] How long the event should repeat. * @return {Number} The ID of the scheduled event. Use this to cancel @@ -282,7 +278,7 @@ function(Tone){ */ Tone.Transport.prototype.scheduleRepeat = function(callback, interval, startTime, duration){ if (interval <= 0){ - throw new Error("repeat events must have an interval larger than 0"); + throw new Error("Tone.Transport: repeat events must have an interval larger than 0"); } var event = { "time" : this.toTicks(startTime), @@ -304,7 +300,7 @@ function(Tone){ * Note that if the given time is less than the current transport time, * the event will be invoked immediately. * @param {Function} callback The callback to invoke once. - * @param {Time} time The time the callback should be invoked. + * @param {TransportTime} time The time the callback should be invoked. * @returns {Number} The ID of the scheduled event. */ Tone.Transport.prototype.scheduleOnce = function(callback, time){ @@ -339,7 +335,7 @@ function(Tone){ * Remove scheduled events from the timeline after * the given time. Repeated events will be removed * if their startTime is after the given time - * @param {Time} [after=0] Clear all events after + * @param {TransportTime} [after=0] Clear all events after * this time. * @returns {Tone.Transport} this */ @@ -352,36 +348,6 @@ function(Tone){ return this; }; - /////////////////////////////////////////////////////////////////////////////// - // QUANTIZATION - /////////////////////////////////////////////////////////////////////////////// - - /** - * Returns the time closest time (equal to or after the given time) that aligns - * to the subidivision. - * @param {Time} time The time value to quantize to the given subdivision - * @param {String} [subdivision="4n"] The subdivision to quantize to. - * @return {Number} the time in seconds until the next subdivision. - * @example - * Tone.Transport.bpm.value = 120; - * Tone.Transport.quantize("3 * 4n", "1m"); //return 0.5 - * //if the clock is started, it will return a value less than 0.5 - */ - Tone.Transport.prototype.quantize = function(time, subdivision){ - subdivision = this.defaultArg(subdivision, "4n"); - var tickTime = this.toTicks(time); - subdivision = this.toTicks(subdivision); - var remainingTicks = subdivision - (tickTime % subdivision); - if (remainingTicks === subdivision){ - remainingTicks = 0; - } - var now = this.now(); - if (this.state === Tone.State.Started){ - now = this._clock._nextTick; - } - return this.toSeconds(time, now) + this.ticksToSeconds(remainingTicks); - }; - /////////////////////////////////////////////////////////////////////////////// // START/STOP/PAUSE /////////////////////////////////////////////////////////////////////////////// @@ -402,7 +368,7 @@ function(Tone){ /** * Start the transport and all sources synced to the transport. * @param {Time} [time=now] The time when the transport should start. - * @param {Time=} offset The timeline offset to start the transport. + * @param {TransportTime=} offset The timeline offset to start the transport. * @returns {Tone.Transport} this * @example * //start the transport in one second starting at beginning of the 5th measure. @@ -411,13 +377,13 @@ function(Tone){ Tone.Transport.prototype.start = function(time, offset){ time = this.toSeconds(time); if (!this.isUndef(offset)){ - offset = this.toTicks(offset); + offset = new Tone.Time(offset); } else { - offset = this.defaultArg(offset, this._clock.ticks); + offset = new Tone.Time(this._clock.ticks, "i"); } //start the clock - this._clock.start(time, offset); - this.trigger("start", time, this.ticksToSeconds(offset)); + this._clock.start(time, offset.toTicks()); + this.trigger("start", time, offset.toSeconds()); return this; }; @@ -481,12 +447,12 @@ function(Tone){ /** * When the Tone.Transport.loop = true, this is the starting position of the loop. * @memberOf Tone.Transport# - * @type {Time} + * @type {TransportTime} * @name loopStart */ Object.defineProperty(Tone.Transport.prototype, "loopStart", { get : function(){ - return this.ticksToSeconds(this._loopStart); + return Tone.TransportTime(this._loopStart, "i").toSeconds(); }, set : function(startPosition){ this._loopStart = this.toTicks(startPosition); @@ -496,12 +462,12 @@ function(Tone){ /** * When the Tone.Transport.loop = true, this is the ending position of the loop. * @memberOf Tone.Transport# - * @type {Time} + * @type {TransportTime} * @name loopEnd */ Object.defineProperty(Tone.Transport.prototype, "loopEnd", { get : function(){ - return this.ticksToSeconds(this._loopEnd); + return Tone.TransportTime(this._loopEnd, "i").toSeconds(); }, set : function(endPosition){ this._loopEnd = this.toTicks(endPosition); @@ -510,8 +476,8 @@ function(Tone){ /** * Set the loop start and stop at the same time. - * @param {Time} startPosition - * @param {Time} endPosition + * @param {TransportTime} startPosition + * @param {TransportTime} endPosition * @returns {Tone.Transport} this * @example * //loop over the first measure @@ -533,11 +499,11 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swing", { get : function(){ - return this._swingAmount * 2; + return this._swingAmount; }, set : function(amount){ //scale the values to a normal range - this._swingAmount = amount * 0.5; + this._swingAmount = amount; } }); @@ -552,7 +518,7 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swingSubdivision", { get : function(){ - return this.toNotation(this._swingTicks + "i"); + return Tone.Time(this._swingTicks, "i").toNotation(); }, set : function(subdivision){ this._swingTicks = this.toTicks(subdivision); @@ -560,25 +526,15 @@ function(Tone){ }); /** - * The Transport's position in MEASURES:BEATS:SIXTEENTHS. + * The Transport's position in Bars:Beats:Sixteenths. * Setting the value will jump to that position right away. - * * @memberOf Tone.Transport# - * @type {TransportTime} + * @type {BarsBeatsSixteenths} * @name position */ Object.defineProperty(Tone.Transport.prototype, "position", { get : function(){ - var quarters = this.ticks / this._ppq; - var measures = Math.floor(quarters / this._timeSignature); - var sixteenths = ((quarters % 1) * 4); - //if the sixteenths aren't a whole number, fix their length - if (sixteenths % 1 > 0){ - sixteenths = sixteenths.toFixed(3); - } - quarters = Math.floor(quarters) % this._timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); + return Tone.TransportTime(this.ticks, "i").toBarsBeatsSixteenths(); }, set : function(progress){ var ticks = this.toTicks(progress); @@ -634,8 +590,9 @@ function(Tone){ return this._ppq; }, set : function(ppq){ + var bpm = this.bpm.value; this._ppq = ppq; - this.bpm.value = this.bpm.value; + this.bpm.value = bpm; } }); @@ -663,6 +620,34 @@ function(Tone){ // SYNCING /////////////////////////////////////////////////////////////////////////////// + /** + * Returns the time aligned to the next subdivision + * of the Transport. If the Transport is not started, + * it will return 0. + * Note: this will not work precisely during tempo ramps. + * @param {Time} subdivision The subdivision to quantize to + * @return {Number} The context time of the next subdivision. + * @example + * Tone.Transport.start(); //the transport must be started + * Tone.Transport.nextSubdivision("4n"); + */ + Tone.Transport.prototype.nextSubdivision = function(subdivision){ + subdivision = this.toSeconds(subdivision); + //if the transport's not started, return 0 + var now; + if (this.state === Tone.State.Started){ + now = this._clock._nextTick; + } else { + return 0; + } + var transportPos = Tone.Time(this.ticks, "i").eval(); + var remainingTime = subdivision - (transportPos % subdivision); + if (remainingTime === 0){ + remainingTime = subdivision; + } + return now + remainingTime; + }; + /** * Attaches the signal to the tempo control signal so that * any changes in the tempo will change the signal in the same @@ -732,109 +717,6 @@ function(Tone){ return this; }; - /////////////////////////////////////////////////////////////////////////////// - // DEPRECATED FUNCTIONS - // (will be removed in r7) - /////////////////////////////////////////////////////////////////////////////// - - /** - * @deprecated Use Tone.scheduleRepeat instead. - * Set a callback for a recurring event. - * @param {function} callback - * @param {Time} interval - * @return {number} the id of the interval - * @example - * //triggers a callback every 8th note with the exact time of the event - * Tone.Transport.setInterval(function(time){ - * envelope.triggerAttack(time); - * }, "8n"); - * @private - */ - Tone.Transport.prototype.setInterval = function(callback, interval){ - console.warn("This method is deprecated. Use Tone.Transport.scheduleRepeat instead."); - return Tone.Transport.scheduleRepeat(callback, interval); - }; - - /** - * @deprecated Use Tone.cancel instead. - * Stop and ongoing interval. - * @param {number} intervalID The ID of interval to remove. The interval - * ID is given as the return value in Tone.Transport.setInterval. - * @return {boolean} true if the event was removed - * @private - */ - Tone.Transport.prototype.clearInterval = function(id){ - console.warn("This method is deprecated. Use Tone.Transport.clear instead."); - return Tone.Transport.clear(id); - }; - - /** - * @deprecated Use Tone.Note instead. - * Set a timeout to occur after time from now. NB: the transport must be - * running for this to be triggered. All timeout events are cleared when the - * transport is stopped. - * - * @param {function} callback - * @param {Time} time The time (from now) that the callback will be invoked. - * @return {number} The id of the timeout. - * @example - * //trigger an event to happen 1 second from now - * Tone.Transport.setTimeout(function(time){ - * player.start(time); - * }, 1) - * @private - */ - Tone.Transport.prototype.setTimeout = function(callback, timeout){ - console.warn("This method is deprecated. Use Tone.Transport.scheduleOnce instead."); - return Tone.Transport.scheduleOnce(callback, timeout); - }; - - /** - * @deprecated Use Tone.Note instead. - * Clear a timeout using it's ID. - * @param {number} intervalID The ID of timeout to remove. The timeout - * ID is given as the return value in Tone.Transport.setTimeout. - * @return {boolean} true if the timeout was removed - * @private - */ - Tone.Transport.prototype.clearTimeout = function(id){ - console.warn("This method is deprecated. Use Tone.Transport.clear instead."); - return Tone.Transport.clear(id); - }; - - /** - * @deprecated Use Tone.Note instead. - * Timeline events are synced to the timeline of the Tone.Transport. - * Unlike Timeout, Timeline events will restart after the - * Tone.Transport has been stopped and restarted. - * - * @param {function} callback - * @param {Time} time - * @return {number} the id for clearing the transportTimeline event - * @example - * //trigger the start of a part on the 16th measure - * Tone.Transport.setTimeline(function(time){ - * part.start(time); - * }, "16m"); - * @private - */ - Tone.Transport.prototype.setTimeline = function(callback, time){ - console.warn("This method is deprecated. Use Tone.Transport.schedule instead."); - return Tone.Transport.schedule(callback, time); - }; - - /** - * @deprecated Use Tone.Note instead. - * Clear the timeline event. - * @param {number} id - * @return {boolean} true if it was removed - * @private - */ - Tone.Transport.prototype.clearTimeline = function(id){ - console.warn("This method is deprecated. Use Tone.Transport.clear instead."); - return Tone.Transport.clear(id); - }; - /////////////////////////////////////////////////////////////////////////////// // INITIALIZATION /////////////////////////////////////////////////////////////////////////////// diff --git a/Tone/core/Type.js b/Tone/core/Type.js deleted file mode 100644 index c05b1c32a..000000000 --- a/Tone/core/Type.js +++ /dev/null @@ -1,816 +0,0 @@ -define(["Tone/core/Tone"], function (Tone) { - - "use strict"; - - /////////////////////////////////////////////////////////////////////////// - // TYPES - /////////////////////////////////////////////////////////////////////////// - - /** - * Units which a value can take on. - * @enum {String} - */ - Tone.Type = { - /** - * The default value is a number which can take on any value between [-Infinity, Infinity] - */ - Default : "number", - /** - * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). - * - * - * - * @typedef {Time} - */ - Time : "time", - /** - * Frequency can be described similar to time, except ultimately the - * values are converted to frequency instead of seconds. A number - * is taken literally as the value in hertz. Additionally any of the - * Time encodings can be used. Note names in the form - * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their - * frequency value. - * @typedef {Frequency} - */ - Frequency : "frequency", - /** - * Normal values are within the range [0, 1]. - * @typedef {NormalRange} - */ - NormalRange : "normalRange", - /** - * AudioRange values are between [-1, 1]. - * @typedef {AudioRange} - */ - AudioRange : "audioRange", - /** - * Decibels are a logarithmic unit of measurement which is useful for volume - * because of the logarithmic way that we perceive loudness. 0 decibels - * means no change in volume. -10db is approximately half as loud and 10db - * is twice is loud. - * @typedef {Decibels} - */ - Decibels : "db", - /** - * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. - * @typedef {Interval} - */ - Interval : "interval", - /** - * Beats per minute. - * @typedef {BPM} - */ - BPM : "bpm", - /** - * The value must be greater than or equal to 0. - * @typedef {Positive} - */ - Positive : "positive", - /** - * A cent is a hundredth of a semitone. - * @typedef {Cents} - */ - Cents : "cents", - /** - * Angle between 0 and 360. - * @typedef {Degrees} - */ - Degrees : "degrees", - /** - * A number representing a midi note. - * @typedef {MIDI} - */ - MIDI : "midi", - /** - * A colon-separated representation of time in the form of - * BARS:QUARTERS:SIXTEENTHS. - * @typedef {TransportTime} - */ - TransportTime : "transportTime", - /** - * Ticks are the basic subunit of the Transport. They are - * the smallest unit of time that the Transport supports. - * @typedef {Ticks} - */ - Ticks : "tick", - /** - * A frequency represented by a letter name, - * accidental and octave. This system is known as - * [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). - * @typedef {Note} - */ - Note : "note", - /** - * One millisecond is a thousandth of a second. - * @typedef {Milliseconds} - */ - Milliseconds : "milliseconds", - /** - * A string representing a duration relative to a measure. - * - * @typedef {Notation} - */ - Notation : "notation", - }; - - /////////////////////////////////////////////////////////////////////////// - // MATCHING TESTS - /////////////////////////////////////////////////////////////////////////// - - /** - * Test if a function is "now-relative", i.e. starts with "+". - * - * @param {String} str The string to test - * @return {boolean} - * @method isNowRelative - * @lends Tone.prototype.isNowRelative - */ - Tone.prototype.isNowRelative = (function(){ - var nowRelative = new RegExp(/^\s*\+(.)+/i); - return function(note){ - return nowRelative.test(note); - }; - })(); - - /** - * Tests if a string is in Ticks notation. - * - * @param {String} str The string to test - * @return {boolean} - * @method isTicks - * @lends Tone.prototype.isTicks - */ - Tone.prototype.isTicks = (function(){ - var tickFormat = new RegExp(/^\d+i$/i); - return function(note){ - return tickFormat.test(note); - }; - })(); - - /** - * Tests if a string is musical notation. - * i.e.: - * - * - * @param {String} str The string to test - * @return {boolean} - * @method isNotation - * @lends Tone.prototype.isNotation - */ - Tone.prototype.isNotation = (function(){ - var notationFormat = new RegExp(/^[0-9]+[mnt]$/i); - return function(note){ - return notationFormat.test(note); - }; - })(); - - /** - * Test if a string is in the transportTime format. - * "Bars:Beats:Sixteenths" - * @param {String} transportTime - * @return {boolean} - * @method isTransportTime - * @lends Tone.prototype.isTransportTime - */ - Tone.prototype.isTransportTime = (function(){ - var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i); - return function(transportTime){ - return transportTimeFormat.test(transportTime); - }; - })(); - - /** - * Test if a string is in Scientific Pitch Notation: i.e. "C4". - * @param {String} note The note to test - * @return {boolean} true if it's in the form of a note - * @method isNote - * @lends Tone.prototype.isNote - * @function - */ - Tone.prototype.isNote = ( function(){ - var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i); - return function(note){ - return noteFormat.test(note); - }; - })(); - - /** - * Test if the input is in the format of number + hz - * i.e.: 10hz - * - * @param {String} freq - * @return {boolean} - * @function - */ - Tone.prototype.isFrequency = (function(){ - var freqFormat = new RegExp(/^\d*\.?\d+hz$/i); - return function(freq){ - return freqFormat.test(freq); - }; - })(); - - /////////////////////////////////////////////////////////////////////////// - // TO SECOND CONVERSIONS - /////////////////////////////////////////////////////////////////////////// - - /** - * @private - * @return {Object} The Transport's BPM if the Transport exists, - * otherwise returns reasonable defaults. - */ - function getTransportBpm(){ - if (Tone.Transport && Tone.Transport.bpm){ - return Tone.Transport.bpm.value; - } else { - return 120; - } - } - - /** - * @private - * @return {Object} The Transport's Time Signature if the Transport exists, - * otherwise returns reasonable defaults. - */ - function getTransportTimeSignature(){ - if (Tone.Transport && Tone.Transport.timeSignature){ - return Tone.Transport.timeSignature; - } else { - return 4; - } - } - - /** - * - * convert notation format strings to seconds - * - * @param {String} notation - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {number} - * - */ - Tone.prototype.notationToSeconds = function(notation, bpm, timeSignature){ - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var beatTime = (60 / bpm); - //special case: 1n = 1m - if (notation === "1n"){ - notation = "1m"; - } - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0){ - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === "t"){ - beats = (4 / subdivision) * 2/3; - } else if (lastLetter === "n"){ - beats = 4 / subdivision; - } else if (lastLetter === "m"){ - beats = subdivision * timeSignature; - } else { - beats = 0; - } - return beatTime * beats; - }; - - /** - * convert transportTime into seconds. - * - * ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - * - * @param {TransportTime} transportTime - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {number} seconds - */ - Tone.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){ - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(":"); - if (split.length === 2){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1){ - quarters = parseFloat(split[0]); - } else if (split.length === 3){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = (measures * timeSignature + quarters + sixteenths / 4); - return beats * (60/bpm); - }; - - /** - * Convert ticks into seconds - * @param {Ticks} ticks - * @param {BPM=} bpm - * @return {number} seconds - */ - Tone.prototype.ticksToSeconds = function(ticks, bpm){ - if (this.isUndef(Tone.Transport)){ - return 0; - } - ticks = parseFloat(ticks); - bpm = this.defaultArg(bpm, getTransportBpm()); - var tickTime = (60/bpm) / Tone.Transport.PPQ; - return tickTime * ticks; - }; - - /** - * Convert a frequency into seconds. - * Accepts numbers and strings: i.e. "10hz" or - * 10 both return 0.1. - * - * @param {Frequency} freq - * @return {number} - */ - Tone.prototype.frequencyToSeconds = function(freq){ - return 1 / parseFloat(freq); - }; - - /** - * Convert a sample count to seconds. - * @param {number} samples - * @return {number} - */ - Tone.prototype.samplesToSeconds = function(samples){ - return samples / this.context.sampleRate; - }; - - /** - * Convert from seconds to samples. - * @param {number} seconds - * @return {number} The number of samples - */ - Tone.prototype.secondsToSamples = function(seconds){ - return seconds * this.context.sampleRate; - }; - - /////////////////////////////////////////////////////////////////////////// - // FROM SECOND CONVERSIONS - /////////////////////////////////////////////////////////////////////////// - - /** - * Convert seconds to transportTime in the form - * "measures:quarters:sixteenths" - * - * @param {Number} seconds - * @param {BPM=} bpm - * @param {Number=} timeSignature - * @return {TransportTime} - */ - Tone.prototype.secondsToTransportTime = function(seconds, bpm, timeSignature){ - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var quarterTime = 60/bpm; - var quarters = seconds / quarterTime; - var measures = Math.floor(quarters / timeSignature); - var sixteenths = (quarters % 1) * 4; - quarters = Math.floor(quarters) % timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); - }; - - /** - * Convert a number in seconds to a frequency. - * @param {number} seconds - * @return {number} - */ - Tone.prototype.secondsToFrequency = function(seconds){ - return 1/seconds; - }; - - /////////////////////////////////////////////////////////////////////////// - // GENERALIZED CONVERSIONS - /////////////////////////////////////////////////////////////////////////// - - /** - * Convert seconds to the closest transportTime in the form - * measures:quarters:sixteenths - * - * @method toTransportTime - * - * @param {Time} time - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {TransportTime} - * - * @lends Tone.prototype.toTransportTime - */ - Tone.prototype.toTransportTime = function(time, bpm, timeSignature){ - var seconds = this.toSeconds(time); - return this.secondsToTransportTime(seconds, bpm, timeSignature); - }; - - /** - * Convert a frequency representation into a number. - * - * @param {Frequency} freq - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} the frequency in hertz - */ - Tone.prototype.toFrequency = function(freq, now){ - if (this.isFrequency(freq)){ - return parseFloat(freq); - } else if (this.isNotation(freq) || this.isTransportTime(freq)) { - return this.secondsToFrequency(this.toSeconds(freq, now)); - } else if (this.isNote(freq)){ - return this.noteToFrequency(freq); - } else { - return freq; - } - }; - - /** - * Convert the time representation into ticks. - * Now-Relative timing will be relative to the current - * Tone.Transport.ticks. - * @param {Time} time - * @return {Ticks} - */ - Tone.prototype.toTicks = function(time){ - if (this.isUndef(Tone.Transport)){ - return 0; - } - var bpm = Tone.Transport.bpm.value; - //get the seconds - var plusNow = 0; - if (this.isNowRelative(time)){ - time = time.replace("+", ""); - plusNow = Tone.Transport.ticks; - } else if (this.isUndef(time)){ - return Tone.Transport.ticks; - } - var seconds = this.toSeconds(time); - var quarter = 60/bpm; - var quarters = seconds / quarter; - var tickNum = quarters * Tone.Transport.PPQ; - //align the tick value - return Math.round(tickNum + plusNow); - }; - - /** - * convert a time into samples - * - * @param {Time} time - * @return {number} - */ - Tone.prototype.toSamples = function(time){ - var seconds = this.toSeconds(time); - return Math.round(seconds * this.context.sampleRate); - }; - - /** - * Convert Time into seconds. - * - * Unlike the method which it overrides, this takes into account - * transporttime and musical notation. - * - * Time : 1.40 - * Notation: 4n|1m|2t - * TransportTime: 2:4:1 (measure:quarters:sixteens) - * Now Relative: +3n - * Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1) - * - * @override - * @param {Time} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} - */ - Tone.prototype.toSeconds = function(time, now){ - now = this.defaultArg(now, this.now()); - if (this.isNumber(time)){ - return time; //assuming that it's seconds - } else if (this.isString(time)){ - var plusTime = 0; - if(this.isNowRelative(time)) { - time = time.replace("+", ""); - plusTime = now; - } - var betweenParens = time.match(/\(([^)(]+)\)/g); - if (betweenParens){ - //evaluate the expressions between the parenthesis - for (var j = 0; j < betweenParens.length; j++){ - //remove the parens - var symbol = betweenParens[j].replace(/[\(\)]/g, ""); - var symbolVal = this.toSeconds(symbol); - time = time.replace(betweenParens[j], symbolVal); - } - } - //test if it is quantized - if (time.indexOf("@") !== -1){ - var quantizationSplit = time.split("@"); - if (!this.isUndef(Tone.Transport)){ - var toQuantize = quantizationSplit[0].trim(); - //if there's no argument it should be evaluated as the current time - if (toQuantize === ""){ - toQuantize = undefined; - } - //if it's now-relative, it should be evaluated by `quantize` - if (plusTime > 0){ - toQuantize = "+" + toQuantize; - plusTime = 0; - } - var subdivision = quantizationSplit[1].trim(); - time = Tone.Transport.quantize(toQuantize, subdivision); - } else { - throw new Error("quantization requires Tone.Transport"); - } - } else { - var components = time.split(/[\(\)\-\+\/\*]/); - if (components.length > 1){ - var originalTime = time; - for(var i = 0; i < components.length; i++){ - var symb = components[i].trim(); - if (symb !== ""){ - var val = this.toSeconds(symb); - time = time.replace(symb, val); - } - } - try { - //eval is evil - time = eval(time); // jshint ignore:line - } catch (e){ - throw new EvalError("cannot evaluate Time: "+originalTime); - } - } else if (this.isNotation(time)){ - time = this.notationToSeconds(time); - } else if (this.isTransportTime(time)){ - time = this.transportTimeToSeconds(time); - } else if (this.isFrequency(time)){ - time = this.frequencyToSeconds(time); - } else if (this.isTicks(time)){ - time = this.ticksToSeconds(time); - } else { - time = parseFloat(time); - } - } - return time + plusTime; - } else { - return now; - } - }; - - - /** - * Convert a Time to Notation. Values will be thresholded to the nearest 128th note. - * @param {Time} time - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {Notation} - */ - Tone.prototype.toNotation = function(time, bpm, timeSignature){ - var testNotations = ["1m", "2n", "4n", "8n", "16n", "32n", "64n", "128n"]; - var retNotation = toNotationHelper.call(this, time, bpm, timeSignature, testNotations); - //try the same thing but with tripelets - var testTripletNotations = ["1m", "2n", "2t", "4n", "4t", "8n", "8t", "16n", "16t", "32n", "32t", "64n", "64t", "128n"]; - var retTripletNotation = toNotationHelper.call(this, time, bpm, timeSignature, testTripletNotations); - //choose the simpler expression of the two - if (retTripletNotation.split("+").length < retNotation.split("+").length){ - return retTripletNotation; - } else { - return retNotation; - } - }; - - /** - * Helper method for Tone.toNotation - * @private - */ - function toNotationHelper(time, bpm, timeSignature, testNotations){ - var seconds = this.toSeconds(time); - var threshold = this.notationToSeconds(testNotations[testNotations.length - 1], bpm, timeSignature); - var retNotation = ""; - for (var i = 0; i < testNotations.length; i++){ - var notationTime = this.notationToSeconds(testNotations[i], bpm, timeSignature); - //account for floating point errors (i.e. round up if the value is 0.999999) - var multiple = seconds / notationTime; - var floatingPointError = 0.000001; - if (1 - multiple % 1 < floatingPointError){ - multiple += floatingPointError; - } - multiple = Math.floor(multiple); - if (multiple > 0){ - if (multiple === 1){ - retNotation += testNotations[i]; - } else { - retNotation += multiple.toString() + "*" + testNotations[i]; - } - seconds -= multiple * notationTime; - if (seconds < threshold){ - break; - } else { - retNotation += " + "; - } - } - } - if (retNotation === ""){ - retNotation = "0"; - } - return retNotation; - } - - /** - * Convert the given value from the type specified by units - * into a number. - * @param {*} val the value to convert - * @return {Number} the number which the value should be set to - */ - Tone.prototype.fromUnits = function(val, units){ - if (this.convert || this.isUndef(this.convert)){ - switch(units){ - case Tone.Type.Time: - return this.toSeconds(val); - case Tone.Type.Frequency: - return this.toFrequency(val); - case Tone.Type.Decibels: - return this.dbToGain(val); - case Tone.Type.NormalRange: - return Math.min(Math.max(val, 0), 1); - case Tone.Type.AudioRange: - return Math.min(Math.max(val, -1), 1); - case Tone.Type.Positive: - return Math.max(val, 0); - default: - return val; - } - } else { - return val; - } - }; - - /** - * Convert a number to the specified units. - * @param {number} val the value to convert - * @return {number} - */ - Tone.prototype.toUnits = function(val, units){ - if (this.convert || this.isUndef(this.convert)){ - switch(units){ - case Tone.Type.Decibels: - return this.gainToDb(val); - default: - return val; - } - } else { - return val; - } - }; - - /////////////////////////////////////////////////////////////////////////// - // FREQUENCY CONVERSIONS - /////////////////////////////////////////////////////////////////////////// - - /** - * Note to scale index - * @type {Object} - */ - var noteToScaleIndex = { - "cbb" : -2, "cb" : -1, "c" : 0, "c#" : 1, "cx" : 2, - "dbb" : 0, "db" : 1, "d" : 2, "d#" : 3, "dx" : 4, - "ebb" : 2, "eb" : 3, "e" : 4, "e#" : 5, "ex" : 6, - "fbb" : 3, "fb" : 4, "f" : 5, "f#" : 6, "fx" : 7, - "gbb" : 5, "gb" : 6, "g" : 7, "g#" : 8, "gx" : 9, - "abb" : 7, "ab" : 8, "a" : 9, "a#" : 10, "ax" : 11, - "bbb" : 9, "bb" : 10, "b" : 11, "b#" : 12, "bx" : 13, - }; - - /** - * scale index to note (sharps) - * @type {Array} - */ - var scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; - - /** - * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch, - * A4's values in Hertz. - * @type {Frequency} - * @static - */ - Tone.A4 = 440; - - /** - * Convert a note name to frequency. - * @param {String} note - * @return {number} - * @example - * var freq = tone.noteToFrequency("A4"); //returns 440 - */ - Tone.prototype.noteToFrequency = function(note){ - //break apart the note by frequency and octave - var parts = note.split(/(-?\d+)/); - if (parts.length === 3){ - var index = noteToScaleIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - var noteNumber = index + (parseInt(octave, 10) + 1) * 12; - return this.midiToFrequency(noteNumber); - } else { - return 0; - } - }; - - /** - * Convert a frequency to a note name (i.e. A4, C#5). - * @param {number} freq - * @return {String} - */ - Tone.prototype.frequencyToNote = function(freq){ - var log = Math.log(freq / Tone.A4) / Math.LN2; - var noteNumber = Math.round(12 * log) + 57; - var octave = Math.floor(noteNumber/12); - if(octave < 0){ - noteNumber += -12 * octave; - } - var noteName = scaleIndexToNote[noteNumber % 12]; - return noteName + octave.toString(); - }; - - /** - * Convert an interval (in semitones) to a frequency ratio. - * - * @param {Interval} interval the number of semitones above the base note - * @return {number} the frequency ratio - * @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 {MIDI} 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) - 1; - var note = midiNumber % 12; - return scaleIndexToNote[note] + octave; - }; - - /** - * Convert a note to it's midi value. - * - * @param {String} note the note name (i.e. "C3") - * @return {MIDI} 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 = noteToScaleIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - return index + (parseInt(octave, 10) + 1) * 12; - } else { - return 0; - } - }; - - /** - * Convert a MIDI note to frequency value. - * - * @param {MIDI} midi The midi number to convert. - * @return {Frequency} the corresponding frequency value - * @example - * tone.midiToFrequency(57); // returns 440 - */ - Tone.prototype.midiToFrequency = function(midi){ - return Tone.A4 * Math.pow(2, (midi - 69) / 12); - }; - - return Tone; -}); \ No newline at end of file diff --git a/Tone/effect/AutoWah.js b/Tone/effect/AutoWah.js index e4010cc23..9ef94ec5c 100644 --- a/Tone/effect/AutoWah.js +++ b/Tone/effect/AutoWah.js @@ -20,7 +20,7 @@ function(Tone){ * @example * var autoWah = new Tone.AutoWah(50, 6, -30).toMaster(); * //initialize the synth and connect to autowah - * var synth = new SimpleSynth.connect(autoWah); + * var synth = new Synth.connect(autoWah); * //Q value influences the effect of the wah - default is 2 * autoWah.Q.value = 6; * //more audible on higher notes diff --git a/Tone/event/Event.js b/Tone/event/Event.js index 66d56537d..4ee096f61 100644 --- a/Tone/event/Event.js +++ b/Tone/event/Event.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/TimelineState"], function (Tone) { +define(["Tone/core/Tone", "Tone/core/Transport", "Tone/type/Type", "Tone/core/TimelineState"], function (Tone) { "use strict"; @@ -152,7 +152,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti if (this._loop){ duration = Infinity; if (this.isNumber(this._loop)){ - duration = (this._loop - 1) * this._getLoopDuration(); + duration = (this._loop) * this._getLoopDuration(); } var nextEvent = this._state.getEventAfter(startTick); if (nextEvent !== null){ @@ -161,9 +161,10 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti if (duration !== Infinity){ //schedule a stop since it's finite duration this._state.setStateAtTime(Tone.State.Stopped, startTick + duration + 1); - duration += "i"; + duration = Tone.Time(duration, "i"); } - event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), this._getLoopDuration().toString() + "i", startTick + "i", duration); + var interval = Tone.Time(this._getLoopDuration(), "i"); + event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), interval, Tone.TransportTime(startTick, "i"), duration); } else { event.id = Tone.Transport.schedule(this._tick.bind(this), startTick + "i"); } @@ -203,7 +204,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti /** * Start the note at the given time. - * @param {Time} time When the note should start. + * @param {TimelinePosition} time When the note should start. * @return {Tone.Event} this */ Tone.Event.prototype.start = function(time){ @@ -221,7 +222,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti /** * Stop the Event at the given time. - * @param {Time} time When the note should stop. + * @param {TimelinePosition} time When the note should stop. * @return {Tone.Event} this */ Tone.Event.prototype.stop = function(time){ @@ -241,7 +242,7 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti /** * Cancel all scheduled events greater than or equal to the given time - * @param {Time} [time=0] The time after which events will be cancel. + * @param {TimelinePosition} [time=0] The time after which events will be cancel. * @return {Tone.Event} this */ Tone.Event.prototype.cancel = function(time){ @@ -326,15 +327,15 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti }); /** - * The loopEnd point is the time the event will loop. - * Note: only loops if Tone.Event.loop is true. + * The loopEnd point is the time the event will loop + * if Tone.Event.loop is true. * @memberOf Tone.Event# - * @type {Boolean|Positive} + * @type {TransportTime} * @name loopEnd */ Object.defineProperty(Tone.Event.prototype, "loopEnd", { get : function(){ - return this.toNotation(this._loopEnd + "i"); + return Tone.TransportTime(this._loopEnd, "i").toNotation(); }, set : function(loopEnd){ this._loopEnd = this.toTicks(loopEnd); @@ -347,12 +348,12 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/Ti /** * The time when the loop should start. * @memberOf Tone.Event# - * @type {Boolean|Positive} + * @type {TransportTime} * @name loopStart */ Object.defineProperty(Tone.Event.prototype, "loopStart", { get : function(){ - return this.toNotation(this._loopStart + "i"); + return Tone.TransportTime(this._loopStart, "i").toNotation(); }, set : function(loopStart){ this._loopStart = this.toTicks(loopStart); diff --git a/Tone/event/Loop.js b/Tone/event/Loop.js index b596be65e..e8f4e1afb 100644 --- a/Tone/event/Loop.js +++ b/Tone/event/Loop.js @@ -12,9 +12,8 @@ define(["Tone/core/Tone", "Tone/event/Event"], function (Tone) { * }, "8n").start(0); * Tone.Transport.start(); * @extends {Tone} - * @param {Function} callback The callback to invoke with the - * event. - * @param {Array} events The events to arpeggiate over. + * @param {Function} callback The callback to invoke with the event. + * @param {Time} interval The time between successive callback calls. */ Tone.Loop = function(){ @@ -60,7 +59,7 @@ define(["Tone/core/Tone", "Tone/event/Event"], function (Tone) { /** * Start the loop at the specified time along the Transport's * timeline. - * @param {Time=} time When to start the Loop. + * @param {TimelinePosition=} time When to start the Loop. * @return {Tone.Loop} this */ Tone.Loop.prototype.start = function(time){ @@ -70,7 +69,7 @@ define(["Tone/core/Tone", "Tone/event/Event"], function (Tone) { /** * Stop the loop at the given time. - * @param {Time=} time When to stop the Arpeggio + * @param {TimelinePosition=} time When to stop the Arpeggio * @return {Tone.Loop} this */ Tone.Loop.prototype.stop = function(time){ @@ -80,7 +79,7 @@ define(["Tone/core/Tone", "Tone/event/Event"], function (Tone) { /** * Cancel all scheduled events greater than or equal to the given time - * @param {Time} [time=0] The time after which events will be cancel. + * @param {TimelinePosition} [time=0] The time after which events will be cancel. * @return {Tone.Loop} this */ Tone.Loop.prototype.cancel = function(time){ diff --git a/Tone/event/Part.js b/Tone/event/Part.js index 768e1de69..124b725f4 100644 --- a/Tone/event/Part.js +++ b/Tone/event/Part.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Transport"], function (Tone) { +define(["Tone/core/Tone", "Tone/event/Event", "Tone/type/Type", "Tone/core/Transport"], function (Tone) { "use strict"; @@ -138,7 +138,7 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans /** * Start the part at the given time. - * @param {Time} time When to start the part. + * @param {TransportTime} time When to start the part. * @param {Time=} offset The offset from the start of the part * to begin playing at. * @return {Tone.Part} this @@ -146,7 +146,11 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans Tone.Part.prototype.start = function(time, offset){ var ticks = this.toTicks(time); if (this._state.getStateAtTime(ticks) !== Tone.State.Started){ - offset = this.defaultArg(offset, 0); + if (this._loop){ + offset = this.defaultArg(offset, this._loopStart); + } else { + offset = this.defaultArg(offset, 0); + } offset = this.toTicks(offset); this._state.addEvent({ "state" : Tone.State.Started, @@ -176,11 +180,14 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans //start it on the next loop ticks += this._getLoopDuration(); } - event.start(ticks + "i"); + event.start(Tone.TransportTime(ticks,"i")); + } else if (event.startOffset < this._loopStart && event.startOffset >= offset) { + event.loop = false; + event.start(Tone.TransportTime(ticks,"i")); } } else { if (event.startOffset >= offset){ - event.start(ticks + "i"); + event.start(Tone.TransportTime(ticks,"i")); } } }; @@ -206,17 +213,16 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans /** * Stop the part at the given time. - * @param {Time} time When to stop the part. + * @param {TimelinePosition} time When to stop the part. * @return {Tone.Part} this */ Tone.Part.prototype.stop = function(time){ var ticks = this.toTicks(time); - if (this._state.getStateAtTime(ticks) === Tone.State.Started){ - this._state.setStateAtTime(Tone.State.Stopped, ticks); - this._forEach(function(event){ - event.stop(time); - }); - } + this._state.cancel(ticks); + this._state.setStateAtTime(Tone.State.Stopped, ticks); + this._forEach(function(event){ + event.stop(time); + }); return this; }; @@ -231,17 +237,17 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans * * part.at("2m", "C2"); //set the value at "2m" to C2. * //if an event didn't exist at that time, it will be created. - * @param {Time} time the time of the event to get or set + * @param {TransportTime} time The time of the event to get or set. * @param {*=} value If a value is passed in, the value of the * event at the given time will be set to it. * @return {Tone.Event} the event at the time */ Tone.Part.prototype.at = function(time, value){ - time = this.toTicks(time); - var tickTime = this.ticksToSeconds(1); + time = Tone.TransportTime(time); + var tickTime = Tone.Time(1, "i").toSeconds(); for (var i = 0; i < this._events.length; i++){ var event = this._events[i]; - if (Math.abs(time - event.startOffset) < tickTime){ + if (Math.abs(time.toTicks() - event.startOffset) < tickTime){ if (!this.isUndef(value)){ event.value = value; } @@ -250,7 +256,7 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans } //if there was no event at that time, create one if (!this.isUndef(value)){ - this.add(time + "i", value); + this.add(time, value); //return the new event return this._events[this._events.length - 1]; } else { @@ -362,7 +368,7 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans /** * Cancel scheduled state change events: i.e. "start" and "stop". - * @param {Time} after The time after which to cancel the scheduled events. + * @param {TimelinePosition} after The time after which to cancel the scheduled events. * @return {Tone.Part} this */ Tone.Part.prototype.cancel = function(after){ @@ -499,12 +505,12 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans * The loopEnd point determines when it will * loop if Tone.Part.loop is true. * @memberOf Tone.Part# - * @type {Boolean|Positive} + * @type {TransportTime} * @name loopEnd */ Object.defineProperty(Tone.Part.prototype, "loopEnd", { get : function(){ - return this.toNotation(this._loopEnd + "i"); + return Tone.TransportTime(this._loopEnd, "i").toNotation(); }, set : function(loopEnd){ this._loopEnd = this.toTicks(loopEnd); @@ -521,12 +527,12 @@ define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Trans * The loopStart point determines when it will * loop if Tone.Part.loop is true. * @memberOf Tone.Part# - * @type {Boolean|Positive} + * @type {TransportTime} * @name loopStart */ Object.defineProperty(Tone.Part.prototype, "loopStart", { get : function(){ - return this.toNotation(this._loopStart + "i"); + return Tone.TransportTime(this._loopStart, "i").toNotation(); }, set : function(loopStart){ this._loopStart = this.toTicks(loopStart); diff --git a/Tone/event/Pattern.js b/Tone/event/Pattern.js index 4733d3aaf..f1ffdb639 100644 --- a/Tone/event/Pattern.js +++ b/Tone/event/Pattern.js @@ -11,11 +11,11 @@ define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], functi * @extends {Tone.Loop} * @param {Function} callback The callback to invoke with the * event. - * @param {Array} events The events to arpeggiate over. + * @param {Array} values The values to arpeggiate over. */ Tone.Pattern = function(){ - var options = this.optionsObject(arguments, ["callback", "events", "pattern"], Tone.Pattern.defaults); + var options = this.optionsObject(arguments, ["callback", "values", "pattern"], Tone.Pattern.defaults); Tone.Loop.call(this, options); @@ -25,7 +25,7 @@ define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], functi * @private */ this._pattern = new Tone.CtrlPattern({ - "values" : options.events, + "values" : options.values, "type" : options.pattern, "index" : options.index }); @@ -41,7 +41,7 @@ define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], functi */ Tone.Pattern.defaults = { "pattern" : Tone.CtrlPattern.Type.Up, - "events" : [], + "values" : [], }; /** @@ -55,7 +55,7 @@ define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], functi }; /** - * The current index in the events array. + * The current index in the values array. * @memberOf Tone.Pattern# * @type {Positive} * @name index @@ -73,9 +73,9 @@ define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], functi * The array of events. * @memberOf Tone.Pattern# * @type {Array} - * @name events + * @name values */ - Object.defineProperty(Tone.Pattern.prototype, "events", { + Object.defineProperty(Tone.Pattern.prototype, "values", { get : function(){ return this._pattern.values; }, diff --git a/Tone/event/Sequence.js b/Tone/event/Sequence.js index bd3653ba0..eb759db27 100644 --- a/Tone/event/Sequence.js +++ b/Tone/event/Sequence.js @@ -77,7 +77,7 @@ define(["Tone/core/Tone", "Tone/event/Part", "Tone/core/Transport"], function (T */ Object.defineProperty(Tone.Sequence.prototype, "subdivision", { get : function(){ - return this.toNotation(this._subdivision + "i"); + return Tone.Time(this._subdivision, "i").toNotation(); } }); @@ -118,8 +118,8 @@ define(["Tone/core/Tone", "Tone/event/Part", "Tone/core/Transport"], function (T } if (this.isArray(value)){ //make a subsequence and add that to the sequence - var subSubdivision = Math.round(this._subdivision / value.length) + "i"; - value = new Tone.Sequence(this._tick.bind(this), value, subSubdivision); + var subSubdivision = Math.round(this._subdivision / value.length); + value = new Tone.Sequence(this._tick.bind(this), value, Tone.Time(subSubdivision, "i")); } Tone.Part.prototype.add.call(this, this._indexTime(index), value); return this; @@ -142,10 +142,10 @@ define(["Tone/core/Tone", "Tone/event/Part", "Tone/core/Transport"], function (T * @private */ Tone.Sequence.prototype._indexTime = function(index){ - if (this.isTicks(index)){ + if (index instanceof Tone.TransportTime){ return index; } else { - return (index * this._subdivision + this.startOffset) + "i"; + return Tone.TransportTime(index * this._subdivision + this.startOffset, "i"); } }; diff --git a/Tone/instrument/AMSynth.js b/Tone/instrument/AMSynth.js index d8415e800..41479a664 100644 --- a/Tone/instrument/AMSynth.js +++ b/Tone/instrument/AMSynth.js @@ -1,13 +1,13 @@ -define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/signal/Signal", "Tone/signal/Multiply", +define(["Tone/core/Tone", "Tone/instrument/Synth", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic", "Tone/signal/AudioToGain"], function(Tone){ "use strict"; /** - * @class AMSynth uses the output of one Tone.MonoSynth to modulate the - * amplitude of another Tone.MonoSynth. The harmonicity (the ratio between - * the two signals) affects the timbre of the output signal the most. + * @class AMSynth uses the output of one Tone.Synth to modulate the + * amplitude of another Tone.Synth. The harmonicity (the ratio between + * the two signals) affects the timbre of the output signal greatly. * Read more about Amplitude Modulation Synthesis on * [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm). * @@ -27,17 +27,42 @@ function(Tone){ /** * The carrier voice. - * @type {Tone.MonoSynth} + * @type {Tone.Synth} */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this._carrier = new Tone.Synth(); + this._carrier.volume.value = -10; + + /** + * The carrier's oscillator + * @type {Tone.Oscillator} + */ + this.oscillator = this._carrier.oscillator; + + /** + * The carrier's envelope + * @type {Tone.Oscillator} + */ + this.envelope = this._carrier.envelope.set(options.envelope); /** * The modulator voice. - * @type {Tone.MonoSynth} + * @type {Tone.Synth} + */ + this._modulator = new Tone.Synth(); + this._modulator.volume.value = -10; + + /** + * The modulator's oscillator which is applied + * to the amplitude of the oscillator + * @type {Tone.Oscillator} */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this.modulation = this._modulator.oscillator.set(options.modulation); + + /** + * The modulator's envelope + * @type {Tone.Oscillator} + */ + this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope); /** * The frequency. @@ -46,6 +71,13 @@ function(Tone){ */ this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + /** + * The detune in cents + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + /** * Harmonicity is the ratio between the two voices. A harmonicity of * 1 is no change. Harmonicity = 2 means a change of an octave. @@ -73,11 +105,12 @@ function(Tone){ this._modulationNode = this.context.createGain(); //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.modulator.chain(this._modulationScale, this._modulationNode.gain); - this.carrier.chain(this._modulationNode, this.output); - this._readOnly(["carrier", "modulator", "frequency", "harmonicity"]); + this.frequency.connect(this._carrier.frequency); + this.frequency.chain(this.harmonicity, this._modulator.frequency); + this.detune.fan(this._carrier.detune, this._modulator.detune); + this._modulator.chain(this._modulationScale, this._modulationNode.gain); + this._carrier.chain(this._modulationNode, this.output); + this._readOnly(["frequency", "harmonicity", "oscillator", "envelope", "modulation", "modulationEnvelope", "detune"]); }; Tone.extend(Tone.AMSynth, Tone.Monophonic); @@ -88,55 +121,24 @@ function(Tone){ */ 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, - "baseFrequency" : 20000, - "octaves" : 0 - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, + "detune" : 0, + "oscillator" : { + "type" : "sine" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.01, + "sustain" : 1, + "release" : 0.5 + }, + "moduation" : { + "type" : "square" }, - "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, - "baseFrequency" : 20, - "octaves" : 6 - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, + "modulationEnvelope" : { + "attack" : 0.5, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 } }; @@ -152,10 +154,8 @@ function(Tone){ //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); + this.envelope.triggerAttack(time, velocity); + this.modulationEnvelope.triggerAttack(time, velocity); return this; }; @@ -167,8 +167,8 @@ function(Tone){ * @returns {Tone.AMSynth} this */ Tone.AMSynth.prototype._triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + this.envelope.triggerRelease(time); + this.modulationEnvelope.triggerRelease(time); return this; }; @@ -178,19 +178,25 @@ function(Tone){ */ Tone.AMSynth.prototype.dispose = function(){ Tone.Monophonic.prototype.dispose.call(this); - this._writable(["carrier", "modulator", "frequency", "harmonicity"]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; + this._writable(["frequency", "harmonicity", "oscillator", "envelope", "modulation", "modulationEnvelope", "detune"]); + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; this.frequency.dispose(); this.frequency = null; + this.detune.dispose(); + this.detune = null; this.harmonicity.dispose(); this.harmonicity = null; this._modulationScale.dispose(); this._modulationScale = null; this._modulationNode.disconnect(); this._modulationNode = null; + this.oscillator = null; + this.envelope = null; + this.modulationEnvelope = null; + this.modulation = null; return this; }; diff --git a/Tone/instrument/DuoSynth.js b/Tone/instrument/DuoSynth.js index e8a72ab56..93ace21f2 100644 --- a/Tone/instrument/DuoSynth.js +++ b/Tone/instrument/DuoSynth.js @@ -70,13 +70,6 @@ function(Tone){ "value" : options.vibratoAmount }); - /** - * the delay before the vibrato starts - * @type {number} - * @private - */ - this._vibratoDelay = this.toSeconds(options.vibratoDelay); - /** * the frequency control * @type {Frequency} @@ -115,7 +108,6 @@ function(Tone){ Tone.DuoSynth.defaults = { "vibratoAmount" : 0.5, "vibratoRate" : 5, - "vibratoDelay" : 1, "harmonicity" : 1.5, "voice0" : { "volume" : -10, diff --git a/Tone/instrument/FMSynth.js b/Tone/instrument/FMSynth.js index 3689bb775..fcaec31e1 100644 --- a/Tone/instrument/FMSynth.js +++ b/Tone/instrument/FMSynth.js @@ -1,11 +1,11 @@ -define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic"], +define(["Tone/core/Tone", "Tone/instrument/Synth", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic"], function(Tone){ "use strict"; /** - * @class FMSynth is composed of two Tone.MonoSynths where one Tone.MonoSynth modulates - * the frequency of a second Tone.MonoSynth. A lot of spectral content + * @class FMSynth is composed of two Tone.Synths where one Tone.Synth modulates + * the frequency of a second Tone.Synth. A lot of spectral content * can be explored using the modulationIndex parameter. Read more about * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm). * @@ -25,17 +25,44 @@ function(Tone){ /** * The carrier voice. - * @type {Tone.MonoSynth} + * @type {Tone.Synth} */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this._carrier = new Tone.Synth(options.carrier); + this._carrier.volume.value = -10; + + + /** + * The carrier's oscillator + * @type {Tone.Oscillator} + */ + this.oscillator = this._carrier.oscillator; + + /** + * The carrier's envelope + * @type {Tone.Oscillator} + */ + this.envelope = this._carrier.envelope.set(options.envelope); /** * The modulator voice. - * @type {Tone.MonoSynth} + * @type {Tone.Synth} + */ + this._modulator = new Tone.Synth(options.modulator); + this._modulator.volume.value = -10; + + + /** + * The modulator's oscillator which is applied + * to the amplitude of the oscillator + * @type {Tone.Oscillator} */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this.modulation = this._modulator.oscillator.set(options.modulation); + + /** + * The modulator's envelope + * @type {Tone.Oscillator} + */ + this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope); /** * The frequency control. @@ -44,6 +71,13 @@ function(Tone){ */ this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + /** + * The detune in cents + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + /** * Harmonicity is the ratio between the two voices. A harmonicity of * 1 is no change. Harmonicity = 2 means a change of an octave. @@ -74,14 +108,15 @@ function(Tone){ 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.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.detune.fan(this._carrier.detune, this._modulator.detune); + this._modulator.connect(this._modulationNode.gain); this._modulationNode.gain.value = 0; - this._modulationNode.connect(this.carrier.frequency); - this.carrier.connect(this.output); - this._readOnly(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]); + this._modulationNode.connect(this._carrier.frequency); + this._carrier.connect(this.output); + this._readOnly(["frequency", "harmonicity", "modulationIndex", "oscillator", "envelope", "modulation", "modulationEnvelope", "detune"]); }; Tone.extend(Tone.FMSynth, Tone.Monophonic); @@ -93,47 +128,24 @@ function(Tone){ 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, - "baseFrequency" : 200, - "octaves" : 8 - } + "detune" : 0, + "oscillator" : { + "type" : "sine" + }, + "envelope" : { + "attack" : 0.01, + "decay" : 0.01, + "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 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.0, - "sustain" : 1, - "release" : 0.5, - "baseFrequency" : 600, - "octaves" : 5 - } + "moduation" : { + "type" : "square" + }, + "modulationEnvelope" : { + "attack" : 0.5, + "decay" : 0.0, + "sustain" : 1, + "release" : 0.5 } }; @@ -146,13 +158,10 @@ function(Tone){ * @private */ Tone.FMSynth.prototype._triggerEnvelopeAttack = function(time, velocity){ - //the port glide time = this.toSeconds(time); //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); - this.carrier.filterEnvelope.triggerAttack(time); - this.modulator.filterEnvelope.triggerAttack(time); + this.envelope.triggerAttack(time, velocity); + this.modulationEnvelope.triggerAttack(time); return this; }; @@ -164,8 +173,9 @@ function(Tone){ * @private */ Tone.FMSynth.prototype._triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + time = this.toSeconds(time); + this.envelope.triggerRelease(time); + this.modulationEnvelope.triggerRelease(time); return this; }; @@ -175,19 +185,25 @@ function(Tone){ */ Tone.FMSynth.prototype.dispose = function(){ Tone.Monophonic.prototype.dispose.call(this); - this._writable(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; + this._writable(["frequency", "harmonicity", "modulationIndex", "oscillator", "envelope", "modulation", "modulationEnvelope", "detune"]); + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; this.frequency.dispose(); this.frequency = null; + this.detune.dispose(); + this.detune = null; this.modulationIndex.dispose(); this.modulationIndex = null; this.harmonicity.dispose(); this.harmonicity = null; this._modulationNode.disconnect(); this._modulationNode = null; + this.oscillator = null; + this.envelope = null; + this.modulationEnvelope = null; + this.modulation = null; return this; }; diff --git a/Tone/instrument/Instrument.js b/Tone/instrument/Instrument.js index 1900f4549..310732be8 100644 --- a/Tone/instrument/Instrument.js +++ b/Tone/instrument/Instrument.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ +define(["Tone/core/Tone", "Tone/type/Type"], function(Tone){ "use strict"; diff --git a/Tone/instrument/DrumSynth.js b/Tone/instrument/MembraneSynth.js similarity index 78% rename from Tone/instrument/DrumSynth.js rename to Tone/instrument/MembraneSynth.js index 4a562ec8b..81cb93111 100644 --- a/Tone/instrument/DrumSynth.js +++ b/Tone/instrument/MembraneSynth.js @@ -5,11 +5,11 @@ function(Tone){ "use strict"; /** - * @class Tone.DrumSynth makes kick and tom sounds using a single oscillator + * @class Tone.MembraneSynth makes kick and tom sounds using a single oscillator * with an amplitude envelope and frequency ramp. A Tone.Oscillator * is routed through a Tone.AmplitudeEnvelope to the output. The drum * quality of the sound comes from the frequency envelope applied - * during during Tone.DrumSynth.triggerAttack(note). The frequency + * during during Tone.MembraneSynth.triggerAttack(note). The frequency * envelope starts at note * .octaves and ramps to * note over the duration of .pitchDecay. * @@ -18,12 +18,12 @@ function(Tone){ * @param {Object} [options] the options available for the synth * see defaults below * @example - * var synth = new Tone.DrumSynth().toMaster(); + * var synth = new Tone.MembraneSynth().toMaster(); * synth.triggerAttackRelease("C2", "8n"); */ - Tone.DrumSynth = function(options){ + Tone.MembraneSynth = function(options){ - options = this.defaultArg(options, Tone.DrumSynth.defaults); + options = this.defaultArg(options, Tone.MembraneSynth.defaults); Tone.Instrument.call(this, options); /** @@ -54,13 +54,13 @@ function(Tone){ this._readOnly(["oscillator", "envelope"]); }; - Tone.extend(Tone.DrumSynth, Tone.Instrument); + Tone.extend(Tone.MembraneSynth, Tone.Instrument); /** * @static * @type {Object} */ - Tone.DrumSynth.defaults = { + Tone.MembraneSynth.defaults = { "pitchDecay" : 0.05, "octaves" : 10, "oscillator" : { @@ -81,11 +81,11 @@ function(Tone){ * @param {Frequency} note the note * @param {Time} [time=now] the time, if not given is now * @param {number} [velocity=1] velocity defaults to 1 - * @returns {Tone.DrumSynth} this + * @returns {Tone.MembraneSynth} this * @example * kick.triggerAttack(60); */ - Tone.DrumSynth.prototype.triggerAttack = function(note, time, velocity) { + Tone.MembraneSynth.prototype.triggerAttack = function(note, time, velocity) { time = this.toSeconds(time); note = this.toFrequency(note); var maxNote = note * this.octaves; @@ -99,18 +99,18 @@ function(Tone){ * Trigger the release portion of the note. * * @param {Time} [time=now] the time the note will release - * @returns {Tone.DrumSynth} this + * @returns {Tone.MembraneSynth} this */ - Tone.DrumSynth.prototype.triggerRelease = function(time){ + Tone.MembraneSynth.prototype.triggerRelease = function(time){ this.envelope.triggerRelease(time); return this; }; /** * Clean up. - * @returns {Tone.DrumSynth} this + * @returns {Tone.MembraneSynth} this */ - Tone.DrumSynth.prototype.dispose = function(){ + Tone.MembraneSynth.prototype.dispose = function(){ Tone.Instrument.prototype.dispose.call(this); this._writable(["oscillator", "envelope"]); this.oscillator.dispose(); @@ -120,5 +120,5 @@ function(Tone){ return this; }; - return Tone.DrumSynth; + return Tone.MembraneSynth; }); \ No newline at end of file diff --git a/Tone/instrument/MetalSynth.js b/Tone/instrument/MetalSynth.js new file mode 100644 index 000000000..4266da59c --- /dev/null +++ b/Tone/instrument/MetalSynth.js @@ -0,0 +1,274 @@ +define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/source/FMOscillator", "Tone/component/Filter", + "Tone/component/FrequencyEnvelope", "Tone/component/AmplitudeEnvelope", "Tone/core/Gain", "Tone/signal/Scale", "Tone/signal/Multiply"], + function (Tone) { + + /** + * Inharmonic ratio of frequencies based on the Roland TR-808 + * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model + * @private + * @static + * @type {Array} + */ + var inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.630, 3.897]; + + /** + * @class A highly inharmonic and spectrally complex source with a highpass filter + * and amplitude envelope which is good for making metalophone sounds. Based + * on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic). + * Inspiration from [Sound on Sound](http://www.soundonsound.com/sos/jul02/articles/synthsecrets0702.asp). + * + * @constructor + * @extends {Tone.Instrument} + * @param {Object} [options] The options availble for the synth + * see defaults below + */ + Tone.MetalSynth = function(options){ + + options = this.defaultArg(options, Tone.MetalSynth.defaults); + Tone.Instrument.call(this, options); + + /** + * The frequency of the cymbal + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + + /** + * The array of FMOscillators + * @type {Array} + * @private + */ + this._oscillators = []; + + /** + * The frequency multipliers + * @type {Array} + * @private + */ + this._freqMultipliers = []; + + /** + * The amplitude for the body + * @type {Tone.Gain} + * @private + */ + this._amplitue = new Tone.Gain(0).connect(this.output); + + /** + * highpass the output + * @type {Tone.Filter} + * @private + */ + this._highpass = new Tone.Filter({ + "type" : "highpass", + "Q" : 0 + }).connect(this._amplitue); + + /** + * The number of octaves the highpass + * filter frequency ramps + * @type {Number} + * @private + */ + this._octaves = options.octaves; + + /** + * Scale the body envelope + * for the bandpass + * @type {Tone.Scale} + * @private + */ + this._filterFreqScaler = new Tone.Scale(options.resonance, 7000); + + /** + * The envelope which is connected both to the + * amplitude and highpass filter's cutoff frequency + * @type {Tone.Envelope} + */ + this.envelope = new Tone.Envelope({ + "attack" : options.envelope.attack, + "attackCurve" : "exponential", + "decay" : options.envelope.decay, + "sustain" : 0, + "release" : options.envelope.release, + }).chain(this._filterFreqScaler, this._highpass.frequency); + this.envelope.connect(this._amplitue.gain); + + for (var i = 0; i < inharmRatios.length; i++){ + var osc = new Tone.FMOscillator({ + "type" : "square", + "modulationType" : "square", + "harmonicity" : options.harmonicity, + "modulationIndex" : options.modulationIndex + }); + osc.connect(this._highpass).start(0); + this._oscillators[i] = osc; + + var mult = new Tone.Multiply(inharmRatios[i]); + this._freqMultipliers[i] = mult; + this.frequency.chain(mult, osc.frequency); + } + + //set the octaves + this.octaves = options.octaves; + + }; + + Tone.extend(Tone.MetalSynth, Tone.Instrument); + + /** + * default values + * @static + * @const + * @type {Object} + */ + Tone.MetalSynth.defaults = { + "frequency" : 200, + "envelope" : { + "attack" : 0.0015, + "decay" : 1.4, + "release" : 0.2 + }, + "harmonicity" : 5.1, + "modulationIndex" : 32, + "resonance" : 4000, + "octaves" : 1.5 + }; + + /** + * Trigger the attack. + * @param {Time} time When the attack should be triggered. + * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at. + * @return {Tone.MetalSynth} this + */ + Tone.MetalSynth.prototype.triggerAttack = function(time, vel) { + time = this.toSeconds(time); + vel = this.defaultArg(vel, 1); + this.envelope.triggerAttack(time, vel); + return this; + }; + + /** + * Trigger the release of the envelope. + * @param {Time} time When the release should be triggered. + * @return {Tone.MetalSynth} this + */ + Tone.MetalSynth.prototype.triggerRelease = function(time) { + time = this.toSeconds(time); + this.envelope.triggerRelease(time); + return this; + }; + + /** + * Trigger the attack and release of the envelope after the given + * duration. + * @param {Time} duration The duration before triggering the release + * @param {Time} time When the attack should be triggered. + * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at. + * @return {Tone.MetalSynth} this + */ + Tone.MetalSynth.prototype.triggerAttackRelease = function(duration, time, velocity) { + var now = this.now(); + time = this.toSeconds(time, now); + duration = this.toSeconds(duration, now); + this.triggerAttack(time, velocity); + this.triggerRelease(time + duration); + return this; + }; + + /** + * The modulationIndex of the oscillators which make up the source. + * see Tone.FMOscillator.modulationIndex + * @memberOf Tone.MetalSynth# + * @type {Positive} + * @name modulationIndex + */ + Object.defineProperty(Tone.MetalSynth.prototype, "modulationIndex", { + get : function(){ + return this._oscillators[0].modulationIndex.value; + }, + set : function(val){ + for (var i = 0; i < this._oscillators.length; i++){ + this._oscillators[i].modulationIndex.value = val; + } + } + }); + + /** + * The harmonicity of the oscillators which make up the source. + * see Tone.FMOscillator.harmonicity + * @memberOf Tone.MetalSynth# + * @type {Positive} + * @name harmonicity + */ + Object.defineProperty(Tone.MetalSynth.prototype, "harmonicity", { + get : function(){ + return this._oscillators[0].harmonicity.value; + }, + set : function(val){ + for (var i = 0; i < this._oscillators.length; i++){ + this._oscillators[i].harmonicity.value = val; + } + } + }); + + /** + * The frequency of the highpass filter attached to the envelope + * @memberOf Tone.MetalSynth# + * @type {Frequency} + * @name resonance + */ + Object.defineProperty(Tone.MetalSynth.prototype, "resonance", { + get : function(){ + return this._filterFreqScaler.min; + }, + set : function(val){ + this._filterFreqScaler.min = val; + this.octaves = this._octaves; + } + }); + + /** + * The number of octaves above the "resonance" frequency + * that the filter ramps during the attack/decay envelope + * @memberOf Tone.MetalSynth# + * @type {Number} + * @name octaves + */ + Object.defineProperty(Tone.MetalSynth.prototype, "octaves", { + get : function(){ + return this._octaves; + }, + set : function(octs){ + this._octaves = octs; + this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, octs); + } + }); + + /** + * Clean up + * @returns {Tone.MetalSynth} this + */ + Tone.MetalSynth.prototype.dispose = function(){ + Tone.Instrument.prototype.dispose.call(this); + for (var i = 0; i < this._oscillators.length; i++){ + this._oscillators[i].dispose(); + this._freqMultipliers[i].dispose(); + } + this._oscillators = null; + this._freqMultipliers = null; + this.frequency.dispose(); + this.frequency = null; + this._filterFreqScaler.dispose(); + this._filterFreqScaler = null; + this._amplitue.dispose(); + this._amplitue = null; + this.envelope.dispose(); + this.envelope = null; + this._highpass.dispose(); + this._highpass = null; + }; + + return Tone.MetalSynth; +}); \ No newline at end of file diff --git a/Tone/instrument/NoiseSynth.js b/Tone/instrument/NoiseSynth.js index 785a2d438..dbb30b50d 100644 --- a/Tone/instrument/NoiseSynth.js +++ b/Tone/instrument/NoiseSynth.js @@ -32,18 +32,6 @@ function(Tone){ */ this.noise = new Tone.Noise(); - /** - * The filter. - * @type {Tone.Filter} - */ - this.filter = new Tone.Filter(options.filter); - - /** - * The filter envelope. - * @type {Tone.FrequencyEnvelope} - */ - this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); - /** * The amplitude envelope. * @type {Tone.AmplitudeEnvelope} @@ -51,12 +39,10 @@ function(Tone){ this.envelope = new Tone.AmplitudeEnvelope(options.envelope); //connect the noise to the output - this.noise.chain(this.filter, this.envelope, this.output); + this.noise.chain(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"]); + this._readOnly(["noise", "envelope"]); }; Tone.extend(Tone.NoiseSynth, Tone.Instrument); @@ -70,23 +56,10 @@ function(Tone){ "noise" : { "type" : "white" }, - "filter" : { - "Q" : 6, - "type" : "highpass", - "rolloff" : -24 - }, "envelope" : { "attack" : 0.005, "decay" : 0.1, "sustain" : 0.0, - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.2, - "sustain" : 0, - "release" : 2, - "baseFrequency" : 20, - "octaves" : 5, } }; @@ -102,7 +75,6 @@ function(Tone){ Tone.NoiseSynth.prototype.triggerAttack = function(time, velocity){ //the envelopes this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); return this; }; @@ -113,7 +85,6 @@ function(Tone){ */ Tone.NoiseSynth.prototype.triggerRelease = function(time){ this.envelope.triggerRelease(time); - this.filterEnvelope.triggerRelease(time); return this; }; @@ -138,15 +109,11 @@ function(Tone){ */ Tone.NoiseSynth.prototype.dispose = function(){ Tone.Instrument.prototype.dispose.call(this); - this._writable(["noise", "filter", "filterEnvelope", "envelope"]); + this._writable(["noise", "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; }; diff --git a/Tone/instrument/PolySynth.js b/Tone/instrument/PolySynth.js index 5a30eee55..75e4928aa 100644 --- a/Tone/instrument/PolySynth.js +++ b/Tone/instrument/PolySynth.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/source/Source"], +define(["Tone/core/Tone", "Tone/instrument/Synth", "Tone/source/Source"], function(Tone){ "use strict"; @@ -13,11 +13,11 @@ function(Tone){ * @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. + * @param {function} [voice=Tone.Synth] The constructor of the voices + * uses Tone.Synth by default. * @example - * //a polysynth composed of 6 Voices of MonoSynth - * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster(); + * //a polysynth composed of 6 Voices of Synth + * var synth = new Tone.PolySynth(6, Tone.Synth).toMaster(); * //set the attributes using the set interface * synth.set("detune", -1200); * //play a chord @@ -28,6 +28,10 @@ function(Tone){ Tone.Instrument.call(this); var options = this.optionsObject(arguments, ["polyphony", "voice"], Tone.PolySynth.defaults); + options = this.defaultArg(options, Tone.Instrument.defaults); + + //max polyphony + options.polyphony = Math.min(Tone.PolySynth.MAX_POLYPHONY, options.polyphony); /** * the array of voices @@ -36,36 +40,38 @@ function(Tone){ this.voices = new Array(options.polyphony); /** - * If there are no more voices available, - * should an active voice be stolen to play the new note? - * @type {Boolean} - */ - this.stealVoices = true; - - /** - * the queue of free voices + * The queue of voices with data about last trigger + * and the triggered note * @private * @type {Array} */ - this._freeVoices = []; + this._triggers = new Array(options.polyphony); /** - * keeps track of which notes are down - * @private - * @type {Object} + * The detune in cents + * @type {Cents} + * @signal */ - this._activeVoices = {}; + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + this._readOnly("detune"); //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); + if (v.hasOwnProperty("detune")){ + this.detune.connect(v.detune); + } + this._triggers[i] = { + release : -1, + note : null, + voice : v + }; } - //make a copy of the voices - this._freeVoices = this.voices.slice(0); - //get the prototypes and properties + //set the volume initially + this.volume.value = options.volume; }; Tone.extend(Tone.PolySynth, Tone.Instrument); @@ -78,7 +84,9 @@ function(Tone){ */ Tone.PolySynth.defaults = { "polyphony" : 4, - "voice" : Tone.MonoSynth + "volume" : 0, + "detune" : 0, + "voice" : Tone.Synth }; /** @@ -96,23 +104,21 @@ function(Tone){ if (!Array.isArray(notes)){ notes = [notes]; } + time = this.toSeconds(time); for (var i = 0; i < notes.length; i++){ var val = notes[i]; - var stringified = JSON.stringify(val); - //retrigger the same note if possible - if (this._activeVoices.hasOwnProperty(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; - } else if (this.stealVoices){ //steal a voice - //take the first voice - for (var voiceName in this._activeVoices){ - this._activeVoices[voiceName].triggerAttack(val, time, velocity); - break; + //trigger the oldest voice + var oldest = this._triggers[0]; + var oldestIndex = 0; + for (var j = 1; j < this._triggers.length; j++){ + if (this._triggers[j].release < oldest.release){ + oldest = this._triggers[j]; + oldestIndex = j; } } + oldest.release = Infinity; + oldest.note = JSON.stringify(val); + oldest.voice.triggerAttack(val, time, velocity); } return this; }; @@ -129,11 +135,21 @@ function(Tone){ * @example * //trigger a chord for a duration of a half note * poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n"); + * @example + * //can pass in an array of durations as well + * poly.triggerAttackRelease(["Eb3", "G4", "C5"], ["2n", "4n", "4n"]); */ Tone.PolySynth.prototype.triggerAttackRelease = function(notes, duration, time, velocity){ time = this.toSeconds(time); this.triggerAttack(notes, time, velocity); - this.triggerRelease(notes, time + this.toSeconds(duration)); + if (this.isArray(duration) && this.isArray(notes)){ + for (var i = 0; i < notes.length; i++){ + var d = duration[Math.min(i, duration.length - 1)]; + this.triggerRelease(notes[i], time + this.toSeconds(d)); + } + } else { + this.triggerRelease(notes, time + this.toSeconds(duration)); + } return this; }; @@ -151,15 +167,16 @@ function(Tone){ if (!Array.isArray(notes)){ notes = [notes]; } + time = this.toSeconds(time); for (var i = 0; i < notes.length; i++){ //get the voice var stringified = JSON.stringify(notes[i]); - var voice = this._activeVoices[stringified]; - if (voice){ - voice.triggerRelease(time); - this._freeVoices.push(voice); - delete this._activeVoices[stringified]; - voice = null; + for (var v = 0; v < this._triggers.length; v++){ + var desc = this._triggers[v]; + if (desc.note === stringified && desc.release > time){ + desc.voice.triggerRelease(time); + desc.release = time; + } } } return this; @@ -207,8 +224,13 @@ function(Tone){ * @return {Tone.PolySynth} this */ Tone.PolySynth.prototype.releaseAll = function(time){ - for (var i = 0; i < this.voices.length; i++){ - this.voices[i].triggerRelease(time); + time = this.toSeconds(time); + for (var i = 0; i < this._triggers.length; i++){ + var desc = this._triggers[i]; + if (desc.release > time){ + desc.release = time; + desc.voice.triggerRelease(time); + } } return this; }; @@ -223,11 +245,21 @@ function(Tone){ this.voices[i].dispose(); this.voices[i] = null; } + this._writable("detune"); + this.detune.dispose(); + this.detune = null; this.voices = null; - this._activeVoices = null; - this._freeVoices = null; + this._triggers = null; return this; }; + /** + * The maximum number of notes that can be allocated + * to a polysynth. + * @type {Number} + * @static + */ + Tone.PolySynth.MAX_POLYPHONY = 20; + return Tone.PolySynth; }); \ No newline at end of file diff --git a/Tone/instrument/Sampler.js b/Tone/instrument/Sampler.js index 964d2de01..48298d741 100644 --- a/Tone/instrument/Sampler.js +++ b/Tone/instrument/Sampler.js @@ -1,92 +1,43 @@ -define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/component/FrequencyEnvelope", - "Tone/component/Filter", "Tone/instrument/Instrument"], +define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/instrument/Instrument"], function(Tone){ "use strict"; /** - * @class A sampler instrument which plays an audio buffer - * through an amplitude envelope and a filter envelope. The sampler takes - * an Object in the constructor which maps a sample name to the URL - * of the sample. Nested Objects will be flattened and can be accessed using - * a dot notation (see the example). - * + * @class Sampler wraps Tone.Player in an AmplitudeEnvelope. * * @constructor * @extends {Tone.Instrument} - * @param {Object|string} urls the urls of the audio file - * @param {Object} [options] the options object for the synth + * @param {String} url the url of the audio file + * @param {Function=} onload The callback to invoke when the sample is loaded. * @example - * var sampler = new Sampler({ - * A : { - * 1 : "./audio/casio/A1.mp3", - * 2 : "./audio/casio/A2.mp3", - * }, - * "B.1" : "./audio/casio/B1.mp3", + * var sampler = new Sampler("./audio/casio/A1.mp3", function(){ + * //repitch the sample down a half step + * sampler.triggerAttack(-1); * }).toMaster(); - * - * //listen for when all the samples have loaded - * Tone.Buffer.onload = function(){ - * sampler.triggerAttack("A.1", time, velocity); - * }; */ - Tone.Sampler = function(urls, options){ + Tone.Sampler = function(){ - options = this.defaultArg(options, Tone.Sampler.defaults); + var options = this.optionsObject(arguments, ["url", "onload"], Tone.Sampler.defaults); Tone.Instrument.call(this, options); /** * The sample player. * @type {Tone.Player} */ - this.player = new Tone.Player(options.player); + this.player = new Tone.Player(options.url, options.onload); this.player.retrigger = true; - /** - * the buffers - * @type {Object} - * @private - */ - this._buffers = {}; - /** * The amplitude envelope. * @type {Tone.AmplitudeEnvelope} */ this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - /** - * The filter envelope. - * @type {Tone.FrequencyEnvelope} - */ - this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); - - /** - * The name of the current sample. - * @type {string} - * @private - */ - this._sample = options.sample; - - /** - * the private reference to the pitch - * @type {number} - * @private - */ - this._pitch = options.pitch; - - /** - * The filter. - * @type {Tone.Filter} - */ - this.filter = new Tone.Filter(options.filter); - - //connections / setup - this._loadBuffers(urls); - this.pitch = options.pitch; - this.player.chain(this.filter, this.envelope, this.output); - this.filterEnvelope.connect(this.filter.frequency); - this._readOnly(["player", "filterEnvelope", "envelope", "filter"]); + this.player.chain(this.envelope, this.output); + this._readOnly(["player", "envelope"]); + this.loop = options.loop; + this.reverse = options.reverse; }; Tone.extend(Tone.Sampler, Tone.Instrument); @@ -96,92 +47,33 @@ function(Tone){ * @static */ Tone.Sampler.defaults = { - "sample" : 0, - "pitch" : 0, - "player" : { - "loop" : false, - }, + "onload" : Tone.noOp, + "loop" : false, + "reverse" : false, "envelope" : { "attack" : 0.001, "decay" : 0, "sustain" : 1, "release" : 0.1 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.001, - "sustain" : 1, - "release" : 0.5, - "baseFrequency" : 20, - "octaves" : 10, - }, - "filter" : { - "type" : "lowpass" - } - }; - - /** - * load the buffers - * @param {Object} urls the urls - * @private - */ - Tone.Sampler.prototype._loadBuffers = function(urls){ - if (this.isString(urls)){ - this._buffers["0"] = new Tone.Buffer(urls, function(){ - this.sample = "0"; - }.bind(this)); - } else { - urls = this._flattenUrls(urls); - for (var buffName in urls){ - this._sample = buffName; - var urlString = urls[buffName]; - this._buffers[buffName] = new Tone.Buffer(urlString); - } } }; /** - * Flatten an object into a single depth object. - * thanks to https://gist.github.com/penguinboy/762197 - * @param {Object} ob - * @return {Object} - * @private - */ - Tone.Sampler.prototype._flattenUrls = function(ob) { - var toReturn = {}; - for (var i in ob) { - if (!ob.hasOwnProperty(i)) continue; - if (this.isObject(ob[i])) { - var flatObject = this._flattenUrls(ob[i]); - for (var x in flatObject) { - if (!flatObject.hasOwnProperty(x)) continue; - toReturn[i + "." + x] = flatObject[x]; - } - } else { - toReturn[i] = ob[i]; - } - } - return toReturn; - }; - - /** - * Start the sample and simultaneously trigger the envelopes. - * @param {string=} sample The name of the sample to trigger, defaults to - * the last sample used. + * Trigger the start of the sample. + * @param {Interval} [pitch=0] The amount the sample should + * be repitched. * @param {Time} [time=now] The time when the sample should start - * @param {number} [velocity=1] The velocity of the note + * @param {NormalRange} [velocity=1] The velocity of the note * @returns {Tone.Sampler} this * @example - * sampler.triggerAttack("B.1"); + * sampler.triggerAttack(0, "+0.1", 0.5); */ - Tone.Sampler.prototype.triggerAttack = function(name, time, velocity){ + Tone.Sampler.prototype.triggerAttack = function(pitch, time, velocity){ time = this.toSeconds(time); - if (name){ - this.sample = name; - } + pitch = this.defaultArg(pitch, 0); + this.player.playbackRate = this.intervalToFrequencyRatio(pitch); this.player.start(time); this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); return this; }; @@ -196,32 +88,23 @@ function(Tone){ */ Tone.Sampler.prototype.triggerRelease = function(time){ time = this.toSeconds(time); - this.filterEnvelope.triggerRelease(time); this.envelope.triggerRelease(time); this.player.stop(this.toSeconds(this.envelope.release) + time); return this; }; /** - * The name of the sample to trigger. + * If the output sample should loop or not. * @memberOf Tone.Sampler# * @type {number|string} - * @name sample - * @example - * //set the sample to "A.2" for next time the sample is triggered - * sampler.sample = "A.2"; + * @name loop */ - Object.defineProperty(Tone.Sampler.prototype, "sample", { + Object.defineProperty(Tone.Sampler.prototype, "loop", { get : function(){ - return this._sample; + return this.player.loop; }, - set : function(name){ - if (this._buffers.hasOwnProperty(name)){ - this._sample = name; - this.player.buffer = this._buffers[name]; - } else { - throw new Error("Sampler does not have a sample named "+name); - } + set : function(loop){ + this.player.loop = loop; } }); @@ -233,34 +116,10 @@ function(Tone){ */ Object.defineProperty(Tone.Sampler.prototype, "reverse", { get : function(){ - for (var i in this._buffers){ - return this._buffers[i].reverse; - } + return this.player.reverse; }, set : function(rev){ - for (var i in this._buffers){ - this._buffers[i].reverse = rev; - } - } - }); - - /** - * Repitch the sampled note by some interval (measured - * in semi-tones). - * @memberOf Tone.Sampler# - * @type {Interval} - * @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); + this.player.reverse = rev; } }); @@ -270,22 +129,13 @@ function(Tone){ */ Tone.Sampler.prototype.dispose = function(){ Tone.Instrument.prototype.dispose.call(this); - this._writable(["player", "filterEnvelope", "envelope", "filter"]); + this._writable(["player", "envelope"]); this.player.dispose(); - this.filterEnvelope.dispose(); - this.envelope.dispose(); - this.filter.dispose(); this.player = null; - this.filterEnvelope = null; + this.envelope.dispose(); 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; -}); +}); \ No newline at end of file diff --git a/Tone/instrument/SimpleAM.js b/Tone/instrument/SimpleAM.js deleted file mode 100644 index 68d50d7e9..000000000 --- a/Tone/instrument/SimpleAM.js +++ /dev/null @@ -1,170 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/SimpleSynth", "Tone/signal/Signal", "Tone/signal/Multiply", - "Tone/instrument/Monophonic", "Tone/signal/AudioToGain"], -function(Tone){ - - "use strict"; - - /** - * @class AMSynth uses the output of one Tone.SimpleSynth to modulate the - * amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between - * the two signals) affects the timbre of the output signal the most. - * Read more about Amplitude Modulation Synthesis on [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm). - * - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var synth = new Tone.SimpleAM().toMaster(); - * synth.triggerAttackRelease("C4", "8n"); - */ - Tone.SimpleAM = function(options){ - - options = this.defaultArg(options, Tone.SimpleAM.defaults); - Tone.Monophonic.call(this, options); - - /** - * The carrier voice. - * @type {Tone.SimpleSynth} - */ - this.carrier = new Tone.SimpleSynth(options.carrier); - - /** - * The modulator voice. - * @type {Tone.SimpleSynth} - */ - this.modulator = new Tone.SimpleSynth(options.modulator); - - /** - * the frequency control - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); - - /** - * The ratio between the carrier and the modulator frequencies. A value of 1 - * makes both voices in unison, a value of 0.5 puts the modulator an octave below - * the carrier. - * @type {Positive} - * @signal - * @example - * //set the modulator an octave above the carrier frequency - * simpleAM.harmonicity.value = 2; - */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; - - /** - * convert the -1,1 output to 0,1 - * @type {Tone.AudioToGain} - * @private - */ - this._modulationScale = new Tone.AudioToGain(); - - /** - * the node where the modulation happens - * @type {GainNode} - * @private - */ - this._modulationNode = this.context.createGain(); - - //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.modulator.chain(this._modulationScale, this._modulationNode.gain); - this.carrier.chain(this._modulationNode, this.output); - this._readOnly(["carrier", "modulator", "frequency", "harmonicity"]); - }; - - Tone.extend(Tone.SimpleAM, Tone.Monophonic); - - /** - * @static - * @type {Object} - */ - Tone.SimpleAM.defaults = { - "harmonicity" : 3, - "carrier" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.01, - "sustain" : 1, - "release" : 0.5 - }, - }, - "modulator" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "envelope" : { - "attack" : 0.5, - "decay" : 0.1, - "sustain" : 1, - "release" : 0.5 - } - } - }; - - /** - * trigger the attack portion of the note - * - * @param {Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.SimpleAM} this - * @private - */ - Tone.SimpleAM.prototype._triggerEnvelopeAttack = function(time, velocity){ - //the port glide - time = this.toSeconds(time); - //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); - return this; - }; - - /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.SimpleAM} this - * @private - */ - Tone.SimpleAM.prototype._triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); - return this; - }; - - /** - * clean up - * @returns {Tone.SimpleAM} this - */ - Tone.SimpleAM.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - this._writable(["carrier", "modulator", "frequency", "harmonicity"]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); - this.frequency = null; - this.harmonicity.dispose(); - this.harmonicity = null; - this._modulationScale.dispose(); - this._modulationScale = null; - this._modulationNode.disconnect(); - this._modulationNode = null; - return this; - }; - - return Tone.SimpleAM; -}); \ No newline at end of file diff --git a/Tone/instrument/SimpleFM.js b/Tone/instrument/SimpleFM.js deleted file mode 100644 index 3b02d6893..000000000 --- a/Tone/instrument/SimpleFM.js +++ /dev/null @@ -1,177 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/SimpleSynth", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic"], -function(Tone){ - - "use strict"; - - /** - * @class SimpleFM is composed of two Tone.SimpleSynths where one Tone.SimpleSynth modulates - * the frequency of a second Tone.SimpleSynth. A lot of spectral content - * can be explored using the Tone.FMSynth.modulationIndex parameter. Read more about - * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm). - * - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var fmSynth = new Tone.SimpleFM().toMaster(); - * fmSynth.triggerAttackRelease("C4", "8n"); - */ - Tone.SimpleFM = function(options){ - - options = this.defaultArg(options, Tone.SimpleFM.defaults); - Tone.Monophonic.call(this, options); - - /** - * The carrier voice. - * @type {Tone.SimpleSynth} - */ - this.carrier = new Tone.SimpleSynth(options.carrier); - this.carrier.volume.value = -10; - - /** - * The modulator voice. - * @type {Tone.SimpleSynth} - */ - this.modulator = new Tone.SimpleSynth(options.modulator); - this.modulator.volume.value = -10; - - /** - * the frequency control - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); - - /** - * Harmonicity is the ratio between the two voices. A harmonicity of - * 1 is no change. Harmonicity = 2 means a change of an octave. - * @type {Positive} - * @signal - * @example - * //pitch voice1 an octave below voice0 - * synth.harmonicity.value = 0.5; - */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; - - /** - * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the - * ratio of the frequency of the modulating signal (mf) to the amplitude of the - * modulating signal (ma) -- as in ma/mf. - * @type {Positive} - * @signal - */ - this.modulationIndex = new Tone.Multiply(options.modulationIndex); - this.modulationIndex.units = Tone.Type.Positive; - - /** - * the node where the modulation happens - * @type {GainNode} - * @private - */ - this._modulationNode = this.context.createGain(); - - //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.frequency.chain(this.modulationIndex, this._modulationNode); - this.modulator.connect(this._modulationNode.gain); - this._modulationNode.gain.value = 0; - this._modulationNode.connect(this.carrier.frequency); - this.carrier.connect(this.output); - this._readOnly(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]);; - }; - - Tone.extend(Tone.SimpleFM, Tone.Monophonic); - - /** - * @static - * @type {Object} - */ - Tone.SimpleFM.defaults = { - "harmonicity" : 3, - "modulationIndex" : 10, - "carrier" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.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 - } - } - }; - - /** - * trigger the attack portion of the note - * - * @param {Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.SimpleFM} this - * @private - */ - Tone.SimpleFM.prototype._triggerEnvelopeAttack = function(time, velocity){ - //the port glide - time = this.toSeconds(time); - //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); - return this; - }; - - /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.SimpleFM} this - * @private - */ - Tone.SimpleFM.prototype._triggerEnvelopeRelease = function(time){ - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); - return this; - }; - - /** - * clean up - * @returns {Tone.SimpleFM} this - */ - Tone.SimpleFM.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - this._writable(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); - this.frequency = null; - this.modulationIndex.dispose(); - this.modulationIndex = null; - this.harmonicity.dispose(); - this.harmonicity = null; - this._modulationNode.disconnect(); - this._modulationNode = null; - return this; - }; - - return Tone.SimpleFM; -}); \ No newline at end of file diff --git a/Tone/instrument/SimpleSynth.js b/Tone/instrument/SimpleSynth.js index 364732292..69eb85f94 100644 --- a/Tone/instrument/SimpleSynth.js +++ b/Tone/instrument/SimpleSynth.js @@ -1,119 +1,19 @@ -define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/source/OmniOscillator", "Tone/signal/Signal", "Tone/instrument/Monophonic"], +define(["Tone/core/Tone", "Tone/instrument/Synth"], function(Tone){ "use strict"; /** - * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator - * routed through a Tone.AmplitudeEnvelope. - * - * + * @class Now called Tone.Synth * @constructor * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var synth = new Tone.SimpleSynth().toMaster(); - * synth.triggerAttackRelease("C4", "8n"); */ Tone.SimpleSynth = function(options){ - - //get the defaults - options = this.defaultArg(options, Tone.SimpleSynth.defaults); - Tone.Monophonic.call(this, options); - - /** - * The oscillator. - * @type {Tone.OmniOscillator} - */ - this.oscillator = new Tone.OmniOscillator(options.oscillator); - - /** - * The frequency control. - * @type {Frequency} - * @signal - */ - this.frequency = this.oscillator.frequency; - - /** - * The detune control. - * @type {Cents} - * @signal - */ - this.detune = this.oscillator.detune; - - /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} - */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - - //connect the oscillators to the output - this.oscillator.chain(this.envelope, this.output); - //start the oscillators - this.oscillator.start(); - this._readOnly(["oscillator", "frequency", "detune", "envelope"]); + console.warn("Tone.SimpleSynth is now called Tone.Synth"); + Tone.Synth.call(this, options); }; - Tone.extend(Tone.SimpleSynth, Tone.Monophonic); - - /** - * @const - * @static - * @type {Object} - */ - Tone.SimpleSynth.defaults = { - "oscillator" : { - "type" : "triangle" - }, - "envelope" : { - "attack" : 0.005, - "decay" : 0.1, - "sustain" : 0.3, - "release" : 1 - } - }; - - /** - * start the attack portion of the envelope - * @param {Time} [time=now] the time the attack should start - * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.SimpleSynth} this - * @private - */ - Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function(time, velocity){ - //the envelopes - this.envelope.triggerAttack(time, velocity); - return this; - }; - - /** - * start the release portion of the envelope - * @param {Time} [time=now] the time the release should start - * @returns {Tone.SimpleSynth} this - * @private - */ - Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function(time){ - this.envelope.triggerRelease(time); - return this; - }; - - - /** - * clean up - * @returns {Tone.SimpleSynth} this - */ - Tone.SimpleSynth.prototype.dispose = function(){ - Tone.Monophonic.prototype.dispose.call(this); - this._writable(["oscillator", "frequency", "detune", "envelope"]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - this.frequency = null; - this.detune = null; - return this; - }; + Tone.extend(Tone.SimpleSynth, Tone.Synth); return Tone.SimpleSynth; }); \ No newline at end of file diff --git a/Tone/instrument/Synth.js b/Tone/instrument/Synth.js new file mode 100644 index 000000000..e325d2d55 --- /dev/null +++ b/Tone/instrument/Synth.js @@ -0,0 +1,119 @@ +define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/source/OmniOscillator", "Tone/signal/Signal", "Tone/instrument/Monophonic"], +function(Tone){ + + "use strict"; + + /** + * @class Tone.Synth is composed simply of a Tone.OmniOscillator + * routed through a Tone.AmplitudeEnvelope. + * + * + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below + * @example + * var synth = new Tone.Synth().toMaster(); + * synth.triggerAttackRelease("C4", "8n"); + */ + Tone.Synth = function(options){ + + //get the defaults + options = this.defaultArg(options, Tone.Synth.defaults); + Tone.Monophonic.call(this, options); + + /** + * The oscillator. + * @type {Tone.OmniOscillator} + */ + this.oscillator = new Tone.OmniOscillator(options.oscillator); + + /** + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = this.oscillator.frequency; + + /** + * The detune control. + * @type {Cents} + * @signal + */ + this.detune = this.oscillator.detune; + + /** + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} + */ + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); + + //connect the oscillators to the output + this.oscillator.chain(this.envelope, this.output); + //start the oscillators + this.oscillator.start(); + this._readOnly(["oscillator", "frequency", "detune", "envelope"]); + }; + + Tone.extend(Tone.Synth, Tone.Monophonic); + + /** + * @const + * @static + * @type {Object} + */ + Tone.Synth.defaults = { + "oscillator" : { + "type" : "triangle" + }, + "envelope" : { + "attack" : 0.005, + "decay" : 0.1, + "sustain" : 0.3, + "release" : 1 + } + }; + + /** + * start the attack portion of the envelope + * @param {Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.Synth} this + * @private + */ + Tone.Synth.prototype._triggerEnvelopeAttack = function(time, velocity){ + //the envelopes + this.envelope.triggerAttack(time, velocity); + return this; + }; + + /** + * start the release portion of the envelope + * @param {Time} [time=now] the time the release should start + * @returns {Tone.Synth} this + * @private + */ + Tone.Synth.prototype._triggerEnvelopeRelease = function(time){ + this.envelope.triggerRelease(time); + return this; + }; + + + /** + * clean up + * @returns {Tone.Synth} this + */ + Tone.Synth.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.Synth; +}); \ No newline at end of file diff --git a/Tone/signal/AND.js b/Tone/signal/AND.js deleted file mode 100644 index f19adb934..000000000 --- a/Tone/signal/AND.js +++ /dev/null @@ -1,51 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Equal"], function(Tone){ - - "use strict"; - - /** - * @class [AND](https://en.wikipedia.org/wiki/Logical_conjunction) - * returns 1 when all the inputs are equal to 1 and returns 0 otherwise. - * - * @extends {Tone.SignalBase} - * @constructor - * @param {number} [inputCount=2] the number of inputs. NOTE: all inputs are - * connected to the single AND input node - * @example - * var and = new Tone.AND(2); - * var sigA = new Tone.Signal(0).connect(and, 0, 0); - * var sigB = new Tone.Signal(1).connect(and, 0, 1); - * //the output of and is 0. - */ - Tone.AND = function(inputCount){ - - inputCount = this.defaultArg(inputCount, 2); - - Tone.call(this, inputCount, 0); - - /** - * @type {Tone.Equal} - * @private - */ - this._equals = this.output = new Tone.Equal(inputCount); - - //make each of the inputs an alias - for (var i = 0; i < inputCount; i++){ - this.input[i] = this._equals; - } - }; - - Tone.extend(Tone.AND, Tone.SignalBase); - - /** - * clean up - * @returns {Tone.AND} this - */ - Tone.AND.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._equals.dispose(); - this._equals = null; - return this; - }; - - return Tone.AND; -}); \ No newline at end of file diff --git a/Tone/signal/Abs.js b/Tone/signal/Abs.js index 46e57e780..198545b3e 100644 --- a/Tone/signal/Abs.js +++ b/Tone/signal/Abs.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/Select", "Tone/signal/Negate", "Tone/signal/LessThan", "Tone/signal/Signal"], +define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/signal/SignalBase"], function(Tone){ "use strict"; @@ -21,27 +21,13 @@ function(Tone){ * @type {Tone.LessThan} * @private */ - this._ltz = new Tone.LessThan(0); - - /** - * @type {Tone.Select} - * @private - */ - this._switch = this.output = new Tone.Select(2); - - /** - * @type {Tone.Negate} - * @private - */ - this._negate = new Tone.Negate(); - - //two signal paths, positive and negative - this.input.connect(this._switch, 0, 0); - this.input.connect(this._negate); - this._negate.connect(this._switch, 0, 1); - - //the control signal - this.input.chain(this._ltz, this._switch.gate); + this._abs = this.input = this.output = new Tone.WaveShaper(function(val){ + if (val === 0){ + return 0; + } else { + return Math.abs(val); + } + }, 127); }; Tone.extend(Tone.Abs, Tone.SignalBase); @@ -52,12 +38,8 @@ function(Tone){ */ Tone.Abs.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._switch.dispose(); - this._switch = null; - this._ltz.dispose(); - this._ltz = null; - this._negate.dispose(); - this._negate = null; + this._abs.dispose(); + this._abs = null; return this; }; diff --git a/Tone/signal/Clip.js b/Tone/signal/Clip.js deleted file mode 100644 index dd4c69a99..000000000 --- a/Tone/signal/Clip.js +++ /dev/null @@ -1,62 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Max", "Tone/signal/Min", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Clip the incoming signal so that the output is always between min and max. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number} min the minimum value of the outgoing signal - * @param {number} max the maximum value of the outgoing signal - * @example - * var clip = new Tone.Clip(0.5, 1); - * var osc = new Tone.Oscillator().connect(clip); - * //clips the output of the oscillator to between 0.5 and 1. - */ - Tone.Clip = function(min, max){ - //make sure the args are in the right order - if (min > max){ - var tmp = min; - min = max; - max = tmp; - } - - /** - * The min clip value - * @type {Number} - * @signal - */ - this.min = this.input = new Tone.Min(max); - this._readOnly("min"); - - /** - * The max clip value - * @type {Number} - * @signal - */ - this.max = this.output = new Tone.Max(min); - this._readOnly("max"); - - this.min.connect(this.max); - }; - - Tone.extend(Tone.Clip, Tone.SignalBase); - - /** - * clean up - * @returns {Tone.Clip} this - */ - Tone.Clip.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._writable("min"); - this.min.dispose(); - this.min = null; - this._writable("max"); - this.max.dispose(); - this.max = null; - return this; - }; - - return Tone.Clip; -}); \ No newline at end of file diff --git a/Tone/signal/Equal.js b/Tone/signal/Equal.js deleted file mode 100644 index 4f6e0a484..000000000 --- a/Tone/signal/Equal.js +++ /dev/null @@ -1,70 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/EqualZero", "Tone/signal/Subtract", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Output 1 if the signal is equal to the value, otherwise outputs 0. - * Can accept two signals if connected to inputs 0 and 1. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number=} value The number to compare the incoming signal to - * @example - * var eq = new Tone.Equal(3); - * var sig = new Tone.Signal(3).connect(eq); - * //the output of eq is 1. - */ - Tone.Equal = function(value){ - - Tone.call(this, 2, 0); - - /** - * subtract the value from the incoming signal - * - * @type {Tone.Add} - * @private - */ - this._sub = this.input[0] = new Tone.Subtract(value); - - /** - * @type {Tone.EqualZero} - * @private - */ - this._equals = this.output = new Tone.EqualZero(); - - this._sub.connect(this._equals); - this.input[1] = this._sub.input[1]; - }; - - Tone.extend(Tone.Equal, Tone.SignalBase); - - /** - * The value to compare to the incoming signal. - * @memberOf Tone.Equal# - * @type {number} - * @name value - */ - Object.defineProperty(Tone.Equal.prototype, "value", { - get : function(){ - return this._sub.value; - }, - set : function(value){ - this._sub.value = value; - } - }); - - /** - * Clean up. - * @returns {Tone.Equal} this - */ - Tone.Equal.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._equals.dispose(); - this._equals = null; - this._sub.dispose(); - this._sub = null; - return this; - }; - - return Tone.Equal; -}); \ No newline at end of file diff --git a/Tone/signal/EqualZero.js b/Tone/signal/EqualZero.js deleted file mode 100644 index 1f04c0014..000000000 --- a/Tone/signal/EqualZero.js +++ /dev/null @@ -1,67 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/GreaterThanZero", "Tone/signal/WaveShaper"], -function(Tone){ - - "use strict"; - - /** - * @class EqualZero outputs 1 when the input is equal to - * 0 and outputs 0 otherwise. - * - * @constructor - * @extends {Tone.SignalBase} - * @example - * var eq0 = new Tone.EqualZero(); - * var sig = new Tone.Signal(0).connect(eq0); - * //the output of eq0 is 1. - */ - Tone.EqualZero = function(){ - - /** - * scale the incoming signal by a large factor - * @private - * @type {Tone.Multiply} - */ - this._scale = this.input = new Tone.Multiply(10000); - - /** - * @type {Tone.WaveShaper} - * @private - */ - this._thresh = new Tone.WaveShaper(function(val){ - if (val === 0){ - return 1; - } else { - return 0; - } - }, 128); - - /** - * threshold the output so that it's 0 or 1 - * @type {Tone.GreaterThanZero} - * @private - */ - this._gtz = this.output = new Tone.GreaterThanZero(); - - //connections - this._scale.chain(this._thresh, this._gtz); - }; - - Tone.extend(Tone.EqualZero, Tone.SignalBase); - - /** - * Clean up. - * @returns {Tone.EqualZero} this - */ - Tone.EqualZero.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._gtz.dispose(); - this._gtz = null; - this._scale.dispose(); - this._scale = null; - this._thresh.dispose(); - this._thresh = null; - return this; - }; - - return Tone.EqualZero; -}); \ No newline at end of file diff --git a/Tone/signal/Expr.js b/Tone/signal/Expr.js index d9323eb3e..992f938dd 100644 --- a/Tone/signal/Expr.js +++ b/Tone/signal/Expr.js @@ -1,8 +1,6 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signal/Multiply", - "Tone/signal/IfThenElse", "Tone/signal/OR", "Tone/signal/AND", "Tone/signal/NOT", - "Tone/signal/GreaterThan", "Tone/signal/LessThan", "Tone/signal/Equal", "Tone/signal/EqualZero", - "Tone/signal/GreaterThanZero", "Tone/signal/Abs", "Tone/signal/Negate", "Tone/signal/Max", - "Tone/signal/Min", "Tone/signal/Modulo", "Tone/signal/Pow", "Tone/signal/AudioToGain"], + "Tone/signal/GreaterThan", "Tone/signal/GreaterThanZero", "Tone/signal/Abs", "Tone/signal/Negate", + "Tone/signal/Modulo", "Tone/signal/Pow", "Tone/signal/AudioToGain"], function(Tone){ "use strict"; @@ -50,7 +48,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa result = this._eval(tree); } catch (e){ this._disposeNodes(); - throw new Error("Could evaluate expression: "+expr); + throw new Error("Tone.Expr: Could evaluate expression: "+expr); } /** @@ -125,32 +123,6 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa regexp : /^abs/, method : applyUnary.bind(this, Tone.Abs) }, - "min" : { - regexp : /^min/, - method : applyBinary.bind(this, Tone.Min) - }, - "max" : { - regexp : /^max/, - method : applyBinary.bind(this, Tone.Max) - }, - "if" : { - regexp : /^if/, - method : function(args, self){ - var op = new Tone.IfThenElse(); - self._eval(args[0]).connect(op.if); - self._eval(args[1]).connect(op.then); - self._eval(args[2]).connect(op.else); - return op; - } - }, - "gt0" : { - regexp : /^gt0/, - method : applyUnary.bind(this, Tone.GreaterThanZero) - }, - "eq0" : { - regexp : /^eq0/, - method : applyUnary.bind(this, Tone.EqualZero) - }, "mod" : { regexp : /^mod/, method : function(args, self){ @@ -201,32 +173,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa regexp : /^\*/, precedence : 0, method : applyBinary.bind(this, Tone.Multiply) - }, - ">" : { - regexp : /^\>/, - precedence : 2, - method : applyBinary.bind(this, Tone.GreaterThan) - }, - "<" : { - regexp : /^input[0] and input[1] - * @example - * var lt = new Tone.LessThan(2); - * var sig = new Tone.Signal(-1).connect(lt); - * //if (sig < 2) lt outputs 1 - */ - Tone.LessThan = function(value){ - - Tone.call(this, 2, 0); - - /** - * negate the incoming signal - * @type {Tone.Negate} - * @private - */ - this._neg = this.input[0] = new Tone.Negate(); - - /** - * input < value === -input > -value - * @type {Tone.GreaterThan} - * @private - */ - this._gt = this.output = new Tone.GreaterThan(); - - /** - * negate the signal coming from the second input - * @private - * @type {Tone.Negate} - */ - this._rhNeg = new Tone.Negate(); - - /** - * the node where the value is set - * @private - * @type {Tone.Signal} - */ - this._param = this.input[1] = new Tone.Signal(value); - - //connect - this._neg.connect(this._gt); - this._param.connect(this._rhNeg); - this._rhNeg.connect(this._gt, 0, 1); - }; - - Tone.extend(Tone.LessThan, Tone.Signal); - - /** - * Clean up. - * @returns {Tone.LessThan} this - */ - Tone.LessThan.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._neg.dispose(); - this._neg = null; - this._gt.dispose(); - this._gt = null; - this._rhNeg.dispose(); - this._rhNeg = null; - this._param.dispose(); - this._param = null; - return this; - }; - - return Tone.LessThan; -}); \ No newline at end of file diff --git a/Tone/signal/Max.js b/Tone/signal/Max.js deleted file mode 100644 index 29470027d..000000000 --- a/Tone/signal/Max.js +++ /dev/null @@ -1,76 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/IfThenElse", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Outputs the greater of two signals. If a number is provided in the constructor - * it will use that instead of the signal. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} max Max value if provided. if not provided, it will use the - * signal value from input 1. - * @example - * var max = new Tone.Max(2); - * var sig = new Tone.Signal(3).connect(max); - * //max outputs 3 - * sig.value = 1; - * //max outputs 2 - * @example - * var max = new Tone.Max(); - * var sigA = new Tone.Signal(3); - * var sigB = new Tone.Signal(4); - * sigA.connect(max, 0, 0); - * sigB.connect(max, 0, 1); - * //output of max is 4. - */ - Tone.Max = function(max){ - - Tone.call(this, 2, 0); - this.input[0] = this.context.createGain(); - - /** - * the max signal - * @type {Tone.Signal} - * @private - */ - this._param = this.input[1] = new Tone.Signal(max); - - /** - * @type {Tone.Select} - * @private - */ - this._ifThenElse = this.output = new Tone.IfThenElse(); - - /** - * @type {Tone.Select} - * @private - */ - this._gt = new Tone.GreaterThan(); - - //connections - this.input[0].chain(this._gt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._param.connect(this._ifThenElse.else); - this._param.connect(this._gt, 0, 1); - }; - - Tone.extend(Tone.Max, Tone.Signal); - - /** - * Clean up. - * @returns {Tone.Max} this - */ - Tone.Max.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._param.dispose(); - this._ifThenElse.dispose(); - this._gt.dispose(); - this._param = null; - this._ifThenElse = null; - this._gt = null; - return this; - }; - - return Tone.Max; -}); \ No newline at end of file diff --git a/Tone/signal/Min.js b/Tone/signal/Min.js deleted file mode 100644 index e233086d5..000000000 --- a/Tone/signal/Min.js +++ /dev/null @@ -1,75 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/LessThan", "Tone/signal/IfThenElse", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Outputs the lesser of two signals. If a number is given - * in the constructor, it will use a signal and a number. - * - * @constructor - * @extends {Tone.Signal} - * @param {number} min The minimum to compare to the incoming signal - * @example - * var min = new Tone.Min(2); - * var sig = new Tone.Signal(3).connect(min); - * //min outputs 2 - * sig.value = 1; - * //min outputs 1 - * @example - * var min = new Tone.Min(); - * var sigA = new Tone.Signal(3); - * var sigB = new Tone.Signal(4); - * sigA.connect(min, 0, 0); - * sigB.connect(min, 0, 1); - * //output of min is 3. - */ - Tone.Min = function(min){ - - Tone.call(this, 2, 0); - this.input[0] = this.context.createGain(); - - /** - * @type {Tone.Select} - * @private - */ - this._ifThenElse = this.output = new Tone.IfThenElse(); - - /** - * @type {Tone.Select} - * @private - */ - this._lt = new Tone.LessThan(); - - /** - * the min signal - * @type {Tone.Signal} - * @private - */ - this._param = this.input[1] = new Tone.Signal(min); - - //connections - this.input[0].chain(this._lt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._param.connect(this._ifThenElse.else); - this._param.connect(this._lt, 0, 1); - }; - - Tone.extend(Tone.Min, Tone.Signal); - - /** - * clean up - * @returns {Tone.Min} this - */ - Tone.Min.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._param.dispose(); - this._ifThenElse.dispose(); - this._lt.dispose(); - this._param = null; - this._ifThenElse = null; - this._lt = null; - return this; - }; - - return Tone.Min; -}); \ No newline at end of file diff --git a/Tone/signal/NOT.js b/Tone/signal/NOT.js deleted file mode 100644 index 3d75f8c97..000000000 --- a/Tone/signal/NOT.js +++ /dev/null @@ -1,21 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/EqualZero"], function(Tone){ - - "use strict"; - - /** - * @class Just an alias for Tone.EqualZero, but has the same effect as a NOT operator. - * Outputs 1 when input equals 0. - * - * @constructor - * @extends {Tone.SignalBase} - * @example - * var not = new Tone.NOT(); - * var sig = new Tone.Signal(1).connect(not); - * //output of not equals 0. - * sig.value = 0; - * //output of not equals 1. - */ - Tone.NOT = Tone.EqualZero; - - return Tone.NOT; -}); \ No newline at end of file diff --git a/Tone/signal/OR.js b/Tone/signal/OR.js deleted file mode 100644 index e26b3b9a2..000000000 --- a/Tone/signal/OR.js +++ /dev/null @@ -1,60 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/GreaterThanZero"], function(Tone){ - - "use strict"; - - /** - * @class [OR](https://en.wikipedia.org/wiki/OR_gate) - * the inputs together. True if at least one of the inputs is true. - * - * @extends {Tone.SignalBase} - * @constructor - * @param {number} [inputCount=2] the input count - * @example - * var or = new Tone.OR(2); - * var sigA = new Tone.Signal(0)connect(or, 0, 0); - * var sigB = new Tone.Signal(1)connect(or, 0, 1); - * //output of or is 1 because at least - * //one of the inputs is equal to 1. - */ - Tone.OR = function(inputCount){ - - inputCount = this.defaultArg(inputCount, 2); - Tone.call(this, inputCount, 0); - - /** - * a private summing node - * @type {GainNode} - * @private - */ - this._sum = this.context.createGain(); - - /** - * @type {Tone.Equal} - * @private - */ - this._gtz = this.output = new Tone.GreaterThanZero(); - - //make each of the inputs an alias - for (var i = 0; i < inputCount; i++){ - this.input[i] = this._sum; - } - this._sum.connect(this._gtz); - }; - - Tone.extend(Tone.OR, Tone.SignalBase); - - /** - * clean up - * @returns {Tone.OR} this - */ - Tone.OR.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._gtz.dispose(); - this._gtz = null; - this._sum.disconnect(); - this._sum = null; - return this; - }; - - return Tone.OR; -}); \ No newline at end of file diff --git a/Tone/signal/Route.js b/Tone/signal/Route.js deleted file mode 100644 index 39e15e905..000000000 --- a/Tone/signal/Route.js +++ /dev/null @@ -1,116 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Route a single input to the specified output. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number} [outputCount=2] the number of inputs the switch accepts - * @example - * var route = new Tone.Route(4); - * var signal = new Tone.Signal(3).connect(route); - * route.select(0); - * //signal is routed through output 0 - * route.select(3); - * //signal is now routed through output 3 - */ - Tone.Route = function(outputCount){ - - outputCount = this.defaultArg(outputCount, 2); - Tone.call(this, 1, outputCount); - - /** - * The control signal. - * @type {Number} - * @signal - */ - this.gate = new Tone.Signal(0); - this._readOnly("gate"); - - //make all the inputs and connect them - for (var i = 0; i < outputCount; i++){ - var routeGate = new RouteGate(i); - this.output[i] = routeGate; - this.gate.connect(routeGate.selecter); - this.input.connect(routeGate); - } - }; - - Tone.extend(Tone.Route, Tone.SignalBase); - - /** - * Routes the signal to one of the outputs and close the others. - * @param {number} [which=0] Open one of the gates (closes the other). - * @param {Time} [time=now] The time when the switch will open. - * @returns {Tone.Route} this - */ - Tone.Route.prototype.select = function(which, time){ - //make sure it's an integer - which = Math.floor(which); - this.gate.setValueAtTime(which, this.toSeconds(time)); - return this; - }; - - /** - * Clean up. - * @returns {Tone.Route} this - */ - Tone.Route.prototype.dispose = function(){ - this._writable("gate"); - this.gate.dispose(); - this.gate = null; - for (var i = 0; i < this.output.length; i++){ - this.output[i].dispose(); - this.output[i] = null; - } - Tone.prototype.dispose.call(this); - return this; - }; - - ////////////START HELPER//////////// - - /** - * helper class for Tone.Route representing a single gate - * @constructor - * @extends {Tone} - * @private - */ - var RouteGate = function(num){ - - /** - * the selector - * @type {Tone.Equal} - */ - this.selecter = new Tone.Equal(num); - - /** - * the gate - * @type {GainNode} - */ - this.gate = this.input = this.output = this.context.createGain(); - - //connect the selecter to the gate gain - this.selecter.connect(this.gate.gain); - }; - - Tone.extend(RouteGate); - - /** - * clean up - * @private - */ - RouteGate.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this.selecter.dispose(); - this.selecter = null; - this.gate.disconnect(); - this.gate = null; - }; - - ////////////END HELPER//////////// - - //return Tone.Route - return Tone.Route; -}); \ No newline at end of file diff --git a/Tone/signal/Select.js b/Tone/signal/Select.js deleted file mode 100644 index da671b8ce..000000000 --- a/Tone/signal/Select.js +++ /dev/null @@ -1,122 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(Tone){ - - "use strict"; - - /** - * @class Select between any number of inputs, sending the one - * selected by the gate signal to the output - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number} [sourceCount=2] the number of inputs the switch accepts - * @example - * var sel = new Tone.Select(2); - * var sigA = new Tone.Signal(10).connect(sel, 0, 0); - * var sigB = new Tone.Signal(20).connect(sel, 0, 1); - * sel.gate.value = 0; - * //sel outputs 10 (the value of sigA); - * sel.gate.value = 1; - * //sel outputs 20 (the value of sigB); - */ - Tone.Select = function(sourceCount){ - - sourceCount = this.defaultArg(sourceCount, 2); - - Tone.call(this, sourceCount, 1); - - /** - * the control signal - * @type {Number} - * @signal - */ - this.gate = new Tone.Signal(0); - this._readOnly("gate"); - - //make all the inputs and connect them - for (var i = 0; i < sourceCount; i++){ - var switchGate = new SelectGate(i); - this.input[i] = switchGate; - this.gate.connect(switchGate.selecter); - switchGate.connect(this.output); - } - }; - - Tone.extend(Tone.Select, Tone.SignalBase); - - /** - * Open a specific input and close the others. - * @param {number} which The gate to open. - * @param {Time} [time=now] The time when the switch will open - * @returns {Tone.Select} this - * @example - * //open input 1 in a half second from now - * sel.select(1, "+0.5"); - */ - Tone.Select.prototype.select = function(which, time){ - //make sure it's an integer - which = Math.floor(which); - this.gate.setValueAtTime(which, this.toSeconds(time)); - return this; - }; - - /** - * Clean up. - * @returns {Tone.Select} this - */ - Tone.Select.prototype.dispose = function(){ - this._writable("gate"); - this.gate.dispose(); - this.gate = null; - for (var i = 0; i < this.input.length; i++){ - this.input[i].dispose(); - this.input[i] = null; - } - Tone.prototype.dispose.call(this); - return this; - }; - - ////////////START HELPER//////////// - - /** - * helper class for Tone.Select representing a single gate - * @constructor - * @extends {Tone} - * @private - */ - var SelectGate = function(num){ - - /** - * the selector - * @type {Tone.Equal} - */ - this.selecter = new Tone.Equal(num); - - /** - * the gate - * @type {GainNode} - */ - this.gate = this.input = this.output = this.context.createGain(); - - //connect the selecter to the gate gain - this.selecter.connect(this.gate.gain); - }; - - Tone.extend(SelectGate); - - /** - * clean up - * @private - */ - SelectGate.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this.selecter.dispose(); - this.gate.disconnect(); - this.selecter = null; - this.gate = null; - }; - - ////////////END HELPER//////////// - - //return Tone.Select - return Tone.Select; -}); \ No newline at end of file diff --git a/Tone/signal/Signal.js b/Tone/signal/Signal.js index c0ab1e590..8d3681f81 100644 --- a/Tone/signal/Signal.js +++ b/Tone/signal/Signal.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/core/Type", "Tone/core/Param", "Tone/core/Gain"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/type/Type", "Tone/core/Param", "Tone/core/Gain"], function(Tone){ "use strict"; diff --git a/Tone/signal/Switch.js b/Tone/signal/Switch.js deleted file mode 100644 index b977ef23b..000000000 --- a/Tone/signal/Switch.js +++ /dev/null @@ -1,99 +0,0 @@ -define(["Tone/core/Tone", "Tone/signal/SignalBase", "Tone/signal/GreaterThan"], function(Tone){ - - "use strict"; - - /** - * @class When the gate is set to 0, the input signal does not pass through to the output. - * If the gate is set to 1, the input signal passes through. - * the gate is initially closed. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {Boolean} [open=false] If the gate is initially open or closed. - * @example - * var sigSwitch = new Tone.Switch(); - * var signal = new Tone.Signal(2).connect(sigSwitch); - * //initially no output from sigSwitch - * sigSwitch.gate.value = 1; - * //open the switch and allow the signal through - * //the output of sigSwitch is now 2. - */ - Tone.Switch = function(open){ - - open = this.defaultArg(open, false); - - Tone.call(this); - - /** - * The control signal for the switch. - * When this value is 0, the input signal will NOT pass through, - * when it is high (1), the input signal will pass through. - * - * @type {Number} - * @signal - */ - this.gate = new Tone.Signal(0); - this._readOnly("gate"); - - /** - * thresh the control signal to either 0 or 1 - * @type {Tone.GreaterThan} - * @private - */ - this._thresh = new Tone.GreaterThan(0.5); - - this.input.connect(this.output); - this.gate.chain(this._thresh, this.output.gain); - - //initially open - if (open){ - this.open(); - } - }; - - Tone.extend(Tone.Switch, Tone.SignalBase); - - /** - * Open the switch at a specific time. - * - * @param {Time} [time=now] The time when the switch will be open. - * @returns {Tone.Switch} this - * @example - * //open the switch to let the signal through - * sigSwitch.open(); - */ - Tone.Switch.prototype.open = function(time){ - this.gate.setValueAtTime(1, this.toSeconds(time)); - return this; - }; - - /** - * Close the switch at a specific time. - * - * @param {Time} [time=now] The time when the switch will be closed. - * @returns {Tone.Switch} this - * @example - * //close the switch a half second from now - * sigSwitch.close("+0.5"); - */ - Tone.Switch.prototype.close = function(time){ - this.gate.setValueAtTime(0, this.toSeconds(time)); - return this; - }; - - /** - * Clean up. - * @returns {Tone.Switch} this - */ - Tone.Switch.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._writable("gate"); - this.gate.dispose(); - this.gate = null; - this._thresh.dispose(); - this._thresh = null; - return this; - }; - - return Tone.Switch; -}); \ No newline at end of file diff --git a/Tone/signal/TimelineSignal.js b/Tone/signal/TimelineSignal.js index 593179dfd..7e5e373d3 100644 --- a/Tone/signal/TimelineSignal.js +++ b/Tone/signal/TimelineSignal.js @@ -12,12 +12,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function Tone.TimelineSignal = function(){ var options = this.optionsObject(arguments, ["value", "units"], Tone.Signal.defaults); - - //constructors - Tone.Signal.apply(this, options); - options.param = this._param; - Tone.Param.call(this, options); - + /** * The scheduled events * @type {Tone.Timeline} @@ -25,6 +20,11 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function */ this._events = new Tone.Timeline(10); + //constructors + Tone.Signal.apply(this, options); + options.param = this._param; + Tone.Param.call(this, options); + /** * The initial scheduled value * @type {Number} @@ -38,11 +38,13 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function /** * The event types of a schedulable signal. * @enum {String} + * @private */ Tone.TimelineSignal.Type = { Linear : "linear", Exponential : "exponential", Target : "target", + Curve : "curve", Set : "set" }; @@ -54,11 +56,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function */ Object.defineProperty(Tone.TimelineSignal.prototype, "value", { get : function(){ - return this._toUnits(this._param.value); + var now = this.now(); + var val = this.getValueAtTime(now); + return this._toUnits(val); }, set : function(value){ var convertedVal = this._fromUnits(value); this._initial = convertedVal; + this.cancelScheduledValues(); this._param.value = convertedVal; } }); @@ -118,15 +123,27 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function * @returns {Tone.TimelineSignal} this */ Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + //get the previous event and make sure it's not starting from 0 + var beforeEvent = this._searchBefore(endTime); + if (beforeEvent && beforeEvent.value === 0){ + //reschedule that event + this.setValueAtTime(this._minOutput, beforeEvent.time); + } value = this._fromUnits(value); - value = Math.max(this._minOutput, value); + var setValue = Math.max(value, this._minOutput); endTime = this.toSeconds(endTime); this._events.addEvent({ "type" : Tone.TimelineSignal.Type.Exponential, - "value" : value, + "value" : setValue, "time" : endTime }); - this._param.exponentialRampToValueAtTime(value, endTime); + //if the ramped to value is 0, make it go to the min output, and then set to 0. + if (value < this._minOutput){ + this._param.exponentialRampToValueAtTime(this._minOutput, endTime - this.sampleTime); + this.setValueAtTime(0, endTime); + } else { + this._param.exponentialRampToValueAtTime(value, endTime); + } return this; }; @@ -153,6 +170,39 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function return this; }; + /** + * Set an array of arbitrary values starting at the given time for the given duration. + * @param {Float32Array} values + * @param {Time} startTime + * @param {Time} duration + * @param {NormalRange} [scaling=1] If the values in the curve should be scaled by some value + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.setValueCurveAtTime = function (values, startTime, duration, scaling) { + scaling = this.defaultArg(scaling, 1); + //copy the array + var floats = new Array(values.length); + for (var i = 0; i < floats.length; i++){ + floats[i] = this._fromUnits(values[i]) * scaling; + } + startTime = this.toSeconds(startTime); + duration = this.toSeconds(duration); + this._events.addEvent({ + "type" : Tone.TimelineSignal.Type.Curve, + "value" : floats, + "time" : startTime, + "duration" : duration + }); + //set the first value + this._param.setValueAtTime(floats[0], startTime); + //schedule a lienar ramp for each of the segments + for (var j = 1; j < floats.length; j++){ + var segmentTime = startTime + (j / (floats.length - 1) * duration); + this._param.linearRampToValueAtTime(floats[j], segmentTime); + } + return this; + }; + /** * Cancels all scheduled parameter changes with times greater than or * equal to startTime. @@ -179,19 +229,34 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function Tone.TimelineSignal.prototype.setRampPoint = function (time) { time = this.toSeconds(time); //get the value at the given time - var val = this.getValueAtTime(time); - //reschedule the next event to end at the given time - var after = this._searchAfter(time); - if (after){ - //cancel the next event(s) + var val = this._toUnits(this.getValueAtTime(time)); + //if there is an event at the given time + //and that even is not a "set" + var before = this._searchBefore(time); + if (before && before.time === time){ + //remove everything after + this.cancelScheduledValues(time + this.sampleTime); + } else if (before && + before.type === Tone.TimelineSignal.Type.Curve && + before.time + before.duration > time){ + //if the curve is still playing + //cancel the curve this.cancelScheduledValues(time); - if (after.type === Tone.TimelineSignal.Type.Linear){ - this.linearRampToValueAtTime(val, time); - } else if (after.type === Tone.TimelineSignal.Type.Exponential){ - this.exponentialRampToValueAtTime(val, time); - } - } - this.setValueAtTime(val, time); + this.linearRampToValueAtTime(val, time); + } else { + //reschedule the next event to end at the given time + var after = this._searchAfter(time); + if (after){ + //cancel the next event(s) + this.cancelScheduledValues(time); + if (after.type === Tone.TimelineSignal.Type.Linear){ + this.linearRampToValueAtTime(val, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential){ + this.exponentialRampToValueAtTime(val, time); + } + } + this.setValueAtTime(val, time); + } return this; }; @@ -269,6 +334,8 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function previouVal = previous.value; } value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (before.type === Tone.TimelineSignal.Type.Curve){ + value = this._curveInterpolate(before.time, before.value, before.duration, time); } else if (after === null){ value = before.value; } else if (after.type === Tone.TimelineSignal.Type.Linear){ @@ -326,6 +393,31 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); }; + /** + * Calculates the the value along the curve produced by setValueCurveAtTime + * @private + */ + Tone.TimelineSignal.prototype._curveInterpolate = function (start, curve, duration, time) { + var len = curve.length; + // If time is after duration, return the last curve value + if (time >= start + duration) { + return curve[len - 1]; + } else if (time <= start){ + return curve[0]; + } else { + var progress = (time - start) / duration; + var lowerIndex = Math.floor((len - 1) * progress); + var upperIndex = Math.ceil((len - 1) * progress); + var lowerVal = curve[lowerIndex]; + var upperVal = curve[upperIndex]; + if (upperIndex === lowerIndex){ + return lowerVal; + } else { + return this._linearInterpolate(lowerIndex, lowerVal, upperIndex, upperVal, progress * (len - 1)); + } + } + }; + /** * Clean up. * @return {Tone.TimelineSignal} this diff --git a/Tone/signal/WaveShaper.js b/Tone/signal/WaveShaper.js index 779faee75..7be05d14c 100644 --- a/Tone/signal/WaveShaper.js +++ b/Tone/signal/WaveShaper.js @@ -70,7 +70,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){ */ Tone.WaveShaper.prototype.setMap = function(mapping){ for (var i = 0, len = this._curve.length; i < len; i++){ - var normalized = (i / (len)) * 2 - 1; + var normalized = (i / (len - 1)) * 2 - 1; this._curve[i] = mapping(normalized, i); } this._shaper.curve = this._curve; @@ -110,7 +110,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){ if (["none", "2x", "4x"].indexOf(oversampling) !== -1){ this._shaper.oversample = oversampling; } else { - throw new Error("invalid oversampling: "+oversampling); + throw new RangeError("Tone.WaveShaper: oversampling must be either 'none', '2x', or '4x'"); } } }); diff --git a/Tone/signal/Zero.js b/Tone/signal/Zero.js new file mode 100644 index 000000000..4fe41a545 --- /dev/null +++ b/Tone/signal/Zero.js @@ -0,0 +1,64 @@ +define(["Tone/core/Tone", "Tone/core/Gain"], function (Tone) { + + /** + * @class Tone.Zero outputs 0's at audio-rate. The reason this has to be + * it's own class is that many browsers optimize out Tone.Signal + * with a value of 0 and will not process nodes further down the graph. + * @extends {Tone} + */ + Tone.Zero = function(){ + + /** + * The gain node + * @type {Tone.Gain} + * @private + */ + this._gain = this.input = this.output = new Tone.Gain(); + + Tone.Zero._zeros.connect(this._gain); + }; + + Tone.extend(Tone.Zero); + + /** + * clean up + * @return {Tone.Zero} this + */ + Tone.Zero.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._gain.dispose(); + this._gain = null; + return this; + }; + + /** + * Generates a constant output of 0. This is so + * the processing graph doesn't optimize out this + * segment of the graph. + * @static + * @private + * @const + * @type {AudioBufferSourceNode} + */ + Tone.Zero._zeros = null; + + /** + * initializer function + */ + Tone._initAudioContext(function(audioContext){ + var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate); + var arr = buffer.getChannelData(0); + for (var i = 0; i < arr.length; i++){ + arr[i] = 0; + } + Tone.Zero._zeros = audioContext.createBufferSource(); + Tone.Zero._zeros.channelCount = 1; + Tone.Zero._zeros.channelCountMode = "explicit"; + Tone.Zero._zeros.buffer = buffer; + Tone.Zero._zeros.loop = true; + Tone.Zero._zeros.start(0); + Tone.Zero._zeros.noGC(); + }); + + return Tone.Zero; +}); \ No newline at end of file diff --git a/Tone/source/AMOscillator.js b/Tone/source/AMOscillator.js new file mode 100644 index 000000000..5caeff6a8 --- /dev/null +++ b/Tone/source/AMOscillator.js @@ -0,0 +1,220 @@ +define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply", + "Tone/core/Gain", "Tone/signal/AudioToGain"], +function(Tone){ + + "use strict"; + + /** + * @class Tone.AMOscillator + * + * @extends {Tone.Oscillator} + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. + * @example + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.AMOscillator("Ab3", "sine", "square").toMaster().start(); + */ + Tone.AMOscillator = function(){ + + var options = this.optionsObject(arguments, ["frequency", "type", "modulationType"], Tone.AMOscillator.defaults); + Tone.Source.call(this, options); + + /** + * The carrier oscillator + * @type {Tone.Oscillator} + * @private + */ + this._carrier = new Tone.Oscillator(options.frequency, options.type); + + /** + * The oscillator's frequency + * @type {Frequency} + * @signal + */ + this.frequency = this._carrier.frequency; + + /** + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = this._carrier.detune; + this.detune.value = options.detune; + + /** + * The modulating oscillator + * @type {Tone.Oscillator} + * @private + */ + this._modulator = new Tone.Oscillator(options.frequency, options.modulationType); + + /** + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} + * @private + */ + this._modulationScale = new Tone.AudioToGain(); + + /** + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. + * @type {Positive} + * @signal + * @example + * //pitch the modulator an octave below carrier + * synth.harmonicity.value = 0.5; + */ + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; + + /** + * the node where the modulation happens + * @type {Tone.Gain} + * @private + */ + this._modulationNode = new Tone.Gain(0); + + //connections + this.frequency.chain(this.harmonicity, this._modulator.frequency); + this.detune.connect(this._modulator.detune); + this._modulator.chain(this._modulationScale, this._modulationNode.gain); + this._carrier.chain(this._modulationNode, this.output); + + this.phase = options.phase; + + this._readOnly(["frequency", "detune", "harmonicity"]); + }; + + Tone.extend(Tone.AMOscillator, Tone.Oscillator); + + /** + * default values + * @static + * @type {Object} + * @const + */ + Tone.AMOscillator.defaults = { + "frequency" : 440, + "detune" : 0, + "phase" : 0, + "modulationType" : "square", + "harmonicity" : 1 + }; + + /** + * start the oscillator + * @param {Time} [time=now] + * @private + */ + Tone.AMOscillator.prototype._start = function(time){ + time = this.toSeconds(time); + this._modulator.start(time); + this._carrier.start(time); + }; + + /** + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private + */ + Tone.AMOscillator.prototype._stop = function(time){ + time = this.toSeconds(time); + this._modulator.stop(time); + this._carrier.stop(time); + }; + + /** + * The type of the carrier oscillator + * @memberOf Tone.AMOscillator# + * @type {string} + * @name type + */ + Object.defineProperty(Tone.AMOscillator.prototype, "type", { + get : function(){ + return this._carrier.type; + }, + set : function(type){ + this._carrier.type = type; + } + }); + + /** + * The type of the modulator oscillator + * @memberOf Tone.AMOscillator# + * @type {string} + * @name modulationType + */ + Object.defineProperty(Tone.AMOscillator.prototype, "modulationType", { + get : function(){ + return this._modulator.type; + }, + set : function(type){ + this._modulator.type = type; + } + }); + + /** + * The phase of the oscillator in degrees. + * @memberOf Tone.AMOscillator# + * @type {number} + * @name phase + */ + Object.defineProperty(Tone.AMOscillator.prototype, "phase", { + get : function(){ + return this._carrier.phase; + }, + set : function(phase){ + this._carrier.phase = phase; + this._modulator.phase = phase; + } + }); + + /** + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.AMOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.AMOscillator.prototype, "partials", { + get : function(){ + return this._carrier.partials; + }, + set : function(partials){ + this._carrier.partials = partials; + } + }); + + /** + * Clean up. + * @return {Tone.AMOscillator} this + */ + Tone.AMOscillator.prototype.dispose = function(){ + Tone.Source.prototype.dispose.call(this); + this._writable(["frequency", "detune", "harmonicity"]); + this.frequency = null; + this.detune = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; + this._modulationNode.dispose(); + this._modulationNode = null; + this._modulationScale.dispose(); + this._modulationScale = null; + return this; + }; + + return Tone.AMOscillator; +}); \ No newline at end of file diff --git a/Tone/source/BufferSource.js b/Tone/source/BufferSource.js new file mode 100644 index 000000000..71cf2ef21 --- /dev/null +++ b/Tone/source/BufferSource.js @@ -0,0 +1,303 @@ +define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function (Tone) { + + /** + * @class Wrapper around the native BufferSourceNode. + * @param {AudioBuffer|Tone.Buffer} buffer The buffer to play + * @param {Function} onended The callback to invoke when the + * buffer is done playing. + */ + Tone.BufferSource = function(){ + + var options = this.optionsObject(arguments, ["buffer", "onended"], Tone.BufferSource.defaults); + + /** + * The callback to invoke after the + * buffer source is done playing. + * @type {Function} + */ + this.onended = options.onended; + + /** + * The time that the buffer was started. + * @type {Number} + * @private + */ + this._startTime = -1; + + /** + * The gain node which envelopes the BufferSource + * @type {GainNode} + * @private + */ + this._gainNode = this.output = this.context.createGain(); + + /** + * The buffer source + * @type {AudioBufferSourceNode} + * @private + */ + this._source = this.context.createBufferSource(); + this._source.connect(this._gainNode); + this._source.onended = this._onended.bind(this); + + /** + * The playbackRate of the buffer + * @type {AudioParam} + */ + this.playbackRate = this._source.playbackRate; + + /** + * The fadeIn time of the amplitude envelope. + * @type {Time} + */ + this.fadeIn = options.fadeIn; + + /** + * The fadeOut time of the amplitude envelope. + * @type {Time} + */ + this.fadeOut = options.fadeOut; + + /** + * The value that the buffer ramps to + * @type {Gain} + * @private + */ + this._gain = 1; + + //set the buffer initially + if (!this.isUndef(options.buffer)){ + this.buffer = options.buffer; + } + + this.loop = options.loop; + }; + + Tone.extend(Tone.BufferSource); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.BufferSource.defaults = { + "onended" : Tone.noOp, + "fadeIn" : 0, + "fadeOut" : 0 + }; + + /** + * Returns the playback state of the source, either "started" or "stopped". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.BufferSource# + * @name state + */ + Object.defineProperty(Tone.BufferSource.prototype, "state", { + get : function(){ + var now = this.now(); + if (this._startTime !== -1 && now > this._startTime){ + return Tone.State.Started; + } else { + return Tone.State.Stopped; + } + } + }); + + /** + * Start the buffer + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample + * to start at. + * @param {Time=} duration How long the sample should play. If no duration + * is given, it will default to the full length + * of the sample (minus any offset) + * @param {Gain} [gain=1] The gain to play the buffer back at. + * @param {Time=} fadeInTime The optional fadeIn ramp time. + * @return {Tone.BufferSource} this + */ + Tone.BufferSource.prototype.start = function(time, offset, duration, gain, fadeInTime){ + if (this._startTime !== -1){ + throw new Error("Tone.BufferSource: can only be started once."); + } + + if (!this.buffer){ + throw new Error("Tone.BufferSource: no buffer set."); + } + + time = this.toSeconds(time); + //if it's a loop the default offset is the loopstart point + if (this.loop){ + offset = this.defaultArg(offset, this.loopStart); + } else { + //otherwise the default offset is 0 + offset = this.defaultArg(offset, 0); + } + offset = this.toSeconds(offset); + //the values in seconds + time = this.toSeconds(time); + + this._source.start(time, offset); + + gain = this.defaultArg(gain, 1); + this._gain = gain; + + //the fadeIn time + if (this.isUndef(fadeInTime)){ + fadeInTime = this.toSeconds(this.fadeIn); + } else { + fadeInTime = this.toSeconds(fadeInTime); + } + + if (fadeInTime > 0){ + this._gainNode.gain.setValueAtTime(0, time); + this._gainNode.gain.linearRampToValueAtTime(this._gain, time + fadeInTime); + } else { + this._gainNode.gain.setValueAtTime(gain, time); + } + + this._startTime = time + fadeInTime; + + if (!this.isUndef(duration)){ + duration = this.defaultArg(duration, this.buffer.duration - offset); + duration = this.toSeconds(duration); + this.stop(time + duration + fadeInTime, fadeInTime); + } + + return this; + }; + + /** + * Stop the buffer. Optionally add a ramp time to fade the + * buffer out. + * @param {Time=} time The time the buffer should stop. + * @param {Time=} fadeOutTime How long the gain should fade out for + * @return {Tone.BufferSource} this + */ + Tone.BufferSource.prototype.stop = function(time, fadeOutTime){ + if (!this.buffer){ + throw new Error("Tone.BufferSource: no buffer set."); + } + + time = this.toSeconds(time); + + //the fadeOut time + if (this.isUndef(fadeOutTime)){ + fadeOutTime = this.toSeconds(this.fadeOut); + } else { + fadeOutTime = this.toSeconds(fadeOutTime); + } + + //cancel the end curve + this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime); + + //set a new one + if (fadeOutTime > 0){ + this._gainNode.gain.setValueAtTime(this._gain, time); + this._gainNode.gain.linearRampToValueAtTime(0, time + fadeOutTime); + time += fadeOutTime; + } else { + this._gainNode.gain.setValueAtTime(0, time); + } + this._source.stop(time); + + return this; + }; + + /** + * Internal callback when the buffer is ended. + * Invokes `onended` and disposes the node. + * @private + */ + Tone.BufferSource.prototype._onended = function(){ + this.onended(this); + this.dispose(); + }; + + /** + * If loop is true, the loop will start at this position. + * @memberOf Tone.BufferSource# + * @type {Time} + * @name loopStart + */ + Object.defineProperty(Tone.BufferSource.prototype, "loopStart", { + get : function(){ + return this._source.loopStart; + }, + set : function(loopStart){ + this._source.loopStart = this.toSeconds(loopStart); + } + }); + + /** + * If loop is true, the loop will end at this position. + * @memberOf Tone.BufferSource# + * @type {Time} + * @name loopEnd + */ + Object.defineProperty(Tone.BufferSource.prototype, "loopEnd", { + get : function(){ + return this._source.loopEnd; + }, + set : function(loopEnd){ + this._source.loopEnd = this.toSeconds(loopEnd); + } + }); + + /** + * The audio buffer belonging to the player. + * @memberOf Tone.BufferSource# + * @type {AudioBuffer} + * @name buffer + */ + Object.defineProperty(Tone.BufferSource.prototype, "buffer", { + get : function(){ + return this._source.buffer; + }, + set : function(buffer){ + if (buffer instanceof Tone.Buffer){ + this._source.buffer = buffer.get(); + } else { + this._source.buffer = buffer; + } + } + }); + + /** + * If the buffer should loop once it's over. + * @memberOf Tone.BufferSource# + * @type {boolean} + * @name loop + */ + Object.defineProperty(Tone.BufferSource.prototype, "loop", { + get : function(){ + return this._source.loop; + }, + set : function(loop){ + this._source.loop = loop; + } + }); + + /** + * Clean up. + * @return {Tone.BufferSource} this + */ + Tone.BufferSource.prototype.dispose = function(){ + this.onended = null; + if (this._source){ + this._source.onended = null; + this._source.disconnect(); + this._source = null; + } + if (this._gainNode){ + this._gainNode.disconnect(); + this._gainNode = null; + } + this._startTime = -1; + this.playbackRate = null; + this.output = null; + return this; + }; + + return Tone.BufferSource; +}); \ No newline at end of file diff --git a/Tone/source/ExternalInput.js b/Tone/source/ExternalInput.js index 36b27d92d..e331cae2d 100644 --- a/Tone/source/ExternalInput.js +++ b/Tone/source/ExternalInput.js @@ -88,11 +88,12 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone /** * wrapper for getUserMedia function * @param {function} callback + * @param {function} error * @private */ - Tone.ExternalInput.prototype._getUserMedia = function(callback){ + Tone.ExternalInput.prototype._getUserMedia = function(callback, error){ if (!Tone.ExternalInput.supported){ - throw new Error("browser does not support 'getUserMedia'"); + error("browser does not support 'getUserMedia'"); } if (Tone.ExternalInput.sources[this._inputNum]){ this._constraints = { @@ -105,7 +106,7 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone this._onStream(stream); callback(); }.bind(this), function(err){ - callback(err); + error(err); }); }; @@ -116,7 +117,7 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone */ Tone.ExternalInput.prototype._onStream = function(stream){ if (!this.isFunction(this.context.createMediaStreamSource)){ - throw new Error("browser does not support the 'MediaStreamSourceNode'"); + throw new Error("Tone.ExternalInput: browser does not support the 'MediaStreamSourceNode'"); } //can only start a new source if the previous one is closed if (!this._stream){ @@ -132,12 +133,18 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone * Open the media stream * @param {function=} callback The callback function to * execute when the stream is open + * @param {function=} error The callback function to execute + * when the media stream can't open. + * This is fired either because the browser + * doesn't support the media stream, + * or the user blocked opening the microphone. * @return {Tone.ExternalInput} this */ - Tone.ExternalInput.prototype.open = function(callback){ + Tone.ExternalInput.prototype.open = function(callback, error){ callback = this.defaultArg(callback, Tone.noOp); + error = this.defaultArg(error, Tone.noOp); Tone.ExternalInput.getSources(function(){ - this._getUserMedia(callback); + this._getUserMedia(callback, error); }.bind(this)); return this; }; diff --git a/Tone/source/FMOscillator.js b/Tone/source/FMOscillator.js new file mode 100644 index 000000000..ac9faed17 --- /dev/null +++ b/Tone/source/FMOscillator.js @@ -0,0 +1,227 @@ +define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply", "Tone/core/Gain"], +function(Tone){ + + "use strict"; + + /** + * @class Tone.FMOscillator + * + * @extends {Tone.Oscillator} + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. + * @example + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.FMOscillator("Ab3", "sine", "square").toMaster().start(); + */ + Tone.FMOscillator = function(){ + + var options = this.optionsObject(arguments, ["frequency", "type", "modulationType"], Tone.FMOscillator.defaults); + Tone.Source.call(this, options); + + /** + * The carrier oscillator + * @type {Tone.Oscillator} + * @private + */ + this._carrier = new Tone.Oscillator(options.frequency, options.type); + + /** + * The oscillator's frequency + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + + /** + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = this._carrier.detune; + this.detune.value = options.detune; + + /** + * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the + * ratio of the frequency of the modulating signal (mf) to the amplitude of the + * modulating signal (ma) -- as in ma/mf. + * @type {Positive} + * @signal + */ + this.modulationIndex = new Tone.Multiply(options.modulationIndex); + this.modulationIndex.units = Tone.Type.Positive; + + /** + * The modulating oscillator + * @type {Tone.Oscillator} + * @private + */ + this._modulator = new Tone.Oscillator(options.frequency, options.modulationType); + + /** + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. + * @type {Positive} + * @signal + * @example + * //pitch the modulator an octave below carrier + * synth.harmonicity.value = 0.5; + */ + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; + + /** + * the node where the modulation happens + * @type {Tone.Gain} + * @private + */ + this._modulationNode = new Tone.Gain(0); + + //connections + 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.connect(this._carrier.frequency); + this._carrier.connect(this.output); + this.detune.connect(this._modulator.detune); + + this.phase = options.phase; + + this._readOnly(["modulationIndex", "frequency", "detune", "harmonicity"]); + }; + + Tone.extend(Tone.FMOscillator, Tone.Oscillator); + + /** + * default values + * @static + * @type {Object} + * @const + */ + Tone.FMOscillator.defaults = { + "frequency" : 440, + "detune" : 0, + "phase" : 0, + "modulationIndex" : 2, + "modulationType" : "square", + "harmonicity" : 1 + }; + + /** + * start the oscillator + * @param {Time} [time=now] + * @private + */ + Tone.FMOscillator.prototype._start = function(time){ + time = this.toSeconds(time); + this._modulator.start(time); + this._carrier.start(time); + }; + + /** + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private + */ + Tone.FMOscillator.prototype._stop = function(time){ + time = this.toSeconds(time); + this._modulator.stop(time); + this._carrier.stop(time); + }; + + /** + * The type of the carrier oscillator + * @memberOf Tone.FMOscillator# + * @type {string} + * @name type + */ + Object.defineProperty(Tone.FMOscillator.prototype, "type", { + get : function(){ + return this._carrier.type; + }, + set : function(type){ + this._carrier.type = type; + } + }); + + /** + * The type of the modulator oscillator + * @memberOf Tone.FMOscillator# + * @type {String} + * @name modulationType + */ + Object.defineProperty(Tone.FMOscillator.prototype, "modulationType", { + get : function(){ + return this._modulator.type; + }, + set : function(type){ + this._modulator.type = type; + } + }); + + /** + * The phase of the oscillator in degrees. + * @memberOf Tone.FMOscillator# + * @type {number} + * @name phase + */ + Object.defineProperty(Tone.FMOscillator.prototype, "phase", { + get : function(){ + return this._carrier.phase; + }, + set : function(phase){ + this._carrier.phase = phase; + this._modulator.phase = phase; + } + }); + + /** + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.FMOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.FMOscillator.prototype, "partials", { + get : function(){ + return this._carrier.partials; + }, + set : function(partials){ + this._carrier.partials = partials; + } + }); + + /** + * Clean up. + * @return {Tone.FMOscillator} this + */ + Tone.FMOscillator.prototype.dispose = function(){ + Tone.Source.prototype.dispose.call(this); + this._writable(["modulationIndex", "frequency", "detune", "harmonicity"]); + this.frequency.dispose(); + this.frequency = null; + this.detune = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; + this._modulationNode.dispose(); + this._modulationNode = null; + this.modulationIndex.dispose(); + this.modulationIndex = null; + return this; + }; + + return Tone.FMOscillator; +}); \ No newline at end of file diff --git a/Tone/source/FatOscillator.js b/Tone/source/FatOscillator.js new file mode 100644 index 000000000..2736479cd --- /dev/null +++ b/Tone/source/FatOscillator.js @@ -0,0 +1,282 @@ +define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply", "Tone/core/Gain"], +function(Tone){ + + "use strict"; + + /** + * @class Tone.FatOscillator + * + * @extends {Tone.Oscillator} + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. + * @example + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.FatOscillator("Ab3", "sine", "square").toMaster().start(); + */ + Tone.FatOscillator = function(){ + + var options = this.optionsObject(arguments, ["frequency", "type", "spread"], Tone.FatOscillator.defaults); + Tone.Source.call(this, options); + + /** + * The oscillator's frequency + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + + /** + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + + /** + * The array of oscillators + * @type {Array} + * @private + */ + this._oscillators = []; + + /** + * The total spread of the oscillators + * @type {Cents} + * @private + */ + this._spread = options.spread; + + /** + * The type of the oscillator + * @type {String} + * @private + */ + this._type = options.type; + + /** + * The phase of the oscillators + * @type {Degrees} + * @private + */ + this._phase = options.phase; + + /** + * The partials array + * @type {Array} + * @private + */ + this._partials = this.defaultArg(options.partials, []); + + //set the count initially + this.count = options.count; + + this._readOnly(["frequency", "detune"]); + }; + + Tone.extend(Tone.FatOscillator, Tone.Oscillator); + + /** + * default values + * @static + * @type {Object} + * @const + */ + Tone.FatOscillator.defaults = { + "frequency" : 440, + "detune" : 0, + "phase" : 0, + "spread" : 20, + "count" : 3, + "type" : "sawtooth" + }; + + /** + * start the oscillator + * @param {Time} [time=now] + * @private + */ + Tone.FatOscillator.prototype._start = function(time){ + time = this.toSeconds(time); + this._forEach(function(osc){ + osc.start(time); + }); + }; + + /** + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private + */ + Tone.FatOscillator.prototype._stop = function(time){ + time = this.toSeconds(time); + this._forEach(function(osc){ + osc.stop(time); + }); + }; + + /** + * Iterate over all of the oscillators + * @param {Function} iterator The iterator function + * @private + */ + Tone.FatOscillator.prototype._forEach = function(iterator){ + for (var i = 0; i < this._oscillators.length; i++){ + iterator.call(this, this._oscillators[i], i); + } + }; + + /** + * The type of the carrier oscillator + * @memberOf Tone.FatOscillator# + * @type {string} + * @name type + */ + Object.defineProperty(Tone.FatOscillator.prototype, "type", { + get : function(){ + return this._type; + }, + set : function(type){ + this._type = type; + this._forEach(function(osc){ + osc.type = type; + }); + } + }); + + /** + * The detune spread between the oscillators. If "count" is + * set to 3 oscillators and the "spread" is set to 40, + * the three oscillators would be detuned like this: [-20, 0, 20] + * for a total detune spread of 40 cents. + * @memberOf Tone.FatOscillator# + * @type {Cents} + * @name spread + */ + Object.defineProperty(Tone.FatOscillator.prototype, "spread", { + get : function(){ + return this._spread; + }, + set : function(spread){ + this._spread = spread; + if (this._oscillators.length > 1){ + var start = -spread/2; + var step = spread / (this._oscillators.length - 1); + this._forEach(function(osc, i){ + osc.detune.value = start + step * i; + }); + } + } + }); + + /** + * The number of detuned oscillators + * @memberOf Tone.FatOscillator# + * @type {Number} + * @name count + */ + Object.defineProperty(Tone.FatOscillator.prototype, "count", { + get : function(){ + return this._oscillators.length; + }, + set : function(count){ + count = Math.max(count, 1); + if (this._oscillators.length !== count){ + // var partials = this.partials; + // var type = this.type; + //dispose the previous oscillators + this._forEach(function(osc){ + osc.dispose(); + }); + this._oscillators = []; + for (var i = 0; i < count; i++){ + var osc = new Tone.Oscillator(); + if (this.type === Tone.Oscillator.Type.Custom){ + osc.partials = this._partials; + } else { + osc.type = this._type; + } + osc.phase = this._phase; + osc.volume.value = -6 - count; + this.frequency.connect(osc.frequency); + this.detune.connect(osc.detune); + osc.connect(this.output); + this._oscillators[i] = osc; + } + //set the spread + this.spread = this._spread; + if (this.state === Tone.State.Started){ + this._forEach(function(osc){ + osc.start(); + }); + } + } + } + }); + + /** + * The phase of the oscillator in degrees. + * @memberOf Tone.FatOscillator# + * @type {Number} + * @name phase + */ + Object.defineProperty(Tone.FatOscillator.prototype, "phase", { + get : function(){ + return this._phase; + }, + set : function(phase){ + this._phase = phase; + this._forEach(function(osc){ + osc.phase = phase; + }); + } + }); + + /** + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.FatOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.FatOscillator.prototype, "partials", { + get : function(){ + return this._partials; + }, + set : function(partials){ + this._partials = partials; + this._type = Tone.Oscillator.Type.Custom; + this._forEach(function(osc){ + osc.partials = partials; + }); + } + }); + + /** + * Clean up. + * @return {Tone.FatOscillator} this + */ + Tone.FatOscillator.prototype.dispose = function(){ + Tone.Source.prototype.dispose.call(this); + this._writable(["frequency", "detune"]); + this.frequency.dispose(); + this.frequency = null; + this.detune.dispose(); + this.detune = null; + this._forEach(function(osc){ + osc.dispose(); + }); + this._oscillators = null; + this._partials = null; + return this; + }; + + return Tone.FatOscillator; +}); \ No newline at end of file diff --git a/Tone/source/GrainPlayer.js b/Tone/source/GrainPlayer.js new file mode 100644 index 000000000..410c8fa42 --- /dev/null +++ b/Tone/source/GrainPlayer.js @@ -0,0 +1,325 @@ +define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Buffer", "Tone/source/MultiPlayer"], +function (Tone) { + + /** + * @class Tone.GrainPlayer implements [granular synthesis](https://en.wikipedia.org/wiki/Granular_synthesis). + * Granular Synthesis enables you to adjust pitch and playback rate independently. The grainSize is the + * amount of time each small chunk of audio is played for and the overlap is the + * amount of crossfading transition time between successive grains. + * @extends {Tone} + * @param {String|Tone.Buffer} url The url to load, or the Tone.Buffer to play. + * @param {Function=} callback The callback to invoke after the url is loaded. + */ + Tone.GrainPlayer = function(){ + + var options = this.optionsObject(arguments, ["url", "onload"], Tone.GrainPlayer.defaults); + + Tone.Source.call(this); + + /** + * The audio buffer belonging to the player. + * @type {Tone.Buffer} + */ + this.buffer = new Tone.Buffer(options.url, options.onload); + + /** + * Plays the buffer with a small envelope + * @type {Tone.MultiPlayer} + * @private + */ + this._player = this.output = new Tone.MultiPlayer(); + + /** + * Create a repeating tick to schedule + * the grains. + * @type {Tone.Clock} + * @private + */ + this._clock = new Tone.Clock(this._tick.bind(this), 1); + + /** + * @type {Number} + * @private + */ + this._loopStart = 0; + + /** + * @type {Number} + * @private + */ + this._loopEnd = 0; + + /** + * @type {Number} + * @private + */ + this._playbackRate = options.playbackRate; + + /** + * @type {Number} + * @private + */ + this._grainSize = options.grainSize; + + /** + * @private + * @type {Number} + */ + this._overlap = options.overlap; + + /** + * Adjust the pitch independently of the playbackRate. + * @type {Cents} + */ + this.detune = options.detune; + + /** + * The amount of time randomly added + * or subtracted from the grain's offset + * @type {Time} + */ + this.drift = options.drift; + + //setup + this.overlap = options.overlap; + this.loop = options.loop; + this.playbackRate = options.playbackRate; + this.grainSize = options.grainSize; + this.loopStart = options.loopStart; + this.loopEnd = options.loopEnd; + this.reverse = options.reverse; + }; + + Tone.extend(Tone.GrainPlayer, Tone.Source); + + /** + * the default parameters + * @static + * @const + * @type {Object} + */ + Tone.GrainPlayer.defaults = { + "onload" : Tone.noOp, + "overlap" : 0.1, + "grainSize" : 0.2, + "drift" : 0.0, + "playbackRate" : 1, + "detune" : 0, + "loop" : false, + "loopStart" : 0, + "loopEnd" : 0, + "reverse" : false + }; + + /** + * Play the buffer at the given startTime. Optionally add an offset + * from the start of the buffer to play from. + * + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample + * to start at. + * @return {Tone.GrainPlayer} this + */ + + /** + * Internal start method + * @param {Time} time + * @param {Time} offset + * @private + */ + Tone.GrainPlayer.prototype._start = function(time, offset){ + offset = this.defaultArg(offset, 0); + offset = this.toSeconds(offset); + time = this.toSeconds(time); + + this._offset = offset; + this._clock.start(time); + }; + + /** + * Internal start method + * @param {Time} time + * @private + */ + Tone.GrainPlayer.prototype._stop = function(time){ + this._clock.stop(time); + this._player.stop(this.buffer, time); + this._offset = 0; + }; + + /** + * Invoked on each clock tick. scheduled a new + * grain at this time. + * @param {Time} time + * @private + */ + Tone.GrainPlayer.prototype._tick = function(time){ + + var bufferDuration = this.buffer.duration; + if (this.loop && this._loopEnd > 0){ + bufferDuration = this._loopEnd; + } + var drift = (Math.random() * 2 - 1) * this.drift; + var offset = this._offset - this._overlap + drift; + var detune = this.detune / 100; + + + var originalFadeIn = this._player.fadeIn; + if (this.loop && this._offset > bufferDuration){ + //play the end + var endSegmentDuration = this._offset - bufferDuration; + this._player.start(this.buffer, time, offset, endSegmentDuration + this._overlap, detune); + + //and play the beginning + offset = this._offset % bufferDuration; + this._offset = this._loopStart; + this._player.fadeIn = 0; + this._player.start(this.buffer, time + endSegmentDuration, this._offset, offset + this._overlap, detune); + } else if (this._offset > bufferDuration){ + //set the state to stopped. + this.stop(time); + } else { + if (offset < 0){ + this._player.fadeIn = Math.max(this._player.fadeIn + offset, 0); + offset = 0; + } + this._player.start(this.buffer, time, offset, this.grainSize + this._overlap, detune); + } + + this._player.fadeIn = originalFadeIn; + //increment the offset + var duration = this._clock._nextTick - time; + this._offset += duration * this._playbackRate; + }; + + /** + * Jump to a specific time and play it. + * @param {Time} offset The offset to jump to. + * @param {Time=} time When to make the jump. + * @return {[type]} [description] + */ + Tone.GrainPlayer.prototype.scrub = function(offset, time){ + this._offset = this.toSeconds(offset); + this._tick(this.toSeconds(time)); + return this; + }; + + /** + * The playback rate of the sample + * @memberOf Tone.GrainPlayer# + * @type {Positive} + * @name playbackRate + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "playbackRate", { + get : function(){ + return this._playbackRate; + }, + set : function(rate){ + this._playbackRate = rate; + this.grainSize = this._grainSize; + } + }); + + /** + * The loop start time. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name loopStart + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "loopStart", { + get : function(){ + return this._loopStart; + }, + set : function(time){ + this._loopStart = this.toSeconds(time); + } + }); + + /** + * The loop end time. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name loopEnd + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "loopEnd", { + get : function(){ + return this._loopEnd; + }, + set : function(time){ + this._loopEnd = this.toSeconds(time); + } + }); + + /** + * The direction the buffer should play in + * @memberOf Tone.GrainPlayer# + * @type {boolean} + * @name reverse + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "reverse", { + get : function(){ + return this.buffer.reverse; + }, + set : function(rev){ + this.buffer.reverse = rev; + } + }); + + /** + * The size of each chunk of audio that the + * buffer is chopped into and played back at. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name grainSize + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "grainSize", { + get : function(){ + return this._grainSize; + }, + set : function(size){ + this._grainSize = this.toSeconds(size); + this._clock.frequency.value = this._playbackRate / this._grainSize; + } + }); + + /** + * This is the duration of the cross-fade between + * sucessive grains. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name overlap + */ + Object.defineProperty(Tone.GrainPlayer.prototype, "overlap", { + get : function(){ + return this._overlap; + }, + set : function(time){ + time = this.toSeconds(time); + this._overlap = time; + if (this._overlap < 0){ + this._player.fadeIn = 0.01; + this._player.fadeOut = 0.01; + } else { + this._player.fadeIn = time; + this._player.fadeOut = time; + } + } + }); + + /** + * Clean up + * @return {Tone.GrainPlayer} this + */ + Tone.GrainPlayer.prototype.dispose = function(){ + Tone.Source.prototype.dispose.call(this); + this.buffer.dispose(); + this.buffer = null; + this._player.dispose(); + this._player = null; + this._clock.dispose(); + this._clock = null; + return this; + }; + + return Tone.GrainPlayer; +}); \ No newline at end of file diff --git a/Tone/source/MultiPlayer.js b/Tone/source/MultiPlayer.js new file mode 100644 index 000000000..01f451943 --- /dev/null +++ b/Tone/source/MultiPlayer.js @@ -0,0 +1,247 @@ +define(["Tone/core/Tone", "Tone/source/BufferSource", "Tone/core/Buffers", + "Tone/source/Source", "Tone/component/Volume"], +function (Tone) { + + /** + * @class Tone.MultiPlayer is well suited for one-shots, multi-sampled istruments + * or any time you need to play a bunch of audio buffers. + * @param {Object|Array|Tone.Buffers} buffers The buffers which are available + * to the MultiPlayer + * @param {Function} onload The callback to invoke when all of the buffers are loaded. + * @extends {Tone} + * @example + * var multiPlayer = new MultiPlayer({ + * "kick" : "path/to/kick.mp3", + * "snare" : "path/to/snare.mp3", + * }, function(){ + * multiPlayer.start("kick"); + * }); + * @example + * //can also store the values in an array + * var multiPlayer = new MultiPlayer(["path/to/kick.mp3", "path/to/snare.mp3"], + * function(){ + * //if an array is passed in, the samples are referenced to by index + * multiPlayer.start(1); + * }); + */ + Tone.MultiPlayer = function(){ + + var options = this.optionsObject(arguments, ["urls", "onload"], Tone.MultiPlayer.defaults); + + if (options.urls instanceof Tone.Buffers){ + /** + * All the buffers belonging to the player. + * @type {Tone.Buffers} + */ + this.buffers = options.urls; + } else { + this.buffers = new Tone.Buffers(options.urls, options.onload); + } + + /** + * Keeps track of the currently playing sources. + * @type {Array} + * @private + */ + this._activeSources = []; + + /** + * The fade in envelope which is applied + * to the beginning of the BufferSource + * @type {Time} + */ + this.fadeIn = options.fadeIn; + + /** + * The fade out envelope which is applied + * to the end of the BufferSource + * @type {Time} + */ + this.fadeOut = options.fadeOut; + + /** + * The output volume node + * @type {Tone.Volume} + * @private + */ + this._volume = this.output = new Tone.Volume(options.volume); + + /** + * The volume of the output in decibels. + * @type {Decibels} + * @signal + * @example + * source.volume.value = -6; + */ + this.volume = this._volume.volume; + this._readOnly("volume"); + + //make the output explicitly stereo + this._volume.output.output.channelCount = 2; + this._volume.output.output.channelCountMode = "explicit"; + //mute initially + this.mute = options.mute; + }; + + Tone.extend(Tone.MultiPlayer, Tone.Source); + + /** + * The defaults + * @type {Object} + */ + Tone.MultiPlayer.defaults = { + "onload" : Tone.noOp, + "fadeIn" : 0, + "fadeOut" : 0 + }; + + /** + * Get the given buffer. + * @param {String|Number|AudioBuffer|Tone.Buffer} buffer + * @return {AudioBuffer} The requested buffer. + * @private + */ + Tone.MultiPlayer.prototype._getBuffer = function(buffer){ + if (this.isNumber(buffer) || this.isString(buffer)){ + return this.buffers.get(buffer).get(); + } else if (buffer instanceof Tone.Buffer){ + return buffer.get(); + } else { + return buffer; + } + }; + + /** + * Start a buffer by name. The `start` method allows a number of options + * to be passed in such as offset, interval, and gain. This is good for multi-sampled + * instruments and sound sprites where samples are repitched played back at different velocities. + * @param {String|AudioBuffer} buffer The name of the buffer to start. + * Or pass in a buffer which will be started. + * @param {Time} time When to start the buffer. + * @param {Time} [offset=0] The offset into the buffer to play from. + * @param {Time=} duration How long to play the buffer for. + * @param {Interval} [interval=0] The interval to repitch the buffer. + * @param {Gain} [gain=1] The gain to play the sample at. + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.start = function(buffer, time, offset, duration, interval, gain){ + buffer = this._getBuffer(buffer); + var source = new Tone.BufferSource(buffer).connect(this.output); + this._activeSources.push(source); + time = this.toSeconds(time); + source.start(time, offset, duration, this.defaultArg(gain, 1), this.fadeIn); + if (duration){ + source.stop(time + this.toSeconds(duration), this.fadeOut); + } + source.onended = this._onended.bind(this); + interval = this.defaultArg(interval, 0); + source.playbackRate.value = this.intervalToFrequencyRatio(interval); + return this; + }; + + /** + * Internal callback when a buffer is done playing. + * @param {Tone.BufferSource} source The stopped source + * @private + */ + Tone.MultiPlayer.prototype._onended = function(source){ + var index = this._activeSources.indexOf(source); + this._activeSources.splice(index, 1); + }; + + /** + * Stop all instances of the currently playing buffer at the given time. + * @param {String|AudioBuffer} buffer The buffer to stop. + * @param {Time=} time When to stop the buffer + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.stop = function(buffer, time){ + buffer = this._getBuffer(buffer); + time = this.toSeconds(time); + for (var i = 0; i < this._activeSources.length; i++){ + if (this._activeSources[i].buffer === buffer){ + this._activeSources[i].stop(time, this.fadeOut); + } + } + return this; + }; + + /** + * Stop all currently playing buffers at the given time. + * @param {Time=} time When to stop the buffers. + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.stopAll = function(time){ + time = this.toSeconds(time); + for (var i = 0; i < this._activeSources.length; i++){ + this._activeSources[i].stop(time, this.fadeOut); + } + return this; + }; + + /** + * Add another buffer to the available buffers. + * @param {String} name The name to that the buffer is refered + * to in start/stop methods. + * @param {String|Tone.Buffer} url The url of the buffer to load + * or the buffer. + * @param {Function} callback The function to invoke after the buffer is loaded. + */ + Tone.MultiPlayer.prototype.add = function(name, url, callback){ + this.buffers.add(name, url, callback); + return this; + }; + + /** + * Returns the playback state of the source. "started" + * if there are any buffers playing. "stopped" otherwise. + * @type {Tone.State} + * @readOnly + * @memberOf Tone.MultiPlayer# + * @name state + */ + Object.defineProperty(Tone.MultiPlayer.prototype, "state", { + get : function(){ + return this._activeSources.length > 0 ? Tone.State.Started : Tone.State.Stopped; + } + }); + + /** + * Mute the output. + * @memberOf Tone.MultiPlayer# + * @type {boolean} + * @name mute + * @example + * //mute the output + * source.mute = true; + */ + Object.defineProperty(Tone.MultiPlayer.prototype, "mute", { + get : function(){ + return this._volume.mute; + }, + set : function(mute){ + this._volume.mute = mute; + } + }); + + /** + * Clean up. + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._volume.dispose(); + this._volume = null; + this._writable("volume"); + this.volume = null; + this.buffers.dispose(); + this.buffers = null; + for (var i = 0; i < this._activeSources.length; i++){ + this._activeSources[i].dispose(); + } + this._activeSources = null; + return this; + }; + + return Tone.MultiPlayer; +}); \ No newline at end of file diff --git a/Tone/source/Noise.js b/Tone/source/Noise.js index 94d308ad4..86bceec9b 100644 --- a/Tone/source/Noise.js +++ b/Tone/source/Noise.js @@ -100,7 +100,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ this._buffer = _brownNoise; break; default : - throw new Error("invalid noise type: "+type) + throw new TypeError("Tone.Noise: invalid type: "+type); } //if it's playing, stop and restart it if (this.state === Tone.State.Started){ @@ -143,7 +143,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ this._source.loop = true; this._source.playbackRate.value = this._playbackRate; this._source.connect(this.output); - this._source.start(this.toSeconds(time)); + this._source.start(this.toSeconds(time), Math.random() * (this._buffer.duration - 0.001)); }; /** diff --git a/Tone/source/OmniOscillator.js b/Tone/source/OmniOscillator.js index c118a2782..7fede3320 100644 --- a/Tone/source/OmniOscillator.js +++ b/Tone/source/OmniOscillator.js @@ -1,22 +1,23 @@ -define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", - "Tone/source/PulseOscillator", "Tone/source/PWMOscillator"], +define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/source/PulseOscillator", "Tone/source/PWMOscillator", + "Tone/source/FMOscillator", "Tone/source/AMOscillator", "Tone/source/FatOscillator"], function(Tone){ "use strict"; /** * @class Tone.OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator, - * and Tone.PWMOscillator into one class, allowing it to have the - * types: sine, square, triangle, sawtooth, pulse or pwm. Additionally, - * OmniOscillator is capable of setting the first x number of partials - * of the oscillator. For example: "sine4" would set be the first 4 - * partials of the sine wave and "triangle8" would set the first - * 8 partials of the triangle wave. + * Tone.PWMOscillator, Tone.FMOscillator, Tone.AMOscillator, and Tone.FatOscillator + * into one class. The oscillator class can be changed by setting the `type`. + * `omniOsc.type = "pwm"` will set it to the Tone.PWMOscillator. Prefixing + * any of the basic types ("sine", "square4", etc.) with "fm", "am", or "fat" + * will use the FMOscillator, AMOscillator or FatOscillator respectively. + * For example: `omniOsc.type = "fatsawtooth"` will create set the oscillator + * to a FatOscillator of type "sawtooth". * * @extends {Tone.Oscillator} * @constructor * @param {Frequency} frequency The initial frequency of the oscillator. - * @param {string} type The type of the oscillator. + * @param {String} type The type of the oscillator. * @example * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); */ @@ -40,25 +41,23 @@ function(Tone){ /** * the type of the oscillator source - * @type {string} + * @type {String} * @private */ this._sourceType = undefined; /** * the oscillator - * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} + * @type {Tone.Oscillator} * @private */ this._oscillator = null; //set the oscillator this.type = options.type; - this.phase = options.phase; this._readOnly(["frequency", "detune"]); - if (this.isArray(options.partials)){ - this.partials = options.partials; - } + //set the options + this.set(options); }; Tone.extend(Tone.OmniOscillator, Tone.Oscillator); @@ -74,18 +73,19 @@ function(Tone){ "detune" : 0, "type" : "sine", "phase" : 0, - "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} + * @enum {String} * @private */ var OmniOscType = { - PulseOscillator : "PulseOscillator", - PWMOscillator : "PWMOscillator", - Oscillator : "Oscillator" + Pulse : "PulseOscillator", + PWM : "PWMOscillator", + Osc : "Oscillator", + FM : "FMOscillator", + AM : "AMOscillator", + Fat : "FatOscillator" }; /** @@ -107,35 +107,54 @@ function(Tone){ }; /** - * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse. + * The type of the oscillator. Can be any of the basic types: sine, square, triangle, sawtooth. Or + * prefix the basic types with "fm", "am", or "fat" to use the FMOscillator, AMOscillator or FatOscillator + * types. The oscillator could also be set to "pwm" or "pulse". All of the parameters of the + * oscillator's class are accessible when the oscillator is set to that type, but throws an error + * when it's not. + * * @memberOf Tone.OmniOscillator# - * @type {string} + * @type {String} * @name type + * @example + * omniOsc.type = "pwm"; + * //modulationFrequency is parameter which is available + * //only when the type is "pwm". + * omniOsc.modulationFrequency.value = 0.5; + * @example + * //an square wave frequency modulated by a sawtooth + * omniOsc.type = "fmsquare"; + * omniOsc.modulationType = "sawtooth"; */ Object.defineProperty(Tone.OmniOscillator.prototype, "type", { get : function(){ - return this._oscillator.type; + var prefix = ""; + if (this._sourceType === OmniOscType.FM){ + prefix = "fm"; + } else if (this._sourceType === OmniOscType.AM){ + prefix = "am"; + } else if (this._sourceType === OmniOscType.Fat){ + prefix = "fat"; + } + return prefix + this._oscillator.type; }, set : function(type){ - if (type.indexOf("sine") === 0 || type.indexOf("square") === 0 || - type.indexOf("triangle") === 0 || type.indexOf("sawtooth") === 0 || type === Tone.Oscillator.Type.Custom){ - if (this._sourceType !== OmniOscType.Oscillator){ - this._sourceType = OmniOscType.Oscillator; - this._createNewOscillator(Tone.Oscillator); - } - this._oscillator.type = type; + if (type.substr(0, 2) === "fm"){ + this._createNewOscillator(OmniOscType.FM); + this._oscillator.type = type.substr(2); + } else if (type.substr(0, 2) === "am"){ + this._createNewOscillator(OmniOscType.AM); + this._oscillator.type = type.substr(2); + } else if (type.substr(0, 3) === "fat"){ + this._createNewOscillator(OmniOscType.Fat); + this._oscillator.type = type.substr(3); } else if (type === "pwm"){ - if (this._sourceType !== OmniOscType.PWMOscillator){ - this._sourceType = OmniOscType.PWMOscillator; - this._createNewOscillator(Tone.PWMOscillator); - } + this._createNewOscillator(OmniOscType.PWM); } else if (type === "pulse"){ - if (this._sourceType !== OmniOscType.PulseOscillator){ - this._sourceType = OmniOscType.PulseOscillator; - this._createNewOscillator(Tone.PulseOscillator); - } + this._createNewOscillator(OmniOscType.Pulse); } else { - throw new Error("Tone.OmniOscillator does not support type "+type); + this._createNewOscillator(OmniOscType.Osc); + this._oscillator.type = type; } } }); @@ -147,6 +166,7 @@ function(Tone){ * following the harmonic series. * Setting this value will automatically set the type to "custom". * The value is an empty array when the type is not "custom". + * This is not available on "pwm" and "pulse" oscillator types. * @memberOf Tone.OmniOscillator# * @type {Array} * @name partials @@ -158,35 +178,55 @@ function(Tone){ return this._oscillator.partials; }, set : function(partials){ - if (this._sourceType !== OmniOscType.Oscillator){ - this.type = Tone.Oscillator.Type.Custom; - } this._oscillator.partials = partials; } }); + /** + * Set a member/attribute of the oscillator. + * @param {Object|String} params + * @param {number=} value + * @param {Time=} rampTime + * @returns {Tone.OmniOscillator} this + */ + Tone.OmniOscillator.prototype.set = function(params, value){ + //make sure the type is set first + if (params === "type"){ + this.type = value; + } else if (this.isObject(params) && params.hasOwnProperty("type")){ + this.type = params.type; + } + //then set the rest + Tone.prototype.set.apply(this, arguments); + return this; + }; + /** * 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.blockTime; - if (this._oscillator !== null){ - var oldOsc = this._oscillator; - oldOsc.stop(now); - //dispose the old one - setTimeout(function(){ - oldOsc.dispose(); - oldOsc = null; - }, this.blockTime * 1000); - } - this._oscillator = new OscillatorConstructor(); - this.frequency.connect(this._oscillator.frequency); - this.detune.connect(this._oscillator.detune); - this._oscillator.connect(this.output); - if (this.state === Tone.State.Started){ - this._oscillator.start(now); + Tone.OmniOscillator.prototype._createNewOscillator = function(oscType){ + if (oscType !== this._sourceType){ + this._sourceType = oscType; + var OscillatorConstructor = Tone[oscType]; + //short delay to avoid clicks on the change + var now = this.now() + this.blockTime; + if (this._oscillator !== null){ + var oldOsc = this._oscillator; + oldOsc.stop(now); + //dispose the old one + setTimeout(function(){ + oldOsc.dispose(); + oldOsc = null; + }, this.blockTime * 1000); + } + this._oscillator = new OscillatorConstructor(); + this.frequency.connect(this._oscillator.frequency); + this.detune.connect(this._oscillator.detune); + this._oscillator.connect(this.output); + if (this.state === Tone.State.Started){ + this._oscillator.start(now); + } } }; @@ -206,7 +246,7 @@ function(Tone){ }); /** - * The width of the oscillator (only if the oscillator is set to pulse) + * The width of the oscillator (only if the oscillator is set to "pulse") * @memberOf Tone.OmniOscillator# * @type {NormalRange} * @signal @@ -218,15 +258,114 @@ function(Tone){ */ Object.defineProperty(Tone.OmniOscillator.prototype, "width", { get : function(){ - if (this._sourceType === OmniOscType.PulseOscillator){ + if (this._sourceType === OmniOscType.Pulse){ return this._oscillator.width; } } }); + /** + * The number of detuned oscillators + * @memberOf Tone.OmniOscillator# + * @type {Number} + * @name count + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "count", { + get : function(){ + if (this._sourceType === OmniOscType.Fat){ + return this._oscillator.count; + } + }, + set : function(count){ + if (this._sourceType === OmniOscType.Fat){ + this._oscillator.count = count; + } + } + }); + + /** + * The detune spread between the oscillators. If "count" is + * set to 3 oscillators and the "spread" is set to 40, + * the three oscillators would be detuned like this: [-20, 0, 20] + * for a total detune spread of 40 cents. See Tone.FatOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @type {Cents} + * @name spread + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "spread", { + get : function(){ + if (this._sourceType === OmniOscType.Fat){ + return this._oscillator.spread; + } + }, + set : function(spread){ + if (this._sourceType === OmniOscType.Fat){ + this._oscillator.spread = spread; + } + } + }); + + /** + * The type of the modulator oscillator. Only if the oscillator + * is set to "am" or "fm" types. see. Tone.AMOscillator or Tone.FMOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @type {String} + * @name modulationType + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "modulationType", { + get : function(){ + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){ + return this._oscillator.modulationType; + } + }, + set : function(mType){ + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){ + this._oscillator.modulationType = mType; + } + } + }); + + /** + * 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. + * See Tone.FMOscillator for more info. + * @type {Positive} + * @signal + * @name modulationIndex + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "modulationIndex", { + get : function(){ + if (this._sourceType === OmniOscType.FM){ + return this._oscillator.modulationIndex; + } + } + }); + + /** + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. See Tone.AMOscillator or Tone.FMOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @signal + * @type {Positive} + * @name harmonicity + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "harmonicity", { + get : function(){ + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){ + return this._oscillator.harmonicity; + } + } + }); + /** * The modulationFrequency Signal of the oscillator - * (only if the oscillator type is set to pwm). + * (only if the oscillator type is set to pwm). See + * Tone.PWMOscillator for more info. * @memberOf Tone.OmniOscillator# * @type {Frequency} * @signal @@ -238,7 +377,7 @@ function(Tone){ */ Object.defineProperty(Tone.OmniOscillator.prototype, "modulationFrequency", { get : function(){ - if (this._sourceType === OmniOscType.PWMOscillator){ + if (this._sourceType === OmniOscType.PWM){ return this._oscillator.modulationFrequency; } } diff --git a/Tone/source/Oscillator.js b/Tone/source/Oscillator.js index 2f8203704..141f404cd 100644 --- a/Tone/source/Oscillator.js +++ b/Tone/source/Oscillator.js @@ -247,7 +247,7 @@ function(Tone){ b = this._partials[n - 1]; break; default: - throw new Error("invalid oscillator type: "+type); + throw new TypeError("Tone.Oscillator: invalid type: "+type); } if (b !== 0){ real[n] = -b * Math.sin(phase * n); diff --git a/Tone/source/PWMOscillator.js b/Tone/source/PWMOscillator.js index 641a5d67b..f2eb65094 100644 --- a/Tone/source/PWMOscillator.js +++ b/Tone/source/PWMOscillator.js @@ -46,7 +46,7 @@ function(Tone){ * @type {Tone.Multiply} * @private */ - this._scale = new Tone.Multiply(1.01); + this._scale = new Tone.Multiply(2); /** * The frequency control. diff --git a/Tone/source/Player.js b/Tone/source/Player.js index 82ca2d8b9..d17a1ab3d 100644 --- a/Tone/source/Player.js +++ b/Tone/source/Player.js @@ -13,9 +13,8 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To * Recommended to use Tone.Buffer.onload instead. * @example * var player = new Tone.Player("./path/to/sample.mp3").toMaster(); - * Tone.Buffer.onload = function(){ - * player.start(); - * } + * //play as soon as the buffer is loaded + * player.autostart = true; */ Tone.Player = function(url){ @@ -149,16 +148,25 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To }; /** - * play the buffer between the desired positions + * Play the buffer at the given startTime. Optionally add an offset + * and/or duration which will play the buffer from a position + * within the buffer for the given duration. * - * @private - * @param {Time} [startTime=now] when the player should start. - * @param {Time} [offset=0] the offset from the beginning of the sample + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample * to start at. - * @param {Time=} duration how long the sample should play. If no duration + * @param {Time=} duration How long the sample should play. If no duration * is given, it will default to the full length * of the sample (minus any offset) * @returns {Tone.Player} this + * @memberOf Tone.Player# + * @method start + * @name start + */ + + /** + * Internal start method + * @private */ Tone.Player.prototype._start = function(startTime, offset, duration){ if (this._buffer.loaded){ @@ -196,7 +204,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To this._source.start(startTime, offset, duration); } } else { - throw Error("tried to start Player before the buffer was loaded"); + throw Error("Tone.Player: tried to start Player before the buffer was loaded"); } return this; }; diff --git a/Tone/source/Source.js b/Tone/source/Source.js index e5312d5f1..7bbddb553 100644 --- a/Tone/source/Source.js +++ b/Tone/source/Source.js @@ -1,5 +1,5 @@ -define(["Tone/core/Tone", "Tone/core/Transport", "Tone/component/Volume", - "Tone/core/Type", "Tone/core/TimelineState", "Tone/signal/Signal"], +define(["Tone/core/Tone", "Tone/core/Transport", "Tone/component/Volume", "Tone/core/Master", + "Tone/type/Type", "Tone/core/TimelineState", "Tone/signal/Signal"], function(Tone){ "use strict"; @@ -55,6 +55,7 @@ function(Tone){ * @private */ this._state = new Tone.TimelineState(Tone.State.Stopped); + this._state.memory = 10; /** * The synced `start` callback function from the transport @@ -84,6 +85,8 @@ function(Tone){ //make the output explicitly stereo this._volume.output.output.channelCount = 2; this._volume.output.output.channelCountMode = "explicit"; + //mute initially + this.mute = options.mute; }; Tone.extend(Tone.Source); @@ -96,6 +99,7 @@ function(Tone){ */ Tone.Source.defaults = { "volume" : 0, + "mute" : false }; /** @@ -111,6 +115,24 @@ function(Tone){ } }); + /** + * Mute the output. + * @memberOf Tone.Source# + * @type {boolean} + * @name mute + * @example + * //mute the output + * source.mute = true; + */ + Object.defineProperty(Tone.Source.prototype, "mute", { + get : function(){ + return this._volume.mute; + }, + set : function(mute){ + this._volume.mute = mute; + } + }); + /** * Start the source at the specified time. If no time is given, * start the source now. @@ -140,11 +162,10 @@ function(Tone){ */ Tone.Source.prototype.stop = function(time){ time = this.toSeconds(time); - if (this._state.getStateAtTime(time) === Tone.State.Started){ - this._state.setStateAtTime(Tone.State.Stopped, time); - if (this._stop){ - this._stop.apply(this, arguments); - } + this._state.cancel(time); + this._state.setStateAtTime(Tone.State.Stopped, time); + if (this._stop){ + this._stop.apply(this, arguments); } return this; }; diff --git a/Tone/type/Frequency.js b/Tone/type/Frequency.js new file mode 100644 index 000000000..bec97987e --- /dev/null +++ b/Tone/type/Frequency.js @@ -0,0 +1,278 @@ +define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) { + + /** + * @class Tone.Frequency is a primitive type for encoding Frequency values. + * Eventually all time values are evaluated to hertz + * using the `eval` method. + * @constructor + * @extends {Tone.TimeBase} + * @param {String|Number} val The time value. + * @param {String=} units The units of the value. + * @example + * Tone.Frequency("C3").eval() // 261 + * Tone.Frequency(38, "midi").eval() // + * Tone.Frequency("C3").transpose(4).eval(); + */ + Tone.Frequency = function(val, units){ + if (this instanceof Tone.Frequency){ + + Tone.TimeBase.call(this, val, units); + + } else { + return new Tone.Frequency(val, units); + } + }; + + Tone.extend(Tone.Frequency, Tone.TimeBase); + + /////////////////////////////////////////////////////////////////////////// + // AUGMENT BASE EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + //clone the expressions so that + //we can add more without modifying the original + Tone.Frequency.prototype._primaryExpressions = Object.create(Tone.TimeBase.prototype._primaryExpressions); + + /* + * midi type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.midi = { + regexp : /^(\d+(?:\.\d+)?midi)/, + method : function(value){ + return this.midiToFrequency(value); + } + }; + + /* + * note type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.note = { + regexp : /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i, + method : function(pitch, octave){ + var index = noteToScaleIndex[pitch.toLowerCase()]; + var noteNumber = index + (parseInt(octave) + 1) * 12; + return this.midiToFrequency(noteNumber); + } + }; + + /* + * BeatsBarsSixteenths type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.tr = { + regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method : function(m, q, s){ + var total = 1; + if (m && m !== "0"){ + total *= this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== "0"){ + total *= this._beatsToUnits(parseFloat(q)); + } + if (s && s !== "0"){ + total *= this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }; + + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Transposes the frequency by the given number of semitones. + * @param {Interval} interval + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").transpose(3); //"C5" + */ + Tone.Frequency.prototype.transpose = function(interval){ + this._expr = function(expr, interval){ + var val = expr(); + return val * this.intervalToFrequencyRatio(interval); + }.bind(this, this._expr, interval); + return this; + }; + + /** + * Takes an array of semitone intervals and returns + * an array of frequencies transposed by those intervals. + * @param {Array} intervals + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").harmonize([0, 3, 7]); //["A4", "C5", "E5"] + */ + Tone.Frequency.prototype.harmonize = function(intervals){ + this._expr = function(expr, intervals){ + var val = expr(); + var ret = []; + for (var i = 0; i < intervals.length; i++){ + ret[i] = val * this.intervalToFrequencyRatio(intervals[i]); + } + return ret; + }.bind(this, this._expr, intervals); + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Return the value of the frequency as a MIDI note + * @return {MIDI} + * @example + * Tone.Frequency("C4").toMidi(); //60 + */ + Tone.Frequency.prototype.toMidi = function(){ + return this.frequencyToMidi(this.eval()); + }; + + /** + * Return the value of the frequency in Scientific Pitch Notation + * @return {Note} + * @example + * Tone.Frequency(69, "midi").toNote(); //"A4" + */ + Tone.Frequency.prototype.toNote = function(){ + var freq = this.eval(); + var log = Math.log(freq / Tone.Frequency.A4) / Math.LN2; + var noteNumber = Math.round(12 * log) + 57; + var octave = Math.floor(noteNumber/12); + if(octave < 0){ + noteNumber += -12 * octave; + } + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); + }; + + /** + * Return the duration of one cycle in seconds. + * @return {Seconds} + */ + Tone.Frequency.prototype.toSeconds = function(){ + return 1 / this.eval(); + }; + + /** + * Return the duration of one cycle in ticks + * @return {Ticks} + */ + Tone.Frequency.prototype.toTicks = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS HELPERS + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private + */ + Tone.Frequency.prototype._frequencyToUnits = function(freq){ + return freq; + }; + + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.Frequency.prototype._ticksToUnits = function(ticks){ + return 1 / ((ticks * 60) / (Tone.Transport.bpm.value * Tone.Transport.PPQ)); + }; + + /** + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.Frequency.prototype._beatsToUnits = function(beats){ + return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats); + }; + + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.Frequency.prototype._secondsToUnits = function(seconds){ + return 1 / seconds; + }; + + /** + * The default units if none are given. + * @private + */ + Tone.Frequency.prototype._defaultUnits = "hz"; + + /////////////////////////////////////////////////////////////////////////// + // FREQUENCY CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Note to scale index + * @type {Object} + */ + var noteToScaleIndex = { + "cbb" : -2, "cb" : -1, "c" : 0, "c#" : 1, "cx" : 2, + "dbb" : 0, "db" : 1, "d" : 2, "d#" : 3, "dx" : 4, + "ebb" : 2, "eb" : 3, "e" : 4, "e#" : 5, "ex" : 6, + "fbb" : 3, "fb" : 4, "f" : 5, "f#" : 6, "fx" : 7, + "gbb" : 5, "gb" : 6, "g" : 7, "g#" : 8, "gx" : 9, + "abb" : 7, "ab" : 8, "a" : 9, "a#" : 10, "ax" : 11, + "bbb" : 9, "bb" : 10, "b" : 11, "b#" : 12, "bx" : 13, + }; + + /** + * scale index to note (sharps) + * @type {Array} + */ + var scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; + + /** + * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch) + * A4's values in Hertz. + * @type {Frequency} + * @static + */ + Tone.Frequency.A4 = 440; + + /** + * Convert a MIDI note to frequency value. + * @param {MIDI} midi The midi number to convert. + * @return {Frequency} the corresponding frequency value + * @example + * tone.midiToFrequency(69); // returns 440 + */ + Tone.Frequency.prototype.midiToFrequency = function(midi){ + return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12); + }; + + /** + * Convert a frequency value to a MIDI note. + * @param {Frequency} frequency The value to frequency value to convert. + * @returns {MIDI} + * @example + * tone.midiToFrequency(440); // returns 69 + */ + Tone.Frequency.prototype.frequencyToMidi = function(frequency){ + return 69 + 12 * Math.log(frequency / Tone.Frequency.A4) / Math.LN2; + }; + + return Tone.Frequency; +}); \ No newline at end of file diff --git a/Tone/type/Time.js b/Tone/type/Time.js new file mode 100644 index 000000000..41c694975 --- /dev/null +++ b/Tone/type/Time.js @@ -0,0 +1,263 @@ +define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) { + + /** + * @class Tone.Time is a primitive type for encoding Time values. + * Eventually all time values are evaluated to seconds + * using the `eval` method. Tone.Time can be constructed + * with or without the `new` keyword. Tone.Time can be passed + * into the parameter of any method which takes time as an argument. + * @constructor + * @extends {Tone.TimeBase} + * @param {String|Number} val The time value. + * @param {String=} units The units of the value. + * @example + * var t = Tone.Time("4n");//encodes a quarter note + * t.mult(4); // multiply that value by 4 + * t.toNotation(); //returns "1m" + */ + Tone.Time = function(val, units){ + if (this instanceof Tone.Time){ + + /** + * If the current clock time should + * be added to the output + * @type {Boolean} + * @private + */ + this._plusNow = false; + + Tone.TimeBase.call(this, val, units); + + } else { + return new Tone.Time(val, units); + } + }; + + Tone.extend(Tone.Time, Tone.TimeBase); + + //clone the expressions so that + //we can add more without modifying the original + Tone.Time.prototype._unaryExpressions = Object.create(Tone.TimeBase.prototype._unaryExpressions); + + /* + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.quantize = { + regexp : /^@/, + method : function(rh){ + return Tone.Transport.nextSubdivision(rh()); + } + }; + + /* + * Adds an additional unary expression + * which adds the current clock time. + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.now = { + regexp : /^\+/, + method : function(lh){ + this._plusNow = true; + return lh(); + } + }; + + /** + * Quantize the time by the given subdivision. Optionally add a + * percentage which will move the time value towards the ideal + * quantized value by that percentage. + * @param {Number|Time} val The subdivision to quantize to + * @param {NormalRange} [percent=1] Move the time value + * towards the quantized value by + * a percentage. + * @return {Tone.Time} this + * @example + * Tone.Time(21).quantize(2).eval() //returns 22 + * Tone.Time(0.6).quantize("4n", 0.5).eval() //returns 0.55 + */ + Tone.Time.prototype.quantize = function(subdiv, percent){ + percent = this.defaultArg(percent, 1); + this._expr = function(expr, subdivision, percent){ + expr = expr(); + subdivision = subdivision.toSeconds(); + var multiple = Math.round(expr / subdivision); + var ideal = multiple * subdivision; + var diff = ideal - expr; + return expr + diff * percent; + }.bind(this, this._expr, new this.constructor(subdiv), percent); + return this; + }; + + /** + * Adds the clock time to the time expression at the + * moment of evaluation. + * @return {Tone.Time} this + */ + Tone.Time.prototype.addNow = function(){ + this._plusNow = true; + return this; + }; + + /** + * @override + * Override the default value return when no arguments are passed in. + * The default value is 'now' + * @private + */ + Tone.Time.prototype._defaultExpr = function(){ + this._plusNow = true; + return this._noOp; + }; + + + //CONVERSIONS////////////////////////////////////////////////////////////// + + /** + * Convert a Time to Notation. Values will be thresholded to the nearest 128th note. + * @return {Notation} + * @example + * //if the Transport is at 120bpm: + * Tone.Time(2).toNotation();//returns "1m" + */ + Tone.Time.prototype.toNotation = function(){ + var time = this.toSeconds(); + var testNotations = ["1m", "2n", "4n", "8n", "16n", "32n", "64n", "128n"]; + var retNotation = this._toNotationHelper(time, testNotations); + //try the same thing but with tripelets + var testTripletNotations = ["1m", "2n", "2t", "4n", "4t", "8n", "8t", "16n", "16t", "32n", "32t", "64n", "64t", "128n"]; + var retTripletNotation = this._toNotationHelper(time, testTripletNotations); + //choose the simpler expression of the two + if (retTripletNotation.split("+").length < retNotation.split("+").length){ + return retTripletNotation; + } else { + return retNotation; + } + }; + + /** + * Helper method for Tone.toNotation + * @param {Number} units + * @param {Array} testNotations + * @return {String} + * @private + */ + Tone.Time.prototype._toNotationHelper = function(units, testNotations){ + //the threshold is the last value in the array + var threshold = this._notationToUnits(testNotations[testNotations.length - 1]); + var retNotation = ""; + for (var i = 0; i < testNotations.length; i++){ + var notationTime = this._notationToUnits(testNotations[i]); + //account for floating point errors (i.e. round up if the value is 0.999999) + var multiple = units / notationTime; + var floatingPointError = 0.000001; + if (1 - multiple % 1 < floatingPointError){ + multiple += floatingPointError; + } + multiple = Math.floor(multiple); + if (multiple > 0){ + if (multiple === 1){ + retNotation += testNotations[i]; + } else { + retNotation += multiple.toString() + "*" + testNotations[i]; + } + units -= multiple * notationTime; + if (units < threshold){ + break; + } else { + retNotation += " + "; + } + } + } + if (retNotation === ""){ + retNotation = "0"; + } + return retNotation; + }; + + /** + * Convert a notation value to the current units + * @param {Notation} notation + * @return {Number} + * @private + */ + Tone.Time.prototype._notationToUnits = function(notation){ + var primaryExprs = this._primaryExpressions; + var notationExprs = [primaryExprs.n, primaryExprs.t, primaryExprs.m]; + for (var i = 0; i < notationExprs.length; i++){ + var expr = notationExprs[i]; + var match = notation.match(expr.regexp); + if (match){ + return expr.method.call(this, match[1]); + } + } + }; + + /** + * Return the time encoded as Bars:Beats:Sixteenths. + * @return {BarsBeatsSixteenths} + */ + Tone.Time.prototype.toBarsBeatsSixteenths = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.toSeconds() / quarterTime; + var measures = Math.floor(quarters / this._timeSignature()); + var sixteenths = (quarters % 1) * 4; + quarters = Math.floor(quarters) % this._timeSignature(); + sixteenths = sixteenths.toString(); + if (sixteenths.length > 3){ + sixteenths = parseFloat(sixteenths).toFixed(3); + } + var progress = [measures, quarters, sixteenths]; + return progress.join(":"); + }; + + /** + * Return the time in ticks. + * @return {Ticks} + */ + Tone.Time.prototype.toTicks = function(){ + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + + /** + * Return the time in samples + * @return {Samples} + */ + Tone.Time.prototype.toSamples = function(){ + return this.toSeconds() * this.context.sampleRate; + }; + + /** + * Return the time as a frequency value + * @return {Frequency} + * @example + * Tone.Time(2).toFrequency(); //0.5 + */ + Tone.Time.prototype.toFrequency = function(){ + return 1/this.eval(); + }; + + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.Time.prototype.toSeconds = function(){ + return this._expr(); + }; + + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.Time.prototype.eval = function(){ + var val = this._expr(); + return val + (this._plusNow?this.now():0); + }; + + return Tone.Time; +}); \ No newline at end of file diff --git a/Tone/type/TimeBase.js b/Tone/type/TimeBase.js new file mode 100644 index 000000000..cd1601fb7 --- /dev/null +++ b/Tone/type/TimeBase.js @@ -0,0 +1,532 @@ +define(["Tone/core/Tone"], function (Tone) { + + /** + * @class Tone.TimeBase is a flexible encoding of time + * which can be evaluated to and from a string. + * Parsing code modified from https://code.google.com/p/tapdigit/ + * Copyright 2011 2012 Ariya Hidayat, New BSD License + * @extends {Tone} + * @param {Time} val The time value as a number or string + * @param {String=} units Unit values + * @example + * Tone.TimeBase(4, "n") + * Tone.TimeBase(2, "t") + * Tone.TimeBase("2t").add("1m") + * Tone.TimeBase("2t + 1m"); + */ + Tone.TimeBase = function(val, units){ + + //allows it to be constructed with or without 'new' + if (this instanceof Tone.TimeBase) { + + /** + * Any expressions parsed from the Time + * @type {Array} + * @private + */ + this._expr = this._noOp; + + //default units + units = this.defaultArg(units, this._defaultUnits); + + //get the value from the given time + if (this.isString(val)){ + this._expr = this._parseExprString(val); + } else if (this.isNumber(val)){ + var method = this._primaryExpressions[units].method; + this._expr = method.bind(this, val); + } else if (this.isUndef(val)){ + //default expression + this._expr = this._defaultExpr(); + } else if (val instanceof Tone.TimeBase){ + this._expr = val._expr; + } + } else { + + return new Tone.TimeBase(val, units); + } + }; + + Tone.extend(Tone.TimeBase); + + /** + * Repalce the current time value with the value + * given by the expression string. + * @param {String} exprString + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.set = function(exprString){ + this._expr = this._parseExprString(exprString); + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // ABSTRACT SYNTAX TREE PARSER + /////////////////////////////////////////////////////////////////////////// + + /** + * All the primary expressions. + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._primaryExpressions = { + "n" : { + regexp : /^(\d+)n/i, + method : function(value){ + value = parseInt(value); + if (value === 1){ + return this._beatsToUnits(this._timeSignature()); + } else { + return this._beatsToUnits(4 / value); + } + } + }, + "t" : { + regexp : /^(\d+)t/i, + method : function(value){ + value = parseInt(value); + return this._beatsToUnits(8 / (parseInt(value) * 3)); + } + }, + "m" : { + regexp : /^(\d+)m/i, + method : function(value){ + return this._beatsToUnits(parseInt(value) * this._timeSignature()); + } + }, + "i" : { + regexp : /^(\d+)i/i, + method : function(value){ + return this._ticksToUnits(parseInt(value)); + } + }, + "hz" : { + regexp : /^(\d+(?:\.\d+)?)hz/i, + method : function(value){ + return this._frequencyToUnits(parseFloat(value)); + } + }, + "tr" : { + regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method : function(m, q, s){ + var total = 0; + if (m && m !== "0"){ + total += this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== "0"){ + total += this._beatsToUnits(parseFloat(q)); + } + if (s && s !== "0"){ + total += this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }, + "s" : { + regexp : /^(\d+(?:\.\d+)?s)/, + method : function(value){ + return this._secondsToUnits(parseFloat(value)); + } + }, + "samples" : { + regexp : /^(\d+)samples/, + method : function(value){ + return parseInt(value) / this.context.sampleRate; + } + }, + "default" : { + regexp : /^(\d+(?:\.\d+)?)/, + method : function(value){ + return this._primaryExpressions[this._defaultUnits].method.call(this, value); + } + } + }; + + /** + * All the binary expressions that TimeBase can accept. + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._binaryExpressions = { + "+" : { + regexp : /^\+/, + precedence : 2, + method : function(lh, rh){ + return lh() + rh(); + } + }, + "-" : { + regexp : /^\-/, + precedence : 2, + method : function(lh, rh){ + return lh() - rh(); + } + }, + "*" : { + regexp : /^\*/, + precedence : 1, + method : function(lh, rh){ + return lh() * rh(); + } + }, + "/" : { + regexp : /^\//, + precedence : 1, + method : function(lh, rh){ + return lh() / rh(); + } + } + }; + + /** + * All the unary expressions. + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._unaryExpressions = { + "neg" : { + regexp : /^\-/, + method : function(lh){ + return -lh(); + } + } + }; + + /** + * Syntactic glue which holds expressions together + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._syntaxGlue = { + "(" : { + regexp : /^\(/ + }, + ")" : { + regexp : /^\)/ + } + }; + + /** + * tokenize the expression based on the Expressions object + * @param {string} expr + * @return {Object} returns two methods on the tokenized list, next and peek + * @private + */ + Tone.TimeBase.prototype._tokenize = function(expr){ + var position = -1; + var tokens = []; + + while(expr.length > 0){ + expr = expr.trim(); + var token = getNextToken(expr, this); + tokens.push(token); + expr = expr.substr(token.value.length); + } + + function getNextToken(expr, context){ + var expressions = ["_binaryExpressions", "_unaryExpressions", "_primaryExpressions", "_syntaxGlue"]; + for (var i = 0; i < expressions.length; i++){ + var group = context[expressions[i]]; + for (var opName in group){ + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null){ + return { + method : op.method, + precedence : op.precedence, + regexp : op.regexp, + value : match[0], + }; + } + } + } + throw new SyntaxError("Tone.TimeBase: Unexpected token "+expr); + } + + return { + next : function(){ + return tokens[++position]; + }, + peek : function(){ + return tokens[position + 1]; + } + }; + }; + + /** + * Given a token, find the value within the groupName + * @param {Object} token + * @param {String} groupName + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._matchGroup = function(token, group, prec) { + var ret = false; + if (!this.isUndef(token)){ + for (var opName in group){ + var op = group[opName]; + if (op.regexp.test(token.value)){ + if (!this.isUndef(prec)){ + if(op.precedence === prec){ + return op; + } + } else { + return op; + } + } + } + } + return ret; + }; + + /** + * Match a binary expression given the token and the precedence + * @param {Lexer} lexer + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._parseBinary = function(lexer, precedence){ + if (this.isUndef(precedence)){ + precedence = 2; + } + var expr; + if (precedence < 0){ + expr = this._parseUnary(lexer); + } else { + expr = this._parseBinary(lexer, precedence - 1); + } + var token = lexer.peek(); + while (token && this._matchGroup(token, this._binaryExpressions, precedence)){ + token = lexer.next(); + expr = token.method.bind(this, expr, this._parseBinary(lexer, precedence - 1)); + token = lexer.peek(); + } + return expr; + }; + + /** + * Match a unary expression. + * @param {Lexer} lexer + * @private + */ + Tone.TimeBase.prototype._parseUnary = function(lexer){ + var token, expr; + token = lexer.peek(); + var op = this._matchGroup(token, this._unaryExpressions); + if (op) { + token = lexer.next(); + expr = this._parseUnary(lexer); + return op.method.bind(this, expr); + } + return this._parsePrimary(lexer); + }; + + /** + * Match a primary expression (a value). + * @param {Lexer} lexer + * @private + */ + Tone.TimeBase.prototype._parsePrimary = function(lexer){ + var token, expr; + token = lexer.peek(); + if (this.isUndef(token)) { + throw new SyntaxError("Tone.TimeBase: Unexpected end of expression"); + } + if (this._matchGroup(token, this._primaryExpressions)) { + token = lexer.next(); + var matching = token.value.match(token.regexp); + return token.method.bind(this, matching[1], matching[2], matching[3]); + } + if (token && token.value === "("){ + lexer.next(); + expr = this._parseBinary(lexer); + token = lexer.next(); + if (!(token && token.value === ")")) { + throw new SyntaxError("Expected )"); + } + return expr; + } + throw new SyntaxError("Tone.TimeBase: Cannot process token " + token.value); + }; + + /** + * Recursively parse the string expression into a syntax tree. + * @param {string} expr + * @return {Function} the bound method to be evaluated later + * @private + */ + Tone.TimeBase.prototype._parseExprString = function(exprString){ + var lexer = this._tokenize(exprString); + var tree = this._parseBinary(lexer); + return tree; + }; + + /////////////////////////////////////////////////////////////////////////// + // DEFAULTS + /////////////////////////////////////////////////////////////////////////// + + /** + * The initial expression value + * @return {Number} The initial value 0 + * @private + */ + Tone.TimeBase.prototype._noOp = function(){ + return 0; + }; + + /** + * The default expression value if no arguments are given + * @private + */ + Tone.TimeBase.prototype._defaultExpr = function(){ + return this._noOp; + }; + + /** + * The default units if none are given. + * @private + */ + Tone.TimeBase.prototype._defaultUnits = "s"; + + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._frequencyToUnits = function(freq){ + return 1/freq; + }; + + /** + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._beatsToUnits = function(beats){ + return (60 / Tone.Transport.bpm.value) * beats; + }; + + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._secondsToUnits = function(seconds){ + return seconds; + }; + + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._ticksToUnits = function(ticks){ + return ticks * (this._beatsToUnits(1) / Tone.Transport.PPQ); + }; + + /** + * Return the time signature. + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._timeSignature = function(){ + return Tone.Transport.timeSignature; + }; + + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + + /** + * Push an expression onto the expression list + * @param {Time} val + * @param {String} type + * @param {String} units + * @return {Tone.TimeBase} + * @private + */ + Tone.TimeBase.prototype._pushExpr = function(val, name, units){ + //create the expression + if (!(val instanceof Tone.TimeBase)){ + val = new Tone.TimeBase(val, units); + } + this._expr = this._binaryExpressions[name].method.bind(this, this._expr, val._expr); + return this; + }; + + /** + * Add to the current value. + * @param {Time} val The value to add + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").add("1m"); //"3m" + */ + Tone.TimeBase.prototype.add = function(val, units){ + return this._pushExpr(val, "+", units); + }; + + /** + * Subtract the value from the current time. + * @param {Time} val The value to subtract + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").sub("1m"); //"1m" + */ + Tone.TimeBase.prototype.sub = function(val, units){ + return this._pushExpr(val, "-", units); + }; + + /** + * Multiply the current value by the given time. + * @param {Time} val The value to multiply + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").mult("2"); //"4m" + */ + Tone.TimeBase.prototype.mult = function(val, units){ + return this._pushExpr(val, "*", units); + }; + + /** + * Divide the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").div(2); //"1m" + */ + Tone.TimeBase.prototype.div = function(val, units){ + return this._pushExpr(val, "/", units); + }; + + /** + * Evaluate the time value. Returns the time + * in seconds. + * @return {Seconds} + */ + Tone.TimeBase.prototype.eval = function(){ + return this._expr(); + }; + + /** + * Clean up + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.dispose = function(){ + this._expr = null; + }; + + return Tone.TimeBase; +}); \ No newline at end of file diff --git a/Tone/type/TransportTime.js b/Tone/type/TransportTime.js new file mode 100644 index 000000000..ef5908d82 --- /dev/null +++ b/Tone/type/TransportTime.js @@ -0,0 +1,82 @@ +define(["Tone/core/Tone", "Tone/type/Time"], function (Tone) { + + /** + * @class Tone.TransportTime is a the time along the Transport's + * timeline. It is similar to Tone.Time, but instead of evaluating + * against the AudioContext's clock, it is evaluated against + * the Transport's position. See [TransportTime wiki](https://github.com/Tonejs/Tone.js/wiki/TransportTime). + * @constructor + * @param {Time} val The time value as a number or string + * @param {String=} units Unit values + * @extends {Tone.Time} + */ + Tone.TransportTime = function(val, units){ + if (this instanceof Tone.TransportTime){ + + Tone.Time.call(this, val, units); + + } else { + return new Tone.TransportTime(val, units); + } + }; + + Tone.extend(Tone.TransportTime, Tone.Time); + + //clone the expressions so that + //we can add more without modifying the original + Tone.TransportTime.prototype._unaryExpressions = Object.create(Tone.Time.prototype._unaryExpressions); + + /** + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private + */ + Tone.TransportTime.prototype._unaryExpressions.quantize = { + regexp : /^@/, + method : function(rh){ + var subdivision = this._secondsToTicks(rh()); + var multiple = Math.ceil(Tone.Transport.ticks / subdivision); + return this._ticksToUnits(multiple * subdivision); + } + }; + + /** + * Convert seconds into ticks + * @param {Seconds} seconds + * @return {Ticks} + * @private + */ + Tone.TransportTime.prototype._secondsToTicks = function(seconds){ + var quarterTime = this._beatsToUnits(1); + var quarters = seconds / quarterTime; + return Math.round(quarters * Tone.Transport.PPQ); + }; + + /** + * Evaluate the time expression. Returns values in ticks + * @return {Ticks} + */ + Tone.TransportTime.prototype.eval = function(){ + var val = this._secondsToTicks(this._expr()); + return val + (this._plusNow ? Tone.Transport.ticks : 0); + }; + + /** + * Return the time in ticks. + * @return {Ticks} + */ + Tone.TransportTime.prototype.toTicks = function(){ + return this.eval(); + }; + + /** + * Return the time as a frequency value + * @return {Frequency} + */ + Tone.TransportTime.prototype.toFrequency = function(){ + return 1/this.toSeconds(); + }; + + return Tone.TransportTime; +}); \ No newline at end of file diff --git a/Tone/type/Type.js b/Tone/type/Type.js new file mode 100644 index 000000000..76e8f6fb3 --- /dev/null +++ b/Tone/type/Type.js @@ -0,0 +1,224 @@ +define(["Tone/core/Tone", "Tone/type/Time", "Tone/type/Frequency", "Tone/type/TransportTime"], +function (Tone) { + + /////////////////////////////////////////////////////////////////////////// + // TYPES + /////////////////////////////////////////////////////////////////////////// + + /** + * Units which a value can take on. + * @enum {String} + */ + Tone.Type = { + /** + * Default units + * @typedef {Default} + */ + Default : "number", + /** + * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). + * + * + * + * @typedef {Time} + */ + Time : "time", + /** + * Frequency can be described similar to time, except ultimately the + * values are converted to frequency instead of seconds. A number + * is taken literally as the value in hertz. Additionally any of the + * Time encodings can be used. Note names in the form + * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their + * frequency value. + * @typedef {Frequency} + */ + Frequency : "frequency", + /** + * TransportTime describes a position along the Transport's timeline. It is + * similar to Time in that it uses all the same encodings, but TransportTime specifically + * pertains to the Transport's timeline, which is startable, stoppable, loopable, and seekable. + * [Read more](https://github.com/Tonejs/Tone.js/wiki/TransportTime) + * @typedef {TransportTime} + */ + TransportTime : "transportTime", + /** + * Ticks are the basic subunit of the Transport. They are + * the smallest unit of time that the Transport supports. + * @typedef {Ticks} + */ + Ticks : "ticks", + /** + * Normal values are within the range [0, 1]. + * @typedef {NormalRange} + */ + NormalRange : "normalRange", + /** + * AudioRange values are between [-1, 1]. + * @typedef {AudioRange} + */ + AudioRange : "audioRange", + /** + * Decibels are a logarithmic unit of measurement which is useful for volume + * because of the logarithmic way that we perceive loudness. 0 decibels + * means no change in volume. -10db is approximately half as loud and 10db + * is twice is loud. + * @typedef {Decibels} + */ + Decibels : "db", + /** + * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. + * @typedef {Interval} + */ + Interval : "interval", + /** + * Beats per minute. + * @typedef {BPM} + */ + BPM : "bpm", + /** + * The value must be greater than or equal to 0. + * @typedef {Positive} + */ + Positive : "positive", + /** + * A cent is a hundredth of a semitone. + * @typedef {Cents} + */ + Cents : "cents", + /** + * Angle between 0 and 360. + * @typedef {Degrees} + */ + Degrees : "degrees", + /** + * A number representing a midi note. + * @typedef {MIDI} + */ + MIDI : "midi", + /** + * A colon-separated representation of time in the form of + * Bars:Beats:Sixteenths. + * @typedef {BarsBeatsSixteenths} + */ + BarsBeatsSixteenths : "barsBeatsSixteenths", + /** + * Sampling is the reduction of a continuous signal to a discrete signal. + * Audio is typically sampled 44100 times per second. + * @typedef {Samples} + */ + Samples : "samples", + /** + * Hertz are a frequency representation defined as one cycle per second. + * @typedef {Hertz} + */ + Hertz : "hertz", + /** + * A frequency represented by a letter name, + * accidental and octave. This system is known as + * [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). + * @typedef {Note} + */ + Note : "note", + /** + * One millisecond is a thousandth of a second. + * @typedef {Milliseconds} + */ + Milliseconds : "milliseconds", + /** + * Seconds are the time unit of the AudioContext. In the end, + * all values need to be evaluated to seconds. + * @typedef {Seconds} + */ + Seconds : "seconds", + /** + * A string representing a duration relative to a measure. + * + * @typedef {Notation} + */ + Notation : "notation", + }; + + /////////////////////////////////////////////////////////////////////////// + // AUGMENT TONE's PROTOTYPE + /////////////////////////////////////////////////////////////////////////// + + /** + * Convert Time into seconds. + * + * Unlike the method which it overrides, this takes into account + * transporttime and musical notation. + * + * Time : 1.40 + * Notation: 4n|1m|2t + * Now Relative: +3n + * Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1) + * + * @param {Time} time + * @return {Seconds} + */ + Tone.prototype.toSeconds = function(time){ + if (this.isNumber(time)){ + return time; + } else if (this.isString(time) || this.isUndef(time)){ + return (new Tone.Time(time)).eval(); + } else if (time instanceof Tone.TransportTime){ + return time.toSeconds(); + } else if (time instanceof Tone.Time){ + return time.eval(); + } else if (time instanceof Tone.Frequency){ + return time.toSeconds(); + } + }; + + /** + * Convert a frequency representation into a number. + * @param {Frequency} freq + * @return {Hertz} the frequency in hertz + */ + Tone.prototype.toFrequency = function(freq){ + if (this.isNumber(freq)){ + return freq; + } else if (this.isString(freq) || this.isUndef(freq)){ + return (new Tone.Frequency(freq)).eval(); + } else if (freq instanceof Tone.Frequency){ + return freq.eval(); + } else if (freq instanceof Tone.Time){ + return freq.toFrequency(); + } + }; + + /** + * Convert a time representation into ticks. + * @param {Time} time + * @return {Ticks} the time in ticks + */ + Tone.prototype.toTicks = function(time){ + if (this.isNumber(time) || this.isString(time) || this.isUndef(time)){ + return (new Tone.TransportTime(time)).eval(); + } else if (time instanceof Tone.Frequency){ + return time.toTicks(); + } else if (time instanceof Tone.TransportTime){ + return time.eval(); + } else if (time instanceof Tone.Time){ + return time.toTicks(); + } + }; + + return Tone; +}); \ No newline at end of file diff --git a/build/Tone.js b/build/Tone.js index a6d5bff5b..22796d0bd 100644 --- a/build/Tone.js +++ b/build/Tone.js @@ -1,5 +1,20 @@ -(function (root) { +(function(root, factory){ + + //UMD + if ( typeof define === "function" && define.amd ) { + define(function() { + return factory(); + }); + } else if (typeof module === "object") { + module.exports = factory(); + } else { + root.Tone = factory(); + } + +}(this, function(){ + "use strict"; + var Tone; //constructs the main Tone object function Main(func){ @@ -8,12 +23,11 @@ //invokes each of the modules with the main Tone object as the argument function Module(func){ func(Tone); - } - /** + } /** * Tone.js * @author Yotam Mann * @license http://opensource.org/licenses/MIT MIT License - * @copyright 2014-2015 Yotam Mann + * @copyright 2014-2016 Yotam Mann */ Main(function () { @@ -67,29 +81,31 @@ OscillatorNode.prototype.setPeriodicWave = OscillatorNode.prototype.setWaveTable; } //extend the connect function to include Tones - AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; - AudioNode.prototype.connect = function (B, outNum, inNum) { - if (B.input) { - if (Array.isArray(B.input)) { - if (isUndef(inNum)) { - inNum = 0; + if (isUndef(AudioNode.prototype._nativeConnect)) { + AudioNode.prototype._nativeConnect = AudioNode.prototype.connect; + AudioNode.prototype.connect = function (B, outNum, inNum) { + if (B.input) { + if (Array.isArray(B.input)) { + if (isUndef(inNum)) { + inNum = 0; + } + this.connect(B.input[inNum]); + } else { + this.connect(B.input, outNum, inNum); } - this.connect(B.input[inNum]); } else { - this.connect(B.input, outNum, inNum); - } - } else { - try { - if (B instanceof AudioNode) { - this._nativeConnect(B, outNum, inNum); - } else { - this._nativeConnect(B, outNum); + try { + if (B instanceof AudioNode) { + this._nativeConnect(B, outNum, inNum); + } else { + this._nativeConnect(B, outNum); + } + } catch (e) { + throw new Error('error connecting to node: ' + B); } - } catch (e) { - throw new Error('error connecting to node: ' + B); } - } - }; + }; + } /////////////////////////////////////////////////////////////////////////// // TONE /////////////////////////////////////////////////////////////////////////// @@ -310,6 +326,13 @@ * @const */ Tone.prototype.blockTime = 128 / Tone.context.sampleRate; + /** + * The time of a single sample + * @type {number} + * @static + * @const + */ + Tone.prototype.sampleTime = 1 / Tone.context.sampleRate; /////////////////////////////////////////////////////////////////////////// // CONNECTIONS /////////////////////////////////////////////////////////////////////////// @@ -373,12 +396,17 @@ }; /** * disconnect the output + * @param {Number|AudioNode} output Either the output index to disconnect + * if the output is an array, or the + * node to disconnect from. * @returns {Tone} this */ - Tone.prototype.disconnect = function (outputNum) { + Tone.prototype.disconnect = function (output) { if (Array.isArray(this.output)) { - outputNum = this.defaultArg(outputNum, 0); - this.output[outputNum].disconnect(); + output = this.defaultArg(output, 0); + this.output[output].disconnect(); + } else if (!this.isUndef(output)) { + this.output.disconnect(output); } else { this.output.disconnect(); } @@ -400,21 +428,6 @@ } return this; }; - /** - * fan out the connection from the first argument to the rest of the arguments - * @param {...AudioParam|Tone|AudioNode} nodes - * @returns {Tone} this - */ - Tone.prototype.connectParallel = function () { - var connectFrom = arguments[0]; - if (arguments.length > 1) { - for (var i = 1; i < arguments.length; i++) { - var connectTo = arguments[i]; - connectFrom.connect(connectTo); - } - } - return this; - }; /** * Connect the output of this node to the rest of the nodes in series. * @example @@ -642,18 +655,36 @@ Tone.prototype.gainToDb = function (gain) { return 20 * (Math.log(gain) / Math.LN10); }; + /** + * Convert an interval (in semitones) to a frequency ratio. + * @param {Interval} interval the number of semitones above the base note + * @return {number} the frequency ratio + * @example + * tone.intervalToFrequencyRatio(0); // 1 + * tone.intervalToFrequencyRatio(12); // 2 + * tone.intervalToFrequencyRatio(-12); // 0.5 + */ + Tone.prototype.intervalToFrequencyRatio = function (interval) { + return Math.pow(2, interval / 12); + }; /////////////////////////////////////////////////////////////////////////// // TIMING /////////////////////////////////////////////////////////////////////////// /** - * Return the current time of the clock + a single buffer frame. - * If this value is used to schedule a value to change, the earliest - * it could be scheduled is the following frame. - * @return {number} the currentTime from the AudioContext + * Return the current time of the AudioContext clock. + * @return {Number} the currentTime from the AudioContext */ Tone.prototype.now = function () { return this.context.currentTime; }; + /** + * Return the current time of the AudioContext clock. + * @return {Number} the currentTime from the AudioContext + * @static + */ + Tone.now = function () { + return Tone.context.currentTime; + }; /////////////////////////////////////////////////////////////////////////// // INHERITANCE /////////////////////////////////////////////////////////////////////////// @@ -722,31 +753,16 @@ newContextCallbacks[i](ctx); } }; - /** - * Bind this to a touchstart event to start the audio on mobile devices. - *
- * http://stackoverflow.com/questions/12517000/no-sound-on-ios-6-web-audio-api/12569290#12569290 - * @static - */ - Tone.startMobile = function () { - var osc = Tone.context.createOscillator(); - var silent = Tone.context.createGain(); - silent.gain.value = 0; - osc.connect(silent); - silent.connect(Tone.context.destination); - var now = Tone.context.currentTime; - osc.start(now); - osc.stop(now + 1); - }; //setup the context Tone._initAudioContext(function (audioContext) { //set the blockTime Tone.prototype.blockTime = 128 / audioContext.sampleRate; + Tone.prototype.sampleTime = 1 / audioContext.sampleRate; _silentNode = audioContext.createGain(); _silentNode.gain.value = 0; _silentNode.connect(audioContext.destination); }); - Tone.version = 'r6'; + Tone.version = 'r7'; console.log('%c * Tone.js ' + Tone.version + ' * ', 'background: #000; color: #fff'); return Tone; }); @@ -855,7 +871,7 @@ */ Tone.WaveShaper.prototype.setMap = function (mapping) { for (var i = 0, len = this._curve.length; i < len; i++) { - var normalized = i / len * 2 - 1; + var normalized = i / (len - 1) * 2 - 1; this._curve[i] = mapping(normalized, i); } this._shaper.curve = this._curve; @@ -897,7 +913,7 @@ ].indexOf(oversampling) !== -1) { this._shaper.oversample = oversampling; } else { - throw new Error('invalid oversampling: ' + oversampling); + throw new RangeError('Tone.WaveShaper: oversampling must be either \'none\', \'2x\', or \'4x\''); } } }); @@ -915,569 +931,610 @@ return Tone.WaveShaper; }); Module(function (Tone) { - - /////////////////////////////////////////////////////////////////////////// - // TYPES - /////////////////////////////////////////////////////////////////////////// /** - * Units which a value can take on. - * @enum {String} - */ - Tone.Type = { - /** - * The default value is a number which can take on any value between [-Infinity, Infinity] - */ - Default: 'number', - /** - * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). - * - * - * - * @typedef {Time} - */ - Time: 'time', - /** - * Frequency can be described similar to time, except ultimately the - * values are converted to frequency instead of seconds. A number - * is taken literally as the value in hertz. Additionally any of the - * Time encodings can be used. Note names in the form - * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their - * frequency value. - * @typedef {Frequency} - */ - Frequency: 'frequency', - /** - * Normal values are within the range [0, 1]. - * @typedef {NormalRange} - */ - NormalRange: 'normalRange', - /** - * AudioRange values are between [-1, 1]. - * @typedef {AudioRange} - */ - AudioRange: 'audioRange', - /** - * Decibels are a logarithmic unit of measurement which is useful for volume - * because of the logarithmic way that we perceive loudness. 0 decibels - * means no change in volume. -10db is approximately half as loud and 10db - * is twice is loud. - * @typedef {Decibels} - */ - Decibels: 'db', - /** - * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. - * @typedef {Interval} - */ - Interval: 'interval', - /** - * Beats per minute. - * @typedef {BPM} - */ - BPM: 'bpm', - /** - * The value must be greater than or equal to 0. - * @typedef {Positive} - */ - Positive: 'positive', - /** - * A cent is a hundredth of a semitone. - * @typedef {Cents} - */ - Cents: 'cents', - /** - * Angle between 0 and 360. - * @typedef {Degrees} - */ - Degrees: 'degrees', - /** - * A number representing a midi note. - * @typedef {MIDI} - */ - MIDI: 'midi', - /** - * A colon-separated representation of time in the form of - * BARS:QUARTERS:SIXTEENTHS. - * @typedef {TransportTime} - */ - TransportTime: 'transportTime', - /** - * Ticks are the basic subunit of the Transport. They are - * the smallest unit of time that the Transport supports. - * @typedef {Ticks} - */ - Ticks: 'tick', - /** - * A frequency represented by a letter name, - * accidental and octave. This system is known as - * [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). - * @typedef {Note} - */ - Note: 'note', - /** - * One millisecond is a thousandth of a second. - * @typedef {Milliseconds} - */ - Milliseconds: 'milliseconds', - /** - * A string representing a duration relative to a measure. - * - * @typedef {Notation} - */ - Notation: 'notation' + * @class Tone.TimeBase is a flexible encoding of time + * which can be evaluated to and from a string. + * Parsing code modified from https://code.google.com/p/tapdigit/ + * Copyright 2011 2012 Ariya Hidayat, New BSD License + * @extends {Tone} + * @param {Time} val The time value as a number or string + * @param {String=} units Unit values + * @example + * Tone.TimeBase(4, "n") + * Tone.TimeBase(2, "t") + * Tone.TimeBase("2t").add("1m") + * Tone.TimeBase("2t + 1m"); + */ + Tone.TimeBase = function (val, units) { + //allows it to be constructed with or without 'new' + if (this instanceof Tone.TimeBase) { + /** + * Any expressions parsed from the Time + * @type {Array} + * @private + */ + this._expr = this._noOp; + //default units + units = this.defaultArg(units, this._defaultUnits); + //get the value from the given time + if (this.isString(val)) { + this._expr = this._parseExprString(val); + } else if (this.isNumber(val)) { + var method = this._primaryExpressions[units].method; + this._expr = method.bind(this, val); + } else if (this.isUndef(val)) { + //default expression + this._expr = this._defaultExpr(); + } else if (val instanceof Tone.TimeBase) { + this._expr = val._expr; + } + } else { + return new Tone.TimeBase(val, units); + } }; - /////////////////////////////////////////////////////////////////////////// - // MATCHING TESTS - /////////////////////////////////////////////////////////////////////////// - /** - * Test if a function is "now-relative", i.e. starts with "+". - * - * @param {String} str The string to test - * @return {boolean} - * @method isNowRelative - * @lends Tone.prototype.isNowRelative - */ - Tone.prototype.isNowRelative = function () { - var nowRelative = new RegExp(/^\s*\+(.)+/i); - return function (note) { - return nowRelative.test(note); - }; - }(); - /** - * Tests if a string is in Ticks notation. - * - * @param {String} str The string to test - * @return {boolean} - * @method isTicks - * @lends Tone.prototype.isTicks - */ - Tone.prototype.isTicks = function () { - var tickFormat = new RegExp(/^\d+i$/i); - return function (note) { - return tickFormat.test(note); - }; - }(); - /** - * Tests if a string is musical notation. - * i.e.: - * - * - * @param {String} str The string to test - * @return {boolean} - * @method isNotation - * @lends Tone.prototype.isNotation - */ - Tone.prototype.isNotation = function () { - var notationFormat = new RegExp(/^[0-9]+[mnt]$/i); - return function (note) { - return notationFormat.test(note); - }; - }(); - /** - * Test if a string is in the transportTime format. - * "Bars:Beats:Sixteenths" - * @param {String} transportTime - * @return {boolean} - * @method isTransportTime - * @lends Tone.prototype.isTransportTime - */ - Tone.prototype.isTransportTime = function () { - var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i); - return function (transportTime) { - return transportTimeFormat.test(transportTime); - }; - }(); - /** - * Test if a string is in Scientific Pitch Notation: i.e. "C4". - * @param {String} note The note to test - * @return {boolean} true if it's in the form of a note - * @method isNote - * @lends Tone.prototype.isNote - * @function - */ - Tone.prototype.isNote = function () { - var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i); - return function (note) { - return noteFormat.test(note); - }; - }(); + Tone.extend(Tone.TimeBase); /** - * Test if the input is in the format of number + hz - * i.e.: 10hz - * - * @param {String} freq - * @return {boolean} - * @function + * Repalce the current time value with the value + * given by the expression string. + * @param {String} exprString + * @return {Tone.TimeBase} this */ - Tone.prototype.isFrequency = function () { - var freqFormat = new RegExp(/^\d*\.?\d+hz$/i); - return function (freq) { - return freqFormat.test(freq); - }; - }(); + Tone.TimeBase.prototype.set = function (exprString) { + this._expr = this._parseExprString(exprString); + return this; + }; /////////////////////////////////////////////////////////////////////////// - // TO SECOND CONVERSIONS + // ABSTRACT SYNTAX TREE PARSER /////////////////////////////////////////////////////////////////////////// /** + * All the primary expressions. * @private - * @return {Object} The Transport's BPM if the Transport exists, - * otherwise returns reasonable defaults. + * @type {Object} */ - function getTransportBpm() { - if (Tone.Transport && Tone.Transport.bpm) { - return Tone.Transport.bpm.value; - } else { - return 120; + Tone.TimeBase.prototype._primaryExpressions = { + 'n': { + regexp: /^(\d+)n/i, + method: function (value) { + value = parseInt(value); + if (value === 1) { + return this._beatsToUnits(this._timeSignature()); + } else { + return this._beatsToUnits(4 / value); + } + } + }, + 't': { + regexp: /^(\d+)t/i, + method: function (value) { + value = parseInt(value); + return this._beatsToUnits(8 / (parseInt(value) * 3)); + } + }, + 'm': { + regexp: /^(\d+)m/i, + method: function (value) { + return this._beatsToUnits(parseInt(value) * this._timeSignature()); + } + }, + 'i': { + regexp: /^(\d+)i/i, + method: function (value) { + return this._ticksToUnits(parseInt(value)); + } + }, + 'hz': { + regexp: /^(\d+(?:\.\d+)?)hz/i, + method: function (value) { + return this._frequencyToUnits(parseFloat(value)); + } + }, + 'tr': { + regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method: function (m, q, s) { + var total = 0; + if (m && m !== '0') { + total += this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== '0') { + total += this._beatsToUnits(parseFloat(q)); + } + if (s && s !== '0') { + total += this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }, + 's': { + regexp: /^(\d+(?:\.\d+)?s)/, + method: function (value) { + return this._secondsToUnits(parseFloat(value)); + } + }, + 'samples': { + regexp: /^(\d+)samples/, + method: function (value) { + return parseInt(value) / this.context.sampleRate; + } + }, + 'default': { + regexp: /^(\d+(?:\.\d+)?)/, + method: function (value) { + return this._primaryExpressions[this._defaultUnits].method.call(this, value); + } } - } + }; /** + * All the binary expressions that TimeBase can accept. * @private - * @return {Object} The Transport's Time Signature if the Transport exists, - * otherwise returns reasonable defaults. + * @type {Object} */ - function getTransportTimeSignature() { - if (Tone.Transport && Tone.Transport.timeSignature) { - return Tone.Transport.timeSignature; - } else { - return 4; - } - } - /** - * - * convert notation format strings to seconds - * - * @param {String} notation - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {number} - * - */ - Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) { - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var beatTime = 60 / bpm; - //special case: 1n = 1m - if (notation === '1n') { - notation = '1m'; - } - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0) { - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === 't') { - beats = 4 / subdivision * 2 / 3; - } else if (lastLetter === 'n') { - beats = 4 / subdivision; - } else if (lastLetter === 'm') { - beats = subdivision * timeSignature; - } else { - beats = 0; + Tone.TimeBase.prototype._binaryExpressions = { + '+': { + regexp: /^\+/, + precedence: 2, + method: function (lh, rh) { + return lh() + rh(); + } + }, + '-': { + regexp: /^\-/, + precedence: 2, + method: function (lh, rh) { + return lh() - rh(); + } + }, + '*': { + regexp: /^\*/, + precedence: 1, + method: function (lh, rh) { + return lh() * rh(); + } + }, + '/': { + regexp: /^\//, + precedence: 1, + method: function (lh, rh) { + return lh() / rh(); + } } - return beatTime * beats; }; /** - * convert transportTime into seconds. - * - * ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - * - * @param {TransportTime} transportTime - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {number} seconds - */ - Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) { - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(':'); - if (split.length === 2) { - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1) { - quarters = parseFloat(split[0]); - } else if (split.length === 3) { - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = measures * timeSignature + quarters + sixteenths / 4; - return beats * (60 / bpm); - }; - /** - * Convert ticks into seconds - * @param {Ticks} ticks - * @param {BPM=} bpm - * @return {number} seconds - */ - Tone.prototype.ticksToSeconds = function (ticks, bpm) { - if (this.isUndef(Tone.Transport)) { - return 0; + * All the unary expressions. + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._unaryExpressions = { + 'neg': { + regexp: /^\-/, + method: function (lh) { + return -lh(); + } } - ticks = parseFloat(ticks); - bpm = this.defaultArg(bpm, getTransportBpm()); - var tickTime = 60 / bpm / Tone.Transport.PPQ; - return tickTime * ticks; }; /** - * Convert a frequency into seconds. - * Accepts numbers and strings: i.e. "10hz" or - * 10 both return 0.1. - * - * @param {Frequency} freq - * @return {number} + * Syntactic glue which holds expressions together + * @private + * @type {Object} + */ + Tone.TimeBase.prototype._syntaxGlue = { + '(': { regexp: /^\(/ }, + ')': { regexp: /^\)/ } + }; + /** + * tokenize the expression based on the Expressions object + * @param {string} expr + * @return {Object} returns two methods on the tokenized list, next and peek + * @private + */ + Tone.TimeBase.prototype._tokenize = function (expr) { + var position = -1; + var tokens = []; + while (expr.length > 0) { + expr = expr.trim(); + var token = getNextToken(expr, this); + tokens.push(token); + expr = expr.substr(token.value.length); + } + function getNextToken(expr, context) { + var expressions = [ + '_binaryExpressions', + '_unaryExpressions', + '_primaryExpressions', + '_syntaxGlue' + ]; + for (var i = 0; i < expressions.length; i++) { + var group = context[expressions[i]]; + for (var opName in group) { + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null) { + return { + method: op.method, + precedence: op.precedence, + regexp: op.regexp, + value: match[0] + }; + } + } + } + throw new SyntaxError('Tone.TimeBase: Unexpected token ' + expr); + } + return { + next: function () { + return tokens[++position]; + }, + peek: function () { + return tokens[position + 1]; + } + }; + }; + /** + * Given a token, find the value within the groupName + * @param {Object} token + * @param {String} groupName + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._matchGroup = function (token, group, prec) { + var ret = false; + if (!this.isUndef(token)) { + for (var opName in group) { + var op = group[opName]; + if (op.regexp.test(token.value)) { + if (!this.isUndef(prec)) { + if (op.precedence === prec) { + return op; + } + } else { + return op; + } + } + } + } + return ret; + }; + /** + * Match a binary expression given the token and the precedence + * @param {Lexer} lexer + * @param {Number} precedence + * @private + */ + Tone.TimeBase.prototype._parseBinary = function (lexer, precedence) { + if (this.isUndef(precedence)) { + precedence = 2; + } + var expr; + if (precedence < 0) { + expr = this._parseUnary(lexer); + } else { + expr = this._parseBinary(lexer, precedence - 1); + } + var token = lexer.peek(); + while (token && this._matchGroup(token, this._binaryExpressions, precedence)) { + token = lexer.next(); + expr = token.method.bind(this, expr, this._parseBinary(lexer, precedence - 1)); + token = lexer.peek(); + } + return expr; + }; + /** + * Match a unary expression. + * @param {Lexer} lexer + * @private */ - Tone.prototype.frequencyToSeconds = function (freq) { - return 1 / parseFloat(freq); + Tone.TimeBase.prototype._parseUnary = function (lexer) { + var token, expr; + token = lexer.peek(); + var op = this._matchGroup(token, this._unaryExpressions); + if (op) { + token = lexer.next(); + expr = this._parseUnary(lexer); + return op.method.bind(this, expr); + } + return this._parsePrimary(lexer); }; /** - * Convert a sample count to seconds. - * @param {number} samples - * @return {number} + * Match a primary expression (a value). + * @param {Lexer} lexer + * @private */ - Tone.prototype.samplesToSeconds = function (samples) { - return samples / this.context.sampleRate; + Tone.TimeBase.prototype._parsePrimary = function (lexer) { + var token, expr; + token = lexer.peek(); + if (this.isUndef(token)) { + throw new SyntaxError('Tone.TimeBase: Unexpected end of expression'); + } + if (this._matchGroup(token, this._primaryExpressions)) { + token = lexer.next(); + var matching = token.value.match(token.regexp); + return token.method.bind(this, matching[1], matching[2], matching[3]); + } + if (token && token.value === '(') { + lexer.next(); + expr = this._parseBinary(lexer); + token = lexer.next(); + if (!(token && token.value === ')')) { + throw new SyntaxError('Expected )'); + } + return expr; + } + throw new SyntaxError('Tone.TimeBase: Cannot process token ' + token.value); }; /** - * Convert from seconds to samples. - * @param {number} seconds - * @return {number} The number of samples + * Recursively parse the string expression into a syntax tree. + * @param {string} expr + * @return {Function} the bound method to be evaluated later + * @private */ - Tone.prototype.secondsToSamples = function (seconds) { - return seconds * this.context.sampleRate; + Tone.TimeBase.prototype._parseExprString = function (exprString) { + var lexer = this._tokenize(exprString); + var tree = this._parseBinary(lexer); + return tree; }; /////////////////////////////////////////////////////////////////////////// - // FROM SECOND CONVERSIONS + // DEFAULTS /////////////////////////////////////////////////////////////////////////// /** - * Convert seconds to transportTime in the form - * "measures:quarters:sixteenths" - * - * @param {Number} seconds - * @param {BPM=} bpm - * @param {Number=} timeSignature - * @return {TransportTime} - */ - Tone.prototype.secondsToTransportTime = function (seconds, bpm, timeSignature) { - bpm = this.defaultArg(bpm, getTransportBpm()); - timeSignature = this.defaultArg(timeSignature, getTransportTimeSignature()); - var quarterTime = 60 / bpm; - var quarters = seconds / quarterTime; - var measures = Math.floor(quarters / timeSignature); - var sixteenths = quarters % 1 * 4; - quarters = Math.floor(quarters) % timeSignature; - var progress = [ - measures, - quarters, - sixteenths - ]; - return progress.join(':'); + * The initial expression value + * @return {Number} The initial value 0 + * @private + */ + Tone.TimeBase.prototype._noOp = function () { + return 0; }; /** - * Convert a number in seconds to a frequency. - * @param {number} seconds - * @return {number} + * The default expression value if no arguments are given + * @private */ - Tone.prototype.secondsToFrequency = function (seconds) { - return 1 / seconds; + Tone.TimeBase.prototype._defaultExpr = function () { + return this._noOp; }; + /** + * The default units if none are given. + * @private + */ + Tone.TimeBase.prototype._defaultUnits = 's'; /////////////////////////////////////////////////////////////////////////// - // GENERALIZED CONVERSIONS + // UNIT CONVERSIONS /////////////////////////////////////////////////////////////////////////// /** - * Convert seconds to the closest transportTime in the form - * measures:quarters:sixteenths - * - * @method toTransportTime - * - * @param {Time} time - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {TransportTime} - * - * @lends Tone.prototype.toTransportTime + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private */ - Tone.prototype.toTransportTime = function (time, bpm, timeSignature) { - var seconds = this.toSeconds(time); - return this.secondsToTransportTime(seconds, bpm, timeSignature); + Tone.TimeBase.prototype._frequencyToUnits = function (freq) { + return 1 / freq; }; /** - * Convert a frequency representation into a number. - * - * @param {Frequency} freq - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} the frequency in hertz - */ - Tone.prototype.toFrequency = function (freq, now) { - if (this.isFrequency(freq)) { - return parseFloat(freq); - } else if (this.isNotation(freq) || this.isTransportTime(freq)) { - return this.secondsToFrequency(this.toSeconds(freq, now)); - } else if (this.isNote(freq)) { - return this.noteToFrequency(freq); - } else { - return freq; + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._beatsToUnits = function (beats) { + return 60 / Tone.Transport.bpm.value * beats; + }; + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._secondsToUnits = function (seconds) { + return seconds; + }; + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._ticksToUnits = function (ticks) { + return ticks * (this._beatsToUnits(1) / Tone.Transport.PPQ); + }; + /** + * Return the time signature. + * @return {Number} + * @private + */ + Tone.TimeBase.prototype._timeSignature = function () { + return Tone.Transport.timeSignature; + }; + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + /** + * Push an expression onto the expression list + * @param {Time} val + * @param {String} type + * @param {String} units + * @return {Tone.TimeBase} + * @private + */ + Tone.TimeBase.prototype._pushExpr = function (val, name, units) { + //create the expression + if (!(val instanceof Tone.TimeBase)) { + val = new Tone.TimeBase(val, units); } + this._expr = this._binaryExpressions[name].method.bind(this, this._expr, val._expr); + return this; }; /** - * Convert the time representation into ticks. - * Now-Relative timing will be relative to the current - * Tone.Transport.ticks. - * @param {Time} time - * @return {Ticks} + * Add to the current value. + * @param {Time} val The value to add + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").add("1m"); //"3m" */ - Tone.prototype.toTicks = function (time) { - if (this.isUndef(Tone.Transport)) { - return 0; + Tone.TimeBase.prototype.add = function (val, units) { + return this._pushExpr(val, '+', units); + }; + /** + * Subtract the value from the current time. + * @param {Time} val The value to subtract + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").sub("1m"); //"1m" + */ + Tone.TimeBase.prototype.sub = function (val, units) { + return this._pushExpr(val, '-', units); + }; + /** + * Multiply the current value by the given time. + * @param {Time} val The value to multiply + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").mult("2"); //"4m" + */ + Tone.TimeBase.prototype.mult = function (val, units) { + return this._pushExpr(val, '*', units); + }; + /** + * Divide the current value by the given time. + * @param {Time} val The value to divide by + * @param {String=} units Optional units to use with the value. + * @return {Tone.TimeBase} this + * @example + * Tone.TimeBase("2m").div(2); //"1m" + */ + Tone.TimeBase.prototype.div = function (val, units) { + return this._pushExpr(val, '/', units); + }; + /** + * Evaluate the time value. Returns the time + * in seconds. + * @return {Seconds} + */ + Tone.TimeBase.prototype.eval = function () { + return this._expr(); + }; + /** + * Clean up + * @return {Tone.TimeBase} this + */ + Tone.TimeBase.prototype.dispose = function () { + this._expr = null; + }; + return Tone.TimeBase; + }); + Module(function (Tone) { + /** + * @class Tone.Time is a primitive type for encoding Time values. + * Eventually all time values are evaluated to seconds + * using the `eval` method. Tone.Time can be constructed + * with or without the `new` keyword. Tone.Time can be passed + * into the parameter of any method which takes time as an argument. + * @constructor + * @extends {Tone.TimeBase} + * @param {String|Number} val The time value. + * @param {String=} units The units of the value. + * @example + * var t = Tone.Time("4n");//encodes a quarter note + * t.mult(4); // multiply that value by 4 + * t.toNotation(); //returns "1m" + */ + Tone.Time = function (val, units) { + if (this instanceof Tone.Time) { + /** + * If the current clock time should + * be added to the output + * @type {Boolean} + * @private + */ + this._plusNow = false; + Tone.TimeBase.call(this, val, units); + } else { + return new Tone.Time(val, units); } - var bpm = Tone.Transport.bpm.value; - //get the seconds - var plusNow = 0; - if (this.isNowRelative(time)) { - time = time.replace('+', ''); - plusNow = Tone.Transport.ticks; - } else if (this.isUndef(time)) { - return Tone.Transport.ticks; + }; + Tone.extend(Tone.Time, Tone.TimeBase); + //clone the expressions so that + //we can add more without modifying the original + Tone.Time.prototype._unaryExpressions = Object.create(Tone.TimeBase.prototype._unaryExpressions); + /* + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.quantize = { + regexp: /^@/, + method: function (rh) { + return Tone.Transport.nextSubdivision(rh()); + } + }; + /* + * Adds an additional unary expression + * which adds the current clock time. + * @type {Object} + * @private + */ + Tone.Time.prototype._unaryExpressions.now = { + regexp: /^\+/, + method: function (lh) { + this._plusNow = true; + return lh(); } - var seconds = this.toSeconds(time); - var quarter = 60 / bpm; - var quarters = seconds / quarter; - var tickNum = quarters * Tone.Transport.PPQ; - //align the tick value - return Math.round(tickNum + plusNow); }; /** - * convert a time into samples - * - * @param {Time} time - * @return {number} + * Quantize the time by the given subdivision. Optionally add a + * percentage which will move the time value towards the ideal + * quantized value by that percentage. + * @param {Number|Time} val The subdivision to quantize to + * @param {NormalRange} [percent=1] Move the time value + * towards the quantized value by + * a percentage. + * @return {Tone.Time} this + * @example + * Tone.Time(21).quantize(2).eval() //returns 22 + * Tone.Time(0.6).quantize("4n", 0.5).eval() //returns 0.55 */ - Tone.prototype.toSamples = function (time) { - var seconds = this.toSeconds(time); - return Math.round(seconds * this.context.sampleRate); + Tone.Time.prototype.quantize = function (subdiv, percent) { + percent = this.defaultArg(percent, 1); + this._expr = function (expr, subdivision, percent) { + expr = expr(); + subdivision = subdivision.toSeconds(); + var multiple = Math.round(expr / subdivision); + var ideal = multiple * subdivision; + var diff = ideal - expr; + return expr + diff * percent; + }.bind(this, this._expr, new this.constructor(subdiv), percent); + return this; + }; + /** + * Adds the clock time to the time expression at the + * moment of evaluation. + * @return {Tone.Time} this + */ + Tone.Time.prototype.addNow = function () { + this._plusNow = true; + return this; }; /** - * Convert Time into seconds. - * - * Unlike the method which it overrides, this takes into account - * transporttime and musical notation. - * - * Time : 1.40 - * Notation: 4n|1m|2t - * TransportTime: 2:4:1 (measure:quarters:sixteens) - * Now Relative: +3n - * Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1) - * * @override - * @param {Time} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} + * Override the default value return when no arguments are passed in. + * The default value is 'now' + * @private */ - Tone.prototype.toSeconds = function (time, now) { - now = this.defaultArg(now, this.now()); - if (this.isNumber(time)) { - return time; //assuming that it's seconds - } else if (this.isString(time)) { - var plusTime = 0; - if (this.isNowRelative(time)) { - time = time.replace('+', ''); - plusTime = now; - } - var betweenParens = time.match(/\(([^)(]+)\)/g); - if (betweenParens) { - //evaluate the expressions between the parenthesis - for (var j = 0; j < betweenParens.length; j++) { - //remove the parens - var symbol = betweenParens[j].replace(/[\(\)]/g, ''); - var symbolVal = this.toSeconds(symbol); - time = time.replace(betweenParens[j], symbolVal); - } - } - //test if it is quantized - if (time.indexOf('@') !== -1) { - var quantizationSplit = time.split('@'); - if (!this.isUndef(Tone.Transport)) { - var toQuantize = quantizationSplit[0].trim(); - //if there's no argument it should be evaluated as the current time - if (toQuantize === '') { - toQuantize = undefined; - } - //if it's now-relative, it should be evaluated by `quantize` - if (plusTime > 0) { - toQuantize = '+' + toQuantize; - plusTime = 0; - } - var subdivision = quantizationSplit[1].trim(); - time = Tone.Transport.quantize(toQuantize, subdivision); - } else { - throw new Error('quantization requires Tone.Transport'); - } - } else { - var components = time.split(/[\(\)\-\+\/\*]/); - if (components.length > 1) { - var originalTime = time; - for (var i = 0; i < components.length; i++) { - var symb = components[i].trim(); - if (symb !== '') { - var val = this.toSeconds(symb); - time = time.replace(symb, val); - } - } - try { - //eval is evil - time = eval(time); // jshint ignore:line - } catch (e) { - throw new EvalError('cannot evaluate Time: ' + originalTime); - } - } else if (this.isNotation(time)) { - time = this.notationToSeconds(time); - } else if (this.isTransportTime(time)) { - time = this.transportTimeToSeconds(time); - } else if (this.isFrequency(time)) { - time = this.frequencyToSeconds(time); - } else if (this.isTicks(time)) { - time = this.ticksToSeconds(time); - } else { - time = parseFloat(time); - } - } - return time + plusTime; - } else { - return now; - } + Tone.Time.prototype._defaultExpr = function () { + this._plusNow = true; + return this._noOp; }; + //CONVERSIONS////////////////////////////////////////////////////////////// /** * Convert a Time to Notation. Values will be thresholded to the nearest 128th note. - * @param {Time} time - * @param {BPM=} bpm - * @param {number=} timeSignature - * @return {Notation} + * @return {Notation} + * @example + * //if the Transport is at 120bpm: + * Tone.Time(2).toNotation();//returns "1m" */ - Tone.prototype.toNotation = function (time, bpm, timeSignature) { + Tone.Time.prototype.toNotation = function () { + var time = this.toSeconds(); var testNotations = [ '1m', '2n', @@ -1488,7 +1545,7 @@ '64n', '128n' ]; - var retNotation = toNotationHelper.call(this, time, bpm, timeSignature, testNotations); + var retNotation = this._toNotationHelper(time, testNotations); //try the same thing but with tripelets var testTripletNotations = [ '1m', @@ -1506,7 +1563,7 @@ '64t', '128n' ]; - var retTripletNotation = toNotationHelper.call(this, time, bpm, timeSignature, testTripletNotations); + var retTripletNotation = this._toNotationHelper(time, testTripletNotations); //choose the simpler expression of the two if (retTripletNotation.split('+').length < retNotation.split('+').length) { return retTripletNotation; @@ -1516,16 +1573,19 @@ }; /** * Helper method for Tone.toNotation + * @param {Number} units + * @param {Array} testNotations + * @return {String} * @private */ - function toNotationHelper(time, bpm, timeSignature, testNotations) { - var seconds = this.toSeconds(time); - var threshold = this.notationToSeconds(testNotations[testNotations.length - 1], bpm, timeSignature); + Tone.Time.prototype._toNotationHelper = function (units, testNotations) { + //the threshold is the last value in the array + var threshold = this._notationToUnits(testNotations[testNotations.length - 1]); var retNotation = ''; for (var i = 0; i < testNotations.length; i++) { - var notationTime = this.notationToSeconds(testNotations[i], bpm, timeSignature); + var notationTime = this._notationToUnits(testNotations[i]); //account for floating point errors (i.e. round up if the value is 0.999999) - var multiple = seconds / notationTime; + var multiple = units / notationTime; var floatingPointError = 0.000001; if (1 - multiple % 1 < floatingPointError) { multiple += floatingPointError; @@ -1537,8 +1597,8 @@ } else { retNotation += multiple.toString() + '*' + testNotations[i]; } - seconds -= multiple * notationTime; - if (seconds < threshold) { + units -= multiple * notationTime; + if (units < threshold) { break; } else { retNotation += ' + '; @@ -1549,61 +1609,298 @@ retNotation = '0'; } return retNotation; - } + }; /** - * Convert the given value from the type specified by units - * into a number. - * @param {*} val the value to convert - * @return {Number} the number which the value should be set to + * Convert a notation value to the current units + * @param {Notation} notation + * @return {Number} + * @private */ - Tone.prototype.fromUnits = function (val, units) { - if (this.convert || this.isUndef(this.convert)) { - switch (units) { - case Tone.Type.Time: - return this.toSeconds(val); - case Tone.Type.Frequency: - return this.toFrequency(val); - case Tone.Type.Decibels: - return this.dbToGain(val); - case Tone.Type.NormalRange: - return Math.min(Math.max(val, 0), 1); - case Tone.Type.AudioRange: - return Math.min(Math.max(val, -1), 1); - case Tone.Type.Positive: - return Math.max(val, 0); - default: - return val; + Tone.Time.prototype._notationToUnits = function (notation) { + var primaryExprs = this._primaryExpressions; + var notationExprs = [ + primaryExprs.n, + primaryExprs.t, + primaryExprs.m + ]; + for (var i = 0; i < notationExprs.length; i++) { + var expr = notationExprs[i]; + var match = notation.match(expr.regexp); + if (match) { + return expr.method.call(this, match[1]); } - } else { - return val; } }; /** - * Convert a number to the specified units. - * @param {number} val the value to convert - * @return {number} + * Return the time encoded as Bars:Beats:Sixteenths. + * @return {BarsBeatsSixteenths} */ - Tone.prototype.toUnits = function (val, units) { - if (this.convert || this.isUndef(this.convert)) { - switch (units) { - case Tone.Type.Decibels: - return this.gainToDb(val); - default: - return val; - } - } else { - return val; + Tone.Time.prototype.toBarsBeatsSixteenths = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.toSeconds() / quarterTime; + var measures = Math.floor(quarters / this._timeSignature()); + var sixteenths = quarters % 1 * 4; + quarters = Math.floor(quarters) % this._timeSignature(); + sixteenths = sixteenths.toString(); + if (sixteenths.length > 3) { + sixteenths = parseFloat(sixteenths).toFixed(3); } + var progress = [ + measures, + quarters, + sixteenths + ]; + return progress.join(':'); }; - /////////////////////////////////////////////////////////////////////////// - // FREQUENCY CONVERSIONS - /////////////////////////////////////////////////////////////////////////// /** - * Note to scale index - * @type {Object} + * Return the time in ticks. + * @return {Ticks} */ - var noteToScaleIndex = { - 'cbb': -2, + Tone.Time.prototype.toTicks = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + /** + * Return the time in samples + * @return {Samples} + */ + Tone.Time.prototype.toSamples = function () { + return this.toSeconds() * this.context.sampleRate; + }; + /** + * Return the time as a frequency value + * @return {Frequency} + * @example + * Tone.Time(2).toFrequency(); //0.5 + */ + Tone.Time.prototype.toFrequency = function () { + return 1 / this.eval(); + }; + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.Time.prototype.toSeconds = function () { + return this._expr(); + }; + /** + * Return the time in seconds. + * @return {Seconds} + */ + Tone.Time.prototype.eval = function () { + var val = this._expr(); + return val + (this._plusNow ? this.now() : 0); + }; + return Tone.Time; + }); + Module(function (Tone) { + /** + * @class Tone.Frequency is a primitive type for encoding Frequency values. + * Eventually all time values are evaluated to hertz + * using the `eval` method. + * @constructor + * @extends {Tone.TimeBase} + * @param {String|Number} val The time value. + * @param {String=} units The units of the value. + * @example + * Tone.Frequency("C3").eval() // 261 + * Tone.Frequency(38, "midi").eval() // + * Tone.Frequency("C3").transpose(4).eval(); + */ + Tone.Frequency = function (val, units) { + if (this instanceof Tone.Frequency) { + Tone.TimeBase.call(this, val, units); + } else { + return new Tone.Frequency(val, units); + } + }; + Tone.extend(Tone.Frequency, Tone.TimeBase); + /////////////////////////////////////////////////////////////////////////// + // AUGMENT BASE EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + //clone the expressions so that + //we can add more without modifying the original + Tone.Frequency.prototype._primaryExpressions = Object.create(Tone.TimeBase.prototype._primaryExpressions); + /* + * midi type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.midi = { + regexp: /^(\d+(?:\.\d+)?midi)/, + method: function (value) { + return this.midiToFrequency(value); + } + }; + /* + * note type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.note = { + regexp: /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i, + method: function (pitch, octave) { + var index = noteToScaleIndex[pitch.toLowerCase()]; + var noteNumber = index + (parseInt(octave) + 1) * 12; + return this.midiToFrequency(noteNumber); + } + }; + /* + * BeatsBarsSixteenths type primary expression + * @type {Object} + * @private + */ + Tone.Frequency.prototype._primaryExpressions.tr = { + regexp: /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/, + method: function (m, q, s) { + var total = 1; + if (m && m !== '0') { + total *= this._beatsToUnits(this._timeSignature() * parseFloat(m)); + } + if (q && q !== '0') { + total *= this._beatsToUnits(parseFloat(q)); + } + if (s && s !== '0') { + total *= this._beatsToUnits(parseFloat(s) / 4); + } + return total; + } + }; + /////////////////////////////////////////////////////////////////////////// + // EXPRESSIONS + /////////////////////////////////////////////////////////////////////////// + /** + * Transposes the frequency by the given number of semitones. + * @param {Interval} interval + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").transpose(3); //"C5" + */ + Tone.Frequency.prototype.transpose = function (interval) { + this._expr = function (expr, interval) { + var val = expr(); + return val * this.intervalToFrequencyRatio(interval); + }.bind(this, this._expr, interval); + return this; + }; + /** + * Takes an array of semitone intervals and returns + * an array of frequencies transposed by those intervals. + * @param {Array} intervals + * @return {Tone.Frequency} this + * @example + * Tone.Frequency("A4").harmonize([0, 3, 7]); //["A4", "C5", "E5"] + */ + Tone.Frequency.prototype.harmonize = function (intervals) { + this._expr = function (expr, intervals) { + var val = expr(); + var ret = []; + for (var i = 0; i < intervals.length; i++) { + ret[i] = val * this.intervalToFrequencyRatio(intervals[i]); + } + return ret; + }.bind(this, this._expr, intervals); + return this; + }; + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + /** + * Return the value of the frequency as a MIDI note + * @return {MIDI} + * @example + * Tone.Frequency("C4").toMidi(); //60 + */ + Tone.Frequency.prototype.toMidi = function () { + return this.frequencyToMidi(this.eval()); + }; + /** + * Return the value of the frequency in Scientific Pitch Notation + * @return {Note} + * @example + * Tone.Frequency(69, "midi").toNote(); //"A4" + */ + Tone.Frequency.prototype.toNote = function () { + var freq = this.eval(); + var log = Math.log(freq / Tone.Frequency.A4) / Math.LN2; + var noteNumber = Math.round(12 * log) + 57; + var octave = Math.floor(noteNumber / 12); + if (octave < 0) { + noteNumber += -12 * octave; + } + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); + }; + /** + * Return the duration of one cycle in seconds. + * @return {Seconds} + */ + Tone.Frequency.prototype.toSeconds = function () { + return 1 / this.eval(); + }; + /** + * Return the duration of one cycle in ticks + * @return {Ticks} + */ + Tone.Frequency.prototype.toTicks = function () { + var quarterTime = this._beatsToUnits(1); + var quarters = this.eval() / quarterTime; + return Math.floor(quarters * Tone.Transport.PPQ); + }; + /////////////////////////////////////////////////////////////////////////// + // UNIT CONVERSIONS HELPERS + /////////////////////////////////////////////////////////////////////////// + /** + * Returns the value of a frequency in the current units + * @param {Frequency} freq + * @return {Number} + * @private + */ + Tone.Frequency.prototype._frequencyToUnits = function (freq) { + return freq; + }; + /** + * Returns the value of a tick in the current time units + * @param {Ticks} ticks + * @return {Number} + * @private + */ + Tone.Frequency.prototype._ticksToUnits = function (ticks) { + return 1 / (ticks * 60 / (Tone.Transport.bpm.value * Tone.Transport.PPQ)); + }; + /** + * Return the value of the beats in the current units + * @param {Number} beats + * @return {Number} + * @private + */ + Tone.Frequency.prototype._beatsToUnits = function (beats) { + return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats); + }; + /** + * Returns the value of a second in the current units + * @param {Seconds} seconds + * @return {Number} + * @private + */ + Tone.Frequency.prototype._secondsToUnits = function (seconds) { + return 1 / seconds; + }; + /** + * The default units if none are given. + * @private + */ + Tone.Frequency.prototype._defaultUnits = 'hz'; + /////////////////////////////////////////////////////////////////////////// + // FREQUENCY CONVERSIONS + /////////////////////////////////////////////////////////////////////////// + /** + * Note to scale index + * @type {Object} + */ + var noteToScaleIndex = { + 'cbb': -2, 'cb': -1, 'c': 0, 'c#': 1, @@ -1658,172 +1955,391 @@ 'B' ]; /** - * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch, + * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch) * A4's values in Hertz. * @type {Frequency} * @static */ - Tone.A4 = 440; + Tone.Frequency.A4 = 440; + /** + * Convert a MIDI note to frequency value. + * @param {MIDI} midi The midi number to convert. + * @return {Frequency} the corresponding frequency value + * @example + * tone.midiToFrequency(69); // returns 440 + */ + Tone.Frequency.prototype.midiToFrequency = function (midi) { + return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12); + }; /** - * Convert a note name to frequency. - * @param {String} note - * @return {number} + * Convert a frequency value to a MIDI note. + * @param {Frequency} frequency The value to frequency value to convert. + * @returns {MIDI} * @example - * var freq = tone.noteToFrequency("A4"); //returns 440 - */ - Tone.prototype.noteToFrequency = function (note) { - //break apart the note by frequency and octave - var parts = note.split(/(-?\d+)/); - if (parts.length === 3) { - var index = noteToScaleIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - var noteNumber = index + (parseInt(octave, 10) + 1) * 12; - return this.midiToFrequency(noteNumber); + * tone.midiToFrequency(440); // returns 69 + */ + Tone.Frequency.prototype.frequencyToMidi = function (frequency) { + return 69 + 12 * Math.log(frequency / Tone.Frequency.A4) / Math.LN2; + }; + return Tone.Frequency; + }); + Module(function (Tone) { + /** + * @class Tone.TransportTime is a the time along the Transport's + * timeline. It is similar to Tone.Time, but instead of evaluating + * against the AudioContext's clock, it is evaluated against + * the Transport's position. See [TransportTime wiki](https://github.com/Tonejs/Tone.js/wiki/TransportTime). + * @constructor + * @param {Time} val The time value as a number or string + * @param {String=} units Unit values + * @extends {Tone.Time} + */ + Tone.TransportTime = function (val, units) { + if (this instanceof Tone.TransportTime) { + Tone.Time.call(this, val, units); } else { - return 0; + return new Tone.TransportTime(val, units); } }; + Tone.extend(Tone.TransportTime, Tone.Time); + //clone the expressions so that + //we can add more without modifying the original + Tone.TransportTime.prototype._unaryExpressions = Object.create(Tone.Time.prototype._unaryExpressions); /** - * Convert a frequency to a note name (i.e. A4, C#5). - * @param {number} freq - * @return {String} + * Adds an additional unary expression + * which quantizes values to the next subdivision + * @type {Object} + * @private */ - Tone.prototype.frequencyToNote = function (freq) { - var log = Math.log(freq / Tone.A4) / Math.LN2; - var noteNumber = Math.round(12 * log) + 57; - var octave = Math.floor(noteNumber / 12); - if (octave < 0) { - noteNumber += -12 * octave; + Tone.TransportTime.prototype._unaryExpressions.quantize = { + regexp: /^@/, + method: function (rh) { + var subdivision = this._secondsToTicks(rh()); + var multiple = Math.ceil(Tone.Transport.ticks / subdivision); + return this._ticksToUnits(multiple * subdivision); } - var noteName = scaleIndexToNote[noteNumber % 12]; - return noteName + octave.toString(); }; /** - * Convert an interval (in semitones) to a frequency ratio. - * - * @param {Interval} interval the number of semitones above the base note - * @return {number} the frequency ratio - * @example - * tone.intervalToFrequencyRatio(0); // returns 1 - * tone.intervalToFrequencyRatio(12); // returns 2 + * Convert seconds into ticks + * @param {Seconds} seconds + * @return {Ticks} + * @private */ - Tone.prototype.intervalToFrequencyRatio = function (interval) { - return Math.pow(2, interval / 12); + Tone.TransportTime.prototype._secondsToTicks = function (seconds) { + var quarterTime = this._beatsToUnits(1); + var quarters = seconds / quarterTime; + return Math.round(quarters * Tone.Transport.PPQ); }; /** - * Convert a midi note number into a note name. - * - * @param {MIDI} midiNumber the midi note number - * @return {String} the note's name and octave - * @example - * tone.midiToNote(60); // returns "C3" + * Evaluate the time expression. Returns values in ticks + * @return {Ticks} */ - Tone.prototype.midiToNote = function (midiNumber) { - var octave = Math.floor(midiNumber / 12) - 1; - var note = midiNumber % 12; - return scaleIndexToNote[note] + octave; + Tone.TransportTime.prototype.eval = function () { + var val = this._secondsToTicks(this._expr()); + return val + (this._plusNow ? Tone.Transport.ticks : 0); }; /** - * Convert a note to it's midi value. - * - * @param {String} note the note name (i.e. "C3") - * @return {MIDI} 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 = noteToScaleIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - return index + (parseInt(octave, 10) + 1) * 12; - } else { - return 0; - } + * Return the time in ticks. + * @return {Ticks} + */ + Tone.TransportTime.prototype.toTicks = function () { + return this.eval(); }; /** - * Convert a MIDI note to frequency value. - * - * @param {MIDI} midi The midi number to convert. - * @return {Frequency} the corresponding frequency value - * @example - * tone.midiToFrequency(57); // returns 440 + * Return the time as a frequency value + * @return {Frequency} */ - Tone.prototype.midiToFrequency = function (midi) { - return Tone.A4 * Math.pow(2, (midi - 69) / 12); + Tone.TransportTime.prototype.toFrequency = function () { + return 1 / this.toSeconds(); }; - return Tone; + return Tone.TransportTime; }); Module(function (Tone) { - + /////////////////////////////////////////////////////////////////////////// + // TYPES + /////////////////////////////////////////////////////////////////////////// /** - * @class Tone.Param wraps the native Web Audio's AudioParam to provide - * additional unit conversion functionality. It also - * serves as a base-class for classes which have a single, - * automatable parameter. - * @extends {Tone} - * @param {AudioParam} param The parameter to wrap. - * @param {Tone.Type} units The units of the audio param. - * @param {Boolean} convert If the param should be converted. + * Units which a value can take on. + * @enum {String} */ - Tone.Param = function () { - var options = this.optionsObject(arguments, [ - 'param', - 'units', - 'convert' - ], Tone.Param.defaults); - /** - * The native parameter to control - * @type {AudioParam} - * @private + Tone.Type = { + /** + * Default units + * @typedef {Default} */ - this._param = this.input = options.param; + Default: 'number', /** - * The units of the parameter - * @type {Tone.Type} + * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). + * + * + * + * @typedef {Time} */ - this.units = options.units; + Time: 'time', /** - * If the value should be converted or not - * @type {Boolean} + * Frequency can be described similar to time, except ultimately the + * values are converted to frequency instead of seconds. A number + * is taken literally as the value in hertz. Additionally any of the + * Time encodings can be used. Note names in the form + * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their + * frequency value. + * @typedef {Frequency} */ - this.convert = options.convert; + Frequency: 'frequency', /** - * True if the signal value is being overridden by - * a connected signal. - * @readOnly - * @type {boolean} - * @private + * TransportTime describes a position along the Transport's timeline. It is + * similar to Time in that it uses all the same encodings, but TransportTime specifically + * pertains to the Transport's timeline, which is startable, stoppable, loopable, and seekable. + * [Read more](https://github.com/Tonejs/Tone.js/wiki/TransportTime) + * @typedef {TransportTime} */ - this.overridden = false; - if (!this.isUndef(options.value)) { - this.value = options.value; - } - }; - Tone.extend(Tone.Param); - /** - * Defaults - * @type {Object} - * @const - */ - Tone.Param.defaults = { - 'units': Tone.Type.Default, - 'convert': true, - 'param': undefined - }; - /** - * The current value of the parameter. - * @memberOf Tone.Param# - * @type {Number} - * @name value - */ + TransportTime: 'transportTime', + /** + * Ticks are the basic subunit of the Transport. They are + * the smallest unit of time that the Transport supports. + * @typedef {Ticks} + */ + Ticks: 'ticks', + /** + * Normal values are within the range [0, 1]. + * @typedef {NormalRange} + */ + NormalRange: 'normalRange', + /** + * AudioRange values are between [-1, 1]. + * @typedef {AudioRange} + */ + AudioRange: 'audioRange', + /** + * Decibels are a logarithmic unit of measurement which is useful for volume + * because of the logarithmic way that we perceive loudness. 0 decibels + * means no change in volume. -10db is approximately half as loud and 10db + * is twice is loud. + * @typedef {Decibels} + */ + Decibels: 'db', + /** + * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. + * @typedef {Interval} + */ + Interval: 'interval', + /** + * Beats per minute. + * @typedef {BPM} + */ + BPM: 'bpm', + /** + * The value must be greater than or equal to 0. + * @typedef {Positive} + */ + Positive: 'positive', + /** + * A cent is a hundredth of a semitone. + * @typedef {Cents} + */ + Cents: 'cents', + /** + * Angle between 0 and 360. + * @typedef {Degrees} + */ + Degrees: 'degrees', + /** + * A number representing a midi note. + * @typedef {MIDI} + */ + MIDI: 'midi', + /** + * A colon-separated representation of time in the form of + * Bars:Beats:Sixteenths. + * @typedef {BarsBeatsSixteenths} + */ + BarsBeatsSixteenths: 'barsBeatsSixteenths', + /** + * Sampling is the reduction of a continuous signal to a discrete signal. + * Audio is typically sampled 44100 times per second. + * @typedef {Samples} + */ + Samples: 'samples', + /** + * Hertz are a frequency representation defined as one cycle per second. + * @typedef {Hertz} + */ + Hertz: 'hertz', + /** + * A frequency represented by a letter name, + * accidental and octave. This system is known as + * [Scientific Pitch Notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). + * @typedef {Note} + */ + Note: 'note', + /** + * One millisecond is a thousandth of a second. + * @typedef {Milliseconds} + */ + Milliseconds: 'milliseconds', + /** + * Seconds are the time unit of the AudioContext. In the end, + * all values need to be evaluated to seconds. + * @typedef {Seconds} + */ + Seconds: 'seconds', + /** + * A string representing a duration relative to a measure. + * + * @typedef {Notation} + */ + Notation: 'notation' + }; + /////////////////////////////////////////////////////////////////////////// + // AUGMENT TONE's PROTOTYPE + /////////////////////////////////////////////////////////////////////////// + /** + * Convert Time into seconds. + * + * Unlike the method which it overrides, this takes into account + * transporttime and musical notation. + * + * Time : 1.40 + * Notation: 4n|1m|2t + * Now Relative: +3n + * Math: 3n+16n or even complicated expressions ((3n*2)/6 + 1) + * + * @param {Time} time + * @return {Seconds} + */ + Tone.prototype.toSeconds = function (time) { + if (this.isNumber(time)) { + return time; + } else if (this.isString(time) || this.isUndef(time)) { + return new Tone.Time(time).eval(); + } else if (time instanceof Tone.TransportTime) { + return time.toSeconds(); + } else if (time instanceof Tone.Time) { + return time.eval(); + } else if (time instanceof Tone.Frequency) { + return time.toSeconds(); + } + }; + /** + * Convert a frequency representation into a number. + * @param {Frequency} freq + * @return {Hertz} the frequency in hertz + */ + Tone.prototype.toFrequency = function (freq) { + if (this.isNumber(freq)) { + return freq; + } else if (this.isString(freq) || this.isUndef(freq)) { + return new Tone.Frequency(freq).eval(); + } else if (freq instanceof Tone.Frequency) { + return freq.eval(); + } else if (freq instanceof Tone.Time) { + return freq.toFrequency(); + } + }; + /** + * Convert a time representation into ticks. + * @param {Time} time + * @return {Ticks} the time in ticks + */ + Tone.prototype.toTicks = function (time) { + if (this.isNumber(time) || this.isString(time) || this.isUndef(time)) { + return new Tone.TransportTime(time).eval(); + } else if (time instanceof Tone.Frequency) { + return time.toTicks(); + } else if (time instanceof Tone.TransportTime) { + return time.eval(); + } else if (time instanceof Tone.Time) { + return time.toTicks(); + } + }; + return Tone; + }); + Module(function (Tone) { + + /** + * @class Tone.Param wraps the native Web Audio's AudioParam to provide + * additional unit conversion functionality. It also + * serves as a base-class for classes which have a single, + * automatable parameter. + * @extends {Tone} + * @param {AudioParam} param The parameter to wrap. + * @param {Tone.Type} units The units of the audio param. + * @param {Boolean} convert If the param should be converted. + */ + Tone.Param = function () { + var options = this.optionsObject(arguments, [ + 'param', + 'units', + 'convert' + ], Tone.Param.defaults); + /** + * The native parameter to control + * @type {AudioParam} + * @private + */ + this._param = this.input = options.param; + /** + * The units of the parameter + * @type {Tone.Type} + */ + this.units = options.units; + /** + * If the value should be converted or not + * @type {Boolean} + */ + this.convert = options.convert; + /** + * True if the signal value is being overridden by + * a connected signal. + * @readOnly + * @type {boolean} + * @private + */ + this.overridden = false; + if (!this.isUndef(options.value)) { + this.value = options.value; + } + }; + Tone.extend(Tone.Param); + /** + * Defaults + * @type {Object} + * @const + */ + Tone.Param.defaults = { + 'units': Tone.Type.Default, + 'convert': true, + 'param': undefined + }; + /** + * The current value of the parameter. + * @memberOf Tone.Param# + * @type {Number} + * @name value + */ Object.defineProperty(Tone.Param.prototype, 'value', { get: function () { return this._toUnits(this._param.value); }, set: function (value) { var convertedVal = this._fromUnits(value); + this._param.cancelScheduledValues(0); this._param.value = convertedVal; } }); @@ -1905,6 +2421,11 @@ Tone.Param.prototype.setRampPoint = function (now) { now = this.defaultArg(now, this.now()); var currentVal = this._param.value; + // exponentialRampToValueAt cannot ever ramp from or to 0 + // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 + if (currentVal === 0) { + currentVal = this._minOutput; + } this._param.setValueAtTime(currentVal, now); return this; }; @@ -1943,18 +2464,16 @@ * @param {number} value The value to ramp to. * @param {Time} rampTime the time that it takes the * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //exponentially ramp to the value 2 over 4 seconds. * signal.exponentialRampToValue(2, 4); */ - Tone.Param.prototype.exponentialRampToValue = function (value, rampTime) { - var now = this.now(); - // exponentialRampToValueAt cannot ever ramp from 0, apparently. - // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 - var currentVal = this.value; - this.setValueAtTime(Math.max(currentVal, this._minOutput), now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); + Tone.Param.prototype.exponentialRampToValue = function (value, rampTime, startTime) { + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.exponentialRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); return this; }; /** @@ -1965,15 +2484,16 @@ * @param {number} value The value to ramp to. * @param {Time} rampTime the time that it takes the * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //linearly ramp to the value 4 over 3 seconds. * signal.linearRampToValue(4, 3); */ - Tone.Param.prototype.linearRampToValue = function (value, rampTime) { - var now = this.now(); - this.setRampPoint(now); - this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); + Tone.Param.prototype.linearRampToValue = function (value, rampTime, startTime) { + startTime = this.toSeconds(startTime); + this.setRampPoint(startTime); + this.linearRampToValueAtTime(value, startTime + this.toSeconds(rampTime)); return this; }; /** @@ -2027,20 +2547,24 @@ * depending on the `units` of the signal * * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value + * @param {Time} rampTime The time that it takes the + * value to ramp from it's current value + * @param {Time} [startTime=now] When the ramp should start. * @returns {Tone.Param} this * @example * //ramp to the value either linearly or exponentially * //depending on the "units" value of the signal * signal.rampTo(0, 10); + * @example + * //schedule it to ramp starting at a specific time + * signal.rampTo(0, 10, 5) */ - Tone.Param.prototype.rampTo = function (value, rampTime) { + Tone.Param.prototype.rampTo = function (value, rampTime, startTime) { rampTime = this.defaultArg(rampTime, 0); - if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) { - this.exponentialRampToValue(value, rampTime); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM || this.units === Tone.Type.Decibels) { + this.exponentialRampToValue(value, rampTime, startTime); } else { - this.linearRampToValue(value, rampTime); + this.linearRampToValue(value, rampTime, startTime); } return this; }; @@ -2285,7 +2809,7 @@ Tone.Timeline.prototype.addEvent = function (event) { //the event needs to have a time attribute if (this.isUndef(event.time)) { - throw new Error('events must have a time attribute'); + throw new Error('Tone.Timeline: events must have a time attribute'); } event.time = this.toSeconds(event.time); if (this._timeline.length) { @@ -2318,7 +2842,7 @@ return this; }; /** - * Get the event whose time is less than or equal to the given time. + * Get the nearest event whose time is less than or equal to the given time. * @param {Number} time The time to query. * @returns {Object} The event object set after that time. */ @@ -2352,6 +2876,11 @@ */ Tone.Timeline.prototype.getEventBefore = function (time) { time = this.toSeconds(time); + var len = this._timeline.length; + //if it's after the last item, return the last item + if (len > 0 && this._timeline[len - 1].time < time) { + return this._timeline[len - 1]; + } var index = this._search(time); if (index - 1 >= 0) { return this._timeline[index - 1]; @@ -2369,7 +2898,19 @@ after = this.toSeconds(after); var index = this._search(after); if (index >= 0) { - this._timeline = this._timeline.slice(0, index); + if (this._timeline[index].time === after) { + //get the first item with that time + for (var i = index; i >= 0; i--) { + if (this._timeline[i].time === after) { + index = i; + } else { + break; + } + } + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = this._timeline.slice(0, index + 1); + } } else { this._timeline = []; } @@ -2398,7 +2939,9 @@ }; /** * Does a binary serach on the timeline array and returns the - * event which is after or equal to the time. + * nearest event index whose time is after or equal to the given time. + * If a time is searched before the first index in the timeline, -1 is returned. + * If the time is after the end, the index of the last item is returned. * @param {Number} time * @return {Number} the index in the timeline array * @private @@ -2407,11 +2950,14 @@ var beginning = 0; var len = this._timeline.length; var end = len; - // continue searching while [imin,imax] is not empty - while (beginning <= end && beginning < len) { + if (len > 0 && this._timeline[len - 1].time <= time) { + return len - 1; + } + while (beginning < end) { // calculate the midpoint for roughly equal partition var midPoint = Math.floor(beginning + (end - beginning) / 2); var event = this._timeline[midPoint]; + var nextEvent = this._timeline[midPoint + 1]; if (event.time === time) { //choose the last one that has the same time for (var i = midPoint; i < this._timeline.length; i++) { @@ -2421,15 +2967,17 @@ } } return midPoint; + } else if (event.time < time && nextEvent.time > time) { + return midPoint; } else if (event.time > time) { //search lower - end = midPoint - 1; + end = midPoint; } else if (event.time < time) { //search upper beginning = midPoint + 1; } } - return beginning - 1; + return -1; }; /** * Internal iterator. Applies extra safety checks for @@ -2556,16 +3104,16 @@ 'value', 'units' ], Tone.Signal.defaults); - //constructors - Tone.Signal.apply(this, options); - options.param = this._param; - Tone.Param.call(this, options); /** * The scheduled events * @type {Tone.Timeline} * @private */ this._events = new Tone.Timeline(10); + //constructors + Tone.Signal.apply(this, options); + options.param = this._param; + Tone.Param.call(this, options); /** * The initial scheduled value * @type {Number} @@ -2577,11 +3125,13 @@ /** * The event types of a schedulable signal. * @enum {String} + * @private */ Tone.TimelineSignal.Type = { Linear: 'linear', Exponential: 'exponential', Target: 'target', + Curve: 'curve', Set: 'set' }; /** @@ -2592,11 +3142,14 @@ */ Object.defineProperty(Tone.TimelineSignal.prototype, 'value', { get: function () { - return this._toUnits(this._param.value); + var now = this.now(); + var val = this.getValueAtTime(now); + return this._toUnits(val); }, set: function (value) { var convertedVal = this._fromUnits(value); this._initial = convertedVal; + this.cancelScheduledValues(); this._param.value = convertedVal; } }); @@ -2652,15 +3205,27 @@ * @returns {Tone.TimelineSignal} this */ Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + //get the previous event and make sure it's not starting from 0 + var beforeEvent = this._searchBefore(endTime); + if (beforeEvent && beforeEvent.value === 0) { + //reschedule that event + this.setValueAtTime(this._minOutput, beforeEvent.time); + } value = this._fromUnits(value); - value = Math.max(this._minOutput, value); + var setValue = Math.max(value, this._minOutput); endTime = this.toSeconds(endTime); this._events.addEvent({ 'type': Tone.TimelineSignal.Type.Exponential, - 'value': value, + 'value': setValue, 'time': endTime }); - this._param.exponentialRampToValueAtTime(value, endTime); + //if the ramped to value is 0, make it go to the min output, and then set to 0. + if (value < this._minOutput) { + this._param.exponentialRampToValueAtTime(this._minOutput, endTime - this.sampleTime); + this.setValueAtTime(0, endTime); + } else { + this._param.exponentialRampToValueAtTime(value, endTime); + } return this; }; /** @@ -2685,6 +3250,38 @@ this._param.setTargetAtTime(value, startTime, timeConstant); return this; }; + /** + * Set an array of arbitrary values starting at the given time for the given duration. + * @param {Float32Array} values + * @param {Time} startTime + * @param {Time} duration + * @param {NormalRange} [scaling=1] If the values in the curve should be scaled by some value + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.setValueCurveAtTime = function (values, startTime, duration, scaling) { + scaling = this.defaultArg(scaling, 1); + //copy the array + var floats = new Array(values.length); + for (var i = 0; i < floats.length; i++) { + floats[i] = this._fromUnits(values[i]) * scaling; + } + startTime = this.toSeconds(startTime); + duration = this.toSeconds(duration); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Curve, + 'value': floats, + 'time': startTime, + 'duration': duration + }); + //set the first value + this._param.setValueAtTime(floats[0], startTime); + //schedule a lienar ramp for each of the segments + for (var j = 1; j < floats.length; j++) { + var segmentTime = startTime + j / (floats.length - 1) * duration; + this._param.linearRampToValueAtTime(floats[j], segmentTime); + } + return this; + }; /** * Cancels all scheduled parameter changes with times greater than or * equal to startTime. @@ -2710,19 +3307,32 @@ Tone.TimelineSignal.prototype.setRampPoint = function (time) { time = this.toSeconds(time); //get the value at the given time - var val = this.getValueAtTime(time); - //reschedule the next event to end at the given time - var after = this._searchAfter(time); - if (after) { - //cancel the next event(s) + var val = this._toUnits(this.getValueAtTime(time)); + //if there is an event at the given time + //and that even is not a "set" + var before = this._searchBefore(time); + if (before && before.time === time) { + //remove everything after + this.cancelScheduledValues(time + this.sampleTime); + } else if (before && before.type === Tone.TimelineSignal.Type.Curve && before.time + before.duration > time) { + //if the curve is still playing + //cancel the curve this.cancelScheduledValues(time); - if (after.type === Tone.TimelineSignal.Type.Linear) { - this.linearRampToValueAtTime(val, time); - } else if (after.type === Tone.TimelineSignal.Type.Exponential) { - this.exponentialRampToValueAtTime(val, time); + this.linearRampToValueAtTime(val, time); + } else { + //reschedule the next event to end at the given time + var after = this._searchAfter(time); + if (after) { + //cancel the next event(s) + this.cancelScheduledValues(time); + if (after.type === Tone.TimelineSignal.Type.Linear) { + this.linearRampToValueAtTime(val, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + this.exponentialRampToValueAtTime(val, time); + } } + this.setValueAtTime(val, time); } - this.setValueAtTime(val, time); return this; }; /** @@ -2794,6 +3404,8 @@ previouVal = previous.value; } value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (before.type === Tone.TimelineSignal.Type.Curve) { + value = this._curveInterpolate(before.time, before.value, before.duration, time); } else if (after === null) { value = before.value; } else if (after.type === Tone.TimelineSignal.Type.Linear) { @@ -2844,6 +3456,30 @@ v0 = Math.max(this._minOutput, v0); return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); }; + /** + * Calculates the the value along the curve produced by setValueCurveAtTime + * @private + */ + Tone.TimelineSignal.prototype._curveInterpolate = function (start, curve, duration, time) { + var len = curve.length; + // If time is after duration, return the last curve value + if (time >= start + duration) { + return curve[len - 1]; + } else if (time <= start) { + return curve[0]; + } else { + var progress = (time - start) / duration; + var lowerIndex = Math.floor((len - 1) * progress); + var upperIndex = Math.ceil((len - 1) * progress); + var lowerVal = curve[lowerIndex]; + var upperVal = curve[upperIndex]; + if (upperIndex === lowerIndex) { + return lowerVal; + } else { + return this._linearInterpolate(lowerIndex, lowerVal, upperIndex, upperVal, progress * (len - 1)); + } + } + }; /** * Clean up. * @return {Tone.TimelineSignal} this @@ -2989,26 +3625,20 @@ * @type {number} * @private */ - this._attackCurve = Tone.Envelope.Type.Linear; + this._attackCurve = 'linear'; /** * the next time the envelope is at standby * @type {number} * @private */ - this._releaseCurve = Tone.Envelope.Type.Exponential; - /** - * the minimum output value - * @type {number} - * @private - */ - this._minOutput = 0.00001; + this._releaseCurve = 'exponential'; /** * the signal * @type {Tone.TimelineSignal} * @private */ this._sig = this.output = new Tone.TimelineSignal(); - this._sig.setValueAtTime(this._minOutput, 0); + this._sig.setValueAtTime(0, 0); //set the attackCurve initially this.attackCurve = options.attackCurve; this.releaseCurve = options.releaseCurve; @@ -3027,12 +3657,6 @@ 'attackCurve': 'linear', 'releaseCurve': 'exponential' }; - /** - * the envelope time multipler - * @type {number} - * @private - */ - Tone.Envelope.prototype._timeMult = 0.25; /** * Read the current value of the envelope. Useful for * syncronizing visual output to the envelope. @@ -3043,46 +3667,100 @@ */ Object.defineProperty(Tone.Envelope.prototype, 'value', { get: function () { - return this._sig.value; + return this.getValueAtTime(this.now()); } }); /** - * The slope of the attack. Either "linear" or "exponential". + * The shape of the attack. + * Can be any of these strings: + * + * Can also be an array which describes the curve. Values + * in the array are evenly subdivided and linearly + * interpolated over the duration of the attack. * @memberOf Tone.Envelope# - * @type {string} + * @type {String|Array} * @name attackCurve * @example * env.attackCurve = "linear"; + * @example + * //can also be an array + * env.attackCurve = [0, 0.2, 0.3, 0.4, 1] */ Object.defineProperty(Tone.Envelope.prototype, 'attackCurve', { get: function () { - 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); + if (this.isString(this._attackCurve)) { + return this._attackCurve; + } else if (this.isArray(this._attackCurve)) { + //look up the name in the curves array + for (var type in Tone.Envelope.Type) { + if (Tone.Envelope.Type[type].In === this._attackCurve) { + return type; + } + } + //otherwise just return the array + return this._attackCurve; + } + }, + set: function (curve) { + //check if it's a valid type + if (Tone.Envelope.Type.hasOwnProperty(curve)) { + var curveDef = Tone.Envelope.Type[curve]; + if (this.isObject(curveDef)) { + this._attackCurve = curveDef.In; + } else { + this._attackCurve = curveDef; + } + } else if (this.isArray(curve)) { + this._attackCurve = curve; + } else { + throw new Error('Tone.Envelope: invalid curve: ' + curve); } } }); /** - * The slope of the Release. Either "linear" or "exponential". + * The shape of the release. See the attack curve types. * @memberOf Tone.Envelope# - * @type {string} + * @type {String|Array} * @name releaseCurve * @example * env.releaseCurve = "linear"; */ Object.defineProperty(Tone.Envelope.prototype, 'releaseCurve', { get: function () { - return this._releaseCurve; + if (this.isString(this._releaseCurve)) { + return this._releaseCurve; + } else if (this.isArray(this._releaseCurve)) { + //look up the name in the curves array + for (var type in Tone.Envelope.Type) { + if (Tone.Envelope.Type[type].Out === this._releaseCurve) { + return type; + } + } + //otherwise just return the array + return this._releaseCurve; + } }, - set: function (type) { - if (type === Tone.Envelope.Type.Linear || type === Tone.Envelope.Type.Exponential) { - this._releaseCurve = type; + set: function (curve) { + //check if it's a valid type + if (Tone.Envelope.Type.hasOwnProperty(curve)) { + var curveDef = Tone.Envelope.Type[curve]; + if (this.isObject(curveDef)) { + this._releaseCurve = curveDef.Out; + } else { + this._releaseCurve = curveDef; + } + } else if (this.isArray(curve)) { + this._releaseCurve = curve; } else { - throw Error('releaseCurve must be either "linear" or "exponential". Invalid type: ', type); + throw new Error('Tone.Envelope: invalid curve: ' + curve); } } }); @@ -3100,18 +3778,39 @@ //to seconds var now = this.now() + this.blockTime; time = this.toSeconds(time, now); - var attack = this.toSeconds(this.attack) + time; + var originalAttack = this.toSeconds(this.attack); + var attack = originalAttack; var decay = this.toSeconds(this.decay); velocity = this.defaultArg(velocity, 1); + //check if it's not a complete attack + var currentValue = this.getValueAtTime(time); + if (currentValue > 0) { + //subtract the current value from the attack time + var attackRate = 1 / attack; + var remainingDistance = 1 - currentValue; + //the attack is now the remaining time + attack = remainingDistance / attackRate; + } //attack - if (this._attackCurve === Tone.Envelope.Type.Linear) { - this._sig.linearRampToValueBetween(velocity, time, attack); - } else { - this._sig.exponentialRampToValueBetween(velocity, time, attack); + if (this._attackCurve === 'linear') { + this._sig.linearRampToValue(velocity, attack, time); + } else if (this._attackCurve === 'exponential') { + this._sig.exponentialRampToValue(velocity, attack, time); + } else if (attack > 0) { + this._sig.setRampPoint(time); + var curve = this._attackCurve; + //take only a portion of the curve + if (attack < originalAttack) { + var percentComplete = 1 - attack / originalAttack; + var sliceIndex = Math.floor(percentComplete * this._attackCurve.length); + curve = this._attackCurve.slice(sliceIndex); + //the first index is the current value + curve[0] = currentValue; + } + this._sig.setValueCurveAtTime(curve, time, attack, velocity); } //decay - this._sig.setValueAtTime(velocity, attack); - this._sig.exponentialRampToValueAtTime(this.sustain * velocity, attack + decay); + this._sig.exponentialRampToValue(velocity * this.sustain, decay, attack + time); return this; }; /** @@ -3125,14 +3824,32 @@ Tone.Envelope.prototype.triggerRelease = function (time) { var now = this.now() + this.blockTime; time = this.toSeconds(time, now); - var release = this.toSeconds(this.release); - if (this._releaseCurve === Tone.Envelope.Type.Linear) { - this._sig.linearRampToValueBetween(this._minOutput, time, time + release); - } else { - this._sig.exponentialRampToValueBetween(this._minOutput, time, release + time); + var currentValue = this.getValueAtTime(time); + if (currentValue > 0) { + var release = this.toSeconds(this.release); + if (this._releaseCurve === 'linear') { + this._sig.linearRampToValue(0, release, time); + } else if (this._releaseCurve === 'exponential') { + this._sig.exponentialRampToValue(0, release, time); + } else { + var curve = this._releaseCurve; + if (this.isArray(curve)) { + this._sig.setRampPoint(time); + this._sig.setValueCurveAtTime(curve, time, release, currentValue); + } + } } return this; }; + /** + * Get the scheduled value at the given time. This will + * return the unconverted (raw) value. + * @param {Number} time The time in seconds. + * @return {Number} The scheduled value at the given time. + */ + Tone.Envelope.prototype.getValueAtTime = function (time) { + return this._sig.getValueAtTime(time); + }; /** * triggerAttackRelease is shorthand for triggerAttack, then waiting * some duration, then triggerRelease. @@ -3150,12 +3867,109 @@ this.triggerRelease(time + this.toSeconds(duration)); return this; }; + /** + * Cancels all scheduled envelope changes after the given time. + * @param {Time} after + * @returns {Tone.Envelope} this + */ + Tone.Envelope.prototype.cancel = function (after) { + this._sig.cancelScheduledValues(after); + return this; + }; /** * Borrows the connect method from Tone.Signal. * @function * @private */ Tone.Envelope.prototype.connect = Tone.Signal.prototype.connect; + /** + * Generate some complex envelope curves. + */ + (function _createCurves() { + var curveLen = 128; + var i, k; + //cosine curve + var cosineCurve = []; + for (i = 0; i < curveLen; i++) { + cosineCurve[i] = Math.sin(i / (curveLen - 1) * (Math.PI / 2)); + } + //ripple curve + var rippleCurve = []; + var rippleCurveFreq = 6.4; + for (i = 0; i < curveLen - 1; i++) { + k = i / (curveLen - 1); + var sineWave = Math.sin(k * (Math.PI * 2) * rippleCurveFreq - Math.PI / 2) + 1; + rippleCurve[i] = sineWave / 10 + k * 0.83; + } + rippleCurve[curveLen - 1] = 1; + //stairs curve + var stairsCurve = []; + var steps = 5; + for (i = 0; i < curveLen; i++) { + stairsCurve[i] = Math.ceil(i / (curveLen - 1) * steps) / steps; + } + //in-out easing curve + var sineCurve = []; + for (i = 0; i < curveLen; i++) { + k = i / (curveLen - 1); + sineCurve[i] = 0.5 * (1 - Math.cos(Math.PI * k)); + } + //a bounce curve + var bounceCurve = []; + for (i = 0; i < curveLen; i++) { + k = i / (curveLen - 1); + var freq = Math.pow(k, 3) * 4 + 0.2; + var val = Math.cos(freq * Math.PI * 2 * k); + bounceCurve[i] = Math.abs(val * (1 - k)); + } + /** + * Invert a value curve to make it work for the release + * @private + */ + function invertCurve(curve) { + var out = new Array(curve.length); + for (var j = 0; j < curve.length; j++) { + out[j] = 1 - curve[j]; + } + return out; + } + /** + * reverse the curve + * @private + */ + function reverseCurve(curve) { + return curve.slice(0).reverse(); + } + /** + * attack and release curve arrays + * @type {Object} + * @private + */ + Tone.Envelope.Type = { + 'linear': 'linear', + 'exponential': 'exponential', + 'bounce': { + In: invertCurve(bounceCurve), + Out: bounceCurve + }, + 'cosine': { + In: cosineCurve, + Out: reverseCurve(cosineCurve) + }, + 'step': { + In: stairsCurve, + Out: invertCurve(stairsCurve) + }, + 'ripple': { + In: rippleCurve, + Out: invertCurve(rippleCurve) + }, + 'sine': { + In: sineCurve, + Out: invertCurve(sineCurve) + } + }; + }()); /** * Disconnect and dispose. * @returns {Tone.Envelope} this @@ -3164,27 +3978,10 @@ Tone.prototype.dispose.call(this); this._sig.dispose(); this._sig = null; + this._attackCurve = null; + this._releaseCurve = null; return this; }; - /** - * The phase of the envelope. - * @enum {string} - */ - Tone.Envelope.Phase = { - Attack: 'attack', - Decay: 'decay', - Sustain: 'sustain', - Release: 'release', - Standby: 'standby' - }; - /** - * The phase of the envelope. - * @enum {string} - */ - Tone.Envelope.Type = { - Linear: 'linear', - Exponential: 'exponential' - }; return Tone.Envelope; }); Module(function (Tone) { @@ -3246,21 +4043,21 @@ * [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode). * Extracts FFT or Waveform data from the incoming signal. * @extends {Tone} + * @param {String=} type The return type of the analysis, either "fft", or "waveform". * @param {Number=} size The size of the FFT. Value must be a power of * two in the range 32 to 32768. - * @param {String=} type The return type of the analysis, either "fft", or "waveform". */ Tone.Analyser = function () { var options = this.optionsObject(arguments, [ - 'size', - 'type' + 'type', + 'size' ], Tone.Analyser.defaults); /** * The analyser node. * @private * @type {AnalyserNode} */ - this._analyser = this.input = this.context.createAnalyser(); + this._analyser = this.input = this.output = this.context.createAnalyser(); /** * The analysis type * @type {String} @@ -3293,7 +4090,7 @@ * @const */ Tone.Analyser.defaults = { - 'size': 2048, + 'size': 1024, 'returnType': 'byte', 'type': 'fft', 'smoothing': 0.8, @@ -3301,7 +4098,7 @@ 'minDecibels': -100 }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse() * @enum {String} */ Tone.Analyser.Type = { @@ -3309,7 +4106,10 @@ FFT: 'fft' }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse(). + * byte values are between [0,255]. float values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @enum {String} */ Tone.Analyser.ReturnType = { @@ -3332,7 +4132,17 @@ if (this._returnType === Tone.Analyser.ReturnType.Byte) { this._analyser.getByteTimeDomainData(this._buffer); } else { - this._analyser.getFloatTimeDomainData(this._buffer); + if (this.isFunction(AnalyserNode.prototype.getFloatTimeDomainData)) { + this._analyser.getFloatTimeDomainData(this._buffer); + } else { + var uint8 = new Uint8Array(this._buffer.length); + this._analyser.getByteTimeDomainData(uint8); + //referenced https://github.com/mohayonao/get-float-time-domain-data + // POLYFILL + for (var i = 0; i < uint8.length; i++) { + this._buffer[i] = (uint8[i] - 128) * 0.0078125; + } + } } } return this._buffer; @@ -3353,9 +4163,11 @@ } }); /** - * The return type of Tone.Analyser.value, either "byte" or "float". + * The return type of Tone.Analyser.analyse(), either "byte" or "float". * When the type is set to "byte" the range of values returned in the array - * are between 0-255, when set to "float" the values are between 0-1. + * are between 0-255. "float" values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -3370,13 +4182,13 @@ } else if (type === Tone.Analyser.ReturnType.Float) { this._buffer = new Float32Array(this._analyser.frequencyBinCount); } else { - throw new Error('Invalid Return Type: ' + type); + throw new TypeError('Tone.Analayser: invalid return type: ' + type); } this._returnType = type; } }); /** - * The analysis function returned by Tone.Analyser.value, either "fft" or "waveform". + * The analysis function returned by Tone.Analyser.analyse(), either "fft" or "waveform". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -3387,7 +4199,7 @@ }, set: function (type) { if (type !== Tone.Analyser.Type.Waveform && type !== Tone.Analyser.Type.FFT) { - throw new Error('Invalid Type: ' + type); + throw new TypeError('Tone.Analyser: invalid type: ' + type); } this._type = type; } @@ -3782,7 +4594,7 @@ } else { return 1; } - }); + }, 127); /** * scale the first thresholded signal by a large value. * this will help with values which are very close to 0 @@ -3811,1768 +4623,1126 @@ Module(function (Tone) { /** - * @class EqualZero outputs 1 when the input is equal to - * 0 and outputs 0 otherwise. + * @class Output 1 if the signal is greater than the value, otherwise outputs 0. + * can compare two signals or a signal and a number. * * @constructor - * @extends {Tone.SignalBase} + * @extends {Tone.Signal} + * @param {number} [value=0] the value to compare to the incoming signal * @example - * var eq0 = new Tone.EqualZero(); - * var sig = new Tone.Signal(0).connect(eq0); - * //the output of eq0 is 1. + * var gt = new Tone.GreaterThan(2); + * var sig = new Tone.Signal(4).connect(gt); + * //output of gt is equal 1. */ - Tone.EqualZero = function () { - /** - * scale the incoming signal by a large factor - * @private - * @type {Tone.Multiply} - */ - this._scale = this.input = new Tone.Multiply(10000); + Tone.GreaterThan = function (value) { + Tone.call(this, 2, 0); /** - * @type {Tone.WaveShaper} + * subtract the amount from the incoming signal + * @type {Tone.Subtract} * @private */ - this._thresh = new Tone.WaveShaper(function (val) { - if (val === 0) { - return 1; - } else { - return 0; - } - }, 128); + this._param = this.input[0] = new Tone.Subtract(value); + this.input[1] = this._param.input[1]; /** - * threshold the output so that it's 0 or 1 + * compare that amount to zero * @type {Tone.GreaterThanZero} * @private */ this._gtz = this.output = new Tone.GreaterThanZero(); - //connections - this._scale.chain(this._thresh, this._gtz); + //connect + this._param.connect(this._gtz); }; - Tone.extend(Tone.EqualZero, Tone.SignalBase); + Tone.extend(Tone.GreaterThan, Tone.Signal); /** - * Clean up. - * @returns {Tone.EqualZero} this + * dispose method + * @returns {Tone.GreaterThan} this */ - Tone.EqualZero.prototype.dispose = function () { + Tone.GreaterThan.prototype.dispose = function () { Tone.prototype.dispose.call(this); + this._param.dispose(); + this._param = null; this._gtz.dispose(); this._gtz = null; - this._scale.dispose(); - this._scale = null; - this._thresh.dispose(); - this._thresh = null; return this; }; - return Tone.EqualZero; + return Tone.GreaterThan; }); Module(function (Tone) { /** - * @class Output 1 if the signal is equal to the value, otherwise outputs 0. - * Can accept two signals if connected to inputs 0 and 1. + * @class Return the absolute value of an incoming signal. * * @constructor * @extends {Tone.SignalBase} - * @param {number=} value The number to compare the incoming signal to * @example - * var eq = new Tone.Equal(3); - * var sig = new Tone.Signal(3).connect(eq); - * //the output of eq is 1. + * var signal = new Tone.Signal(-1); + * var abs = new Tone.Abs(); + * signal.connect(abs); + * //the output of abs is 1. */ - Tone.Equal = function (value) { - Tone.call(this, 2, 0); - /** - * subtract the value from the incoming signal - * - * @type {Tone.Add} - * @private - */ - this._sub = this.input[0] = new Tone.Subtract(value); + Tone.Abs = function () { + Tone.call(this, 1, 0); /** - * @type {Tone.EqualZero} + * @type {Tone.LessThan} * @private */ - this._equals = this.output = new Tone.EqualZero(); - this._sub.connect(this._equals); - this.input[1] = this._sub.input[1]; + this._abs = this.input = this.output = new Tone.WaveShaper(function (val) { + if (val === 0) { + return 0; + } else { + return Math.abs(val); + } + }, 127); }; - Tone.extend(Tone.Equal, Tone.SignalBase); - /** - * The value to compare to the incoming signal. - * @memberOf Tone.Equal# - * @type {number} - * @name value - */ - Object.defineProperty(Tone.Equal.prototype, 'value', { - get: function () { - return this._sub.value; - }, - set: function (value) { - this._sub.value = value; - } - }); + Tone.extend(Tone.Abs, Tone.SignalBase); /** - * Clean up. - * @returns {Tone.Equal} this + * dispose method + * @returns {Tone.Abs} this */ - Tone.Equal.prototype.dispose = function () { + Tone.Abs.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._equals.dispose(); - this._equals = null; - this._sub.dispose(); - this._sub = null; + this._abs.dispose(); + this._abs = null; return this; }; - return Tone.Equal; + return Tone.Abs; }); Module(function (Tone) { /** - * @class Select between any number of inputs, sending the one - * selected by the gate signal to the output + * @class Signal-rate modulo operator. Only works in AudioRange [-1, 1] and for modulus + * values in the NormalRange. * * @constructor * @extends {Tone.SignalBase} - * @param {number} [sourceCount=2] the number of inputs the switch accepts + * @param {NormalRange} modulus The modulus to apply. * @example - * var sel = new Tone.Select(2); - * var sigA = new Tone.Signal(10).connect(sel, 0, 0); - * var sigB = new Tone.Signal(20).connect(sel, 0, 1); - * sel.gate.value = 0; - * //sel outputs 10 (the value of sigA); - * sel.gate.value = 1; - * //sel outputs 20 (the value of sigB); - */ - Tone.Select = function (sourceCount) { - sourceCount = this.defaultArg(sourceCount, 2); - Tone.call(this, sourceCount, 1); - /** - * the control signal - * @type {Number} - * @signal + * var mod = new Tone.Modulo(0.2) + * var sig = new Tone.Signal(0.5).connect(mod); + * //mod outputs 0.1 + */ + Tone.Modulo = function (modulus) { + Tone.call(this, 1, 1); + /** + * A waveshaper gets the integer multiple of + * the input signal and the modulus. + * @private + * @type {Tone.WaveShaper} */ - this.gate = new Tone.Signal(0); - this._readOnly('gate'); - //make all the inputs and connect them - for (var i = 0; i < sourceCount; i++) { - var switchGate = new SelectGate(i); - this.input[i] = switchGate; - this.gate.connect(switchGate.selecter); - switchGate.connect(this.output); - } + this._shaper = new Tone.WaveShaper(Math.pow(2, 16)); + /** + * the integer multiple is multiplied by the modulus + * @type {Tone.Multiply} + * @private + */ + this._multiply = new Tone.Multiply(); + /** + * and subtracted from the input signal + * @type {Tone.Subtract} + * @private + */ + this._subtract = this.output = new Tone.Subtract(); + /** + * the modulus signal + * @type {Tone.Signal} + * @private + */ + this._modSignal = new Tone.Signal(modulus); + //connections + this.input.fan(this._shaper, this._subtract); + this._modSignal.connect(this._multiply, 0, 0); + this._shaper.connect(this._multiply, 0, 1); + this._multiply.connect(this._subtract, 0, 1); + this._setWaveShaper(modulus); }; - Tone.extend(Tone.Select, Tone.SignalBase); + Tone.extend(Tone.Modulo, Tone.SignalBase); /** - * Open a specific input and close the others. - * @param {number} which The gate to open. - * @param {Time} [time=now] The time when the switch will open - * @returns {Tone.Select} this - * @example - * //open input 1 in a half second from now - * sel.select(1, "+0.5"); + * @param {number} mod the modulus to apply + * @private */ - Tone.Select.prototype.select = function (which, time) { - //make sure it's an integer - which = Math.floor(which); - this.gate.setValueAtTime(which, this.toSeconds(time)); - return this; + Tone.Modulo.prototype._setWaveShaper = function (mod) { + this._shaper.setMap(function (val) { + var multiple = Math.floor((val + 0.0001) / mod); + return multiple; + }); }; /** - * Clean up. - * @returns {Tone.Select} this + * The modulus value. + * @memberOf Tone.Modulo# + * @type {NormalRange} + * @name value */ - 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; + Object.defineProperty(Tone.Modulo.prototype, 'value', { + get: function () { + return this._modSignal.value; + }, + set: function (mod) { + this._modSignal.value = mod; + this._setWaveShaper(mod); } - Tone.prototype.dispose.call(this); - return this; - }; - ////////////START HELPER//////////// - /** - * helper class for Tone.Select representing a single gate - * @constructor - * @extends {Tone} - * @private - */ - var SelectGate = function (num) { - /** - * the selector - * @type {Tone.Equal} - */ - this.selecter = new Tone.Equal(num); - /** - * the gate - * @type {GainNode} - */ - this.gate = this.input = this.output = this.context.createGain(); - //connect the selecter to the gate gain - this.selecter.connect(this.gate.gain); - }; - Tone.extend(SelectGate); + }); /** - * clean up - * @private + * clean up + * @returns {Tone.Modulo} this */ - SelectGate.prototype.dispose = function () { + Tone.Modulo.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this.selecter.dispose(); - this.gate.disconnect(); - this.selecter = null; - this.gate = null; - }; - ////////////END HELPER//////////// - //return Tone.Select - return Tone.Select; + this._shaper.dispose(); + this._shaper = null; + this._multiply.dispose(); + this._multiply = null; + this._subtract.dispose(); + this._subtract = null; + this._modSignal.dispose(); + this._modSignal = null; + return this; + }; + return Tone.Modulo; }); Module(function (Tone) { /** - * @class IfThenElse has three inputs. When the first input (if) is true (i.e. === 1), - * then it will pass the second input (then) through to the output, otherwise, - * if it's not true (i.e. === 0) then it will pass the third input (else) - * through to the output. + * @class AudioToGain converts an input in AudioRange [-1,1] to NormalRange [0,1]. + * See Tone.GainToAudio. * * @extends {Tone.SignalBase} * @constructor * @example - * var ifThenElse = new Tone.IfThenElse(); - * var ifSignal = new Tone.Signal(1).connect(ifThenElse.if); - * var pwmOsc = new Tone.PWMOscillator().connect(ifThenElse.then); - * var pulseOsc = new Tone.PulseOscillator().connect(ifThenElse.else); - * //ifThenElse outputs pwmOsc - * signal.value = 0; - * //now ifThenElse outputs pulseOsc + * var a2g = new Tone.AudioToGain(); */ - Tone.IfThenElse = function () { - Tone.call(this, 3, 0); + Tone.AudioToGain = function () { /** - * the selector node which is responsible for the routing - * @type {Tone.Select} + * @type {WaveShaperNode} * @private */ - this._selector = this.output = new Tone.Select(2); - //the input mapping - this.if = this.input[0] = this._selector.gate; - this.then = this.input[1] = this._selector.input[1]; - this.else = this.input[2] = this._selector.input[0]; + this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { + return (x + 1) / 2; + }); }; - Tone.extend(Tone.IfThenElse, Tone.SignalBase); + Tone.extend(Tone.AudioToGain, Tone.SignalBase); /** * clean up - * @returns {Tone.IfThenElse} this + * @returns {Tone.AudioToGain} this */ - Tone.IfThenElse.prototype.dispose = function () { + Tone.AudioToGain.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._selector.dispose(); - this._selector = null; - this.if = null; - this.then = null; - this.else = null; + this._norm.dispose(); + this._norm = null; return this; }; - return Tone.IfThenElse; + return Tone.AudioToGain; }); Module(function (Tone) { /** - * @class [OR](https://en.wikipedia.org/wiki/OR_gate) - * the inputs together. True if at least one of the inputs is true. + * @class Evaluate an expression at audio rate.

+ * Parsing code modified from https://code.google.com/p/tapdigit/ + * Copyright 2011 2012 Ariya Hidayat, New BSD License * * @extends {Tone.SignalBase} * @constructor - * @param {number} [inputCount=2] the input count + * @param {string} expr the expression to generate * @example - * var or = new Tone.OR(2); - * var sigA = new Tone.Signal(0)connect(or, 0, 0); - * var sigB = new Tone.Signal(1)connect(or, 0, 1); - * //output of or is 1 because at least - * //one of the inputs is equal to 1. - */ - Tone.OR = function (inputCount) { - inputCount = this.defaultArg(inputCount, 2); - Tone.call(this, inputCount, 0); + * //adds the signals from input[0] and input[1]. + * var expr = new Tone.Expr("$0 + $1"); + */ + Tone.Expr = function () { + var expr = this._replacements(Array.prototype.slice.call(arguments)); + var inputCount = this._parseInputs(expr); /** - * a private summing node - * @type {GainNode} + * hold onto all of the nodes for disposal + * @type {Array} * @private */ - this._sum = this.context.createGain(); + this._nodes = []; /** - * @type {Tone.Equal} - * @private + * The inputs. The length is determined by the expression. + * @type {Array} */ - this._gtz = this.output = new Tone.GreaterThanZero(); - //make each of the inputs an alias + this.input = new Array(inputCount); + //create a gain for each input for (var i = 0; i < inputCount; i++) { - this.input[i] = this._sum; + this.input[i] = this.context.createGain(); + } + //parse the syntax tree + var tree = this._parseTree(expr); + //evaluate the results + var result; + try { + result = this._eval(tree); + } catch (e) { + this._disposeNodes(); + throw new Error('Tone.Expr: Could evaluate expression: ' + expr); } - this._sum.connect(this._gtz); + /** + * The output node is the result of the expression + * @type {Tone} + */ + this.output = result; }; - Tone.extend(Tone.OR, Tone.SignalBase); - /** - * clean up - * @returns {Tone.OR} this + Tone.extend(Tone.Expr, Tone.SignalBase); + //some helpers to cut down the amount of code + function applyBinary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + self._eval(args[1]).connect(op, 0, 1); + return op; + } + function applyUnary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + return op; + } + function getNumber(arg) { + return arg ? parseFloat(arg) : undefined; + } + function literalNumber(arg) { + return arg && arg.args ? parseFloat(arg.args) : undefined; + } + /* + * the Expressions that Tone.Expr can parse. + * + * each expression belongs to a group and contains a regexp + * for selecting the operator as well as that operators method + * + * @type {Object} + * @private */ - Tone.OR.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._gtz.dispose(); - this._gtz = null; - this._sum.disconnect(); - this._sum = null; - return this; + Tone.Expr._Expressions = { + //values + 'value': { + 'signal': { + regexp: /^\d+\.\d+|^\d+/, + method: function (arg) { + var sig = new Tone.Signal(getNumber(arg)); + return sig; + } + }, + 'input': { + regexp: /^\$\d/, + method: function (arg, self) { + return self.input[getNumber(arg.substr(1))]; + } + } + }, + //syntactic glue + 'glue': { + '(': { regexp: /^\(/ }, + ')': { regexp: /^\)/ }, + ',': { regexp: /^,/ } + }, + //functions + 'func': { + 'abs': { + regexp: /^abs/, + method: applyUnary.bind(this, Tone.Abs) + }, + 'mod': { + regexp: /^mod/, + method: function (args, self) { + var modulus = literalNumber(args[1]); + var op = new Tone.Modulo(modulus); + self._eval(args[0]).connect(op); + return op; + } + }, + 'pow': { + regexp: /^pow/, + method: function (args, self) { + var exp = literalNumber(args[1]); + var op = new Tone.Pow(exp); + self._eval(args[0]).connect(op); + return op; + } + }, + 'a2g': { + regexp: /^a2g/, + method: function (args, self) { + var op = new Tone.AudioToGain(); + self._eval(args[0]).connect(op); + return op; + } + } + }, + //binary expressions + 'binary': { + '+': { + regexp: /^\+/, + precedence: 1, + method: applyBinary.bind(this, Tone.Add) + }, + '-': { + regexp: /^\-/, + precedence: 1, + method: function (args, self) { + //both unary and binary op + if (args.length === 1) { + return applyUnary(Tone.Negate, args, self); + } else { + return applyBinary(Tone.Subtract, args, self); + } + } + }, + '*': { + regexp: /^\*/, + precedence: 0, + method: applyBinary.bind(this, Tone.Multiply) + } + }, + //unary expressions + 'unary': { + '-': { + regexp: /^\-/, + method: applyUnary.bind(this, Tone.Negate) + }, + '!': { + regexp: /^\!/, + method: applyUnary.bind(this, Tone.NOT) + } + } }; - return Tone.OR; - }); - Module(function (Tone) { - /** - * @class [AND](https://en.wikipedia.org/wiki/Logical_conjunction) - * returns 1 when all the inputs are equal to 1 and returns 0 otherwise. - * - * @extends {Tone.SignalBase} - * @constructor - * @param {number} [inputCount=2] the number of inputs. NOTE: all inputs are - * connected to the single AND input node - * @example - * var and = new Tone.AND(2); - * var sigA = new Tone.Signal(0).connect(and, 0, 0); - * var sigB = new Tone.Signal(1).connect(and, 0, 1); - * //the output of and is 0. + * @param {string} expr the expression string + * @return {number} the input count + * @private */ - Tone.AND = function (inputCount) { - inputCount = this.defaultArg(inputCount, 2); - Tone.call(this, inputCount, 0); - /** - * @type {Tone.Equal} - * @private - */ - this._equals = this.output = new Tone.Equal(inputCount); - //make each of the inputs an alias - for (var i = 0; i < inputCount; i++) { - this.input[i] = this._equals; + Tone.Expr.prototype._parseInputs = function (expr) { + var inputArray = expr.match(/\$\d/g); + var inputMax = 0; + if (inputArray !== null) { + for (var i = 0; i < inputArray.length; i++) { + var inputNum = parseInt(inputArray[i].substr(1)) + 1; + inputMax = Math.max(inputMax, inputNum); + } } + return inputMax; }; - Tone.extend(Tone.AND, Tone.SignalBase); /** - * clean up - * @returns {Tone.AND} this + * @param {Array} args an array of arguments + * @return {string} the results of the replacements being replaced + * @private */ - Tone.AND.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._equals.dispose(); - this._equals = null; - return this; + Tone.Expr.prototype._replacements = function (args) { + var expr = args.shift(); + for (var i = 0; i < args.length; i++) { + expr = expr.replace(/\%/i, args[i]); + } + return expr; }; - return Tone.AND; - }); - Module(function (Tone) { - /** - * @class Just an alias for Tone.EqualZero, but has the same effect as a NOT operator. - * Outputs 1 when input equals 0. - * - * @constructor - * @extends {Tone.SignalBase} - * @example - * var not = new Tone.NOT(); - * var sig = new Tone.Signal(1).connect(not); - * //output of not equals 0. - * sig.value = 0; - * //output of not equals 1. + * tokenize the expression based on the Expressions object + * @param {string} expr + * @return {Object} returns two methods on the tokenized list, next and peek + * @private */ - Tone.NOT = Tone.EqualZero; - return Tone.NOT; - }); - Module(function (Tone) { - + Tone.Expr.prototype._tokenize = function (expr) { + var position = -1; + var tokens = []; + while (expr.length > 0) { + expr = expr.trim(); + var token = getNextToken(expr); + tokens.push(token); + expr = expr.substr(token.value.length); + } + function getNextToken(expr) { + for (var type in Tone.Expr._Expressions) { + var group = Tone.Expr._Expressions[type]; + for (var opName in group) { + var op = group[opName]; + var reg = op.regexp; + var match = expr.match(reg); + if (match !== null) { + return { + type: type, + value: match[0], + method: op.method + }; + } + } + } + throw new SyntaxError('Tone.Expr: Unexpected token ' + expr); + } + return { + next: function () { + return tokens[++position]; + }, + peek: function () { + return tokens[position + 1]; + } + }; + }; /** - * @class Output 1 if the signal is greater than the value, otherwise outputs 0. - * can compare two signals or a signal and a number. + * recursively parse the string expression into a syntax tree * - * @constructor - * @extends {Tone.Signal} - * @param {number} [value=0] the value to compare to the incoming signal - * @example - * var gt = new Tone.GreaterThan(2); - * var sig = new Tone.Signal(4).connect(gt); - * //output of gt is equal 1. + * @param {string} expr + * @return {Object} + * @private */ - Tone.GreaterThan = function (value) { - Tone.call(this, 2, 0); - /** - * subtract the amount from the incoming signal - * @type {Tone.Subtract} - * @private - */ - this._param = this.input[0] = new Tone.Subtract(value); - this.input[1] = this._param.input[1]; - /** - * compare that amount to zero - * @type {Tone.GreaterThanZero} - * @private - */ - this._gtz = this.output = new Tone.GreaterThanZero(); - //connect - this._param.connect(this._gtz); + Tone.Expr.prototype._parseTree = function (expr) { + var lexer = this._tokenize(expr); + var isUndef = this.isUndef.bind(this); + function matchSyntax(token, syn) { + return !isUndef(token) && token.type === 'glue' && token.value === syn; + } + function matchGroup(token, groupName, prec) { + var ret = false; + var group = Tone.Expr._Expressions[groupName]; + if (!isUndef(token)) { + for (var opName in group) { + var op = group[opName]; + if (op.regexp.test(token.value)) { + if (!isUndef(prec)) { + if (op.precedence === prec) { + return true; + } + } else { + return true; + } + } + } + } + return ret; + } + function parseExpression(precedence) { + if (isUndef(precedence)) { + precedence = 5; + } + var expr; + if (precedence < 0) { + expr = parseUnary(); + } else { + expr = parseExpression(precedence - 1); + } + var token = lexer.peek(); + while (matchGroup(token, 'binary', precedence)) { + token = lexer.next(); + expr = { + operator: token.value, + method: token.method, + args: [ + expr, + parseExpression(precedence - 1) + ] + }; + token = lexer.peek(); + } + return expr; + } + function parseUnary() { + var token, expr; + token = lexer.peek(); + if (matchGroup(token, 'unary')) { + token = lexer.next(); + expr = parseUnary(); + return { + operator: token.value, + method: token.method, + args: [expr] + }; + } + return parsePrimary(); + } + function parsePrimary() { + var token, expr; + token = lexer.peek(); + if (isUndef(token)) { + throw new SyntaxError('Tone.Expr: Unexpected termination of expression'); + } + if (token.type === 'func') { + token = lexer.next(); + return parseFunctionCall(token); + } + if (token.type === 'value') { + token = lexer.next(); + return { + method: token.method, + args: token.value + }; + } + if (matchSyntax(token, '(')) { + lexer.next(); + expr = parseExpression(); + token = lexer.next(); + if (!matchSyntax(token, ')')) { + throw new SyntaxError('Expected )'); + } + return expr; + } + throw new SyntaxError('Tone.Expr: Parse error, cannot process token ' + token.value); + } + function parseFunctionCall(func) { + var token, args = []; + token = lexer.next(); + if (!matchSyntax(token, '(')) { + throw new SyntaxError('Tone.Expr: Expected ( in a function call "' + func.value + '"'); + } + token = lexer.peek(); + if (!matchSyntax(token, ')')) { + args = parseArgumentList(); + } + token = lexer.next(); + if (!matchSyntax(token, ')')) { + throw new SyntaxError('Tone.Expr: Expected ) in a function call "' + func.value + '"'); + } + return { + method: func.method, + args: args, + name: name + }; + } + function parseArgumentList() { + var token, expr, args = []; + while (true) { + expr = parseExpression(); + if (isUndef(expr)) { + // TODO maybe throw exception? + break; + } + args.push(expr); + token = lexer.peek(); + if (!matchSyntax(token, ',')) { + break; + } + lexer.next(); + } + return args; + } + return parseExpression(); }; - Tone.extend(Tone.GreaterThan, Tone.Signal); /** - * dispose method - * @returns {Tone.GreaterThan} this + * recursively evaluate the expression tree + * @param {Object} tree + * @return {AudioNode} the resulting audio node from the expression + * @private */ - Tone.GreaterThan.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._param.dispose(); - this._param = null; - this._gtz.dispose(); - this._gtz = null; - return this; + Tone.Expr.prototype._eval = function (tree) { + if (!this.isUndef(tree)) { + var node = tree.method(tree.args, this); + this._nodes.push(node); + return node; + } }; - return Tone.GreaterThan; - }); - Module(function (Tone) { - /** - * @class Output 1 if the signal is less than the value, otherwise outputs 0. - * Can compare two signals or a signal and a number. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} value The value to compare to the incoming signal. - * If no value is provided, it will compare - * input[0] and input[1] - * @example - * var lt = new Tone.LessThan(2); - * var sig = new Tone.Signal(-1).connect(lt); - * //if (sig < 2) lt outputs 1 + * dispose all the nodes + * @private */ - Tone.LessThan = function (value) { - Tone.call(this, 2, 0); - /** - * negate the incoming signal - * @type {Tone.Negate} - * @private - */ - this._neg = this.input[0] = new Tone.Negate(); - /** - * input < value === -input > -value - * @type {Tone.GreaterThan} - * @private - */ - this._gt = this.output = new Tone.GreaterThan(); - /** - * negate the signal coming from the second input - * @private - * @type {Tone.Negate} - */ - this._rhNeg = new Tone.Negate(); - /** - * the node where the value is set - * @private - * @type {Tone.Signal} - */ - this._param = this.input[1] = new Tone.Signal(value); - //connect - this._neg.connect(this._gt); - this._param.connect(this._rhNeg); - this._rhNeg.connect(this._gt, 0, 1); + Tone.Expr.prototype._disposeNodes = function () { + for (var i = 0; i < this._nodes.length; i++) { + var node = this._nodes[i]; + if (this.isFunction(node.dispose)) { + node.dispose(); + } else if (this.isFunction(node.disconnect)) { + node.disconnect(); + } + node = null; + this._nodes[i] = null; + } + this._nodes = null; }; - Tone.extend(Tone.LessThan, Tone.Signal); /** - * Clean up. - * @returns {Tone.LessThan} this + * clean up */ - Tone.LessThan.prototype.dispose = function () { + Tone.Expr.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._neg.dispose(); - this._neg = null; - this._gt.dispose(); - this._gt = null; - this._rhNeg.dispose(); - this._rhNeg = null; - this._param.dispose(); - this._param = null; - return this; + this._disposeNodes(); }; - return Tone.LessThan; + return Tone.Expr; }); Module(function (Tone) { /** - * @class Return the absolute value of an incoming signal. - * - * @constructor + * @class Convert an incoming signal between 0, 1 to an equal power gain scale. + * * @extends {Tone.SignalBase} + * @constructor * @example - * var signal = new Tone.Signal(-1); - * var abs = new Tone.Abs(); - * signal.connect(abs); - * //the output of abs is 1. + * var eqPowGain = new Tone.EqualPowerGain(); */ - Tone.Abs = function () { - Tone.call(this, 1, 0); - /** - * @type {Tone.LessThan} - * @private - */ - this._ltz = new Tone.LessThan(0); - /** - * @type {Tone.Select} - * @private - */ - this._switch = this.output = new Tone.Select(2); + Tone.EqualPowerGain = function () { /** - * @type {Tone.Negate} + * @type {Tone.WaveShaper} * @private */ - this._negate = new Tone.Negate(); - //two signal paths, positive and negative - this.input.connect(this._switch, 0, 0); - this.input.connect(this._negate); - this._negate.connect(this._switch, 0, 1); - //the control signal - this.input.chain(this._ltz, this._switch.gate); + this._eqPower = this.input = this.output = new Tone.WaveShaper(function (val) { + if (Math.abs(val) < 0.001) { + //should output 0 when input is 0 + return 0; + } else { + return this.equalPowerScale(val); + } + }.bind(this), 4096); }; - Tone.extend(Tone.Abs, Tone.SignalBase); + Tone.extend(Tone.EqualPowerGain, Tone.SignalBase); /** - * dispose method - * @returns {Tone.Abs} this + * clean up + * @returns {Tone.EqualPowerGain} this */ - Tone.Abs.prototype.dispose = function () { + Tone.EqualPowerGain.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._switch.dispose(); - this._switch = null; - this._ltz.dispose(); - this._ltz = null; - this._negate.dispose(); - this._negate = null; + this._eqPower.dispose(); + this._eqPower = null; return this; }; - return Tone.Abs; + return Tone.EqualPowerGain; }); Module(function (Tone) { /** - * @class Outputs the greater of two signals. If a number is provided in the constructor - * it will use that instead of the signal. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} max Max value if provided. if not provided, it will use the - * signal value from input 1. - * @example - * var max = new Tone.Max(2); - * var sig = new Tone.Signal(3).connect(max); - * //max outputs 3 - * sig.value = 1; - * //max outputs 2 - * @example - * var max = new Tone.Max(); - * var sigA = new Tone.Signal(3); - * var sigB = new Tone.Signal(4); - * sigA.connect(max, 0, 0); - * sigB.connect(max, 0, 1); - * //output of max is 4. + * @class Tone.Crossfade provides equal power fading between two inputs. + * More on crossfading technique [here](https://en.wikipedia.org/wiki/Fade_(audio_engineering)#Crossfading). + * + * @constructor + * @extends {Tone} + * @param {NormalRange} [initialFade=0.5] + * @example + * var crossFade = new Tone.CrossFade(0.5); + * //connect effect A to crossfade from + * //effect output 0 to crossfade input 0 + * effectA.connect(crossFade, 0, 0); + * //connect effect B to crossfade from + * //effect output 0 to crossfade input 1 + * effectB.connect(crossFade, 0, 1); + * crossFade.fade.value = 0; + * // ^ only effectA is output + * crossFade.fade.value = 1; + * // ^ only effectB is output + * crossFade.fade.value = 0.5; + * // ^ the two signals are mixed equally. */ - Tone.Max = function (max) { - Tone.call(this, 2, 0); - this.input[0] = this.context.createGain(); + Tone.CrossFade = function (initialFade) { + Tone.call(this, 2, 1); /** - * the max signal - * @type {Tone.Signal} - * @private + * Alias for input[0]. + * @type {GainNode} */ - this._param = this.input[1] = new Tone.Signal(max); + this.a = this.input[0] = this.context.createGain(); /** - * @type {Tone.Select} - * @private + * Alias for input[1]. + * @type {GainNode} */ - this._ifThenElse = this.output = new Tone.IfThenElse(); + this.b = this.input[1] = this.context.createGain(); /** - * @type {Tone.Select} - * @private + * The mix between the two inputs. A fade value of 0 + * will output 100% input[0] and + * a value of 1 will output 100% input[1]. + * @type {NormalRange} + * @signal */ - this._gt = new Tone.GreaterThan(); - //connections - this.input[0].chain(this._gt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._param.connect(this._ifThenElse.else); - this._param.connect(this._gt, 0, 1); - }; - Tone.extend(Tone.Max, Tone.Signal); - /** - * Clean up. - * @returns {Tone.Max} this - */ - Tone.Max.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._param.dispose(); - this._ifThenElse.dispose(); - this._gt.dispose(); - this._param = null; - this._ifThenElse = null; - this._gt = null; - return this; - }; - return Tone.Max; - }); - Module(function (Tone) { - - /** - * @class Outputs the lesser of two signals. If a number is given - * in the constructor, it will use a signal and a number. - * - * @constructor - * @extends {Tone.Signal} - * @param {number} min The minimum to compare to the incoming signal - * @example - * var min = new Tone.Min(2); - * var sig = new Tone.Signal(3).connect(min); - * //min outputs 2 - * sig.value = 1; - * //min outputs 1 - * @example - * var min = new Tone.Min(); - * var sigA = new Tone.Signal(3); - * var sigB = new Tone.Signal(4); - * sigA.connect(min, 0, 0); - * sigB.connect(min, 0, 1); - * //output of min is 3. - */ - Tone.Min = function (min) { - Tone.call(this, 2, 0); - this.input[0] = this.context.createGain(); + this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange); /** - * @type {Tone.Select} + * equal power gain cross fade * @private + * @type {Tone.EqualPowerGain} */ - this._ifThenElse = this.output = new Tone.IfThenElse(); + this._equalPowerA = new Tone.EqualPowerGain(); /** - * @type {Tone.Select} + * equal power gain cross fade * @private + * @type {Tone.EqualPowerGain} */ - this._lt = new Tone.LessThan(); + this._equalPowerB = new Tone.EqualPowerGain(); /** - * the min signal - * @type {Tone.Signal} + * invert the incoming signal * @private + * @type {Tone} */ - this._param = this.input[1] = new Tone.Signal(min); + this._invert = new Tone.Expr('1 - $0'); //connections - this.input[0].chain(this._lt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._param.connect(this._ifThenElse.else); - this._param.connect(this._lt, 0, 1); + this.a.connect(this.output); + this.b.connect(this.output); + this.fade.chain(this._equalPowerB, this.b.gain); + this.fade.chain(this._invert, this._equalPowerA, this.a.gain); + this._readOnly('fade'); }; - Tone.extend(Tone.Min, Tone.Signal); + Tone.extend(Tone.CrossFade); /** * clean up - * @returns {Tone.Min} this + * @returns {Tone.CrossFade} this */ - Tone.Min.prototype.dispose = function () { + Tone.CrossFade.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._param.dispose(); - this._ifThenElse.dispose(); - this._lt.dispose(); - this._param = null; - this._ifThenElse = null; - this._lt = null; + this._writable('fade'); + this._equalPowerA.dispose(); + this._equalPowerA = null; + this._equalPowerB.dispose(); + this._equalPowerB = null; + this.fade.dispose(); + this.fade = null; + this._invert.dispose(); + this._invert = null; + this.a.disconnect(); + this.a = null; + this.b.disconnect(); + this.b = null; return this; }; - return Tone.Min; + return Tone.CrossFade; }); Module(function (Tone) { /** - * @class Signal-rate modulo operator. Only works in AudioRange [-1, 1] and for modulus - * values in the NormalRange. + * @class Tone.Filter is a filter which allows for all of the same native methods + * as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface). + * Tone.Filter has the added ability to set the filter rolloff at -12 + * (default), -24 and -48. * * @constructor - * @extends {Tone.SignalBase} - * @param {NormalRange} modulus The modulus to apply. + * @extends {Tone} + * @param {Frequency|Object} [frequency] The cutoff frequency of the filter. + * @param {string=} type The type of filter. + * @param {number=} rolloff The drop in decibels per octave after the cutoff frequency. + * 3 choices: -12, -24, and -48 * @example - * var mod = new Tone.Modulo(0.2) - * var sig = new Tone.Signal(0.5).connect(mod); - * //mod outputs 0.1 + * var filter = new Tone.Filter(200, "highpass"); */ - Tone.Modulo = function (modulus) { - Tone.call(this, 1, 1); + Tone.Filter = function () { + Tone.call(this); + var options = this.optionsObject(arguments, [ + 'frequency', + 'type', + 'rolloff' + ], Tone.Filter.defaults); /** - * A waveshaper gets the integer multiple of - * the input signal and the modulus. + * the filter(s) + * @type {Array} * @private - * @type {Tone.WaveShaper} */ - this._shaper = new Tone.WaveShaper(Math.pow(2, 16)); + this._filters = []; /** - * the integer multiple is multiplied by the modulus - * @type {Tone.Multiply} - * @private + * The cutoff frequency of the filter. + * @type {Frequency} + * @signal */ - this._multiply = new Tone.Multiply(); + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * and subtracted from the input signal - * @type {Tone.Subtract} + * The detune parameter + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(0, Tone.Type.Cents); + /** + * The gain of the filter, only used in certain filter types + * @type {Number} + * @signal + */ + this.gain = new Tone.Signal({ + 'value': options.gain, + 'convert': false + }); + /** + * The Q or Quality of the filter + * @type {Positive} + * @signal + */ + this.Q = new Tone.Signal(options.Q); + /** + * the type of the filter + * @type {string} * @private */ - this._subtract = this.output = new Tone.Subtract(); + this._type = options.type; /** - * the modulus signal - * @type {Tone.Signal} + * the rolloff value of the filter + * @type {number} * @private */ - this._modSignal = new Tone.Signal(modulus); - //connections - this.input.fan(this._shaper, this._subtract); - this._modSignal.connect(this._multiply, 0, 0); - this._shaper.connect(this._multiply, 0, 1); - this._multiply.connect(this._subtract, 0, 1); - this._setWaveShaper(modulus); + this._rolloff = options.rolloff; + //set the rolloff; + this.rolloff = options.rolloff; + this._readOnly([ + 'detune', + 'frequency', + 'gain', + 'Q' + ]); }; - Tone.extend(Tone.Modulo, Tone.SignalBase); + Tone.extend(Tone.Filter); /** - * @param {number} mod the modulus to apply - * @private + * the default parameters + * + * @static + * @type {Object} */ - Tone.Modulo.prototype._setWaveShaper = function (mod) { - this._shaper.setMap(function (val) { - var multiple = Math.floor((val + 0.0001) / mod); - return multiple; - }); + Tone.Filter.defaults = { + 'type': 'lowpass', + 'frequency': 350, + 'rolloff': -12, + 'Q': 1, + 'gain': 0 }; /** - * The modulus value. - * @memberOf Tone.Modulo# - * @type {NormalRange} - * @name value + * The type of the filter. Types: "lowpass", "highpass", + * "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking". + * @memberOf Tone.Filter# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Modulo.prototype, 'value', { + Object.defineProperty(Tone.Filter.prototype, 'type', { get: function () { - return this._modSignal.value; + return this._type; }, - set: function (mod) { - this._modSignal.value = mod; - this._setWaveShaper(mod); + set: function (type) { + var types = [ + 'lowpass', + 'highpass', + 'bandpass', + 'lowshelf', + 'highshelf', + 'notch', + 'allpass', + 'peaking' + ]; + if (types.indexOf(type) === -1) { + throw new TypeError('Tone.Filter: invalid type ' + type); + } + this._type = type; + for (var i = 0; i < this._filters.length; i++) { + this._filters[i].type = type; + } } }); /** - * clean up - * @returns {Tone.Modulo} this + * The rolloff of the filter which is the drop in db + * per octave. Implemented internally by cascading filters. + * Only accepts the values -12, -24, -48 and -96. + * @memberOf Tone.Filter# + * @type {number} + * @name rolloff */ - Tone.Modulo.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; - this._multiply.dispose(); - this._multiply = null; - this._subtract.dispose(); - this._subtract = null; - this._modSignal.dispose(); - this._modSignal = null; + Object.defineProperty(Tone.Filter.prototype, 'rolloff', { + get: function () { + return this._rolloff; + }, + set: function (rolloff) { + rolloff = parseInt(rolloff, 10); + var possibilities = [ + -12, + -24, + -48, + -96 + ]; + var cascadingCount = possibilities.indexOf(rolloff); + //check the rolloff is valid + if (cascadingCount === -1) { + throw new RangeError('Tone.Filter: rolloff can only be -12, -24, -48 or -96'); + } + cascadingCount += 1; + this._rolloff = rolloff; + //first disconnect the filters and throw them away + this.input.disconnect(); + for (var i = 0; i < this._filters.length; i++) { + this._filters[i].disconnect(); + this._filters[i] = null; + } + this._filters = new Array(cascadingCount); + for (var count = 0; count < cascadingCount; count++) { + var filter = this.context.createBiquadFilter(); + filter.type = this._type; + this.frequency.connect(filter.frequency); + this.detune.connect(filter.detune); + this.Q.connect(filter.Q); + this.gain.connect(filter.gain); + this._filters[count] = filter; + } + //connect them up + var connectionChain = [this.input].concat(this._filters).concat([this.output]); + this.connectSeries.apply(this, connectionChain); + } + }); + /** + * Clean up. + * @return {Tone.Filter} this + */ + Tone.Filter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + for (var i = 0; i < this._filters.length; i++) { + this._filters[i].disconnect(); + this._filters[i] = null; + } + this._filters = null; + this._writable([ + 'detune', + 'frequency', + 'gain', + 'Q' + ]); + this.frequency.dispose(); + this.Q.dispose(); + this.frequency = null; + this.Q = null; + this.detune.dispose(); + this.detune = null; + this.gain.dispose(); + this.gain = null; return this; }; - return Tone.Modulo; + return Tone.Filter; }); Module(function (Tone) { /** - * @class AudioToGain converts an input in AudioRange [-1,1] to NormalRange [0,1]. - * See Tone.GainToAudio. + * @class Split the incoming signal into three bands (low, mid, high) + * with two crossover frequency controls. * - * @extends {Tone.SignalBase} + * @extends {Tone} * @constructor - * @example - * var a2g = new Tone.AudioToGain(); + * @param {Frequency|Object} [lowFrequency] the low/mid crossover frequency + * @param {Frequency} [highFrequency] the mid/high crossover frequency */ - Tone.AudioToGain = function () { + Tone.MultibandSplit = function () { + var options = this.optionsObject(arguments, [ + 'lowFrequency', + 'highFrequency' + ], Tone.MultibandSplit.defaults); /** - * @type {WaveShaperNode} + * the input + * @type {GainNode} * @private */ - this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { - return (x + 1) / 2; - }); + this.input = this.context.createGain(); + /** + * the outputs + * @type {Array} + * @private + */ + this.output = new Array(3); + /** + * The low band. Alias for output[0] + * @type {Tone.Filter} + */ + this.low = this.output[0] = new Tone.Filter(0, 'lowpass'); + /** + * the lower filter of the mid band + * @type {Tone.Filter} + * @private + */ + this._lowMidFilter = new Tone.Filter(0, 'highpass'); + /** + * The mid band output. Alias for output[1] + * @type {Tone.Filter} + */ + this.mid = this.output[1] = new Tone.Filter(0, 'lowpass'); + /** + * The high band output. Alias for output[2] + * @type {Tone.Filter} + */ + this.high = this.output[2] = new Tone.Filter(0, 'highpass'); + /** + * The low/mid crossover frequency. + * @type {Frequency} + * @signal + */ + this.lowFrequency = new Tone.Signal(options.lowFrequency, Tone.Type.Frequency); + /** + * The mid/high crossover frequency. + * @type {Frequency} + * @signal + */ + this.highFrequency = new Tone.Signal(options.highFrequency, Tone.Type.Frequency); + /** + * The quality of all the filters + * @type {Number} + * @signal + */ + this.Q = new Tone.Signal(options.Q); + this.input.fan(this.low, this.high); + this.input.chain(this._lowMidFilter, this.mid); + //the frequency control signal + this.lowFrequency.connect(this.low.frequency); + this.lowFrequency.connect(this._lowMidFilter.frequency); + this.highFrequency.connect(this.mid.frequency); + this.highFrequency.connect(this.high.frequency); + //the Q value + this.Q.connect(this.low.Q); + this.Q.connect(this._lowMidFilter.Q); + this.Q.connect(this.mid.Q); + this.Q.connect(this.high.Q); + this._readOnly([ + 'high', + 'mid', + 'low', + 'highFrequency', + 'lowFrequency' + ]); }; - Tone.extend(Tone.AudioToGain, Tone.SignalBase); + Tone.extend(Tone.MultibandSplit); /** - * clean up - * @returns {Tone.AudioToGain} this + * @private + * @static + * @type {Object} */ - Tone.AudioToGain.prototype.dispose = function () { + Tone.MultibandSplit.defaults = { + 'lowFrequency': 400, + 'highFrequency': 2500, + 'Q': 1 + }; + /** + * Clean up. + * @returns {Tone.MultibandSplit} this + */ + Tone.MultibandSplit.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._norm.dispose(); - this._norm = null; + this._writable([ + 'high', + 'mid', + 'low', + 'highFrequency', + 'lowFrequency' + ]); + this.low.dispose(); + this.low = null; + this._lowMidFilter.dispose(); + this._lowMidFilter = null; + this.mid.dispose(); + this.mid = null; + this.high.dispose(); + this.high = null; + this.lowFrequency.dispose(); + this.lowFrequency = null; + this.highFrequency.dispose(); + this.highFrequency = null; + this.Q.dispose(); + this.Q = null; return this; }; - return Tone.AudioToGain; + return Tone.MultibandSplit; }); Module(function (Tone) { /** - * @class Evaluate an expression at audio rate.

- * Parsing code modified from https://code.google.com/p/tapdigit/ - * Copyright 2011 2012 Ariya Hidayat, New BSD License + * @class Tone.EQ3 is a three band EQ with control over low, mid, and high gain as + * well as the low and high crossover frequencies. * - * @extends {Tone.SignalBase} * @constructor - * @param {string} expr the expression to generate + * @extends {Tone} + * + * @param {Decibels|Object} [lowLevel] The gain applied to the lows. + * @param {Decibels} [midLevel] The gain applied to the mid. + * @param {Decibels} [highLevel] The gain applied to the high. * @example - * //adds the signals from input[0] and input[1]. - * var expr = new Tone.Expr("$0 + $1"); + * var eq = new Tone.EQ3(-10, 3, -20); */ - Tone.Expr = function () { - var expr = this._replacements(Array.prototype.slice.call(arguments)); - var inputCount = this._parseInputs(expr); + Tone.EQ3 = function () { + var options = this.optionsObject(arguments, [ + 'low', + 'mid', + 'high' + ], Tone.EQ3.defaults); /** - * hold onto all of the nodes for disposal - * @type {Array} + * the output node + * @type {GainNode} * @private */ - this._nodes = []; + this.output = this.context.createGain(); /** - * The inputs. The length is determined by the expression. - * @type {Array} + * the multiband split + * @type {Tone.MultibandSplit} + * @private */ - this.input = new Array(inputCount); - //create a gain for each input - for (var i = 0; i < inputCount; i++) { - this.input[i] = this.context.createGain(); - } - //parse the syntax tree - var tree = this._parseTree(expr); - //evaluate the results - var result; - try { - result = this._eval(tree); - } catch (e) { - this._disposeNodes(); - throw new Error('Could evaluate expression: ' + expr); - } - /** - * The output node is the result of the expression - * @type {Tone} - */ - this.output = result; - }; - Tone.extend(Tone.Expr, Tone.SignalBase); - //some helpers to cut down the amount of code - function applyBinary(Constructor, args, self) { - var op = new Constructor(); - self._eval(args[0]).connect(op, 0, 0); - self._eval(args[1]).connect(op, 0, 1); - return op; - } - function applyUnary(Constructor, args, self) { - var op = new Constructor(); - self._eval(args[0]).connect(op, 0, 0); - return op; - } - function getNumber(arg) { - return arg ? parseFloat(arg) : undefined; - } - function literalNumber(arg) { - return arg && arg.args ? parseFloat(arg.args) : undefined; - } - /* - * the Expressions that Tone.Expr can parse. - * - * each expression belongs to a group and contains a regexp - * for selecting the operator as well as that operators method - * - * @type {Object} - * @private - */ - Tone.Expr._Expressions = { - //values - 'value': { - 'signal': { - regexp: /^\d+\.\d+|^\d+/, - method: function (arg) { - var sig = new Tone.Signal(getNumber(arg)); - return sig; - } - }, - 'input': { - regexp: /^\$\d/, - method: function (arg, self) { - return self.input[getNumber(arg.substr(1))]; - } - } - }, - //syntactic glue - 'glue': { - '(': { regexp: /^\(/ }, - ')': { regexp: /^\)/ }, - ',': { regexp: /^,/ } - }, - //functions - 'func': { - 'abs': { - regexp: /^abs/, - method: applyUnary.bind(this, Tone.Abs) - }, - 'min': { - regexp: /^min/, - method: applyBinary.bind(this, Tone.Min) - }, - 'max': { - regexp: /^max/, - method: applyBinary.bind(this, Tone.Max) - }, - 'if': { - regexp: /^if/, - method: function (args, self) { - var op = new Tone.IfThenElse(); - self._eval(args[0]).connect(op.if); - self._eval(args[1]).connect(op.then); - self._eval(args[2]).connect(op.else); - return op; - } - }, - 'gt0': { - regexp: /^gt0/, - method: applyUnary.bind(this, Tone.GreaterThanZero) - }, - 'eq0': { - regexp: /^eq0/, - method: applyUnary.bind(this, Tone.EqualZero) - }, - 'mod': { - regexp: /^mod/, - method: function (args, self) { - var modulus = literalNumber(args[1]); - var op = new Tone.Modulo(modulus); - self._eval(args[0]).connect(op); - return op; - } - }, - 'pow': { - regexp: /^pow/, - method: function (args, self) { - var exp = literalNumber(args[1]); - var op = new Tone.Pow(exp); - self._eval(args[0]).connect(op); - return op; - } - }, - 'a2g': { - regexp: /^a2g/, - method: function (args, self) { - var op = new Tone.AudioToGain(); - self._eval(args[0]).connect(op); - return op; - } - } - }, - //binary expressions - 'binary': { - '+': { - regexp: /^\+/, - precedence: 1, - method: applyBinary.bind(this, Tone.Add) - }, - '-': { - regexp: /^\-/, - precedence: 1, - method: function (args, self) { - //both unary and binary op - if (args.length === 1) { - return applyUnary(Tone.Negate, args, self); - } else { - return applyBinary(Tone.Subtract, args, self); - } - } - }, - '*': { - regexp: /^\*/, - precedence: 0, - method: applyBinary.bind(this, Tone.Multiply) - }, - '>': { - regexp: /^\>/, - precedence: 2, - method: applyBinary.bind(this, Tone.GreaterThan) - }, - '<': { - regexp: /^ 0) { - expr = expr.trim(); - var token = getNextToken(expr); - tokens.push(token); - expr = expr.substr(token.value.length); - } - function getNextToken(expr) { - for (var type in Tone.Expr._Expressions) { - var group = Tone.Expr._Expressions[type]; - for (var opName in group) { - var op = group[opName]; - var reg = op.regexp; - var match = expr.match(reg); - if (match !== null) { - return { - type: type, - value: match[0], - method: op.method - }; - } - } - } - throw new SyntaxError('Unexpected token ' + expr); - } - return { - next: function () { - return tokens[++position]; - }, - peek: function () { - return tokens[position + 1]; - } - }; - }; - /** - * recursively parse the string expression into a syntax tree - * - * @param {string} expr - * @return {Object} - * @private - */ - Tone.Expr.prototype._parseTree = function (expr) { - var lexer = this._tokenize(expr); - var isUndef = this.isUndef.bind(this); - function matchSyntax(token, syn) { - return !isUndef(token) && token.type === 'glue' && token.value === syn; - } - function matchGroup(token, groupName, prec) { - var ret = false; - var group = Tone.Expr._Expressions[groupName]; - if (!isUndef(token)) { - for (var opName in group) { - var op = group[opName]; - if (op.regexp.test(token.value)) { - if (!isUndef(prec)) { - if (op.precedence === prec) { - return true; - } - } else { - return true; - } - } - } - } - return ret; - } - function parseExpression(precedence) { - if (isUndef(precedence)) { - precedence = 5; - } - var expr; - if (precedence < 0) { - expr = parseUnary(); - } else { - expr = parseExpression(precedence - 1); - } - var token = lexer.peek(); - while (matchGroup(token, 'binary', precedence)) { - token = lexer.next(); - expr = { - operator: token.value, - method: token.method, - args: [ - expr, - parseExpression(precedence) - ] - }; - token = lexer.peek(); - } - return expr; - } - function parseUnary() { - var token, expr; - token = lexer.peek(); - if (matchGroup(token, 'unary')) { - token = lexer.next(); - expr = parseUnary(); - return { - operator: token.value, - method: token.method, - args: [expr] - }; - } - return parsePrimary(); - } - function parsePrimary() { - var token, expr; - token = lexer.peek(); - if (isUndef(token)) { - throw new SyntaxError('Unexpected termination of expression'); - } - if (token.type === 'func') { - token = lexer.next(); - return parseFunctionCall(token); - } - if (token.type === 'value') { - token = lexer.next(); - return { - method: token.method, - args: token.value - }; - } - if (matchSyntax(token, '(')) { - lexer.next(); - expr = parseExpression(); - token = lexer.next(); - if (!matchSyntax(token, ')')) { - throw new SyntaxError('Expected )'); - } - return expr; - } - throw new SyntaxError('Parse error, cannot process token ' + token.value); - } - function parseFunctionCall(func) { - var token, args = []; - token = lexer.next(); - if (!matchSyntax(token, '(')) { - throw new SyntaxError('Expected ( in a function call "' + func.value + '"'); - } - token = lexer.peek(); - if (!matchSyntax(token, ')')) { - args = parseArgumentList(); - } - token = lexer.next(); - if (!matchSyntax(token, ')')) { - throw new SyntaxError('Expected ) in a function call "' + func.value + '"'); - } - return { - method: func.method, - args: args, - name: name - }; - } - function parseArgumentList() { - var token, expr, args = []; - while (true) { - expr = parseExpression(); - if (isUndef(expr)) { - // TODO maybe throw exception? - break; - } - args.push(expr); - token = lexer.peek(); - if (!matchSyntax(token, ',')) { - break; - } - lexer.next(); - } - return args; - } - return parseExpression(); - }; - /** - * recursively evaluate the expression tree - * @param {Object} tree - * @return {AudioNode} the resulting audio node from the expression - * @private - */ - Tone.Expr.prototype._eval = function (tree) { - if (!this.isUndef(tree)) { - var node = tree.method(tree.args, this); - this._nodes.push(node); - return node; - } - }; - /** - * dispose all the nodes - * @private - */ - Tone.Expr.prototype._disposeNodes = function () { - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - if (this.isFunction(node.dispose)) { - node.dispose(); - } else if (this.isFunction(node.disconnect)) { - node.disconnect(); - } - node = null; - this._nodes[i] = null; - } - this._nodes = null; - }; - /** - * clean up - */ - Tone.Expr.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._disposeNodes(); - }; - return Tone.Expr; - }); - Module(function (Tone) { - - /** - * @class Convert an incoming signal between 0, 1 to an equal power gain scale. - * - * @extends {Tone.SignalBase} - * @constructor - * @example - * var eqPowGain = new Tone.EqualPowerGain(); - */ - Tone.EqualPowerGain = function () { - /** - * @type {Tone.WaveShaper} - * @private - */ - this._eqPower = this.input = this.output = new Tone.WaveShaper(function (val) { - if (Math.abs(val) < 0.001) { - //should output 0 when input is 0 - return 0; - } else { - return this.equalPowerScale(val); - } - }.bind(this), 4096); - }; - Tone.extend(Tone.EqualPowerGain, Tone.SignalBase); - /** - * clean up - * @returns {Tone.EqualPowerGain} this - */ - Tone.EqualPowerGain.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._eqPower.dispose(); - this._eqPower = null; - return this; - }; - return Tone.EqualPowerGain; - }); - Module(function (Tone) { - - /** - * @class Tone.Crossfade provides equal power fading between two inputs. - * More on crossfading technique [here](https://en.wikipedia.org/wiki/Fade_(audio_engineering)#Crossfading). - * - * @constructor - * @extends {Tone} - * @param {NormalRange} [initialFade=0.5] - * @example - * var crossFade = new Tone.CrossFade(0.5); - * //connect effect A to crossfade from - * //effect output 0 to crossfade input 0 - * effectA.connect(crossFade, 0, 0); - * //connect effect B to crossfade from - * //effect output 0 to crossfade input 1 - * effectB.connect(crossFade, 0, 1); - * crossFade.fade.value = 0; - * // ^ only effectA is output - * crossFade.fade.value = 1; - * // ^ only effectB is output - * crossFade.fade.value = 0.5; - * // ^ the two signals are mixed equally. - */ - Tone.CrossFade = function (initialFade) { - Tone.call(this, 2, 1); - /** - * Alias for input[0]. - * @type {GainNode} - */ - this.a = this.input[0] = this.context.createGain(); - /** - * Alias for input[1]. - * @type {GainNode} - */ - this.b = this.input[1] = this.context.createGain(); - /** - * The mix between the two inputs. A fade value of 0 - * will output 100% input[0] and - * a value of 1 will output 100% input[1]. - * @type {NormalRange} - * @signal - */ - this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange); - /** - * equal power gain cross fade - * @private - * @type {Tone.EqualPowerGain} - */ - this._equalPowerA = new Tone.EqualPowerGain(); - /** - * equal power gain cross fade - * @private - * @type {Tone.EqualPowerGain} - */ - this._equalPowerB = new Tone.EqualPowerGain(); - /** - * invert the incoming signal - * @private - * @type {Tone} - */ - this._invert = new Tone.Expr('1 - $0'); - //connections - this.a.connect(this.output); - this.b.connect(this.output); - this.fade.chain(this._equalPowerB, this.b.gain); - this.fade.chain(this._invert, this._equalPowerA, this.a.gain); - this._readOnly('fade'); - }; - Tone.extend(Tone.CrossFade); - /** - * clean up - * @returns {Tone.CrossFade} this - */ - Tone.CrossFade.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable('fade'); - this._equalPowerA.dispose(); - this._equalPowerA = null; - this._equalPowerB.dispose(); - this._equalPowerB = null; - this.fade.dispose(); - this.fade = null; - this._invert.dispose(); - this._invert = null; - this.a.disconnect(); - this.a = null; - this.b.disconnect(); - this.b = null; - return this; - }; - return Tone.CrossFade; - }); - Module(function (Tone) { - - /** - * @class Tone.Filter is a filter which allows for all of the same native methods - * as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface). - * Tone.Filter has the added ability to set the filter rolloff at -12 - * (default), -24 and -48. - * - * @constructor - * @extends {Tone} - * @param {Frequency|Object} [frequency] The cutoff frequency of the filter. - * @param {string=} type The type of filter. - * @param {number=} rolloff The drop in decibels per octave after the cutoff frequency. - * 3 choices: -12, -24, and -48 - * @example - * var filter = new Tone.Filter(200, "highpass"); - */ - Tone.Filter = function () { - Tone.call(this); - var options = this.optionsObject(arguments, [ - 'frequency', - 'type', - 'rolloff' - ], Tone.Filter.defaults); - /** - * the filter(s) - * @type {Array} - * @private - */ - this._filters = []; - /** - * The cutoff frequency of the filter. - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); - /** - * The detune parameter - * @type {Cents} - * @signal - */ - this.detune = new Tone.Signal(0, Tone.Type.Cents); - /** - * The gain of the filter, only used in certain filter types - * @type {Number} - * @signal - */ - this.gain = new Tone.Signal({ - 'value': options.gain, - 'convert': false - }); - /** - * The Q or Quality of the filter - * @type {Positive} - * @signal - */ - this.Q = new Tone.Signal(options.Q); - /** - * the type of the filter - * @type {string} - * @private - */ - this._type = options.type; - /** - * the rolloff value of the filter - * @type {number} - * @private - */ - this._rolloff = options.rolloff; - //set the rolloff; - this.rolloff = options.rolloff; - this._readOnly([ - 'detune', - 'frequency', - 'gain', - 'Q' - ]); - }; - Tone.extend(Tone.Filter); - /** - * the default parameters - * - * @static - * @type {Object} - */ - Tone.Filter.defaults = { - 'type': 'lowpass', - 'frequency': 350, - 'rolloff': -12, - 'Q': 1, - 'gain': 0 - }; - /** - * The type of the filter. Types: "lowpass", "highpass", - * "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking". - * @memberOf Tone.Filter# - * @type {string} - * @name type - */ - Object.defineProperty(Tone.Filter.prototype, 'type', { - get: function () { - return this._type; - }, - set: function (type) { - var types = [ - 'lowpass', - 'highpass', - 'bandpass', - 'lowshelf', - 'highshelf', - 'notch', - 'allpass', - 'peaking' - ]; - if (types.indexOf(type) === -1) { - throw new Error('Tone.Filter does not have filter type ' + type); - } - this._type = type; - for (var i = 0; i < this._filters.length; i++) { - this._filters[i].type = type; - } - } - }); - /** - * The rolloff of the filter which is the drop in db - * per octave. Implemented internally by cascading filters. - * Only accepts the values -12, -24, -48 and -96. - * @memberOf Tone.Filter# - * @type {number} - * @name rolloff - */ - Object.defineProperty(Tone.Filter.prototype, 'rolloff', { - get: function () { - return this._rolloff; - }, - set: function (rolloff) { - rolloff = parseInt(rolloff, 10); - var possibilities = [ - -12, - -24, - -48, - -96 - ]; - var cascadingCount = possibilities.indexOf(rolloff); - //check the rolloff is valid - if (cascadingCount === -1) { - throw new Error('Filter rolloff can only be -12, -24, -48 or -96'); - } - cascadingCount += 1; - this._rolloff = rolloff; - //first disconnect the filters and throw them away - this.input.disconnect(); - for (var i = 0; i < this._filters.length; i++) { - this._filters[i].disconnect(); - this._filters[i] = null; - } - this._filters = new Array(cascadingCount); - for (var count = 0; count < cascadingCount; count++) { - var filter = this.context.createBiquadFilter(); - filter.type = this._type; - this.frequency.connect(filter.frequency); - this.detune.connect(filter.detune); - this.Q.connect(filter.Q); - this.gain.connect(filter.gain); - this._filters[count] = filter; - } - //connect them up - var connectionChain = [this.input].concat(this._filters).concat([this.output]); - this.connectSeries.apply(this, connectionChain); - } - }); - /** - * Clean up. - * @return {Tone.Filter} this - */ - Tone.Filter.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - for (var i = 0; i < this._filters.length; i++) { - this._filters[i].disconnect(); - this._filters[i] = null; - } - this._filters = null; - this._writable([ - 'detune', - 'frequency', - 'gain', - 'Q' - ]); - this.frequency.dispose(); - this.Q.dispose(); - this.frequency = null; - this.Q = null; - this.detune.dispose(); - this.detune = null; - this.gain.dispose(); - this.gain = null; - return this; - }; - return Tone.Filter; - }); - Module(function (Tone) { - - /** - * @class Split the incoming signal into three bands (low, mid, high) - * with two crossover frequency controls. - * - * @extends {Tone} - * @constructor - * @param {Frequency|Object} [lowFrequency] the low/mid crossover frequency - * @param {Frequency} [highFrequency] the mid/high crossover frequency - */ - Tone.MultibandSplit = function () { - var options = this.optionsObject(arguments, [ - 'lowFrequency', - 'highFrequency' - ], Tone.MultibandSplit.defaults); - /** - * the input - * @type {GainNode} - * @private - */ - this.input = this.context.createGain(); - /** - * the outputs - * @type {Array} - * @private - */ - this.output = new Array(3); - /** - * The low band. Alias for output[0] - * @type {Tone.Filter} - */ - this.low = this.output[0] = new Tone.Filter(0, 'lowpass'); - /** - * the lower filter of the mid band - * @type {Tone.Filter} - * @private - */ - this._lowMidFilter = new Tone.Filter(0, 'highpass'); - /** - * The mid band output. Alias for output[1] - * @type {Tone.Filter} - */ - this.mid = this.output[1] = new Tone.Filter(0, 'lowpass'); - /** - * The high band output. Alias for output[2] - * @type {Tone.Filter} - */ - this.high = this.output[2] = new Tone.Filter(0, 'highpass'); - /** - * The low/mid crossover frequency. - * @type {Frequency} - * @signal - */ - this.lowFrequency = new Tone.Signal(options.lowFrequency, Tone.Type.Frequency); - /** - * The mid/high crossover frequency. - * @type {Frequency} - * @signal - */ - this.highFrequency = new Tone.Signal(options.highFrequency, Tone.Type.Frequency); - /** - * The quality of all the filters - * @type {Number} - * @signal - */ - this.Q = new Tone.Signal(options.Q); - this.input.fan(this.low, this.high); - this.input.chain(this._lowMidFilter, this.mid); - //the frequency control signal - this.lowFrequency.connect(this.low.frequency); - this.lowFrequency.connect(this._lowMidFilter.frequency); - this.highFrequency.connect(this.mid.frequency); - this.highFrequency.connect(this.high.frequency); - //the Q value - this.Q.connect(this.low.Q); - this.Q.connect(this._lowMidFilter.Q); - this.Q.connect(this.mid.Q); - this.Q.connect(this.high.Q); - this._readOnly([ - 'high', - 'mid', - 'low', - 'highFrequency', - 'lowFrequency' - ]); - }; - Tone.extend(Tone.MultibandSplit); - /** - * @private - * @static - * @type {Object} - */ - Tone.MultibandSplit.defaults = { - 'lowFrequency': 400, - 'highFrequency': 2500, - 'Q': 1 - }; - /** - * Clean up. - * @returns {Tone.MultibandSplit} this - */ - Tone.MultibandSplit.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'high', - 'mid', - 'low', - 'highFrequency', - 'lowFrequency' - ]); - this.low.dispose(); - this.low = null; - this._lowMidFilter.dispose(); - this._lowMidFilter = null; - this.mid.dispose(); - this.mid = null; - this.high.dispose(); - this.high = null; - this.lowFrequency.dispose(); - this.lowFrequency = null; - this.highFrequency.dispose(); - this.highFrequency = null; - this.Q.dispose(); - this.Q = null; - return this; - }; - return Tone.MultibandSplit; - }); - Module(function (Tone) { - - /** - * @class Tone.EQ3 is a three band EQ with control over low, mid, and high gain as - * well as the low and high crossover frequencies. - * - * @constructor - * @extends {Tone} - * - * @param {Decibels|Object} [lowLevel] The gain applied to the lows. - * @param {Decibels} [midLevel] The gain applied to the mid. - * @param {Decibels} [highLevel] The gain applied to the high. - * @example - * var eq = new Tone.EQ3(-10, 3, -20); - */ - Tone.EQ3 = function () { - var options = this.optionsObject(arguments, [ - 'low', - 'mid', - 'high' - ], Tone.EQ3.defaults); - /** - * the output node - * @type {GainNode} - * @private - */ - this.output = this.context.createGain(); - /** - * the multiband split - * @type {Tone.MultibandSplit} - * @private - */ - this._multibandSplit = this.input = new Tone.MultibandSplit({ - 'lowFrequency': options.lowFrequency, - 'highFrequency': options.highFrequency - }); + this._multibandSplit = this.input = new Tone.MultibandSplit({ + 'lowFrequency': options.lowFrequency, + 'highFrequency': options.highFrequency + }); /** * The gain for the lower signals * @type {Tone.Gain} @@ -6053,8 +6223,8 @@ */ Tone.Follower.prototype._setAttackRelease = function (attack, release) { var minTime = this.blockTime; - attack = this.secondsToFrequency(this.toSeconds(attack)); - release = this.secondsToFrequency(this.toSeconds(release)); + attack = Tone.Time(attack).toFrequency(); + release = Tone.Time(release).toFrequency(); attack = Math.max(attack, minTime); release = Math.max(release, minTime); this._frequencyValues.setMap(function (val) { @@ -6546,13 +6716,7 @@ * @type {Number} * @private */ - this._computedLookAhead = 1 / 60; - /** - * The value afterwhich events are thrown out - * @type {Number} - * @private - */ - this._threshold = 0.5; + this._computedLookAhead = UPDATE_RATE / 1000; /** * The next time the callback is scheduled. * @type {Number} @@ -6564,7 +6728,7 @@ * @type {Number} * @private */ - this._lastUpdate = 0; + this._lastUpdate = -1; /** * The id of the requestAnimationFrame * @type {Number} @@ -6591,15 +6755,15 @@ */ this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * A pre-binded loop function to save a tiny bit of overhead - * of rebinding the function on every frame. - * @type {Function} + * The loop function bound to its context. + * This is necessary to remove the event in the end. + * @type {Function} * @private */ this._boundLoop = this._loop.bind(this); + //bind a callback to the worker thread + Tone.Clock._worker.addEventListener('message', this._boundLoop); this._readOnly('frequency'); - //start the loop - this._loop(); }; Tone.extend(Tone.Clock); /** @@ -6676,9 +6840,8 @@ */ Tone.Clock.prototype.stop = function (time) { time = this.toSeconds(time); - if (this._state.getStateAtTime(time) !== Tone.State.Stopped) { - this._state.setStateAtTime(Tone.State.Stopped, time); - } + this._state.cancel(time); + this._state.setStateAtTime(Tone.State.Stopped, time); return this; }; /** @@ -6699,19 +6862,18 @@ * when the page was loaded. * @private */ - Tone.Clock.prototype._loop = function (time) { - this._loopID = requestAnimationFrame(this._boundLoop); + Tone.Clock.prototype._loop = function () { //compute the look ahead if (this._lookAhead === 'auto') { - if (!this.isUndef(time)) { - var diff = (time - this._lastUpdate) / 1000; - this._lastUpdate = time; - //throw away large differences - if (diff < this._threshold) { - //averaging - this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; - } + var time = this.now(); + if (this._lastUpdate !== -1) { + var diff = time - this._lastUpdate; + //max size on the diff + diff = Math.min(10 * UPDATE_RATE / 1000, diff); + //averaging + this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10; } + this._lastUpdate = time; } else { this._computedLookAhead = this._lookAhead; } @@ -6733,10 +6895,6 @@ } if (state === Tone.State.Started) { while (now + lookAhead > this._nextTick) { - //catch up - if (now > this._nextTick + this._threshold) { - this._nextTick = now; - } var tickTime = this._nextTick; this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick); this.callback(tickTime); @@ -6765,15 +6923,44 @@ Tone.Clock.prototype.dispose = function () { cancelAnimationFrame(this._loopID); Tone.TimelineState.prototype.dispose.call(this); + Tone.Clock._worker.removeEventListener('message', this._boundLoop); this._writable('frequency'); this.frequency.dispose(); this.frequency = null; - this._boundLoop = Tone.noOp; + this._boundLoop = null; this._nextTick = Infinity; this.callback = null; this._state.dispose(); this._state = null; }; + //URL Shim + window.URL = window.URL || window.webkitURL; + /** + * The update rate in Milliseconds + * @const + * @type {Number} + * @private + */ + var UPDATE_RATE = 20; + /** + * The script which runs in a web worker + * @type {Blob} + * @private + */ + var blob = new Blob(['setInterval(function(){self.postMessage(\'tick\')}, ' + UPDATE_RATE + ')']); + /** + * Create a blob url from the Blob + * @type {URL} + * @private + */ + var blobUrl = URL.createObjectURL(blob); + /** + * The Worker which generates a regular callback + * @type {Worker} + * @private + * @static + */ + Tone.Clock._worker = new Worker(blobUrl); return Tone.Clock; }); Module(function (Tone) { @@ -6923,7 +7110,7 @@ */ Tone.IntervalTimeline.prototype.addEvent = function (event) { if (this.isUndef(event.time) || this.isUndef(event.duration)) { - throw new Error('events must have time and duration parameters'); + throw new Error('Tone.IntervalTimeline: events must have time and duration parameters'); } var node = new IntervalNode(event.time, event.time + event.duration, event); if (this._root === null) { @@ -7193,7 +7380,7 @@ * @param {Function} callback The callback to invoke with every item * @returns {Tone.IntervalTimeline} this */ - Tone.IntervalTimeline.prototype.forEachOverlap = function (time, callback) { + Tone.IntervalTimeline.prototype.forEachAtTime = function (time, callback) { time = this.toSeconds(time); if (this._root !== null) { var results = []; @@ -7314,7 +7501,7 @@ this.left.search(point, results); } // Check this node - if (this.low <= point && this.high >= point) { + if (this.low <= point && this.high > point) { results.push(this); } // If p is to the left of the time of this interval, @@ -7472,17 +7659,12 @@ * @singleton * @example * //repeated event every 8th note - * Tone.Transport.setInterval(function(time){ + * Tone.Transport.scheduleRepeat(function(time){ * //do something with the time * }, "8n"); * @example - * //one time event 1 second in the future - * Tone.Transport.setTimeout(function(time){ - * //do something with the time - * }, 1); - * @example - * //event fixed to the Transports timeline. - * Tone.Transport.setTimeline(function(time){ + * //schedule an event on the 16th measure + * Tone.Transport.schedule(function(time){ * //do something with the time * }, "16:0:0"); */ @@ -7591,13 +7773,13 @@ /////////////////////////////////////////////////////////////////////// // SWING ////////////////////////////////////////////////////////////////////// - var swingSeconds = this.notationToSeconds(TransportConstructor.defaults.swingSubdivision, TransportConstructor.defaults.bpm, TransportConstructor.defaults.timeSignature); /** * The subdivision of the swing * @type {Ticks} * @private */ - this._swingTicks = swingSeconds / (60 / TransportConstructor.defaults.bpm) * this._ppq; + this._swingTicks = TransportConstructor.defaults.PPQ / 2; + //8n /** * The swing amount * @type {NormalRange} @@ -7615,11 +7797,11 @@ Tone.Transport.defaults = { 'bpm': 120, 'swing': 0, - 'swingSubdivision': '16n', + 'swingSubdivision': '8n', 'timeSignature': 4, 'loopStart': 0, 'loopEnd': '4m', - 'PPQ': 48 + 'PPQ': 192 }; /////////////////////////////////////////////////////////////////////////////// // TICKS @@ -7630,36 +7812,39 @@ * @private */ Tone.Transport.prototype._processTick = function (tickTime) { + var ticks = this._clock.ticks; //handle swing - if (this._swingAmount > 0 && this._clock.ticks % this._ppq !== 0 && //not on a downbeat - this._clock.ticks % this._swingTicks === 0) { + if (this._swingAmount > 0 && ticks % this._ppq !== 0 && //not on a downbeat + ticks % (this._swingTicks * 2) !== 0) { //add some swing - tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount; + var progress = ticks % (this._swingTicks * 2) / (this._swingTicks * 2); + var amount = Math.sin(progress * Math.PI) * this._swingAmount; + tickTime += Tone.Time(this._swingTicks * 2 / 3, 'i').eval() * amount; } //do the loop test if (this.loop) { - if (this._clock.ticks === this._loopEnd) { + if (ticks === this._loopEnd) { this.ticks = this._loopStart; + ticks = this._loopStart; this.trigger('loop', tickTime); } } - var ticks = this._clock.ticks; + //process the single occurrence events + this._onceEvents.forEachBefore(ticks, function (event) { + event.callback(tickTime); + }); + //and clear the single occurrence timeline + this._onceEvents.cancelBefore(ticks); //fire the next tick events if their time has come this._timeline.forEachAtTime(ticks, function (event) { event.callback(tickTime); }); //process the repeated events - this._repeatedEvents.forEachOverlap(ticks, function (event) { + this._repeatedEvents.forEachAtTime(ticks, function (event) { if ((ticks - event.time) % event.interval === 0) { event.callback(tickTime); } }); - //process the single occurrence events - this._onceEvents.forEachBefore(ticks, function (event) { - event.callback(tickTime); - }); - //and clear the single occurrence timeline - this._onceEvents.cancelBefore(ticks); }; /////////////////////////////////////////////////////////////////////////////// // SCHEDULABLE EVENTS @@ -7667,7 +7852,7 @@ /** * Schedule an event along the timeline. * @param {Function} callback The callback to be invoked at the time. - * @param {Time} time The time to invoke the callback at. + * @param {TransportTime} time The time to invoke the callback at. * @return {Number} The id of the event which can be used for canceling the event. * @example * //trigger the callback when the Transport reaches the desired time @@ -7695,7 +7880,7 @@ * @param {Function} callback The callback to invoke. * @param {Time} interval The duration between successive * callbacks. - * @param {Time=} startTime When along the timeline the events should + * @param {TimelinePosition=} startTime When along the timeline the events should * start being invoked. * @param {Time} [duration=Infinity] How long the event should repeat. * @return {Number} The ID of the scheduled event. Use this to cancel @@ -7706,7 +7891,7 @@ */ Tone.Transport.prototype.scheduleRepeat = function (callback, interval, startTime, duration) { if (interval <= 0) { - throw new Error('repeat events must have an interval larger than 0'); + throw new Error('Tone.Transport: repeat events must have an interval larger than 0'); } var event = { 'time': this.toTicks(startTime), @@ -7727,7 +7912,7 @@ * Note that if the given time is less than the current transport time, * the event will be invoked immediately. * @param {Function} callback The callback to invoke once. - * @param {Time} time The time the callback should be invoked. + * @param {TransportTime} time The time the callback should be invoked. * @returns {Number} The ID of the scheduled event. */ Tone.Transport.prototype.scheduleOnce = function (callback, time) { @@ -7760,7 +7945,7 @@ * Remove scheduled events from the timeline after * the given time. Repeated events will be removed * if their startTime is after the given time - * @param {Time} [after=0] Clear all events after + * @param {TransportTime} [after=0] Clear all events after * this time. * @returns {Tone.Transport} this */ @@ -7773,34 +7958,6 @@ return this; }; /////////////////////////////////////////////////////////////////////////////// - // QUANTIZATION - /////////////////////////////////////////////////////////////////////////////// - /** - * Returns the time closest time (equal to or after the given time) that aligns - * to the subidivision. - * @param {Time} time The time value to quantize to the given subdivision - * @param {String} [subdivision="4n"] The subdivision to quantize to. - * @return {Number} the time in seconds until the next subdivision. - * @example - * Tone.Transport.bpm.value = 120; - * Tone.Transport.quantize("3 * 4n", "1m"); //return 0.5 - * //if the clock is started, it will return a value less than 0.5 - */ - Tone.Transport.prototype.quantize = function (time, subdivision) { - subdivision = this.defaultArg(subdivision, '4n'); - var tickTime = this.toTicks(time); - subdivision = this.toTicks(subdivision); - var remainingTicks = subdivision - tickTime % subdivision; - if (remainingTicks === subdivision) { - remainingTicks = 0; - } - var now = this.now(); - if (this.state === Tone.State.Started) { - now = this._clock._nextTick; - } - return this.toSeconds(time, now) + this.ticksToSeconds(remainingTicks); - }; - /////////////////////////////////////////////////////////////////////////////// // START/STOP/PAUSE /////////////////////////////////////////////////////////////////////////////// /** @@ -7818,7 +7975,7 @@ /** * Start the transport and all sources synced to the transport. * @param {Time} [time=now] The time when the transport should start. - * @param {Time=} offset The timeline offset to start the transport. + * @param {TransportTime=} offset The timeline offset to start the transport. * @returns {Tone.Transport} this * @example * //start the transport in one second starting at beginning of the 5th measure. @@ -7827,13 +7984,13 @@ Tone.Transport.prototype.start = function (time, offset) { time = this.toSeconds(time); if (!this.isUndef(offset)) { - offset = this.toTicks(offset); + offset = new Tone.Time(offset); } else { - offset = this.defaultArg(offset, this._clock.ticks); + offset = new Tone.Time(this._clock.ticks, 'i'); } //start the clock - this._clock.start(time, offset); - this.trigger('start', time, this.ticksToSeconds(offset)); + this._clock.start(time, offset.toTicks()); + this.trigger('start', time, offset.toSeconds()); return this; }; /** @@ -7891,12 +8048,12 @@ /** * When the Tone.Transport.loop = true, this is the starting position of the loop. * @memberOf Tone.Transport# - * @type {Time} + * @type {TransportTime} * @name loopStart */ Object.defineProperty(Tone.Transport.prototype, 'loopStart', { get: function () { - return this.ticksToSeconds(this._loopStart); + return Tone.TransportTime(this._loopStart, 'i').toSeconds(); }, set: function (startPosition) { this._loopStart = this.toTicks(startPosition); @@ -7905,12 +8062,12 @@ /** * When the Tone.Transport.loop = true, this is the ending position of the loop. * @memberOf Tone.Transport# - * @type {Time} + * @type {TransportTime} * @name loopEnd */ Object.defineProperty(Tone.Transport.prototype, 'loopEnd', { get: function () { - return this.ticksToSeconds(this._loopEnd); + return Tone.TransportTime(this._loopEnd, 'i').toSeconds(); }, set: function (endPosition) { this._loopEnd = this.toTicks(endPosition); @@ -7918,8 +8075,8 @@ }); /** * Set the loop start and stop at the same time. - * @param {Time} startPosition - * @param {Time} endPosition + * @param {TransportTime} startPosition + * @param {TransportTime} endPosition * @returns {Tone.Transport} this * @example * //loop over the first measure @@ -7940,11 +8097,11 @@ */ Object.defineProperty(Tone.Transport.prototype, 'swing', { get: function () { - return this._swingAmount * 2; + return this._swingAmount; }, set: function (amount) { //scale the values to a normal range - this._swingAmount = amount * 0.5; + this._swingAmount = amount; } }); /** @@ -7958,36 +8115,22 @@ */ Object.defineProperty(Tone.Transport.prototype, 'swingSubdivision', { get: function () { - return this.toNotation(this._swingTicks + 'i'); + return Tone.Time(this._swingTicks, 'i').toNotation(); }, set: function (subdivision) { this._swingTicks = this.toTicks(subdivision); } }); /** - * The Transport's position in MEASURES:BEATS:SIXTEENTHS. + * The Transport's position in Bars:Beats:Sixteenths. * Setting the value will jump to that position right away. - * * @memberOf Tone.Transport# - * @type {TransportTime} + * @type {BarsBeatsSixteenths} * @name position */ Object.defineProperty(Tone.Transport.prototype, 'position', { get: function () { - var quarters = this.ticks / this._ppq; - var measures = Math.floor(quarters / this._timeSignature); - var sixteenths = quarters % 1 * 4; - //if the sixteenths aren't a whole number, fix their length - if (sixteenths % 1 > 0) { - sixteenths = sixteenths.toFixed(3); - } - quarters = Math.floor(quarters) % this._timeSignature; - var progress = [ - measures, - quarters, - sixteenths - ]; - return progress.join(':'); + return Tone.TransportTime(this.ticks, 'i').toBarsBeatsSixteenths(); }, set: function (progress) { var ticks = this.toTicks(progress); @@ -8040,8 +8183,9 @@ return this._ppq; }, set: function (ppq) { + var bpm = this.bpm.value; this._ppq = ppq; - this.bpm.value = this.bpm.value; + this.bpm.value = bpm; } }); /** @@ -8065,6 +8209,33 @@ /////////////////////////////////////////////////////////////////////////////// // SYNCING /////////////////////////////////////////////////////////////////////////////// + /** + * Returns the time aligned to the next subdivision + * of the Transport. If the Transport is not started, + * it will return 0. + * Note: this will not work precisely during tempo ramps. + * @param {Time} subdivision The subdivision to quantize to + * @return {Number} The context time of the next subdivision. + * @example + * Tone.Transport.start(); //the transport must be started + * Tone.Transport.nextSubdivision("4n"); + */ + Tone.Transport.prototype.nextSubdivision = function (subdivision) { + subdivision = this.toSeconds(subdivision); + //if the transport's not started, return 0 + var now; + if (this.state === Tone.State.Started) { + now = this._clock._nextTick; + } else { + return 0; + } + var transportPos = Tone.Time(this.ticks, 'i').eval(); + var remainingTime = subdivision - transportPos % subdivision; + if (remainingTime === 0) { + remainingTime = subdivision; + } + return now + remainingTime; + }; /** * Attaches the signal to the tempo control signal so that * any changes in the tempo will change the signal in the same @@ -8129,103 +8300,7 @@ this._onceEvents = null; this._repeatedEvents.dispose(); this._repeatedEvents = null; - return this; - }; - /////////////////////////////////////////////////////////////////////////////// - // DEPRECATED FUNCTIONS - // (will be removed in r7) - /////////////////////////////////////////////////////////////////////////////// - /** - * @deprecated Use Tone.scheduleRepeat instead. - * Set a callback for a recurring event. - * @param {function} callback - * @param {Time} interval - * @return {number} the id of the interval - * @example - * //triggers a callback every 8th note with the exact time of the event - * Tone.Transport.setInterval(function(time){ - * envelope.triggerAttack(time); - * }, "8n"); - * @private - */ - Tone.Transport.prototype.setInterval = function (callback, interval) { - console.warn('This method is deprecated. Use Tone.Transport.scheduleRepeat instead.'); - return Tone.Transport.scheduleRepeat(callback, interval); - }; - /** - * @deprecated Use Tone.cancel instead. - * Stop and ongoing interval. - * @param {number} intervalID The ID of interval to remove. The interval - * ID is given as the return value in Tone.Transport.setInterval. - * @return {boolean} true if the event was removed - * @private - */ - Tone.Transport.prototype.clearInterval = function (id) { - console.warn('This method is deprecated. Use Tone.Transport.clear instead.'); - return Tone.Transport.clear(id); - }; - /** - * @deprecated Use Tone.Note instead. - * Set a timeout to occur after time from now. NB: the transport must be - * running for this to be triggered. All timeout events are cleared when the - * transport is stopped. - * - * @param {function} callback - * @param {Time} time The time (from now) that the callback will be invoked. - * @return {number} The id of the timeout. - * @example - * //trigger an event to happen 1 second from now - * Tone.Transport.setTimeout(function(time){ - * player.start(time); - * }, 1) - * @private - */ - Tone.Transport.prototype.setTimeout = function (callback, timeout) { - console.warn('This method is deprecated. Use Tone.Transport.scheduleOnce instead.'); - return Tone.Transport.scheduleOnce(callback, timeout); - }; - /** - * @deprecated Use Tone.Note instead. - * Clear a timeout using it's ID. - * @param {number} intervalID The ID of timeout to remove. The timeout - * ID is given as the return value in Tone.Transport.setTimeout. - * @return {boolean} true if the timeout was removed - * @private - */ - Tone.Transport.prototype.clearTimeout = function (id) { - console.warn('This method is deprecated. Use Tone.Transport.clear instead.'); - return Tone.Transport.clear(id); - }; - /** - * @deprecated Use Tone.Note instead. - * Timeline events are synced to the timeline of the Tone.Transport. - * Unlike Timeout, Timeline events will restart after the - * Tone.Transport has been stopped and restarted. - * - * @param {function} callback - * @param {Time} time - * @return {number} the id for clearing the transportTimeline event - * @example - * //trigger the start of a part on the 16th measure - * Tone.Transport.setTimeline(function(time){ - * part.start(time); - * }, "16m"); - * @private - */ - Tone.Transport.prototype.setTimeline = function (callback, time) { - console.warn('This method is deprecated. Use Tone.Transport.schedule instead.'); - return Tone.Transport.schedule(callback, time); - }; - /** - * @deprecated Use Tone.Note instead. - * Clear the timeline event. - * @param {number} id - * @return {boolean} true if it was removed - * @private - */ - Tone.Transport.prototype.clearTimeline = function (id) { - console.warn('This method is deprecated. Use Tone.Transport.clear instead.'); - return Tone.Transport.clear(id); + return this; }; /////////////////////////////////////////////////////////////////////////////// // INITIALIZATION @@ -8270,6 +8345,18 @@ * @private */ this.output = this.input = new Tone.Gain(options.volume, Tone.Type.Decibels); + /** + * The unmuted volume + * @type {Decibels} + * @private + */ + this._unmutedVolume = 0; + /** + * if the volume is muted + * @type {Boolean} + * @private + */ + this._muted = false; /** * The volume control in decibels. * @type {Decibels} @@ -8277,6 +8364,8 @@ */ this.volume = this.output.gain; this._readOnly('volume'); + //set the mute initially + this.mute = options.mute; }; Tone.extend(Tone.Volume); /** @@ -8285,7 +8374,34 @@ * @const * @static */ - Tone.Volume.defaults = { 'volume': 0 }; + Tone.Volume.defaults = { + 'volume': 0, + 'mute': false + }; + /** + * Mute the output. + * @memberOf Tone.Volume# + * @type {boolean} + * @name mute + * @example + * //mute the output + * volume.mute = true; + */ + Object.defineProperty(Tone.Volume.prototype, 'mute', { + get: function () { + return this._muted; + }, + set: function (mute) { + if (!this._muted && mute) { + this._unmutedVolume = this.volume.value; + //maybe it should ramp here? + this.volume.value = -Infinity; + } else if (this._muted && !mute) { + this.volume.value = this._unmutedVolume; + } + this._muted = mute; + } + }); /** * clean up * @returns {Tone.Volume} this @@ -8300,6 +8416,146 @@ }; return Tone.Volume; }); + Module(function (Tone) { + + /** + * @class A single master output which is connected to the + * AudioDestinationNode (aka your speakers). + * It provides useful conveniences such as the ability + * to set the volume and mute the entire application. + * It also gives you the ability to apply master effects to your application. + *

+ * Like Tone.Transport, A single Tone.Master is created + * on initialization and you do not need to explicitly construct one. + * + * @constructor + * @extends {Tone} + * @singleton + * @example + * //the audio will go from the oscillator to the speakers + * oscillator.connect(Tone.Master); + * //a convenience for connecting to the master output is also provided: + * oscillator.toMaster(); + * //the above two examples are equivalent. + */ + Tone.Master = function () { + Tone.call(this); + /** + * The private volume node + * @type {Tone.Volume} + * @private + */ + this._volume = this.output = new Tone.Volume(); + /** + * The volume of the master output. + * @type {Decibels} + * @signal + */ + this.volume = this._volume.volume; + this._readOnly('volume'); + //connections + this.input.chain(this.output, this.context.destination); + }; + Tone.extend(Tone.Master); + /** + * @type {Object} + * @const + */ + Tone.Master.defaults = { + 'volume': 0, + 'mute': false + }; + /** + * Mute the output. + * @memberOf Tone.Master# + * @type {boolean} + * @name mute + * @example + * //mute the output + * Tone.Master.mute = true; + */ + Object.defineProperty(Tone.Master.prototype, 'mute', { + get: function () { + return this._volume.mute; + }, + set: function (mute) { + this._volume.mute = mute; + } + }); + /** + * Add a master effects chain. NOTE: this will disconnect any nodes which were previously + * chained in the master effects chain. + * @param {AudioNode|Tone...} args All arguments will be connected in a row + * and the Master will be routed through it. + * @return {Tone.Master} this + * @example + * //some overall compression to keep the levels in check + * var masterCompressor = new Tone.Compressor({ + * "threshold" : -6, + * "ratio" : 3, + * "attack" : 0.5, + * "release" : 0.1 + * }); + * //give a little boost to the lows + * var lowBump = new Tone.Filter(200, "lowshelf"); + * //route everything through the filter + * //and compressor before going to the speakers + * Tone.Master.chain(lowBump, masterCompressor); + */ + Tone.Master.prototype.chain = function () { + this.input.disconnect(); + this.input.chain.apply(this.input, arguments); + arguments[arguments.length - 1].connect(this.output); + }; + /** + * Clean up + * @return {Tone.Master} this + */ + Tone.Master.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable('volume'); + this._volume.dispose(); + this._volume = null; + this.volume = null; + }; + /////////////////////////////////////////////////////////////////////////// + // AUGMENT TONE's PROTOTYPE + /////////////////////////////////////////////////////////////////////////// + /** + * Connect 'this' to the master output. Shorthand for this.connect(Tone.Master) + * @returns {Tone} this + * @example + * //connect an oscillator to the master output + * var osc = new Tone.Oscillator().toMaster(); + */ + Tone.prototype.toMaster = function () { + this.connect(Tone.Master); + return this; + }; + /** + * Also augment AudioNode's prototype to include toMaster + * as a convenience + * @returns {AudioNode} this + */ + AudioNode.prototype.toMaster = function () { + this.connect(Tone.Master); + return this; + }; + var MasterConstructor = Tone.Master; + /** + * initialize the module and listen for new audio contexts + */ + Tone._initAudioContext(function () { + //a single master output + if (!Tone.prototype.isUndef(Tone.Master)) { + Tone.Master = new MasterConstructor(); + } else { + MasterConstructor.prototype.dispose.call(Tone.Master); + MasterConstructor.call(Tone.Master); + } + }); + return Tone.Master; + }); Module(function (Tone) { /** @@ -8349,6 +8605,7 @@ * @private */ this._state = new Tone.TimelineState(Tone.State.Stopped); + this._state.memory = 10; /** * The synced `start` callback function from the transport * @type {Function} @@ -8374,6 +8631,8 @@ //make the output explicitly stereo this._volume.output.output.channelCount = 2; this._volume.output.output.channelCountMode = 'explicit'; + //mute initially + this.mute = options.mute; }; Tone.extend(Tone.Source); /** @@ -8382,7 +8641,10 @@ * @const * @type {Object} */ - Tone.Source.defaults = { 'volume': 0 }; + Tone.Source.defaults = { + 'volume': 0, + 'mute': false + }; /** * Returns the playback state of the source, either "started" or "stopped". * @type {Tone.State} @@ -8395,6 +8657,23 @@ return this._state.getStateAtTime(this.now()); } }); + /** + * Mute the output. + * @memberOf Tone.Source# + * @type {boolean} + * @name mute + * @example + * //mute the output + * source.mute = true; + */ + Object.defineProperty(Tone.Source.prototype, 'mute', { + get: function () { + return this._volume.mute; + }, + set: function (mute) { + this._volume.mute = mute; + } + }); /** * Start the source at the specified time. If no time is given, * start the source now. @@ -8423,11 +8702,10 @@ */ Tone.Source.prototype.stop = function (time) { time = this.toSeconds(time); - if (this._state.getStateAtTime(time) === Tone.State.Started) { - this._state.setStateAtTime(Tone.State.Stopped, time); - if (this._stop) { - this._stop.apply(this, arguments); - } + this._state.cancel(time); + this._state.setStateAtTime(Tone.State.Stopped, time); + if (this._stop) { + this._stop.apply(this, arguments); } return this; }; @@ -8711,7 +8989,7 @@ b = this._partials[n - 1]; break; default: - throw new Error('invalid oscillator type: ' + type); + throw new TypeError('Tone.Oscillator: invalid type: ' + type); } if (b !== 0) { real[n] = -b * Math.sin(phase * n); @@ -8827,6 +9105,62 @@ }; return Tone.Oscillator; }); + Module(function (Tone) { + /** + * @class Tone.Zero outputs 0's at audio-rate. The reason this has to be + * it's own class is that many browsers optimize out Tone.Signal + * with a value of 0 and will not process nodes further down the graph. + * @extends {Tone} + */ + Tone.Zero = function () { + /** + * The gain node + * @type {Tone.Gain} + * @private + */ + this._gain = this.input = this.output = new Tone.Gain(); + Tone.Zero._zeros.connect(this._gain); + }; + Tone.extend(Tone.Zero); + /** + * clean up + * @return {Tone.Zero} this + */ + Tone.Zero.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._gain.dispose(); + this._gain = null; + return this; + }; + /** + * Generates a constant output of 0. This is so + * the processing graph doesn't optimize out this + * segment of the graph. + * @static + * @private + * @const + * @type {AudioBufferSourceNode} + */ + Tone.Zero._zeros = null; + /** + * initializer function + */ + Tone._initAudioContext(function (audioContext) { + var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate); + var arr = buffer.getChannelData(0); + for (var i = 0; i < arr.length; i++) { + arr[i] = 0; + } + Tone.Zero._zeros = audioContext.createBufferSource(); + Tone.Zero._zeros.channelCount = 1; + Tone.Zero._zeros.channelCountMode = 'explicit'; + Tone.Zero._zeros.buffer = buffer; + Tone.Zero._zeros.loop = true; + Tone.Zero._zeros.start(0); + Tone.Zero._zeros.noGC(); + }); + return Tone.Zero; + }); Module(function (Tone) { /** @@ -8883,6 +9217,12 @@ * @private */ this._stoppedSignal = new Tone.Signal(0, Tone.Type.AudioRange); + /** + * Just outputs zeros. + * @type {Tone.Zero} + * @private + */ + this._zeros = new Tone.Zero(); /** * The value that the LFO outputs when it's stopped * @type {AudioRange} @@ -8908,6 +9248,7 @@ this.units = options.units; //connect it up this._oscillator.chain(this._a2g, this._scaler); + this._zeros.connect(this._a2g); this._stoppedSignal.connect(this._a2g); this._readOnly([ 'amplitude', @@ -9062,6 +9403,20 @@ this.max = currentMax; } }); + /** + * Mute the output. + * @memberOf Tone.LFO# + * @type {Boolean} + * @name mute + */ + Object.defineProperty(Tone.LFO.prototype, 'mute', { + get: function () { + return this._oscillator.mute; + }, + set: function (mute) { + this._oscillator.mute = mute; + } + }); /** * Returns the playback state of the source, either "started" or "stopped". * @type {Tone.State} @@ -9120,6 +9475,8 @@ this._oscillator = null; this._stoppedSignal.dispose(); this._stoppedSignal = null; + this._zeros.dispose(); + this._zeros = null; this._scaler.dispose(); this._scaler = null; this._a2g.dispose(); @@ -9369,96 +9726,63 @@ /** * @class Tone.Meter gets the [RMS](https://en.wikipedia.org/wiki/Root_mean_square) - * of an input signal with some averaging applied. - * It can also get the raw value of the signal or the value in dB. For signal - * processing, it's better to use Tone.Follower which will produce an audio-rate - * envelope follower instead of needing to poll the Meter to get the output. - *

- * Meter was inspired by [Chris Wilsons Volume Meter](https://github.com/cwilso/volume-meter/blob/master/volume-meter.js). + * of an input signal with some averaging applied. It can also get the raw + * value of the input signal. * * @constructor * @extends {Tone} - * @param {number} [channels=1] number of channels being metered - * @param {number} [smoothing=0.8] amount of smoothing applied to the volume - * @param {number} [clipMemory=0.5] number in seconds that a "clip" should be remembered + * @param {String} type Either "level" or "signal". + * @param {Number} smoothing The amount of smoothing applied between frames. * @example * var meter = new Tone.Meter(); * var mic = new Tone.Microphone().start(); * //connect mic to the meter * mic.connect(meter); - * //use getLevel or getDb - * //to access meter level - * meter.getLevel(); + * //the current level of the mic input + * var level = meter.value; */ Tone.Meter = function () { var options = this.optionsObject(arguments, [ - 'channels', + 'type', 'smoothing' ], Tone.Meter.defaults); - //extends Unit - Tone.call(this); - /** - * The channel count - * @type {number} - * @private - */ - this._channels = options.channels; /** - * The amount which the decays of the meter are smoothed. Small values - * will follow the contours of the incoming envelope more closely than large values. - * @type {NormalRange} - */ - this.smoothing = options.smoothing; - /** - * The amount of time a clip is remember for. - * @type {Time} - */ - this.clipMemory = options.clipMemory; - /** - * The value above which the signal is considered clipped. - * @type {Number} - */ - this.clipLevel = options.clipLevel; - /** - * the rms for each of the channels - * @private - * @type {Array} + * The type of the meter, either "level" or "signal". + * A "level" meter will return the volume level (rms) of the + * input signal and a "signal" meter will return + * the signal value of the input. + * @type {String} */ - this._volume = new Array(this._channels); - /** - * the raw values for each of the channels + this.type = options.type; + /** + * The analyser node which computes the levels. * @private - * @type {Array} + * @type {Tone.Analyser} */ - this._values = new Array(this._channels); - //zero out the volume array - for (var i = 0; i < this._channels; i++) { - this._volume[i] = 0; - this._values[i] = 0; - } - /** - * last time the values clipped - * @private - * @type {Array} + this.input = this.output = this._analyser = new Tone.Analyser('waveform', 512); + this._analyser.returnType = 'float'; + /** + * The amount of carryover between the current and last frame. + * Only applied meter for "level" type. + * @type {Number} */ - this._lastClip = new Array(this._channels); - //zero out the clip array - for (var j = 0; j < this._lastClip.length; j++) { - this._lastClip[j] = 0; - } - /** + this.smoothing = options.smoothing; + /** + * The last computed value + * @type {Number} * @private - * @type {ScriptProcessorNode} */ - this._jsNode = this.context.createScriptProcessor(options.bufferSize, this._channels, 1); - this._jsNode.onaudioprocess = this._onprocess.bind(this); - //so it doesn't get garbage collected - this._jsNode.noGC(); - //signal just passes - this.input.connect(this.output); - this.input.connect(this._jsNode); + this._lastValue = 0; }; Tone.extend(Tone.Meter); + /** + * @private + * @enum {String} + */ + Tone.Meter.Type = { + Level: 'level', + Signal: 'signal' + }; /** * The defaults * @type {Object} @@ -9467,89 +9791,47 @@ */ Tone.Meter.defaults = { 'smoothing': 0.8, - 'bufferSize': 1024, - 'clipMemory': 0.5, - 'clipLevel': 0.9, - 'channels': 1 - }; - /** - * called on each processing frame - * @private - * @param {AudioProcessingEvent} event - */ - Tone.Meter.prototype._onprocess = function (event) { - var bufferSize = this._jsNode.bufferSize; - var smoothing = this.smoothing; - for (var channel = 0; channel < this._channels; channel++) { - var input = event.inputBuffer.getChannelData(channel); - var sum = 0; - var total = 0; - var x; - for (var i = 0; i < bufferSize; i++) { - x = input[i]; - total += x; - sum += x * x; - } - var average = total / bufferSize; - var rms = Math.sqrt(sum / bufferSize); - if (rms > 0.9) { - this._lastClip[channel] = Date.now(); - } - this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing); - this._values[channel] = average; - } + 'type': Tone.Meter.Type.Level }; /** - * Get the rms of the signal. - * @param {number} [channel=0] which channel - * @return {number} the value + * The current value of the meter. A value of 1 is + * "unity". + * @memberOf Tone.Meter# + * @type {Number} + * @name value + * @readOnly */ - Tone.Meter.prototype.getLevel = function (channel) { - channel = this.defaultArg(channel, 0); - var vol = this._volume[channel]; - if (vol < 0.00001) { - return 0; - } else { - return vol; + Object.defineProperty(Tone.Meter.prototype, 'value', { + get: function () { + var signal = this._analyser.analyse(); + if (this.type === Tone.Meter.Type.Level) { + //rms + var sum = 0; + for (var i = 0; i < signal.length; i++) { + sum += Math.pow(signal[i], 2); + } + var rms = Math.sqrt(sum / signal.length); + //smooth it + rms = Math.max(rms, this._lastValue * this.smoothing); + this._lastValue = rms; + //scale it + var unity = 0.35; + var val = rms / unity; + //scale the output curve + return Math.sqrt(val); + } else { + return signal[0]; + } } - }; - /** - * Get the raw value of the signal. - * @param {number=} channel - * @return {number} - */ - Tone.Meter.prototype.getValue = function (channel) { - channel = this.defaultArg(channel, 0); - return this._values[channel]; - }; - /** - * Get the volume of the signal in dB - * @param {number=} channel - * @return {Decibels} - */ - Tone.Meter.prototype.getDb = function (channel) { - return this.gainToDb(this.getLevel(channel)); - }; - /** - * @returns {boolean} if the audio has clipped. The value resets - * based on the clipMemory defined. - */ - Tone.Meter.prototype.isClipped = function (channel) { - channel = this.defaultArg(channel, 0); - return Date.now() - this._lastClip[channel] < this._clipMemory * 1000; - }; + }); /** * Clean up. * @returns {Tone.Meter} this */ Tone.Meter.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._jsNode.disconnect(); - this._jsNode.onaudioprocess = null; - this._jsNode = null; - this._volume = null; - this._values = null; - this._lastClip = null; + this._analyser.dispose(); + this._analyser = null; return this; }; return Tone.Meter; @@ -9997,60 +10279,21 @@ }; return Tone.MultibandCompressor; }); - Module(function (Tone) { - - /** - * @class Maps a NormalRange [0, 1] to an AudioRange [-1, 1]. - * See also Tone.AudioToGain. - * - * @extends {Tone.SignalBase} - * @constructor - * @example - * var g2a = new Tone.GainToAudio(); - */ - Tone.GainToAudio = function () { - /** - * @type {WaveShaperNode} - * @private - */ - this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { - return Math.abs(x) * 2 - 1; - }); - }; - Tone.extend(Tone.GainToAudio, Tone.SignalBase); - /** - * clean up - * @returns {Tone.GainToAudio} this - */ - Tone.GainToAudio.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._norm.dispose(); - this._norm = null; - return this; - }; - return Tone.GainToAudio; - }); Module(function (Tone) { /** * @class Tone.Panner is an equal power Left/Right Panner and does not - * support 3D. Panner uses the StereoPannerNode when available. + * support 3D. Panner uses the StereoPannerNode when available. * * @constructor * @extends {Tone} - * @param {NormalRange} [initialPan=0.5] The initail panner value (defaults to 0.5 = center) + * @param {NormalRange} [initialPan=0] The initail panner value (defaults to 0 = center) * @example * //pan the input signal hard right. * var panner = new Tone.Panner(1); */ Tone.Panner = function (initialPan) { Tone.call(this); - /** - * indicates if the panner is using the new StereoPannerNode internally - * @type {boolean} - * @private - */ - this._hasStereoPanner = this.isFunction(this.context.createStereoPanner); if (this._hasStereoPanner) { /** * the panner node @@ -10059,19 +10302,11 @@ */ this._panner = this.input = this.output = this.context.createStereoPanner(); /** - * The pan control. 0 = hard left, 1 = hard right. + * The pan control. -1 = hard left, 1 = hard right. * @type {NormalRange} * @signal */ - this.pan = new Tone.Signal(0, Tone.Type.NormalRange); - /** - * scale the pan signal to between -1 and 1 - * @type {Tone.WaveShaper} - * @private - */ - this._scalePan = new Tone.GainToAudio(); - //connections - this.pan.chain(this._scalePan, this._panner.pan); + this.pan = this._panner.pan; } else { /** * the dry/wet knob @@ -10090,12 +10325,26 @@ */ this._splitter = this.input = new Tone.Split(); /** - * The pan control. 0 = hard left, 1 = hard right. - * @type {NormalRange} + * The pan control. -1 = hard left, 1 = hard right. + * @type {AudioRange} * @signal */ - this.pan = this._crossFade.fade; + this.pan = new Tone.Signal(0, Tone.Type.AudioRange); + /** + * always sends 0 + * @type {Tone.Zero} + * @private + */ + this._zero = new Tone.Zero(); + /** + * The analog to gain conversion + * @type {Tone.AudioToGain} + * @private + */ + this._a2g = new Tone.AudioToGain(); //CONNECTIONS: + this._zero.connect(this._a2g); + this.pan.chain(this._a2g, this._crossFade.fade); //left channel is a, right channel is b this._splitter.connect(this._crossFade, 0, 0); this._splitter.connect(this._crossFade, 1, 1); @@ -10104,10 +10353,16 @@ this._crossFade.b.connect(this._merger, 0, 1); } //initial value - this.pan.value = this.defaultArg(initialPan, 0.5); + this.pan.value = this.defaultArg(initialPan, 0); this._readOnly('pan'); }; Tone.extend(Tone.Panner); + /** + * indicates if the panner is using the new StereoPannerNode internally + * @type {boolean} + * @private + */ + Tone.Panner.prototype._hasStereoPanner = Tone.prototype.isFunction(Tone.context.createStereoPanner); /** * Clean up. * @returns {Tone.Panner} this @@ -10118,18 +10373,20 @@ if (this._hasStereoPanner) { this._panner.disconnect(); this._panner = null; - this.pan.dispose(); this.pan = null; - this._scalePan.dispose(); - this._scalePan = null; } else { + this._zero.dispose(); + this._zero = null; this._crossFade.dispose(); this._crossFade = null; this._splitter.dispose(); this._splitter = null; this._merger.dispose(); this._merger = null; + this.pan.dispose(); this.pan = null; + this._a2g.dispose(); + this._a2g = null; } return this; }; @@ -10229,8 +10486,8 @@ * * @example * var interp = new Tone.CtrlInterpolate([ - * ["C3", "G4", "E5"], - * ["D4", "F#4", "E5"], + * [2, 4, 5], + * [9, 3, 2], * ]); * @param {Array} values The array of values to interpolate over * @param {Positive} index The initial interpolation index. @@ -10322,8 +10579,6 @@ Tone.CtrlInterpolate.prototype._toNumber = function (val) { if (this.isNumber(val)) { return val; - } else if (this.isNote(val)) { - return this.toFrequency(val); } else { //otherwise assume that it's Time... return this.toSeconds(val); @@ -11054,6 +11309,12 @@ var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress; Tone.Buffer.trigger('progress', completed / Tone.Buffer._totalDownloads); }; + /** + * A path which is prefixed before every url. + * @type {String} + * @static + */ + Tone.Buffer.baseUrl = ''; /** * Makes an xhr reqest for the selected url then decodes * the file as an audio buffer. Invokes @@ -11066,15 +11327,14 @@ */ Tone.Buffer.load = function (url, callback) { var request = new XMLHttpRequest(); - request.open('GET', url, true); + request.open('GET', Tone.Buffer.baseUrl + 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); + }, function () { + throw new Error('Tone.Buffer: could not decode audio data:' + url); }); }; //send the request @@ -11082,27 +11342,152 @@ return request; }; /** - * @deprecated us on([event]) instead + * Checks a url's extension to see if the current browser can play that file type. + * @param {String} url The url/extension to test + * @return {Boolean} If the file extension can be played + * @static + * @example + * Tone.Buffer.supportsType("wav"); //returns true + * Tone.Buffer.supportsType("path/to/file.wav"); //returns true + */ + Tone.Buffer.supportsType = function (url) { + var extension = url.split('.'); + extension = extension[extension.length - 1]; + var response = document.createElement('audio').canPlayType('audio/' + extension); + return response !== ''; + }; + return Tone.Buffer; + }); + Module(function (Tone) { + /** + * @class A data structure for holding multiple buffers. + * + * @param {Object|Array} urls An object literal or array + * of urls to load. + * @param {Function=} callback The callback to invoke when + * the buffers are loaded. + * @extends {Tone} + * @example + * //load a whole bank of piano samples + * var pianoSamples = new Tone.Buffers({ + * "C4" : "path/to/C4.mp3" + * "C#4" : "path/to/C#4.mp3" + * "D4" : "path/to/D4.mp3" + * "D#4" : "path/to/D#4.mp3" + * ... + * }, function(){ + * //play one of the samples when they all load + * player.buffer = pianoSamples.get("C4"); + * player.start(); + * }); + * */ - Object.defineProperty(Tone.Buffer, 'onload', { - set: function (cb) { - console.warn('Tone.Buffer.onload is deprecated, use Tone.Buffer.on(\'load\', callback)'); - Tone.Buffer.on('load', cb); + Tone.Buffers = function (urls, onload, baseUrl) { + /** + * All of the buffers + * @type {Object} + * @private + */ + this._buffers = {}; + /** + * A path which is prefixed before every url. + * @type {String} + */ + this.baseUrl = this.defaultArg(baseUrl, ''); + urls = this._flattenUrls(urls); + this._loadingCount = 0; + //add each one + for (var key in urls) { + this._loadingCount++; + this.add(key, urls[key], this._bufferLoaded.bind(this, onload)); } - }); - Object.defineProperty(Tone.Buffer, 'onprogress', { - set: function (cb) { - console.warn('Tone.Buffer.onprogress is deprecated, use Tone.Buffer.on(\'progress\', callback)'); - Tone.Buffer.on('progress', cb); + }; + Tone.extend(Tone.Buffers); + /** + * Get a buffer by name. If an array was loaded, + * then use the array index. + * @param {String|Number} name The key or index of the + * buffer. + * @return {Tone.Buffer} + */ + Tone.Buffers.prototype.get = function (name) { + if (this._buffers.hasOwnProperty(name)) { + return this._buffers[name]; + } else { + throw new Error('Tone.Buffers: no buffer named' + name); } - }); - Object.defineProperty(Tone.Buffer, 'onerror', { - set: function (cb) { - console.warn('Tone.Buffer.onerror is deprecated, use Tone.Buffer.on(\'error\', callback)'); - Tone.Buffer.on('error', cb); + }; + /** + * A buffer was loaded. decrement the counter. + * @param {Function} callback + * @private + */ + Tone.Buffers.prototype._bufferLoaded = function (callback) { + this._loadingCount--; + if (this._loadingCount === 0 && callback) { + callback(this); } - }); - return Tone.Buffer; + }; + /** + * Add a buffer by name and url to the Buffers + * @param {String} name A unique name to give + * the buffer + * @param {String|Tone.Buffer|Audiobuffer} url Either the url of the bufer, + * or a buffer which will be added + * with the given name. + * @param {Function=} callback The callback to invoke + * when the url is loaded. + */ + Tone.Buffers.prototype.add = function (name, url, callback) { + callback = this.defaultArg(callback, Tone.noOp); + if (url instanceof Tone.Buffer) { + this._buffers[name] = url; + callback(this); + } else if (url instanceof AudioBuffer) { + this._buffers[name] = new Tone.Buffer(url); + callback(this); + } else if (this.isString(url)) { + this._buffers[name] = new Tone.Buffer(this.baseUrl + url, callback); + } + return this; + }; + /** + * Flatten an object into a single depth object. + * thanks to https://gist.github.com/penguinboy/762197 + * @param {Object} ob + * @return {Object} + * @private + */ + Tone.Buffers.prototype._flattenUrls = function (ob) { + var toReturn = {}; + for (var i in ob) { + if (!ob.hasOwnProperty(i)) + continue; + if (this.isObject(ob[i])) { + var flatObject = this._flattenUrls(ob[i]); + for (var x in flatObject) { + if (!flatObject.hasOwnProperty(x)) + continue; + toReturn[i + '.' + x] = flatObject[x]; + } + } else { + toReturn[i] = ob[i]; + } + } + return toReturn; + }; + /** + * Clean up. + * @return {Tone.Buffers} this + */ + Tone.Buffers.prototype.dispose = function () { + for (var name in this._buffers) { + this._buffers[name].dispose(); + } + this._buffers = null; + return this; + }; + return Tone.Buffers; }); Module(function (Tone) { @@ -11203,4870 +11588,5241 @@ 'delayTime': 0 }; /** - * Clean up. - * @return {Tone.Delay} this + * Clean up. + * @return {Tone.Delay} this + */ + Tone.Delay.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._delayNode.disconnect(); + this._delayNode = null; + this._writable('delayTime'); + this.delayTime = null; + return this; + }; + return Tone.Delay; + }); + Module(function (Tone) { + + /** + * @class Tone.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 wet control. + * + * @constructor + * @extends {Tone} + * @param {NormalRange|Object} [wet] The starting wet value. + */ + 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 is how much of the effected + * will pass through to the output. 1 = 100% effected + * signal, 0 = 100% dry signal. + * @type {NormalRange} + * @signal + */ + this.wet = this._dryWet.fade; + /** + * connect the effectSend to the input of hte effect + * @type {GainNode} + * @private + */ + this.effectSend = this.context.createGain(); + /** + * connect the output of the effect to the effectReturn + * @type {GainNode} + * @private + */ + this.effectReturn = this.context.createGain(); + //connections + this.input.connect(this._dryWet.a); + this.input.connect(this.effectSend); + this.effectReturn.connect(this._dryWet.b); + this._dryWet.connect(this.output); + this._readOnly(['wet']); + }; + Tone.extend(Tone.Effect); + /** + * @static + * @type {Object} + */ + Tone.Effect.defaults = { 'wet': 1 }; + /** + * 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; + }; + /** + * Clean up. + * @returns {Tone.Effect} this */ - Tone.Delay.prototype.dispose = function () { - Tone.Param.prototype.dispose.call(this); - this._delayNode.disconnect(); - this._delayNode = null; - this._writable('delayTime'); - this.delayTime = null; + 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.Delay; + return Tone.Effect; }); Module(function (Tone) { /** - * @class A single master output which is connected to the - * AudioDestinationNode (aka your speakers). - * It provides useful conveniences such as the ability - * to set the volume and mute the entire application. - * It also gives you the ability to apply master effects to your application. - *

- * Like Tone.Transport, A single Tone.Master is created - * on initialization and you do not need to explicitly construct one. + * @class Tone.AutoFilter is a Tone.Filter with a Tone.LFO connected to the filter cutoff frequency. + * Setting the LFO rate and depth allows for control over the filter modulation rate + * and depth. * * @constructor - * @extends {Tone} - * @singleton + * @extends {Tone.Effect} + * @param {Time|Object} [frequency] The rate of the LFO. + * @param {Frequency=} baseFrequency The lower value of the LFOs oscillation + * @param {Frequency=} octaves The number of octaves above the baseFrequency * @example - * //the audio will go from the oscillator to the speakers - * oscillator.connect(Tone.Master); - * //a convenience for connecting to the master output is also provided: - * oscillator.toMaster(); - * //the above two examples are equivalent. + * //create an autofilter and start it's LFO + * var autoFilter = new Tone.AutoFilter("4n").toMaster().start(); + * //route an oscillator through the filter and start it + * var oscillator = new Tone.Oscillator().connect(autoFilter).start(); */ - Tone.Master = function () { - Tone.call(this); - /** - * the unmuted volume - * @type {number} - * @private - */ - this._unmutedVolume = 1; + Tone.AutoFilter = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'baseFrequency', + 'octaves' + ], Tone.AutoFilter.defaults); + Tone.Effect.call(this, options); /** - * if the master is muted - * @type {boolean} + * the lfo which drives the filter cutoff + * @type {Tone.LFO} * @private */ - this._muted = false; + this._lfo = new Tone.LFO({ + 'frequency': options.frequency, + 'amplitude': options.depth + }); /** - * The private volume node - * @type {Tone.Volume} - * @private + * The range of the filter modulating between the min and max frequency. + * 0 = no modulation. 1 = full modulation. + * @type {NormalRange} + * @signal */ - this._volume = this.output = new Tone.Volume(); + this.depth = this._lfo.amplitude; /** - * The volume of the master output. - * @type {Decibels} + * How fast the filter modulates between min and max. + * @type {Frequency} * @signal */ - this.volume = this._volume.volume; - this._readOnly('volume'); + this.frequency = this._lfo.frequency; + /** + * The filter node + * @type {Tone.Filter} + */ + this.filter = new Tone.Filter(options.filter); + /** + * The octaves placeholder + * @type {Positive} + * @private + */ + this._octaves = 0; //connections - this.input.chain(this.output, this.context.destination); + this.connectEffect(this.filter); + this._lfo.connect(this.filter.frequency); + this.type = options.type; + this._readOnly([ + 'frequency', + 'depth' + ]); + this.octaves = options.octaves; + this.baseFrequency = options.baseFrequency; }; - Tone.extend(Tone.Master); + //extend Effect + Tone.extend(Tone.AutoFilter, Tone.Effect); /** + * defaults + * @static * @type {Object} - * @const */ - Tone.Master.defaults = { - 'volume': 0, - 'mute': false + Tone.AutoFilter.defaults = { + 'frequency': 1, + 'type': 'sine', + 'depth': 1, + 'baseFrequency': 200, + 'octaves': 2.6, + 'filter': { + 'type': 'lowpass', + 'rolloff': -12, + 'Q': 1 + } }; /** - * Mute the output. - * @memberOf Tone.Master# - * @type {boolean} - * @name mute - * @example - * //mute the output - * Tone.Master.mute = true; + * Start the effect. + * @param {Time} [time=now] When the LFO will start. + * @returns {Tone.AutoFilter} this */ - Object.defineProperty(Tone.Master.prototype, 'mute', { - get: function () { - return this._muted; - }, - set: function (mute) { - if (!this._muted && mute) { - this._unmutedVolume = this.volume.value; - //maybe it should ramp here? - this.volume.value = -Infinity; - } else if (this._muted && !mute) { - this.volume.value = this._unmutedVolume; - } - this._muted = mute; - } - }); + Tone.AutoFilter.prototype.start = function (time) { + this._lfo.start(time); + return this; + }; /** - * Add a master effects chain. NOTE: this will disconnect any nodes which were previously - * chained in the master effects chain. - * @param {AudioNode|Tone...} args All arguments will be connected in a row - * and the Master will be routed through it. - * @return {Tone.Master} this - * @example - * //some overall compression to keep the levels in check - * var masterCompressor = new Tone.Compressor({ - * "threshold" : -6, - * "ratio" : 3, - * "attack" : 0.5, - * "release" : 0.1 - * }); - * //give a little boost to the lows - * var lowBump = new Tone.Filter(200, "lowshelf"); - * //route everything through the filter - * //and compressor before going to the speakers - * Tone.Master.chain(lowBump, masterCompressor); + * Stop the effect. + * @param {Time} [time=now] When the LFO will stop. + * @returns {Tone.AutoFilter} this */ - Tone.Master.prototype.chain = function () { - this.input.disconnect(); - this.input.chain.apply(this.input, arguments); - arguments[arguments.length - 1].connect(this.output); + Tone.AutoFilter.prototype.stop = function (time) { + this._lfo.stop(time); + return this; }; /** - * Clean up - * @return {Tone.Master} this + * Sync the filter to the transport. + * @param {Time} [delay=0] Delay time before starting the effect after the + * Transport has started. + * @returns {Tone.AutoFilter} this */ - Tone.Master.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable('volume'); - this._volume.dispose(); - this._volume = null; - this.volume = null; + Tone.AutoFilter.prototype.sync = function (delay) { + this._lfo.sync(delay); + return this; }; - /////////////////////////////////////////////////////////////////////////// - // AUGMENT TONE's PROTOTYPE - /////////////////////////////////////////////////////////////////////////// /** - * Connect 'this' to the master output. Shorthand for this.connect(Tone.Master) - * @returns {Tone} this - * @example - * //connect an oscillator to the master output - * var osc = new Tone.Oscillator().toMaster(); + * Unsync the filter from the transport. + * @returns {Tone.AutoFilter} this */ - Tone.prototype.toMaster = function () { - this.connect(Tone.Master); + Tone.AutoFilter.prototype.unsync = function () { + this._lfo.unsync(); return this; }; /** - * Also augment AudioNode's prototype to include toMaster - * as a convenience - * @returns {AudioNode} this + * Type of oscillator attached to the AutoFilter. + * Possible values: "sine", "square", "triangle", "sawtooth". + * @memberOf Tone.AutoFilter# + * @type {string} + * @name type + */ + Object.defineProperty(Tone.AutoFilter.prototype, 'type', { + get: function () { + return this._lfo.type; + }, + set: function (type) { + this._lfo.type = type; + } + }); + /** + * The minimum value of the filter's cutoff frequency. + * @memberOf Tone.AutoFilter# + * @type {Frequency} + * @name min + */ + Object.defineProperty(Tone.AutoFilter.prototype, 'baseFrequency', { + get: function () { + return this._lfo.min; + }, + set: function (freq) { + this._lfo.min = this.toFrequency(freq); + } + }); + /** + * The maximum value of the filter's cutoff frequency. + * @memberOf Tone.AutoFilter# + * @type {Positive} + * @name octaves + */ + Object.defineProperty(Tone.AutoFilter.prototype, 'octaves', { + get: function () { + return this._octaves; + }, + set: function (oct) { + this._octaves = oct; + this._lfo.max = this.baseFrequency * Math.pow(2, oct); + } + }); + /** + * Clean up. + * @returns {Tone.AutoFilter} this */ - AudioNode.prototype.toMaster = function () { - this.connect(Tone.Master); + 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; }; - var MasterConstructor = Tone.Master; - /** - * initialize the module and listen for new audio contexts - */ - Tone._initAudioContext(function () { - //a single master output - if (!Tone.prototype.isUndef(Tone.Master)) { - Tone.Master = new MasterConstructor(); - } else { - MasterConstructor.prototype.dispose.call(Tone.Master); - MasterConstructor.call(Tone.Master); - } - }); - return Tone.Master; + return Tone.AutoFilter; }); Module(function (Tone) { /** - * @class A timed note. Creating a note will register a callback - * which will be invoked on the channel at the time with - * whatever value was specified. + * @class Tone.AutoPanner is a Tone.Panner with an LFO connected to the pan amount. + * More on using autopanners [here](https://www.ableton.com/en/blog/autopan-chopper-effect-and-more-liveschool/). * * @constructor - * @param {number|string} channel the channel name of the note - * @param {Time} time the time when the note will occur - * @param {string|number|Object|Array} value the value of the note + * @extends {Tone.Effect} + * @param {Frequency|Object} [frequency] Rate of left-right oscillation. + * @example + * //create an autopanner and start it's LFO + * var autoPanner = new Tone.AutoPanner("4n").toMaster().start(); + * //route an oscillator through the panner and start it + * var oscillator = new Tone.Oscillator().connect(autoPanner).start(); */ - Tone.Note = function (channel, time, value) { + Tone.AutoPanner = function () { + var options = this.optionsObject(arguments, ['frequency'], Tone.AutoPanner.defaults); + Tone.Effect.call(this, options); /** - * the value of the note. This value is returned - * when the channel callback is invoked. - * - * @type {string|number|Object} + * the lfo which drives the panning + * @type {Tone.LFO} + * @private */ - this.value = value; + this._lfo = new Tone.LFO({ + 'frequency': options.frequency, + 'amplitude': options.depth, + 'min': 0, + 'max': 1 + }); /** - * the channel name or number - * - * @type {string|number} - * @private + * The amount of panning between left and right. + * 0 = always center. 1 = full range between left and right. + * @type {NormalRange} + * @signal */ - this._channel = channel; + this.depth = this._lfo.amplitude; /** - * an internal reference to the id of the timeline - * callback which is set. - * - * @type {number} + * the panner node which does the panning + * @type {Tone.Panner} * @private */ - this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time); + this._panner = new Tone.Panner(); + /** + * How fast the panner modulates between left and right. + * @type {Frequency} + * @signal + */ + this.frequency = this._lfo.frequency; + //connections + this.connectEffect(this._panner); + this._lfo.connect(this._panner.pan); + this.type = options.type; + this._readOnly([ + 'depth', + 'frequency' + ]); }; + //extend Effect + Tone.extend(Tone.AutoPanner, Tone.Effect); /** - * invoked by the timeline - * @private - * @param {number} time the time at which the note should play + * defaults + * @static + * @type {Object} */ - Tone.Note.prototype._trigger = function (time) { - //invoke the callback - channelCallbacks(this._channel, time, this.value); + Tone.AutoPanner.defaults = { + 'frequency': 1, + 'type': 'sine', + 'depth': 1 }; /** - * clean up - * @returns {Tone.Note} this + * Start the effect. + * @param {Time} [time=now] When the LFO will start. + * @returns {Tone.AutoPanner} this */ - Tone.Note.prototype.dispose = function () { - Tone.Transport.clearTimeline(this._timelineID); - this.value = null; + Tone.AutoPanner.prototype.start = function (time) { + this._lfo.start(time); return this; }; /** - * @private - * @static - * @type {Object} + * Stop the effect. + * @param {Time} [time=now] When the LFO will stop. + * @returns {Tone.AutoPanner} this */ - var NoteChannels = {}; + Tone.AutoPanner.prototype.stop = function (time) { + this._lfo.stop(time); + return this; + }; /** - * invoke all of the callbacks on a specific channel - * @private + * Sync the panner to the transport. + * @param {Time} [delay=0] Delay time before starting the effect after the + * Transport has started. + * @returns {Tone.AutoPanner} this */ - 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); - } - } - } - } + Tone.AutoPanner.prototype.sync = function (delay) { + this._lfo.sync(delay); + return this; + }; /** - * 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 + * Unsync the panner from the transport + * @returns {Tone.AutoPanner} this */ - Tone.Note.route = function (channel, callback) { - if (NoteChannels.hasOwnProperty(channel)) { - NoteChannels[channel].push(callback); - } else { - NoteChannels[channel] = [callback]; - } + Tone.AutoPanner.prototype.unsync = function () { + this._lfo.unsync(); + 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. + * Type of oscillator attached to the AutoFilter. + * Possible values: "sine", "square", "triangle", "sawtooth". + * @memberOf Tone.AutoFilter# + * @type {string} + * @name type */ - 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); - } + Object.defineProperty(Tone.AutoPanner.prototype, 'type', { + get: function () { + return this._lfo.type; + }, + set: function (type) { + this._lfo.type = type; } - }; + }); /** - * 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 if (typeof noteDescription === 'object') { - note = new Tone.Note(inst, noteDescription.time, noteDescription); - } else { - note = new Tone.Note(inst, noteDescription); - } - notes.push(note); - } - } else { - throw new TypeError('score parts must be Arrays'); - } - } - return notes; + * 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.Note; + return Tone.AutoPanner; }); Module(function (Tone) { /** - * @class Tone.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 wet control. + * @class Tone.AutoWah connects a Tone.Follower to a bandpass filter (Tone.Filter). + * The frequency of the filter is adjusted proportionally to the + * incoming signal's amplitude. Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna). * * @constructor - * @extends {Tone} - * @param {NormalRange|Object} [wet] The starting wet value. + * @extends {Tone.Effect} + * @param {Frequency|Object} [baseFrequency] The frequency the filter is set + * to at the low point of the wah + * @param {Positive} [octaves] The number of octaves above the baseFrequency + * the filter will sweep to when fully open + * @param {Decibels} [sensitivity] The decibel threshold sensitivity for + * the incoming signal. Normal range of -40 to 0. + * @example + * var autoWah = new Tone.AutoWah(50, 6, -30).toMaster(); + * //initialize the synth and connect to autowah + * var synth = new Synth.connect(autoWah); + * //Q value influences the effect of the wah - default is 2 + * autoWah.Q.value = 6; + * //more audible on higher notes + * synth.triggerAttackRelease("C4", "8n") */ - Tone.Effect = function () { - Tone.call(this); - //get all of the defaults - var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults); + Tone.AutoWah = function () { + var options = this.optionsObject(arguments, [ + 'baseFrequency', + 'octaves', + 'sensitivity' + ], Tone.AutoWah.defaults); + Tone.Effect.call(this, options); /** - * the drywet knob to control the amount of effect - * @type {Tone.CrossFade} + * The envelope follower. Set the attack/release + * timing to adjust how the envelope is followed. + * @type {Tone.Follower} * @private */ - this._dryWet = new Tone.CrossFade(options.wet); + this.follower = new Tone.Follower(options.follower); /** - * The wet control is how much of the effected - * will pass through to the output. 1 = 100% effected - * signal, 0 = 100% dry signal. - * @type {NormalRange} - * @signal + * scales the follower value to the frequency domain + * @type {Tone} + * @private */ - this.wet = this._dryWet.fade; + this._sweepRange = new Tone.ScaleExp(0, 1, 0.5); /** - * connect the effectSend to the input of hte effect + * @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.effectSend = this.context.createGain(); + 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 {Number} + * @signal + */ + this.gain = this._peaking.gain; /** - * connect the output of the effect to the effectReturn - * @type {GainNode} - * @private + * The quality of the filter. + * @type {Positive} + * @signal */ - 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']); + 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.Effect); + Tone.extend(Tone.AutoWah, Tone.Effect); /** * @static * @type {Object} */ - Tone.Effect.defaults = { 'wet': 1 }; + Tone.AutoWah.defaults = { + 'baseFrequency': 100, + 'octaves': 6, + 'sensitivity': 0, + 'Q': 2, + 'gain': 2, + 'follower': { + 'attack': 0.3, + 'release': 0.5 + } + }; /** - * chains the effect in between the effectSend and effectReturn - * @param {Tone} effect + * The number of octaves that the filter will sweep above the + * baseFrequency. + * @memberOf Tone.AutoWah# + * @type {Number} + * @name octaves + */ + Object.defineProperty(Tone.AutoWah.prototype, 'octaves', { + get: function () { + return this._octaves; + }, + set: function (octaves) { + this._octaves = octaves; + this._setSweepRange(); + } + }); + /** + * The base frequency from which the sweep will start from. + * @memberOf Tone.AutoWah# + * @type {Frequency} + * @name baseFrequency + */ + Object.defineProperty(Tone.AutoWah.prototype, 'baseFrequency', { + get: function () { + return this._baseFrequency; + }, + set: function (baseFreq) { + this._baseFrequency = baseFreq; + this._setSweepRange(); + } + }); + /** + * The sensitivity to control how responsive to the input signal the filter is. + * @memberOf Tone.AutoWah# + * @type {Decibels} + * @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 - * @returns {Tone.Effect} this */ - Tone.Effect.prototype.connectEffect = function (effect) { - this.effectSend.chain(effect, this.effectReturn); - return this; + 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.Effect} this + * Clean up. + * @returns {Tone.AutoWah} 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; + 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.Effect; + return Tone.AutoWah; }); Module(function (Tone) { /** - * @class Tone.AutoFilter is a Tone.Filter with a Tone.LFO connected to the filter cutoff frequency. - * Setting the LFO rate and depth allows for control over the filter modulation rate - * and depth. + * @class Tone.Bitcrusher downsamples the incoming signal to a different bitdepth. + * Lowering the bitdepth of the signal creates distortion. Read more about Bitcrushing + * on [Wikipedia](https://en.wikipedia.org/wiki/Bitcrusher). * * @constructor * @extends {Tone.Effect} - * @param {Time|Object} [frequency] The rate of the LFO. - * @param {Frequency=} baseFrequency The lower value of the LFOs oscillation - * @param {Frequency=} octaves The number of octaves above the baseFrequency + * @param {Number} bits The number of bits to downsample the signal. Nominal range + * of 1 to 8. * @example - * //create an autofilter and start it's LFO - * var autoFilter = new Tone.AutoFilter("4n").toMaster().start(); - * //route an oscillator through the filter and start it - * var oscillator = new Tone.Oscillator().connect(autoFilter).start(); + * //initialize crusher and route a synth through it + * var crusher = new Tone.BitCrusher(4).toMaster(); + * var synth = new Tone.MonoSynth().connect(crusher); */ - Tone.AutoFilter = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'baseFrequency', - 'octaves' - ], Tone.AutoFilter.defaults); + 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 lfo which drives the filter cutoff - * @type {Tone.LFO} + * Subtract the input signal and the modulus of the input signal + * @type {Tone.Subtract} * @private */ - this._lfo = new Tone.LFO({ - 'frequency': options.frequency, - 'amplitude': options.depth - }); - /** - * The range of the filter modulating between the min and max frequency. - * 0 = no modulation. 1 = full modulation. - * @type {NormalRange} - * @signal - */ - this.depth = this._lfo.amplitude; - /** - * How fast the filter modulates between min and max. - * @type {Frequency} - * @signal - */ - this.frequency = this._lfo.frequency; + this._subtract = new Tone.Subtract(); /** - * The filter node - * @type {Tone.Filter} + * The mod function + * @type {Tone.Modulo} + * @private */ - this.filter = new Tone.Filter(options.filter); + this._modulo = new Tone.Modulo(invStepSize); /** - * The octaves placeholder - * @type {Positive} + * keeps track of the bits + * @type {number} * @private */ - this._octaves = 0; - //connections - this.connectEffect(this.filter); - this._lfo.connect(this.filter.frequency); - this.type = options.type; - this._readOnly([ - 'frequency', - 'depth' - ]); - this.octaves = options.octaves; - this.baseFrequency = options.baseFrequency; + this._bits = options.bits; + //connect it up + this.effectSend.fan(this._subtract, this._modulo); + this._modulo.connect(this._subtract, 0, 1); + this._subtract.connect(this.effectReturn); }; - //extend Effect - Tone.extend(Tone.AutoFilter, Tone.Effect); + Tone.extend(Tone.BitCrusher, Tone.Effect); /** - * defaults + * the default values * @static * @type {Object} */ - Tone.AutoFilter.defaults = { - 'frequency': 1, - 'type': 'sine', - 'depth': 1, - 'baseFrequency': 200, - 'octaves': 2.6, - 'filter': { - 'type': 'lowpass', - 'rolloff': -12, - 'Q': 1 - } - }; + Tone.BitCrusher.defaults = { 'bits': 4 }; /** - * Start the effect. - * @param {Time} [time=now] When the LFO will start. - * @returns {Tone.AutoFilter} this + * The bit depth of the effect. Nominal range of 1-8. + * @memberOf Tone.BitCrusher# + * @type {number} + * @name bits */ - Tone.AutoFilter.prototype.start = function (time) { - this._lfo.start(time); - return this; - }; + 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; + } + }); /** - * Stop the effect. - * @param {Time} [time=now] When the LFO will stop. - * @returns {Tone.AutoFilter} this + * Clean up. + * @returns {Tone.BitCrusher} this */ - Tone.AutoFilter.prototype.stop = function (time) { - this._lfo.stop(time); + Tone.BitCrusher.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._subtract.dispose(); + this._subtract = null; + this._modulo.dispose(); + this._modulo = null; return this; }; + return Tone.BitCrusher; + }); + Module(function (Tone) { + /** - * Sync the filter to the transport. - * @param {Time} [delay=0] Delay time before starting the effect after the - * Transport has started. - * @returns {Tone.AutoFilter} this + * @class Tone.ChebyShev is a Chebyshev waveshaper, an effect which is good + * for making different types of distortion sounds. + * Note that odd orders sound very different from even ones, + * and order = 1 is no change. + * Read more at [music.columbia.edu](http://music.columbia.edu/cmc/musicandcomputers/chapter4/04_06.php). + * + * @extends {Tone.Effect} + * @constructor + * @param {Positive|Object} [order] The order of the chebyshev polynomial. Normal range between 1-100. + * @example + * //create a new cheby + * var cheby = new Tone.Chebyshev(50); + * //create a monosynth connected to our cheby + * synth = new Tone.MonoSynth().connect(cheby); */ - Tone.AutoFilter.prototype.sync = function (delay) { - this._lfo.sync(delay); - return this; + Tone.Chebyshev = function () { + var options = this.optionsObject(arguments, ['order'], Tone.Chebyshev.defaults); + Tone.Effect.call(this, options); + /** + * @type {WaveShaperNode} + * @private + */ + this._shaper = new Tone.WaveShaper(4096); + /** + * holds onto the order of the filter + * @type {number} + * @private + */ + this._order = options.order; + this.connectEffect(this._shaper); + this.order = options.order; + this.oversample = options.oversample; }; + Tone.extend(Tone.Chebyshev, Tone.Effect); /** - * Unsync the filter from the transport. - * @returns {Tone.AutoFilter} this + * @static + * @const + * @type {Object} */ - Tone.AutoFilter.prototype.unsync = function () { - this._lfo.unsync(); - return this; + Tone.Chebyshev.defaults = { + 'order': 1, + 'oversample': 'none' }; /** - * Type of oscillator attached to the AutoFilter. - * Possible values: "sine", "square", "triangle", "sawtooth". - * @memberOf Tone.AutoFilter# - * @type {string} - * @name type + * 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 */ - Object.defineProperty(Tone.AutoFilter.prototype, 'type', { - get: function () { - return this._lfo.type; - }, - set: function (type) { - this._lfo.type = type; + 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 minimum value of the filter's cutoff frequency. - * @memberOf Tone.AutoFilter# - * @type {Frequency} - * @name min + * The order of the Chebyshev polynomial which creates + * the equation which is applied to the incoming + * signal through a Tone.WaveShaper. The equations + * are in the form:
+ * order 2: 2x^2 + 1
+ * order 3: 4x^3 + 3x
+ * @memberOf Tone.Chebyshev# + * @type {Positive} + * @name order */ - Object.defineProperty(Tone.AutoFilter.prototype, 'baseFrequency', { + Object.defineProperty(Tone.Chebyshev.prototype, 'order', { get: function () { - return this._lfo.min; + return this._order; }, - set: function (freq) { - this._lfo.min = this.toFrequency(freq); + set: function (order) { + this._order = order; + var curve = new Array(4096); + var len = curve.length; + for (var i = 0; i < len; ++i) { + var x = i * 2 / len - 1; + if (x === 0) { + //should output 0 when input is 0 + curve[i] = 0; + } else { + curve[i] = this._getCoefficient(x, order, {}); + } + } + this._shaper.curve = curve; } }); /** - * The maximum value of the filter's cutoff frequency. - * @memberOf Tone.AutoFilter# - * @type {Positive} - * @name octaves + * The oversampling of the effect. Can either be "none", "2x" or "4x". + * @memberOf Tone.Chebyshev# + * @type {string} + * @name oversample */ - Object.defineProperty(Tone.AutoFilter.prototype, 'octaves', { + Object.defineProperty(Tone.Chebyshev.prototype, 'oversample', { get: function () { - return this._octaves; + return this._shaper.oversample; }, - set: function (oct) { - this._octaves = oct; - this._lfo.max = this.baseFrequency * Math.pow(2, oct); + set: function (oversampling) { + this._shaper.oversample = oversampling; } }); /** * Clean up. - * @returns {Tone.AutoFilter} this + * @returns {Tone.Chebyshev} this */ - Tone.AutoFilter.prototype.dispose = function () { + Tone.Chebyshev.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; + this._shaper.dispose(); + this._shaper = null; return this; }; - return Tone.AutoFilter; + return Tone.Chebyshev; }); Module(function (Tone) { /** - * @class Tone.AutoPanner is a Tone.Panner with an LFO connected to the pan amount. - * More on using autopanners [here](https://www.ableton.com/en/blog/autopan-chopper-effect-and-more-liveschool/). + * @class Base class for Stereo effects. Provides effectSendL/R and effectReturnL/R. + * + * @constructor + * @extends {Tone.Effect} + */ + Tone.StereoEffect = function () { + Tone.call(this); + //get the defaults + var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults); + /** + * the drywet knob to control the amount of effect + * @type {Tone.CrossFade} + * @private + */ + this._dryWet = new Tone.CrossFade(options.wet); + /** + * The wet control, i.e. how much of the effected + * will pass through to the output. + * @type {NormalRange} + * @signal + */ + this.wet = this._dryWet.fade; + /** + * then split it + * @type {Tone.Split} + * @private + */ + this._split = new Tone.Split(); + /** + * the effects send LEFT + * @type {GainNode} + * @private + */ + this.effectSendL = this._split.left; + /** + * the effects send RIGHT + * @type {GainNode} + * @private + */ + this.effectSendR = this._split.right; + /** + * the stereo effect merger + * @type {Tone.Merge} + * @private + */ + this._merge = new Tone.Merge(); + /** + * the effect return LEFT + * @type {GainNode} + * @private + */ + this.effectReturnL = this._merge.left; + /** + * the effect return RIGHT + * @type {GainNode} + * @private + */ + this.effectReturnR = this._merge.right; + //connections + this.input.connect(this._split); + //dry wet connections + this.input.connect(this._dryWet, 0, 0); + this._merge.connect(this._dryWet, 0, 1); + this._dryWet.connect(this.output); + this._readOnly(['wet']); + }; + Tone.extend(Tone.StereoEffect, Tone.Effect); + /** + * Clean up. + * @returns {Tone.StereoEffect} this + */ + Tone.StereoEffect.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._dryWet.dispose(); + this._dryWet = null; + this._split.dispose(); + this._split = null; + this._merge.dispose(); + this._merge = null; + this.effectSendL = null; + this.effectSendR = null; + this.effectReturnL = null; + this.effectReturnR = null; + this._writable(['wet']); + this.wet = null; + return this; + }; + return Tone.StereoEffect; + }); + Module(function (Tone) { + + /** + * @class Tone.FeedbackEffect provides a loop between an + * audio source and its own output. This is a base-class + * for feedback effects. * * @constructor * @extends {Tone.Effect} - * @param {Frequency|Object} [frequency] Rate of left-right oscillation. - * @example - * //create an autopanner and start it's LFO - * var autoPanner = new Tone.AutoPanner("4n").toMaster().start(); - * //route an oscillator through the panner and start it - * var oscillator = new Tone.Oscillator().connect(autoPanner).start(); + * @param {NormalRange|Object} [feedback] The initial feedback value. */ - Tone.AutoPanner = function () { - var options = this.optionsObject(arguments, ['frequency'], Tone.AutoPanner.defaults); + Tone.FeedbackEffect = function () { + var options = this.optionsObject(arguments, ['feedback']); + options = this.defaultArg(options, Tone.FeedbackEffect.defaults); Tone.Effect.call(this, options); /** - * the lfo which drives the panning - * @type {Tone.LFO} - * @private - */ - this._lfo = new Tone.LFO({ - 'frequency': options.frequency, - 'amplitude': options.depth, - 'min': 0, - 'max': 1 - }); - /** - * The amount of panning between left and right. - * 0 = always center. 1 = full range between left and right. - * @type {NormalRange} - * @signal + * The amount of signal which is fed back into the effect input. + * @type {NormalRange} + * @signal */ - this.depth = this._lfo.amplitude; + this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); /** - * 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 between left and right. - * @type {Frequency} - * @signal - */ - this.frequency = this._lfo.frequency; - //connections - this.connectEffect(this._panner); - this._lfo.connect(this._panner.pan); - this.type = options.type; - this._readOnly([ - 'depth', - 'frequency' - ]); + this._feedbackGain = this.context.createGain(); + //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', - 'depth': 1 - }; - /** - * Start the effect. - * @param {Time} [time=now] When the LFO will start. - * @returns {Tone.AutoPanner} this - */ - Tone.AutoPanner.prototype.start = function (time) { - this._lfo.start(time); - return this; - }; - /** - * Stop the effect. - * @param {Time} [time=now] When the LFO will stop. - * @returns {Tone.AutoPanner} this - */ - Tone.AutoPanner.prototype.stop = function (time) { - this._lfo.stop(time); - return this; - }; + Tone.FeedbackEffect.defaults = { 'feedback': 0.125 }; /** - * Sync the panner to the transport. - * @param {Time} [delay=0] Delay time before starting the effect after the - * Transport has started. - * @returns {Tone.AutoPanner} this + * Clean up. + * @returns {Tone.FeedbackEffect} this */ - Tone.AutoPanner.prototype.sync = function (delay) { - this._lfo.sync(delay); + Tone.FeedbackEffect.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._writable(['feedback']); + this.feedback.dispose(); + this.feedback = null; + this._feedbackGain.disconnect(); + this._feedbackGain = null; return this; }; + return Tone.FeedbackEffect; + }); + Module(function (Tone) { + /** - * Unsync the panner from the transport - * @returns {Tone.AutoPanner} this + * @class Just like a stereo feedback effect, but the feedback is routed from left to right + * and right to left instead of on the same channel. + * + * @constructor + * @extends {Tone.FeedbackEffect} */ - Tone.AutoPanner.prototype.unsync = function () { - this._lfo.unsync(); - return this; + Tone.StereoXFeedbackEffect = function () { + var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); + Tone.StereoEffect.call(this, options); + /** + * The amount of feedback from the output + * back into the input of the effect (routed + * across left and right channels). + * @type {NormalRange} + * @signal + */ + this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); + /** + * the left side feeback + * @type {GainNode} + * @private + */ + this._feedbackLR = this.context.createGain(); + /** + * the right side feeback + * @type {GainNode} + * @private + */ + this._feedbackRL = this.context.createGain(); + //connect it up + this.effectReturnL.chain(this._feedbackLR, this.effectSendR); + this.effectReturnR.chain(this._feedbackRL, this.effectSendL); + this.feedback.fan(this._feedbackLR.gain, this._feedbackRL.gain); + this._readOnly(['feedback']); }; - /** - * Type of oscillator attached to the AutoFilter. - * Possible values: "sine", "square", "triangle", "sawtooth". - * @memberOf Tone.AutoFilter# - * @type {string} - * @name type - */ - Object.defineProperty(Tone.AutoPanner.prototype, 'type', { - get: function () { - return this._lfo.type; - }, - set: function (type) { - this._lfo.type = type; - } - }); + Tone.extend(Tone.StereoXFeedbackEffect, Tone.FeedbackEffect); /** * clean up - * @returns {Tone.AutoPanner} this + * @returns {Tone.StereoXFeedbackEffect} 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; + 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.AutoPanner; + return Tone.StereoXFeedbackEffect; }); Module(function (Tone) { /** - * @class Tone.AutoWah connects a Tone.Follower to a bandpass filter (Tone.Filter). - * The frequency of the filter is adjusted proportionally to the - * incoming signal's amplitude. Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna). + * @class Tone.Chorus is a stereo chorus effect with feedback composed of + * a left and right delay with a Tone.LFO applied to the delayTime of each channel. + * Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna/blob/master/tuna.js). + * Read more on the chorus effect on [SoundOnSound](http://www.soundonsound.com/sos/jun04/articles/synthsecrets.htm). * - * @constructor - * @extends {Tone.Effect} - * @param {Frequency|Object} [baseFrequency] The frequency the filter is set - * to at the low point of the wah - * @param {Positive} [octaves] The number of octaves above the baseFrequency - * the filter will sweep to when fully open - * @param {Decibels} [sensitivity] The decibel threshold sensitivity for - * the incoming signal. Normal range of -40 to 0. - * @example - * var autoWah = new Tone.AutoWah(50, 6, -30).toMaster(); - * //initialize the synth and connect to autowah - * var synth = new SimpleSynth.connect(autoWah); - * //Q value influences the effect of the wah - default is 2 - * autoWah.Q.value = 6; - * //more audible on higher notes - * synth.triggerAttackRelease("C4", "8n") + * @constructor + * @extends {Tone.StereoXFeedbackEffect} + * @param {Frequency|Object} [frequency] The frequency of the LFO. + * @param {Milliseconds} [delayTime] The delay of the chorus effect in ms. + * @param {NormalRange} [depth] The depth of the chorus. + * @example + * var chorus = new Tone.Chorus(4, 2.5, 0.5); + * var synth = new Tone.PolySynth(4, Tone.MonoSynth).connect(chorus); + * synth.triggerAttackRelease(["C3","E3","G3"], "8n"); */ - Tone.AutoWah = function () { + Tone.Chorus = function () { var options = this.optionsObject(arguments, [ - 'baseFrequency', - 'octaves', - 'sensitivity' - ], Tone.AutoWah.defaults); - Tone.Effect.call(this, options); - /** - * The envelope follower. Set the attack/release - * timing to adjust how the envelope is followed. - * @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); + '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 sensitivity - * @type {GainNode} + * the lfo which controls the delayTime + * @type {Tone.LFO} * @private */ - this._inputBoost = this.context.createGain(); + this._lfoL = new Tone.LFO({ + 'frequency': options.frequency, + 'min': 0, + 'max': 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({ + 'frequency': options.frequency, + 'min': 0, + 'max': 1, + '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 {Number} - * @signal + * delay for right + * @type {DelayNode} + * @private */ - this.gain = this._peaking.gain; + this._delayNodeR = this.context.createDelay(); /** - * The quality of the filter. - * @type {Positive} + * The frequency of the LFO which modulates the delayTime. + * @type {Frequency} * @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' - ]); + this.frequency = this._lfoL.frequency; + //connections + this.effectSendL.chain(this._delayNodeL, this.effectReturnL); + this.effectSendR.chain(this._delayNodeR, this.effectReturnR); + //and pass through to make the detune apparent + 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']); + this.spread = options.spread; }; - 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', + 'spread': 180 }; /** - * The number of octaves that the filter will sweep above the - * baseFrequency. - * @memberOf Tone.AutoWah# - * @type {Number} - * @name octaves + * The depth of the effect. A depth of 1 makes the delayTime + * modulate between 0 and 2*delayTime (centered around the delayTime). + * @memberOf Tone.Chorus# + * @type {NormalRange} + * @name depth */ - Object.defineProperty(Tone.AutoWah.prototype, 'octaves', { + Object.defineProperty(Tone.Chorus.prototype, 'depth', { get: function () { - return this._octaves; + return this._depth; }, - set: function (octaves) { - this._octaves = octaves; - this._setSweepRange(); + 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 {Frequency} - * @name baseFrequency + * The delayTime in milliseconds of the chorus. A larger delayTime + * will give a more pronounced effect. Nominal range a delayTime + * is between 2 and 20ms. + * @memberOf Tone.Chorus# + * @type {Milliseconds} + * @name delayTime */ - Object.defineProperty(Tone.AutoWah.prototype, 'baseFrequency', { + Object.defineProperty(Tone.Chorus.prototype, 'delayTime', { get: function () { - return this._baseFrequency; + return this._delayTime * 1000; }, - set: function (baseFreq) { - this._baseFrequency = baseFreq; - this._setSweepRange(); + set: function (delayTime) { + this._delayTime = delayTime / 1000; + this.depth = this._depth; } }); /** - * The sensitivity to control how responsive to the input signal the filter is. - * @memberOf Tone.AutoWah# - * @type {Decibels} - * @name sensitivity + * The oscillator type of the LFO. + * @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); + return this._lfoL.type; }, - set: function (sensitivy) { - this._inputBoost.gain.value = 1 / this.dbToGain(sensitivy); + set: function (type) { + this._lfoL.type = type; + this._lfoR.type = type; } }); - /** - * sets the sweep range of the scaler - * @private + /** + * Amount of stereo spread. When set to 0, both LFO's will be panned centrally. + * When set to 180, LFO's will be panned hard left and right respectively. + * @memberOf Tone.Chorus# + * @type {Degrees} + * @name spread */ - 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); - }; + Object.defineProperty(Tone.Chorus.prototype, 'spread', { + get: function () { + return this._lfoR.phase - this._lfoL.phase; //180 + }, + set: function (spread) { + this._lfoL.phase = 90 - spread / 2; + this._lfoR.phase = spread / 2 + 90; + } + }); /** - * Clean up. - * @returns {Tone.AutoWah} this + * Clean up. + * @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._writable([ - 'gain', - 'Q' - ]); - 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; }); Module(function (Tone) { /** - * @class Tone.Bitcrusher downsamples the incoming signal to a different bitdepth. - * Lowering the bitdepth of the signal creates distortion. Read more about Bitcrushing - * on [Wikipedia](https://en.wikipedia.org/wiki/Bitcrusher). - * + * @class Tone.Convolver is a wrapper around the Native Web Audio + * [ConvolverNode](http://webaudio.github.io/web-audio-api/#the-convolvernode-interface). + * Convolution is useful for reverb and filter emulation. Read more about convolution reverb on + * [Wikipedia](https://en.wikipedia.org/wiki/Convolution_reverb). + * * @constructor * @extends {Tone.Effect} - * @param {Number} bits The number of bits to downsample the signal. Nominal range - * of 1 to 8. + * @param {string|Tone.Buffer|Object} [url] The URL of the impulse response or the Tone.Buffer + * contianing the impulse response. * @example - * //initialize crusher and route a synth through it - * var crusher = new Tone.BitCrusher(4).toMaster(); - * var synth = new Tone.MonoSynth().connect(crusher); + * //initializing the convolver with an impulse response + * var convolver = new Tone.Convolver("./path/to/ir.wav"); + * convolver.toMaster(); + * //after the buffer has loaded + * Tone.Buffer.onload = function(){ + * //testing out convolution with a noise burst + * var burst = new Tone.NoiseSynth().connect(convolver); + * burst.triggerAttackRelease("16n"); + * }; */ - Tone.BitCrusher = function () { - var options = this.optionsObject(arguments, ['bits'], Tone.BitCrusher.defaults); + Tone.Convolver = function () { + 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} - * @private - */ - this._bits = options.bits; - //connect it up - this.effectSend.fan(this._subtract, this._modulo); - this._modulo.connect(this._subtract, 0, 1); - this._subtract.connect(this.effectReturn); + * the convolution buffer + * @type {Tone.Buffer} + * @private + */ + this._buffer = new Tone.Buffer(options.url, function (buffer) { + this.buffer = buffer; + options.onload(); + }.bind(this)); + this.connectEffect(this._convolver); }; - Tone.extend(Tone.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': Tone.noOp + }; /** - * The bit depth of the effect. Nominal range of 1-8. - * @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(); } }); + /** + * 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; + }; /** * Clean up. - * @returns {Tone.BitCrusher} this + * @returns {Tone.Convolver} this */ - Tone.BitCrusher.prototype.dispose = function () { + 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; }); Module(function (Tone) { /** - * @class Tone.ChebyShev is a Chebyshev waveshaper, an effect which is good - * for making different types of distortion sounds. - * Note that odd orders sound very different from even ones, - * and order = 1 is no change. - * Read more at [music.columbia.edu](http://music.columbia.edu/cmc/musicandcomputers/chapter4/04_06.php). + * @class Tone.Distortion is a simple distortion effect using Tone.WaveShaper. + * Algorithm from [a stackoverflow answer](http://stackoverflow.com/a/22313408). * * @extends {Tone.Effect} * @constructor - * @param {Positive|Object} [order] The order of the chebyshev polynomial. Normal range between 1-100. + * @param {Number|Object} [distortion] The amount of distortion (nominal range of 0-1) * @example - * //create a new cheby - * var cheby = new Tone.Chebyshev(50); - * //create a monosynth connected to our cheby - * synth = new Tone.MonoSynth().connect(cheby); + * var dist = new Tone.Distortion(0.8).toMaster(); + * var fm = new Tone.SimpleFM().connect(dist); + * //this sounds good on bass notes + * fm.triggerAttackRelease("A1", "8n"); */ - Tone.Chebyshev = function () { - var options = this.optionsObject(arguments, ['order'], Tone.Chebyshev.defaults); + Tone.Distortion = function () { + var options = this.optionsObject(arguments, ['distortion'], Tone.Distortion.defaults); Tone.Effect.call(this, options); /** - * @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 which creates - * the equation which is applied to the incoming - * signal through a Tone.WaveShaper. The equations - * are in the form:
- * order 2: 2x^2 + 1
- * order 3: 4x^3 + 3x
- * @memberOf Tone.Chebyshev# - * @type {Positive} - * @name order + * The amount of distortion. + * @memberOf Tone.Distortion# + * @type {NormalRange} + * @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', { - get: function () { - return this._shaper.oversample; - }, - set: function (oversampling) { - this._shaper.oversample = oversampling; - } - }); - /** - * Clean up. - * @returns {Tone.Chebyshev} this - */ - Tone.Chebyshev.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; - return this; - }; - return Tone.Chebyshev; - }); - Module(function (Tone) { - - /** - * @class Base class for Stereo effects. Provides effectSendL/R and effectReturnL/R. - * - * @constructor - * @extends {Tone.Effect} - */ - Tone.StereoEffect = function () { - Tone.call(this); - //get the defaults - var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults); - /** - * the drywet knob to control the amount of effect - * @type {Tone.CrossFade} - * @private - */ - this._dryWet = new Tone.CrossFade(options.wet); - /** - * The wet control, i.e. how much of the effected - * will pass through to the output. - * @type {NormalRange} - * @signal - */ - this.wet = this._dryWet.fade; - /** - * then split it - * @type {Tone.Split} - * @private - */ - this._split = new Tone.Split(); - /** - * the effects send LEFT - * @type {GainNode} - * @private - */ - this.effectSendL = this._split.left; - /** - * the effects send RIGHT - * @type {GainNode} - * @private - */ - this.effectSendR = this._split.right; - /** - * the stereo effect merger - * @type {Tone.Merge} - * @private - */ - this._merge = new Tone.Merge(); - /** - * the effect return LEFT - * @type {GainNode} - * @private - */ - this.effectReturnL = this._merge.left; - /** - * the effect return RIGHT - * @type {GainNode} - * @private - */ - this.effectReturnR = this._merge.right; - //connections - this.input.connect(this._split); - //dry wet connections - this.input.connect(this._dryWet, 0, 0); - this._merge.connect(this._dryWet, 0, 1); - this._dryWet.connect(this.output); - this._readOnly(['wet']); - }; - Tone.extend(Tone.StereoEffect, Tone.Effect); + Object.defineProperty(Tone.Distortion.prototype, 'oversample', { + get: function () { + return this._shaper.oversample; + }, + set: function (oversampling) { + this._shaper.oversample = oversampling; + } + }); /** * Clean up. - * @returns {Tone.StereoEffect} this + * @returns {Tone.Distortion} this */ - Tone.StereoEffect.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._dryWet.dispose(); - this._dryWet = null; - this._split.dispose(); - this._split = null; - this._merge.dispose(); - this._merge = null; - this.effectSendL = null; - this.effectSendR = null; - this.effectReturnL = null; - this.effectReturnR = null; - this._writable(['wet']); - this.wet = null; + Tone.Distortion.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; return this; }; - return Tone.StereoEffect; + return Tone.Distortion; }); Module(function (Tone) { /** - * @class Tone.FeedbackEffect provides a loop between an - * audio source and its own output. This is a base-class - * for feedback effects. + * @class Tone.FeedbackDelay is a DelayNode in which part of output + * signal is fed back into the delay. * * @constructor - * @extends {Tone.Effect} - * @param {NormalRange|Object} [feedback] The initial feedback value. + * @extends {Tone.FeedbackEffect} + * @param {Time|Object} [delayTime] The delay applied to the incoming signal. + * @param {NormalRange=} feedback The amount of the effected signal which + * is fed back through the delay. + * @example + * var feedbackDelay = new Tone.FeedbackDelay("8n", 0.5).toMaster(); + * var tom = new Tone.DrumSynth({ + * "octaves" : 4, + * "pitchDecay" : 0.1 + * }).connect(feedbackDelay); + * tom.triggerAttackRelease("A2","32n"); */ - Tone.FeedbackEffect = function () { - var options = this.optionsObject(arguments, ['feedback']); - options = this.defaultArg(options, Tone.FeedbackEffect.defaults); - Tone.Effect.call(this, options); + Tone.FeedbackDelay = function () { + var options = this.optionsObject(arguments, [ + 'delayTime', + 'feedback' + ], Tone.FeedbackDelay.defaults); + Tone.FeedbackEffect.call(this, options); /** - * The amount of signal which is fed back into the effect input. - * @type {NormalRange} + * The delayTime of the DelayNode. + * @type {Time} * @signal */ - this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); + this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); /** - * the gain which controls the feedback - * @type {GainNode} + * the delay node + * @type {DelayNode} * @private */ - this._feedbackGain = this.context.createGain(); - //the feedback loop - this.effectReturn.chain(this._feedbackGain, this.effectSend); - this.feedback.connect(this._feedbackGain.gain); - this._readOnly(['feedback']); + 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.FeedbackEffect, Tone.Effect); + Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); /** + * The default values. + * @const * @static * @type {Object} */ - Tone.FeedbackEffect.defaults = { 'feedback': 0.125 }; + Tone.FeedbackDelay.defaults = { 'delayTime': 0.25 }; /** - * Clean up. - * @returns {Tone.FeedbackEffect} this + * clean up + * @returns {Tone.FeedbackDelay} this */ - Tone.FeedbackEffect.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._writable(['feedback']); - this.feedback.dispose(); - this.feedback = null; - this._feedbackGain.disconnect(); - this._feedbackGain = null; + 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.FeedbackEffect; + return Tone.FeedbackDelay; }); Module(function (Tone) { /** - * @class Just like a stereo feedback effect, but the feedback is routed from left to right - * and right to left instead of on the same channel. - * - * @constructor - * @extends {Tone.FeedbackEffect} + * an array of comb filter delay values from Freeverb implementation + * @static + * @private + * @type {Array} */ - Tone.StereoXFeedbackEffect = function () { - var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); - Tone.StereoEffect.call(this, options); - /** - * The amount of feedback from the output - * back into the input of the effect (routed - * across left and right channels). - * @type {NormalRange} - * @signal - */ - this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); - /** - * the left side feeback - * @type {GainNode} - * @private - */ - this._feedbackLR = this.context.createGain(); - /** - * the right side feeback - * @type {GainNode} - * @private - */ - this._feedbackRL = this.context.createGain(); - //connect it up - this.effectReturnL.chain(this._feedbackLR, this.effectSendR); - this.effectReturnR.chain(this._feedbackRL, this.effectSendL); - this.feedback.fan(this._feedbackLR.gain, this._feedbackRL.gain); - this._readOnly(['feedback']); - }; - Tone.extend(Tone.StereoXFeedbackEffect, Tone.FeedbackEffect); + var combFilterTunings = [ + 1557 / 44100, + 1617 / 44100, + 1491 / 44100, + 1422 / 44100, + 1277 / 44100, + 1356 / 44100, + 1188 / 44100, + 1116 / 44100 + ]; /** - * clean up - * @returns {Tone.StereoXFeedbackEffect} this + * an array of allpass filter frequency values from Freeverb implementation + * @private + * @static + * @type {Array} */ - Tone.StereoXFeedbackEffect.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - this._writable(['feedback']); - this.feedback.dispose(); - this.feedback = null; - this._feedbackLR.disconnect(); - this._feedbackLR = null; - this._feedbackRL.disconnect(); - this._feedbackRL = null; - return this; - }; - return Tone.StereoXFeedbackEffect; - }); - Module(function (Tone) { - + var allpassFilterFrequencies = [ + 225, + 556, + 441, + 341 + ]; /** - * @class Tone.Chorus is a stereo chorus effect with feedback composed of - * a left and right delay with a Tone.LFO applied to the delayTime of each channel. - * Inspiration from [Tuna.js](https://github.com/Dinahmoe/tuna/blob/master/tuna.js). - * Read more on the chorus effect on [SoundOnSound](http://www.soundonsound.com/sos/jun04/articles/synthsecrets.htm). + * @class Tone.Freeverb is a reverb based on [Freeverb](https://ccrma.stanford.edu/~jos/pasp/Freeverb.html). + * Read more on reverb on [SoundOnSound](http://www.soundonsound.com/sos/may00/articles/reverb.htm). * - * @constructor - * @extends {Tone.StereoXFeedbackEffect} - * @param {Frequency|Object} [frequency] The frequency of the LFO. - * @param {Milliseconds} [delayTime] The delay of the chorus effect in ms. - * @param {NormalRange} [depth] The depth of the chorus. - * @example - * var chorus = new Tone.Chorus(4, 2.5, 0.5); - * var synth = new Tone.PolySynth(4, Tone.MonoSynth).connect(chorus); - * synth.triggerAttackRelease(["C3","E3","G3"], "8n"); + * @extends {Tone.Effect} + * @constructor + * @param {NormalRange|Object} [roomSize] Correlated to the decay time. + * @param {Frequency} [dampening] The cutoff frequency of a lowpass filter as part + * of the reverb. + * @example + * var freeverb = new Tone.Freeverb().toMaster(); + * freeverb.dampening.value = 1000; + * //routing synth through the reverb + * var synth = new Tone.AMSynth().connect(freeverb); */ - Tone.Chorus = function () { + Tone.Freeverb = function () { var options = this.optionsObject(arguments, [ - 'frequency', - 'delayTime', - 'depth' - ], Tone.Chorus.defaults); - Tone.StereoXFeedbackEffect.call(this, options); - /** - * the depth of the chorus - * @type {number} - * @private - */ - this._depth = options.depth; - /** - * the delayTime - * @type {number} - * @private - */ - this._delayTime = options.delayTime / 1000; + 'roomSize', + 'dampening' + ], Tone.Freeverb.defaults); + Tone.StereoEffect.call(this, options); /** - * the lfo which controls the delayTime - * @type {Tone.LFO} - * @private + * The roomSize value between. A larger roomSize + * will result in a longer decay. + * @type {NormalRange} + * @signal */ - this._lfoL = new Tone.LFO({ - 'frequency': options.frequency, - 'min': 0, - 'max': 1 - }); + this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); /** - * another LFO for the right side with a 180 degree phase diff - * @type {Tone.LFO} - * @private + * The amount of dampening of the reverberant signal. + * @type {Frequency} + * @signal */ - this._lfoR = new Tone.LFO({ - 'frequency': options.frequency, - 'min': 0, - 'max': 1, - 'phase': 180 - }); + this.dampening = new Tone.Signal(options.dampening, Tone.Type.Frequency); /** - * delay for left - * @type {DelayNode} + * the comb filters + * @type {Array} * @private */ - this._delayNodeL = this.context.createDelay(); + this._combFilters = []; /** - * delay for right - * @type {DelayNode} + * the allpass filters on the left + * @type {Array} * @private */ - this._delayNodeR = this.context.createDelay(); + this._allpassFiltersL = []; /** - * The frequency of the LFO which modulates the delayTime. - * @type {Frequency} - * @signal + * the allpass filters on the right + * @type {Array} + * @private */ - this.frequency = this._lfoL.frequency; - //connections - this.effectSendL.chain(this._delayNodeL, this.effectReturnL); - this.effectSendR.chain(this._delayNodeR, this.effectReturnR); - //and pass through to make the detune apparent - 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']); - this.spread = options.spread; + 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.Chorus, Tone.StereoXFeedbackEffect); + Tone.extend(Tone.Freeverb, Tone.StereoEffect); /** * @static * @type {Object} */ - Tone.Chorus.defaults = { - 'frequency': 1.5, - 'delayTime': 3.5, - 'depth': 0.7, - 'feedback': 0.1, - 'type': 'sine', - 'spread': 180 + Tone.Freeverb.defaults = { + 'roomSize': 0.7, + 'dampening': 3000 }; /** - * The depth of the effect. A depth of 1 makes the delayTime - * modulate between 0 and 2*delayTime (centered around the delayTime). - * @memberOf Tone.Chorus# - * @type {NormalRange} - * @name depth + * Clean up. + * @returns {Tone.Freeverb} 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 = 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; + Tone.Freeverb.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); + for (var al = 0; al < this._allpassFiltersL.length; al++) { + this._allpassFiltersL[al].disconnect(); + this._allpassFiltersL[al] = null; } - }); + this._allpassFiltersL = null; + for (var ar = 0; ar < this._allpassFiltersR.length; ar++) { + this._allpassFiltersR[ar].disconnect(); + this._allpassFiltersR[ar] = null; + } + this._allpassFiltersR = null; + for (var cf = 0; cf < this._combFilters.length; cf++) { + this._combFilters[cf].dispose(); + this._combFilters[cf] = null; + } + this._combFilters = null; + this._writable([ + 'roomSize', + 'dampening' + ]); + this.roomSize.dispose(); + this.roomSize = null; + this.dampening.dispose(); + this.dampening = null; + return this; + }; + return Tone.Freeverb; + }); + Module(function (Tone) { + /** - * The delayTime in milliseconds of the chorus. A larger delayTime - * will give a more pronounced effect. Nominal range a delayTime - * is between 2 and 20ms. - * @memberOf Tone.Chorus# - * @type {Milliseconds} - * @name delayTime + * an array of the comb filter delay time values + * @private + * @static + * @type {Array} */ - Object.defineProperty(Tone.Chorus.prototype, 'delayTime', { - get: function () { - return this._delayTime * 1000; - }, - set: function (delayTime) { - this._delayTime = delayTime / 1000; - this.depth = this._depth; - } - }); + var combFilterDelayTimes = [ + 1687 / 25000, + 1601 / 25000, + 2053 / 25000, + 2251 / 25000 + ]; /** - * The oscillator type of the LFO. - * @memberOf Tone.Chorus# - * @type {string} - * @name type + * the resonances of each of the comb filters + * @private + * @static + * @type {Array} */ - Object.defineProperty(Tone.Chorus.prototype, 'type', { - get: function () { - return this._lfoL.type; - }, - set: function (type) { - this._lfoL.type = type; - this._lfoR.type = type; - } - }); - /** - * Amount of stereo spread. When set to 0, both LFO's will be panned centrally. - * When set to 180, LFO's will be panned hard left and right respectively. - * @memberOf Tone.Chorus# - * @type {Degrees} - * @name spread + var combFilterResonances = [ + 0.773, + 0.802, + 0.753, + 0.733 + ]; + /** + * the allpass filter frequencies + * @private + * @static + * @type {Array} */ - Object.defineProperty(Tone.Chorus.prototype, 'spread', { - get: function () { - return this._lfoR.phase - this._lfoL.phase; //180 - }, - set: function (spread) { - this._lfoL.phase = 90 - spread / 2; - this._lfoR.phase = spread / 2 + 90; + var allpassFilterFreqs = [ + 347, + 113, + 37 + ]; + /** + * @class Tone.JCReverb is a simple [Schroeder Reverberator](https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html) + * tuned by John Chowning in 1970. + * It is made up of three allpass filters and four Tone.FeedbackCombFilter. + * + * + * @extends {Tone.Effect} + * @constructor + * @param {NormalRange|Object} [roomSize] Coorelates to the decay time. + * @example + * var reverb = new Tone.JCReverb(0.4).connect(Tone.Master); + * var delay = new Tone.FeedbackDelay(0.5); + * //connecting the synth to reverb through delay + * var synth = new Tone.DuoSynth().chain(delay, reverb); + * synth.triggerAttackRelease("A4","8n"); + */ + Tone.JCReverb = function () { + var options = this.optionsObject(arguments, ['roomSize'], Tone.JCReverb.defaults); + Tone.StereoEffect.call(this, options); + /** + * room size control values between [0,1] + * @type {NormalRange} + * @signal + */ + this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); + /** + * scale the room size + * @type {Tone.Scale} + * @private + */ + this._scaleRoomSize = new Tone.Scale(-0.733, 0.197); + /** + * a series of allpass filters + * @type {Array} + * @private + */ + this._allpassFilters = []; + /** + * parallel feedback comb filters + * @type {Array} + * @private + */ + this._feedbackCombFilters = []; + //make the allpass filters + for (var af = 0; af < allpassFilterFreqs.length; af++) { + var allpass = this.context.createBiquadFilter(); + allpass.type = 'allpass'; + allpass.frequency.value = allpassFilterFreqs[af]; + this._allpassFilters.push(allpass); + } + //and the comb filters + for (var cf = 0; cf < combFilterDelayTimes.length; cf++) { + var fbcf = new Tone.FeedbackCombFilter(combFilterDelayTimes[cf], 0.1); + this._scaleRoomSize.connect(fbcf.resonance); + fbcf.resonance.value = combFilterResonances[cf]; + this._allpassFilters[this._allpassFilters.length - 1].connect(fbcf); + if (cf < combFilterDelayTimes.length / 2) { + fbcf.connect(this.effectReturnL); + } else { + fbcf.connect(this.effectReturnR); + } + this._feedbackCombFilters.push(fbcf); } - }); + //chain the allpass filters together + this.roomSize.connect(this._scaleRoomSize); + this.connectSeries.apply(this, this._allpassFilters); + this.effectSendL.connect(this._allpassFilters[0]); + this.effectSendR.connect(this._allpassFilters[0]); + this._readOnly(['roomSize']); + }; + Tone.extend(Tone.JCReverb, Tone.StereoEffect); + /** + * the default values + * @static + * @const + * @type {Object} + */ + Tone.JCReverb.defaults = { 'roomSize': 0.5 }; /** * Clean up. - * @returns {Tone.Chorus} this + * @returns {Tone.JCReverb} this */ - Tone.Chorus.prototype.dispose = function () { - Tone.StereoXFeedbackEffect.prototype.dispose.call(this); - this._lfoL.dispose(); - this._lfoL = null; - this._lfoR.dispose(); - this._lfoR = null; - this._delayNodeL.disconnect(); - this._delayNodeL = null; - this._delayNodeR.disconnect(); - this._delayNodeR = null; - this._writable('frequency'); - this.frequency = null; + 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.Chorus; + return Tone.JCReverb; }); Module(function (Tone) { /** - * @class Tone.Convolver is a wrapper around the Native Web Audio - * [ConvolverNode](http://webaudio.github.io/web-audio-api/#the-convolvernode-interface). - * Convolution is useful for reverb and filter emulation. Read more about convolution reverb on - * [Wikipedia](https://en.wikipedia.org/wiki/Convolution_reverb). - * - * @constructor + * @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. + * Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587). + *

+ * This is a base-class for Mid/Side Effects. + * * @extends {Tone.Effect} - * @param {string|Tone.Buffer|Object} [url] The URL of the impulse response or the Tone.Buffer - * contianing the impulse response. - * @example - * //initializing the convolver with an impulse response - * var convolver = new Tone.Convolver("./path/to/ir.wav"); - * convolver.toMaster(); - * //after the buffer has loaded - * Tone.Buffer.onload = function(){ - * //testing out convolution with a noise burst - * var burst = new Tone.NoiseSynth().connect(convolver); - * burst.triggerAttackRelease("16n"); - * }; + * @constructor */ - Tone.Convolver = function () { - var options = this.optionsObject(arguments, ['url'], Tone.Convolver.defaults); - Tone.Effect.call(this, options); + Tone.MidSideEffect = function () { + Tone.Effect.apply(this, arguments); /** - * convolver node - * @type {ConvolverNode} + * The mid/side split + * @type {Tone.MidSideSplit} * @private */ - this._convolver = this.context.createConvolver(); + this._midSideSplit = new Tone.MidSideSplit(); /** - * the convolution buffer - * @type {Tone.Buffer} + * The mid/side merge + * @type {Tone.MidSideMerge} * @private */ - this._buffer = new Tone.Buffer(options.url, function (buffer) { - this.buffer = buffer; - options.onload(); - }.bind(this)); - this.connectEffect(this._convolver); - }; - Tone.extend(Tone.Convolver, Tone.Effect); - /** - * @static - * @const - * @type {Object} - */ - Tone.Convolver.defaults = { - 'url': '', - 'onload': Tone.noOp - }; - /** - * The convolver's buffer - * @memberOf Tone.Convolver# - * @type {AudioBuffer} - * @name buffer - */ - Object.defineProperty(Tone.Convolver.prototype, 'buffer', { - get: function () { - return this._buffer.get(); - }, - set: function (buffer) { - this._buffer.set(buffer); - this._convolver.buffer = this._buffer.get(); - } - }); - /** - * Load an impulse response url as an audio buffer. - * Decodes the audio asynchronously and invokes - * the callback once the audio buffer loads. - * @param {string} url The url of the buffer to load. - * filetype support depends on the - * browser. - * @param {function=} callback - * @returns {Tone.Convolver} this - */ - Tone.Convolver.prototype.load = function (url, callback) { - this._buffer.load(url, function (buff) { - this.buffer = buff; - if (callback) { - callback(); - } - }.bind(this)); - return this; + this._midSideMerge = new Tone.MidSideMerge(); + /** + * The mid send. Connect to mid processing + * @type {Tone.Expr} + * @private + */ + this.midSend = this._midSideSplit.mid; + /** + * The side send. Connect to side processing + * @type {Tone.Expr} + * @private + */ + this.sideSend = this._midSideSplit.side; + /** + * The mid return connection + * @type {GainNode} + * @private + */ + this.midReturn = this._midSideMerge.mid; + /** + * The side return connection + * @type {GainNode} + * @private + */ + 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.Convolver} this + * @returns {Tone.MidSideEffect} this */ - Tone.Convolver.prototype.dispose = function () { + Tone.MidSideEffect.prototype.dispose = function () { Tone.Effect.prototype.dispose.call(this); - this._convolver.disconnect(); - this._convolver = null; - this._buffer.dispose(); - this._buffer = 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.Convolver; + return Tone.MidSideEffect; }); Module(function (Tone) { /** - * @class Tone.Distortion is a simple distortion effect using Tone.WaveShaper. - * Algorithm from [a stackoverflow answer](http://stackoverflow.com/a/22313408). + * @class Tone.Phaser is a phaser effect. Phasers work by changing the phase + * of different frequency components of an incoming signal. Read more on + * [Wikipedia](https://en.wikipedia.org/wiki/Phaser_(effect)). + * Inspiration for this phaser comes from [Tuna.js](https://github.com/Dinahmoe/tuna/). * - * @extends {Tone.Effect} - * @constructor - * @param {Number|Object} [distortion] The amount of distortion (nominal range of 0-1) - * @example - * var dist = new Tone.Distortion(0.8).toMaster(); - * var fm = new Tone.SimpleFM().connect(dist); - * //this sounds good on bass notes - * fm.triggerAttackRelease("A1", "8n"); + * @extends {Tone.StereoEffect} + * @constructor + * @param {Frequency|Object} [frequency] The speed of the phasing. + * @param {number} [octaves] The octaves of the effect. + * @param {Frequency} [baseFrequency] The base frequency of the filters. + * @example + * var phaser = new Tone.Phaser({ + * "frequency" : 15, + * "octaves" : 5, + * "baseFrequency" : 1000 + * }).toMaster(); + * var synth = new Tone.FMSynth().connect(phaser); + * synth.triggerAttackRelease("E3", "2n"); */ - Tone.Distortion = function () { - var options = this.optionsObject(arguments, ['distortion'], Tone.Distortion.defaults); - Tone.Effect.call(this, options); + Tone.Phaser = function () { + //set the defaults + var options = this.optionsObject(arguments, [ + 'frequency', + 'octaves', + 'baseFrequency' + ], Tone.Phaser.defaults); + Tone.StereoEffect.call(this, options); + /** + * the lfo which controls the frequency on the left side + * @type {Tone.LFO} + * @private + */ + this._lfoL = new Tone.LFO(options.frequency, 0, 1); + /** + * the lfo which controls the frequency on the right side + * @type {Tone.LFO} + * @private + */ + this._lfoR = new Tone.LFO(options.frequency, 0, 1); + this._lfoR.phase = 180; + /** + * the base modulation frequency + * @type {number} + * @private + */ + this._baseFrequency = options.baseFrequency; + /** + * the octaves of the phasing + * @type {number} + * @private + */ + this._octaves = options.octaves; /** - * @type {Tone.WaveShaper} + * The quality factor of the filters + * @type {Positive} + * @signal + */ + this.Q = new Tone.Signal(options.Q, Tone.Type.Positive); + /** + * the array of filters for the left side + * @type {Array} * @private */ - this._shaper = new Tone.WaveShaper(4096); + this._filtersL = this._makeFilters(options.stages, this._lfoL, this.Q); /** - * holds the distortion amount - * @type {number} - * @private + * the array of filters for the left side + * @type {Array} + * @private */ - this._distortion = options.distortion; - this.connectEffect(this._shaper); - this.distortion = options.distortion; - this.oversample = options.oversample; + this._filtersR = this._makeFilters(options.stages, this._lfoR, this.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); + //control the frequency with one LFO + this._lfoL.frequency.connect(this._lfoR.frequency); + //set the options + this.baseFrequency = options.baseFrequency; + this.octaves = options.octaves; + //start the lfo + this._lfoL.start(); + this._lfoR.start(); + this._readOnly([ + 'frequency', + 'Q' + ]); }; - Tone.extend(Tone.Distortion, Tone.Effect); + Tone.extend(Tone.Phaser, Tone.StereoEffect); /** + * defaults * @static - * @const - * @type {Object} + * @type {object} */ - Tone.Distortion.defaults = { - 'distortion': 0.4, - 'oversample': 'none' + Tone.Phaser.defaults = { + 'frequency': 0.5, + 'octaves': 3, + 'stages': 10, + 'Q': 10, + 'baseFrequency': 350 }; /** - * The amount of distortion. - * @memberOf Tone.Distortion# - * @type {NormalRange} - * @name distortion + * @param {number} stages + * @returns {Array} the number of filters all connected together + * @private */ - Object.defineProperty(Tone.Distortion.prototype, 'distortion', { + 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'; + Q.connect(filter.Q); + connectToFreq.connect(filter.frequency); + filters[i] = filter; + } + this.connectSeries.apply(this, filters); + return filters; + }; + /** + * The number of octaves the phase goes above + * the baseFrequency + * @memberOf Tone.Phaser# + * @type {Positive} + * @name octaves + */ + Object.defineProperty(Tone.Phaser.prototype, 'octaves', { get: function () { - return this._distortion; + return this._octaves; }, - 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)); - } - }); + set: function (octaves) { + this._octaves = octaves; + var max = this._baseFrequency * Math.pow(2, octaves); + this._lfoL.max = max; + this._lfoR.max = max; } }); /** - * The oversampling of the effect. Can either be "none", "2x" or "4x". - * @memberOf Tone.Distortion# - * @type {string} - * @name oversample + * The the base frequency of the filters. + * @memberOf Tone.Phaser# + * @type {number} + * @name baseFrequency */ - Object.defineProperty(Tone.Distortion.prototype, 'oversample', { + Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', { get: function () { - return this._shaper.oversample; + return this._baseFrequency; }, - set: function (oversampling) { - this._shaper.oversample = oversampling; + set: function (freq) { + this._baseFrequency = freq; + this._lfoL.min = freq; + this._lfoR.min = freq; + this.octaves = this._octaves; } }); /** - * Clean up. - * @returns {Tone.Distortion} this + * clean up + * @returns {Tone.Phaser} this */ - Tone.Distortion.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; + Tone.Phaser.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); + this._writable([ + 'frequency', + 'Q' + ]); + this.Q.dispose(); + this.Q = 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.frequency = null; return this; }; - return Tone.Distortion; + return Tone.Phaser; }); Module(function (Tone) { /** - * @class Tone.FeedbackDelay is a DelayNode in which part of output - * signal is fed back into the delay. + * @class Tone.PingPongDelay is a feedback delay effect where the echo is heard + * first in one channel and next in the opposite channel. In a stereo + * system these are the right and left channels. + * PingPongDelay in more simplified terms is two Tone.FeedbackDelays + * with independent delay values. Each delay is routed to one channel + * (left or right), and the channel triggered second will always + * trigger at the same interval after the first. * - * @constructor - * @extends {Tone.FeedbackEffect} - * @param {Time|Object} [delayTime] The delay applied to the incoming signal. + * @constructor + * @extends {Tone.StereoXFeedbackEffect} + * @param {Time|Object} [delayTime] The delayTime between consecutive echos. * @param {NormalRange=} feedback The amount of the effected signal which - * is fed back through the delay. + * is fed back through the delay. * @example - * var feedbackDelay = new Tone.FeedbackDelay("8n", 0.5).toMaster(); - * var tom = new Tone.DrumSynth({ - * "octaves" : 4, - * "pitchDecay" : 0.1 - * }).connect(feedbackDelay); - * tom.triggerAttackRelease("A2","32n"); + * var pingPong = new Tone.PingPongDelay("4n", 0.2).toMaster(); + * var drum = new Tone.DrumSynth().connect(pingPong); + * drum.triggerAttackRelease("C4", "32n"); */ - Tone.FeedbackDelay = function () { + Tone.PingPongDelay = function () { var options = this.optionsObject(arguments, [ 'delayTime', 'feedback' - ], Tone.FeedbackDelay.defaults); - Tone.FeedbackEffect.call(this, options); + ], Tone.PingPongDelay.defaults); + Tone.StereoXFeedbackEffect.call(this, options); /** - * The delayTime of the DelayNode. - * @type {Time} - * @signal + * the delay node on the left side + * @type {DelayNode} + * @private */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + this._leftDelay = this.context.createDelay(options.maxDelayTime); /** - * the delay node + * the delay node on the right side * @type {DelayNode} * @private */ - this._delayNode = this.context.createDelay(4); - // connect it up - this.connectEffect(this._delayNode); - this.delayTime.connect(this._delayNode.delayTime); + this._rightDelay = this.context.createDelay(options.maxDelayTime); + /** + * the predelay on the right side + * @type {DelayNode} + * @private + */ + this._rightPreDelay = this.context.createDelay(options.maxDelayTime); + /** + * the delay time signal + * @type {Time} + * @signal + */ + this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + //connect it up + this.effectSendL.chain(this._leftDelay, this.effectReturnL); + this.effectSendR.chain(this._rightPreDelay, this._rightDelay, this.effectReturnR); + this.delayTime.fan(this._leftDelay.delayTime, this._rightDelay.delayTime, this._rightPreDelay.delayTime); + //rearranged the feedback to be after the rightPreDelay + this._feedbackLR.disconnect(); + this._feedbackLR.connect(this._rightDelay); this._readOnly(['delayTime']); }; - Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); + Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect); /** - * The default values. - * @const * @static * @type {Object} */ - Tone.FeedbackDelay.defaults = { 'delayTime': 0.25 }; + Tone.PingPongDelay.defaults = { + 'delayTime': 0.25, + 'maxDelayTime': 1 + }; /** - * clean up - * @returns {Tone.FeedbackDelay} this + * Clean up. + * @returns {Tone.PingPongDelay} this */ - Tone.FeedbackDelay.prototype.dispose = function () { - Tone.FeedbackEffect.prototype.dispose.call(this); - this.delayTime.dispose(); - this._delayNode.disconnect(); - this._delayNode = null; + Tone.PingPongDelay.prototype.dispose = function () { + Tone.StereoXFeedbackEffect.prototype.dispose.call(this); + this._leftDelay.disconnect(); + this._leftDelay = null; + this._rightDelay.disconnect(); + this._rightDelay = null; + this._rightPreDelay.disconnect(); + this._rightPreDelay = null; this._writable(['delayTime']); + this.delayTime.dispose(); this.delayTime = null; return this; }; - return Tone.FeedbackDelay; + return Tone.PingPongDelay; }); Module(function (Tone) { /** - * an array of comb filter delay values from Freeverb implementation - * @static - * @private - * @type {Array} - */ - var combFilterTunings = [ - 1557 / 44100, - 1617 / 44100, - 1491 / 44100, - 1422 / 44100, - 1277 / 44100, - 1356 / 44100, - 1188 / 44100, - 1116 / 44100 - ]; - /** - * an array of allpass filter frequency values from Freeverb implementation - * @private - * @static - * @type {Array} - */ - var allpassFilterFrequencies = [ - 225, - 556, - 441, - 341 - ]; - /** - * @class Tone.Freeverb is a reverb based on [Freeverb](https://ccrma.stanford.edu/~jos/pasp/Freeverb.html). - * Read more on reverb on [SoundOnSound](http://www.soundonsound.com/sos/may00/articles/reverb.htm). - * - * @extends {Tone.Effect} - * @constructor - * @param {NormalRange|Object} [roomSize] Correlated to the decay time. - * @param {Frequency} [dampening] The cutoff frequency of a lowpass filter as part - * of the reverb. - * @example - * var freeverb = new Tone.Freeverb().toMaster(); - * freeverb.dampening.value = 1000; - * //routing synth through the reverb - * var synth = new Tone.AMSynth().connect(freeverb); + * @class Tone.PitchShift does near-realtime pitch shifting to the incoming signal. + * The effect is achieved by speeding up or slowing down the delayTime + * of a DelayNode using a sawtooth wave. + * Algorithm found in [this pdf](http://dsp-book.narod.ru/soundproc.pdf). + * Additional reference by [Miller Pucket](http://msp.ucsd.edu/techniques/v0.11/book-html/node115.html). + * + * @extends {Tone.FeedbackEffect} + * @param {Interval=} pitch The interval to transpose the incoming signal by. */ - Tone.Freeverb = function () { - var options = this.optionsObject(arguments, [ - 'roomSize', - 'dampening' - ], Tone.Freeverb.defaults); - Tone.StereoEffect.call(this, options); + Tone.PitchShift = function () { + var options = this.optionsObject(arguments, ['pitch'], Tone.PitchShift.defaults); + Tone.FeedbackEffect.call(this, options); /** - * The roomSize value between. A larger roomSize - * will result in a longer decay. - * @type {NormalRange} - * @signal + * The pitch signal + * @type {Tone.Signal} + * @private */ - this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); + this._frequency = new Tone.Signal(0); /** - * The amount of dampening of the reverberant signal. - * @type {Frequency} - * @signal + * Uses two DelayNodes to cover up the jump in + * the sawtooth wave. + * @type {DelayNode} + * @private */ - this.dampening = new Tone.Signal(options.dampening, Tone.Type.Frequency); + this._delayA = new Tone.Delay(0, 1); /** - * the comb filters - * @type {Array} + * The first LFO. + * @type {Tone.LFO} * @private */ - this._combFilters = []; + this._lfoA = new Tone.LFO({ + 'min': 0, + 'max': 0.1, + 'type': 'sawtooth' + }).connect(this._delayA.delayTime); /** - * the allpass filters on the left - * @type {Array} + * The second DelayNode + * @type {DelayNode} * @private */ - this._allpassFiltersL = []; + this._delayB = new Tone.Delay(0, 1); /** - * the allpass filters on the right - * @type {Array} + * The first LFO. + * @type {Tone.LFO} * @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' - ]); + this._lfoB = new Tone.LFO({ + 'min': 0, + 'max': 0.1, + 'type': 'sawtooth', + 'phase': 180 + }).connect(this._delayB.delayTime); + /** + * Crossfade quickly between the two delay lines + * to cover up the jump in the sawtooth wave + * @type {Tone.CrossFade} + * @private + */ + this._crossFade = new Tone.CrossFade(); + /** + * LFO which alternates between the two + * delay lines to cover up the disparity in the + * sawtooth wave. + * @type {Tone.LFO} + * @private + */ + this._crossFadeLFO = new Tone.LFO({ + 'min': 0, + 'max': 1, + 'type': 'triangle', + 'phase': 90 + }).connect(this._crossFade.fade); + /** + * The delay node + * @type {Tone.Delay} + * @private + */ + this._feedbackDelay = new Tone.Delay(options.delayTime); + /** + * The amount of delay on the input signal + * @type {Time} + * @signal + */ + this.delayTime = this._feedbackDelay.delayTime; + this._readOnly('delayTime'); + /** + * Hold the current pitch + * @type {Number} + * @private + */ + this._pitch = options.pitch; + /** + * Hold the current windowSize + * @type {Number} + * @private + */ + this._windowSize = options.windowSize; + //connect the two delay lines up + this._delayA.connect(this._crossFade.a); + this._delayB.connect(this._crossFade.b); + //connect the frequency + this._frequency.fan(this._lfoA.frequency, this._lfoB.frequency, this._crossFadeLFO.frequency); + //route the input + this.effectSend.fan(this._delayA, this._delayB); + this._crossFade.chain(this._feedbackDelay, this.effectReturn); + //start the LFOs at the same time + var now = this.now(); + this._lfoA.start(now); + this._lfoB.start(now); + this._crossFadeLFO.start(now); + //set the initial value + this.windowSize = this._windowSize; }; - Tone.extend(Tone.Freeverb, Tone.StereoEffect); + Tone.extend(Tone.PitchShift, Tone.FeedbackEffect); /** + * default values * @static * @type {Object} + * @const + */ + Tone.PitchShift.defaults = { + 'pitch': 0, + 'windowSize': 0.1, + 'delayTime': 0, + 'feedback': 0 + }; + /** + * Repitch the incoming signal by some interval (measured + * in semi-tones). + * @memberOf Tone.PitchShift# + * @type {Interval} + * @name pitch + * @example + * pitchShift.pitch = -12; //down one octave + * pitchShift.pitch = 7; //up a fifth + */ + Object.defineProperty(Tone.PitchShift.prototype, 'pitch', { + get: function () { + return this._pitch; + }, + set: function (interval) { + this._pitch = interval; + var factor = 0; + if (interval < 0) { + this._lfoA.min = 0; + this._lfoA.max = this._windowSize; + this._lfoB.min = 0; + this._lfoB.max = this._windowSize; + factor = this.intervalToFrequencyRatio(interval - 1) + 1; + } else { + this._lfoA.min = this._windowSize; + this._lfoA.max = 0; + this._lfoB.min = this._windowSize; + this._lfoB.max = 0; + factor = this.intervalToFrequencyRatio(interval) - 1; + } + this._frequency.value = factor * (1.2 / this._windowSize); + } + }); + /** + * The window size corresponds roughly to the sample length in a looping sampler. + * Smaller values are desirable for a less noticeable delay time of the pitch shifted + * signal, but larger values will result in smoother pitch shifting for larger intervals. + * A nominal range of 0.03 to 0.1 is recommended. + * @memberOf Tone.PitchShift# + * @type {Time} + * @name windowSize + * @example + * pitchShift.windowSize = 0.1; */ - Tone.Freeverb.defaults = { - 'roomSize': 0.7, - 'dampening': 3000 - }; + Object.defineProperty(Tone.PitchShift.prototype, 'windowSize', { + get: function () { + return this._windowSize; + }, + set: function (size) { + this._windowSize = this.toSeconds(size); + this.pitch = this._pitch; + } + }); /** - * Clean up. - * @returns {Tone.Freeverb} this + * Clean up. + * @return {Tone.PitchShift} this */ - Tone.Freeverb.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - for (var al = 0; al < this._allpassFiltersL.length; al++) { - this._allpassFiltersL[al].disconnect(); - this._allpassFiltersL[al] = null; - } - this._allpassFiltersL = null; - for (var ar = 0; ar < this._allpassFiltersR.length; ar++) { - this._allpassFiltersR[ar].disconnect(); - this._allpassFiltersR[ar] = null; - } - this._allpassFiltersR = null; - for (var cf = 0; cf < this._combFilters.length; cf++) { - this._combFilters[cf].dispose(); - this._combFilters[cf] = null; - } - this._combFilters = null; - this._writable([ - 'roomSize', - 'dampening' - ]); - this.roomSize.dispose(); - this.roomSize = null; - this.dampening.dispose(); - this.dampening = null; + Tone.PitchShift.prototype.dispose = function () { + Tone.FeedbackEffect.prototype.dispose.call(this); + this._frequency.dispose(); + this._frequency = null; + this._delayA.disconnect(); + this._delayA = null; + this._delayB.disconnect(); + this._delayB = null; + this._lfoA.dispose(); + this._lfoA = null; + this._lfoB.dispose(); + this._lfoB = null; + this._crossFade.dispose(); + this._crossFade = null; + this._crossFadeLFO.dispose(); + this._crossFadeLFO = null; + this._writable('delayTime'); + this._feedbackDelay.dispose(); + this._feedbackDelay = null; + this.delayTime = null; return this; }; - return Tone.Freeverb; + return Tone.PitchShift; }); Module(function (Tone) { /** - * an array of the comb filter delay time values - * @private - * @static - * @type {Array} - */ - var combFilterDelayTimes = [ - 1687 / 25000, - 1601 / 25000, - 2053 / 25000, - 2251 / 25000 - ]; - /** - * the resonances of each of the comb filters - * @private - * @static - * @type {Array} - */ - var combFilterResonances = [ - 0.773, - 0.802, - 0.753, - 0.733 - ]; - /** - * the allpass filter frequencies - * @private - * @static - * @type {Array} - */ - var allpassFilterFreqs = [ - 347, - 113, - 37 - ]; - /** - * @class Tone.JCReverb is a simple [Schroeder Reverberator](https://ccrma.stanford.edu/~jos/pasp/Schroeder_Reverberators.html) - * tuned by John Chowning in 1970. - * It is made up of three allpass filters and four Tone.FeedbackCombFilter. - * + * @class Base class for stereo feedback effects where the effectReturn + * is fed back into the same channel. * - * @extends {Tone.Effect} - * @constructor - * @param {NormalRange|Object} [roomSize] Coorelates to the decay time. - * @example - * var reverb = new Tone.JCReverb(0.4).connect(Tone.Master); - * var delay = new Tone.FeedbackDelay(0.5); - * //connecting the synth to reverb through delay - * var synth = new Tone.DuoSynth().chain(delay, reverb); - * synth.triggerAttackRelease("A4","8n"); + * @constructor + * @extends {Tone.FeedbackEffect} */ - Tone.JCReverb = function () { - var options = this.optionsObject(arguments, ['roomSize'], Tone.JCReverb.defaults); + Tone.StereoFeedbackEffect = function () { + var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); Tone.StereoEffect.call(this, options); /** - * room size control values between [0,1] + * controls the amount of feedback * @type {NormalRange} * @signal */ - this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); - /** - * scale the room size - * @type {Tone.Scale} - * @private - */ - this._scaleRoomSize = new Tone.Scale(-0.733, 0.197); + this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); /** - * a series of allpass filters - * @type {Array} + * the left side feeback + * @type {GainNode} * @private */ - this._allpassFilters = []; + this._feedbackL = this.context.createGain(); /** - * parallel feedback comb filters - * @type {Array} + * the right side feeback + * @type {GainNode} * @private */ - this._feedbackCombFilters = []; - //make the allpass filters - for (var af = 0; af < allpassFilterFreqs.length; af++) { - var allpass = this.context.createBiquadFilter(); - allpass.type = 'allpass'; - allpass.frequency.value = allpassFilterFreqs[af]; - this._allpassFilters.push(allpass); - } - //and the comb filters - for (var cf = 0; cf < combFilterDelayTimes.length; cf++) { - var fbcf = new Tone.FeedbackCombFilter(combFilterDelayTimes[cf], 0.1); - this._scaleRoomSize.connect(fbcf.resonance); - fbcf.resonance.value = combFilterResonances[cf]; - this._allpassFilters[this._allpassFilters.length - 1].connect(fbcf); - if (cf < combFilterDelayTimes.length / 2) { - fbcf.connect(this.effectReturnL); - } else { - fbcf.connect(this.effectReturnR); - } - this._feedbackCombFilters.push(fbcf); - } - //chain the allpass filters together - this.roomSize.connect(this._scaleRoomSize); - this.connectSeries.apply(this, this._allpassFilters); - this.effectSendL.connect(this._allpassFilters[0]); - this.effectSendR.connect(this._allpassFilters[0]); - this._readOnly(['roomSize']); + 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.JCReverb, Tone.StereoEffect); - /** - * the default values - * @static - * @const - * @type {Object} - */ - Tone.JCReverb.defaults = { 'roomSize': 0.5 }; + Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect); /** - * Clean up. - * @returns {Tone.JCReverb} this + * clean up + * @returns {Tone.StereoFeedbackEffect} this */ - Tone.JCReverb.prototype.dispose = function () { + Tone.StereoFeedbackEffect.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; + 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.JCReverb; + return Tone.StereoFeedbackEffect; }); Module(function (Tone) { /** - * @class Mid/Side processing separates the the 'mid' signal - * (which comes out of both the left and the right channel) - * and the 'side' (which only comes out of the the side channels) - * and effects them separately before being recombined. - * Applies a Mid/Side seperation and recombination. + * @class Applies a width factor to the mid/side seperation. + * 0 is all mid and 1 is all side. * Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587). *

- * This is a base-class for Mid/Side Effects. + * + * Mid *= 2*(1-width)
+ * Side *= 2*width + *
* - * @extends {Tone.Effect} + * @extends {Tone.MidSideEffect} * @constructor + * @param {NormalRange|Object} [width] The stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change. */ - Tone.MidSideEffect = function () { - Tone.Effect.apply(this, arguments); - /** - * The mid/side split - * @type {Tone.MidSideSplit} - * @private - */ - this._midSideSplit = new Tone.MidSideSplit(); - /** - * The mid/side merge - * @type {Tone.MidSideMerge} - * @private - */ - this._midSideMerge = new Tone.MidSideMerge(); + Tone.StereoWidener = function () { + var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults); + Tone.MidSideEffect.call(this, options); /** - * The mid send. Connect to mid processing - * @type {Tone.Expr} - * @private + * The width control. 0 = 100% mid. 1 = 100% side. 0.5 = no change. + * @type {NormalRange} + * @signal */ - this.midSend = this._midSideSplit.mid; + this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); /** - * The side send. Connect to side processing + * Mid multiplier * @type {Tone.Expr} * @private */ - this.sideSend = this._midSideSplit.side; + this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))'); /** - * The mid return connection - * @type {GainNode} + * Side multiplier + * @type {Tone.Expr} * @private */ - this.midReturn = this._midSideMerge.mid; + this._sideMult = new Tone.Expr('$0 * ($1 * $2)'); /** - * The side return connection - * @type {GainNode} + * constant output of 2 + * @type {Tone} * @private */ - this.sideReturn = this._midSideMerge.side; - //the connections - this.effectSend.connect(this._midSideSplit); - this._midSideMerge.connect(this.effectReturn); + this._two = new Tone.Signal(2); + //the mid chain + this._two.connect(this._midMult, 0, 1); + this.width.connect(this._midMult, 0, 2); + //the side chain + this._two.connect(this._sideMult, 0, 1); + this.width.connect(this._sideMult, 0, 2); + //connect it to the effect send/return + this.midSend.chain(this._midMult, this.midReturn); + this.sideSend.chain(this._sideMult, this.sideReturn); + this._readOnly(['width']); }; - Tone.extend(Tone.MidSideEffect, Tone.Effect); + Tone.extend(Tone.StereoWidener, Tone.MidSideEffect); + /** + * the default values + * @static + * @type {Object} + */ + Tone.StereoWidener.defaults = { 'width': 0.5 }; /** * Clean up. - * @returns {Tone.MidSideEffect} this + * @returns {Tone.StereoWidener} this */ - Tone.MidSideEffect.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._midSideSplit.dispose(); - this._midSideSplit = null; - this._midSideMerge.dispose(); - this._midSideMerge = null; - this.midSend = null; - this.sideSend = null; - this.midReturn = null; - this.sideReturn = null; + 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.MidSideEffect; + return Tone.StereoWidener; }); Module(function (Tone) { /** - * @class Tone.Phaser is a phaser effect. Phasers work by changing the phase - * of different frequency components of an incoming signal. Read more on - * [Wikipedia](https://en.wikipedia.org/wiki/Phaser_(effect)). - * Inspiration for this phaser comes from [Tuna.js](https://github.com/Dinahmoe/tuna/). + * @class Tone.Tremolo modulates the amplitude of an incoming signal using a Tone.LFO. + * The type, frequency, and depth of the LFO is controllable. * - * @extends {Tone.StereoEffect} - * @constructor - * @param {Frequency|Object} [frequency] The speed of the phasing. - * @param {number} [octaves] The octaves of the effect. - * @param {Frequency} [baseFrequency] The base frequency of the filters. - * @example - * var phaser = new Tone.Phaser({ - * "frequency" : 15, - * "octaves" : 5, - * "baseFrequency" : 1000 - * }).toMaster(); - * var synth = new Tone.FMSynth().connect(phaser); - * synth.triggerAttackRelease("E3", "2n"); + * @extends {Tone.StereoEffect} + * @constructor + * @param {Frequency} [frequency] The rate of the effect. + * @param {NormalRange} [depth] The depth of the effect. + * @example + * //create a tremolo and start it's LFO + * var tremolo = new Tone.Tremolo(9, 0.75).toMaster().start(); + * //route an oscillator through the tremolo and start it + * var oscillator = new Tone.Oscillator().connect(tremolo).start(); */ - Tone.Phaser = function () { - //set the defaults + Tone.Tremolo = function () { var options = this.optionsObject(arguments, [ 'frequency', - 'octaves', - 'baseFrequency' - ], Tone.Phaser.defaults); + 'depth' + ], Tone.Tremolo.defaults); Tone.StereoEffect.call(this, options); /** - * the lfo which controls the frequency on the left side - * @type {Tone.LFO} + * The tremelo LFO in the left channel + * @type {Tone.LFO} * @private */ - this._lfoL = new Tone.LFO(options.frequency, 0, 1); + this._lfoL = new Tone.LFO({ + 'phase': options.spread, + 'min': 1, + 'max': 0 + }); /** - * the lfo which controls the frequency on the right side - * @type {Tone.LFO} + * The tremelo LFO in the left channel + * @type {Tone.LFO} * @private */ - this._lfoR = new Tone.LFO(options.frequency, 0, 1); - this._lfoR.phase = 180; + this._lfoR = new Tone.LFO({ + 'phase': options.spread, + 'min': 1, + 'max': 0 + }); /** - * the base modulation frequency - * @type {number} + * Where the gain is multiplied + * @type {Tone.Gain} * @private */ - this._baseFrequency = options.baseFrequency; + this._amplitudeL = new Tone.Gain(); /** - * the octaves of the phasing - * @type {number} + * Where the gain is multiplied + * @type {Tone.Gain} * @private */ - this._octaves = options.octaves; + this._amplitudeR = new Tone.Gain(); /** - * The quality factor of the filters - * @type {Positive} + * The frequency of the tremolo. + * @type {Frequency} * @signal */ - this.Q = new Tone.Signal(options.Q, Tone.Type.Positive); - /** - * the array of filters for the left side - * @type {Array} - * @private - */ - this._filtersL = this._makeFilters(options.stages, this._lfoL, this.Q); - /** - * the array of filters for the left side - * @type {Array} - * @private - */ - this._filtersR = this._makeFilters(options.stages, this._lfoR, this.Q); + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * the frequency of the effect - * @type {Tone.Signal} + * The depth of the effect. A depth of 0, has no effect + * on the amplitude, and a depth of 1 makes the amplitude + * modulate fully between 0 and 1. + * @type {NormalRange} + * @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); - //control the frequency with one LFO - this._lfoL.frequency.connect(this._lfoR.frequency); - //set the options - this.baseFrequency = options.baseFrequency; - this.octaves = options.octaves; - //start the lfo - this._lfoL.start(); - this._lfoR.start(); + this.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange); this._readOnly([ 'frequency', - 'Q' + 'depth' ]); + this.effectSendL.chain(this._amplitudeL, this.effectReturnL); + this.effectSendR.chain(this._amplitudeR, this.effectReturnR); + this._lfoL.connect(this._amplitudeL.gain); + this._lfoR.connect(this._amplitudeR.gain); + this.frequency.fan(this._lfoL.frequency, this._lfoR.frequency); + this.depth.fan(this._lfoR.amplitude, this._lfoL.amplitude); + this.type = options.type; + this.spread = options.spread; }; - Tone.extend(Tone.Phaser, Tone.StereoEffect); + Tone.extend(Tone.Tremolo, Tone.StereoEffect); /** - * defaults * @static - * @type {object} + * @const + * @type {Object} */ - Tone.Phaser.defaults = { - 'frequency': 0.5, - 'octaves': 3, - 'stages': 10, - 'Q': 10, - 'baseFrequency': 350 + Tone.Tremolo.defaults = { + 'frequency': 10, + 'type': 'sine', + 'depth': 0.5, + 'spread': 180 }; /** - * @param {number} stages - * @returns {Array} the number of filters all connected together - * @private + * Start the tremolo. + * @param {Time} [time=now] When the tremolo begins. + * @returns {Tone.Tremolo} 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'; - Q.connect(filter.Q); - connectToFreq.connect(filter.frequency); - filters[i] = filter; - } - this.connectSeries.apply(this, filters); - return filters; + Tone.Tremolo.prototype.start = function (time) { + this._lfoL.start(time); + this._lfoR.start(time); + return this; }; /** - * The number of octaves the phase goes above - * the baseFrequency - * @memberOf Tone.Phaser# - * @type {Positive} - * @name octaves + * Stop the tremolo. + * @param {Time} [time=now] When the tremolo stops. + * @returns {Tone.Tremolo} this + */ + Tone.Tremolo.prototype.stop = function (time) { + this._lfoL.stop(time); + this._lfoR.stop(time); + return this; + }; + /** + * Sync the effect to the transport. + * @param {Time} [delay=0] Delay time before starting the effect after the + * Transport has started. + * @returns {Tone.AutoFilter} this + */ + Tone.Tremolo.prototype.sync = function (delay) { + this._lfoL.sync(delay); + this._lfoR.sync(delay); + return this; + }; + /** + * Unsync the filter from the transport + * @returns {Tone.Tremolo} this + */ + Tone.Tremolo.prototype.unsync = function () { + this._lfoL.unsync(); + this._lfoR.unsync(); + return this; + }; + /** + * The Tremolo's oscillator type. + * @memberOf Tone.Tremolo# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Phaser.prototype, 'octaves', { + Object.defineProperty(Tone.Tremolo.prototype, 'type', { get: function () { - return this._octaves; + return this._lfoL.type; }, - set: function (octaves) { - this._octaves = octaves; - var max = this._baseFrequency * Math.pow(2, octaves); - this._lfoL.max = max; - this._lfoR.max = max; + set: function (type) { + this._lfoL.type = type; + this._lfoR.type = type; } }); - /** - * The the base frequency of the filters. - * @memberOf Tone.Phaser# - * @type {number} - * @name baseFrequency + /** + * Amount of stereo spread. When set to 0, both LFO's will be panned centrally. + * When set to 180, LFO's will be panned hard left and right respectively. + * @memberOf Tone.Tremolo# + * @type {Degrees} + * @name spread */ - Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', { + Object.defineProperty(Tone.Tremolo.prototype, 'spread', { get: function () { - return this._baseFrequency; + return this._lfoR.phase - this._lfoL.phase; //180 }, - set: function (freq) { - this._baseFrequency = freq; - this._lfoL.min = freq; - this._lfoR.min = freq; - this.octaves = this._octaves; + set: function (spread) { + this._lfoL.phase = 90 - spread / 2; + this._lfoR.phase = spread / 2 + 90; } }); /** * clean up - * @returns {Tone.Phaser} this + * @returns {Tone.Tremolo} this */ - Tone.Phaser.prototype.dispose = function () { + Tone.Tremolo.prototype.dispose = function () { Tone.StereoEffect.prototype.dispose.call(this); this._writable([ 'frequency', - 'Q' + 'depth' ]); - this.Q.dispose(); - this.Q = 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._amplitudeL.dispose(); + this._amplitudeL = null; + this._amplitudeR.dispose(); + this._amplitudeR = null; this.frequency = null; + this.depth = null; return this; }; - return Tone.Phaser; + return Tone.Tremolo; }); Module(function (Tone) { /** - * @class Tone.PingPongDelay is a feedback delay effect where the echo is heard - * first in one channel and next in the opposite channel. In a stereo - * system these are the right and left channels. - * PingPongDelay in more simplified terms is two Tone.FeedbackDelays - * with independent delay values. Each delay is routed to one channel - * (left or right), and the channel triggered second will always - * trigger at the same interval after the first. - * - * @constructor - * @extends {Tone.StereoXFeedbackEffect} - * @param {Time|Object} [delayTime] The delayTime between consecutive echos. - * @param {NormalRange=} feedback The amount of the effected signal which - * is fed back through the delay. - * @example - * var pingPong = new Tone.PingPongDelay("4n", 0.2).toMaster(); - * var drum = new Tone.DrumSynth().connect(pingPong); - * drum.triggerAttackRelease("C4", "32n"); + * @class A Vibrato effect composed of a Tone.Delay and a Tone.LFO. The LFO + * modulates the delayTime of the delay, causing the pitch to rise + * and fall. + * @extends {Tone.Effect} + * @param {Frequency} frequency The frequency of the vibrato. + * @param {NormalRange} depth The amount the pitch is modulated. */ - Tone.PingPongDelay = function () { + Tone.Vibrato = function () { var options = this.optionsObject(arguments, [ - 'delayTime', - 'feedback' - ], Tone.PingPongDelay.defaults); - Tone.StereoXFeedbackEffect.call(this, options); + 'frequency', + 'depth' + ], Tone.Vibrato.defaults); + Tone.Effect.call(this, options); /** - * the delay node on the left side - * @type {DelayNode} + * The delay node used for the vibrato effect + * @type {Tone.Delay} * @private */ - this._leftDelay = this.context.createDelay(options.maxDelayTime); + this._delayNode = new Tone.Delay(0, options.maxDelay); /** - * the delay node on the right side - * @type {DelayNode} + * The LFO used to control the vibrato + * @type {Tone.LFO} * @private */ - this._rightDelay = this.context.createDelay(options.maxDelayTime); + this._lfo = new Tone.LFO({ + 'type': options.type, + 'min': 0, + 'max': options.maxDelay, + 'frequency': options.frequency, + 'phase': -90 //offse the phase so the resting position is in the center + }).start().connect(this._delayNode.delayTime); /** - * the predelay on the right side - * @type {DelayNode} - * @private + * The frequency of the vibrato + * @type {Frequency} + * @signal */ - this._rightPreDelay = this.context.createDelay(options.maxDelayTime); + this.frequency = this._lfo.frequency; /** - * the delay time signal - * @type {Time} + * The depth of the vibrato. + * @type {NormalRange} * @signal */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); - //connect it up - this.effectSendL.chain(this._leftDelay, this.effectReturnL); - this.effectSendR.chain(this._rightPreDelay, this._rightDelay, this.effectReturnR); - this.delayTime.fan(this._leftDelay.delayTime, this._rightDelay.delayTime, this._rightPreDelay.delayTime); - //rearranged the feedback to be after the rightPreDelay - this._feedbackLR.disconnect(); - this._feedbackLR.connect(this._rightDelay); - this._readOnly(['delayTime']); + this.depth = this._lfo.amplitude; + this.depth.value = options.depth; + this._readOnly([ + 'frequency', + 'depth' + ]); + this.effectSend.chain(this._delayNode, this.effectReturn); }; - Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect); + Tone.extend(Tone.Vibrato, Tone.Effect); /** - * @static - * @type {Object} + * The defaults + * @type {Object} + * @const */ - Tone.PingPongDelay.defaults = { - 'delayTime': 0.25, - 'maxDelayTime': 1 + Tone.Vibrato.defaults = { + 'maxDelay': 0.005, + 'frequency': 5, + 'depth': 0.1, + 'type': 'sine' }; /** - * Clean up. - * @returns {Tone.PingPongDelay} this + * Type of oscillator attached to the Vibrato. + * @memberOf Tone.Vibrato# + * @type {string} + * @name type */ - 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; + Object.defineProperty(Tone.Vibrato.prototype, 'type', { + get: function () { + return this._lfo.type; + }, + set: function (type) { + this._lfo.type = type; + } + }); + /** + * Clean up. + * @returns {Tone.Vibrato} this + */ + Tone.Vibrato.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._delayNode.dispose(); + this._delayNode = null; + this._lfo.dispose(); + this._lfo = null; + this._writable([ + 'frequency', + 'depth' + ]); + this.frequency = null; + this.depth = null; }; - return Tone.PingPongDelay; + return Tone.Vibrato; }); Module(function (Tone) { /** - * @class Tone.PitchShift does near-realtime pitch shifting to the incoming signal. - * The effect is achieved by speeding up or slowing down the delayTime - * of a DelayNode using a sawtooth wave. - * Algorithm found in [this pdf](http://dsp-book.narod.ru/soundproc.pdf). - * Additional reference by [Miller Pucket](http://msp.ucsd.edu/techniques/v0.11/book-html/node115.html). - * - * @extends {Tone.FeedbackEffect} - * @param {Interval=} pitch The interval to transpose the incoming signal by. + * @class Tone.Event abstracts away Tone.Transport.schedule and provides a schedulable + * callback for a single or repeatable events along the timeline. + * + * @extends {Tone} + * @param {function} callback The callback to invoke at the time. + * @param {*} value The value or values which should be passed to + * the callback function on invocation. + * @example + * var chord = new Tone.Event(function(time, chord){ + * //the chord as well as the exact time of the event + * //are passed in as arguments to the callback function + * }, ["D4", "E4", "F4"]); + * //start the chord at the beginning of the transport timeline + * chord.start(); + * //loop it every measure for 8 measures + * chord.loop = 8; + * chord.loopEnd = "1m"; */ - Tone.PitchShift = function () { - var options = this.optionsObject(arguments, ['pitch'], Tone.PitchShift.defaults); - Tone.FeedbackEffect.call(this, options); + Tone.Event = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'value' + ], Tone.Event.defaults); /** - * The pitch signal - * @type {Tone.Signal} + * Loop value + * @type {Boolean|Positive} * @private */ - this._frequency = new Tone.Signal(0); + this._loop = options.loop; /** - * Uses two DelayNodes to cover up the jump in - * the sawtooth wave. - * @type {DelayNode} - * @private + * The callback to invoke. + * @type {Function} */ - this._delayA = new Tone.Delay(0, 1); + this.callback = options.callback; /** - * The first LFO. - * @type {Tone.LFO} - * @private - */ - this._lfoA = new Tone.LFO({ - 'min': 0, - 'max': 0.1, - 'type': 'sawtooth' - }).connect(this._delayA.delayTime); + * The value which is passed to the + * callback function. + * @type {*} + * @private + */ + this.value = options.value; /** - * The second DelayNode - * @type {DelayNode} + * When the note is scheduled to start. + * @type {Number} * @private */ - this._delayB = new Tone.Delay(0, 1); + this._loopStart = this.toTicks(options.loopStart); /** - * The first LFO. - * @type {Tone.LFO} + * When the note is scheduled to start. + * @type {Number} * @private */ - this._lfoB = new Tone.LFO({ - 'min': 0, - 'max': 0.1, - 'type': 'sawtooth', - 'phase': 180 - }).connect(this._delayB.delayTime); + this._loopEnd = this.toTicks(options.loopEnd); /** - * Crossfade quickly between the two delay lines - * to cover up the jump in the sawtooth wave - * @type {Tone.CrossFade} + * Tracks the scheduled events + * @type {Tone.TimelineState} * @private */ - this._crossFade = new Tone.CrossFade(); + this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * LFO which alternates between the two - * delay lines to cover up the disparity in the - * sawtooth wave. - * @type {Tone.LFO} + * The playback speed of the note. A speed of 1 + * is no change. * @private + * @type {Positive} */ - this._crossFadeLFO = new Tone.LFO({ - 'min': 0, - 'max': 1, - 'type': 'triangle', - 'phase': 90 - }).connect(this._crossFade.fade); + this._playbackRate = 1; /** - * The delay node - * @type {Tone.Delay} + * A delay time from when the event is scheduled to start + * @type {Ticks} * @private */ - this._feedbackDelay = new Tone.Delay(options.delayTime); + this._startOffset = 0; /** - * The amount of delay on the input signal - * @type {Time} - * @signal + * The probability that the callback will be invoked + * at the scheduled time. + * @type {NormalRange} + * @example + * //the callback will be invoked 50% of the time + * event.probability = 0.5; */ - this.delayTime = this._feedbackDelay.delayTime; - this._readOnly('delayTime'); + this.probability = options.probability; /** - * Hold the current pitch - * @type {Number} - * @private + * If set to true, will apply small (+/-0.02 seconds) random variation + * to the callback time. If the value is given as a time, it will randomize + * by that amount. + * @example + * event.humanize = true; + * @type {Boolean|Time} */ - this._pitch = options.pitch; + this.humanize = options.humanize; /** - * Hold the current windowSize - * @type {Number} - * @private + * If mute is true, the callback won't be + * invoked. + * @type {Boolean} */ - this._windowSize = options.windowSize; - //connect the two delay lines up - this._delayA.connect(this._crossFade.a); - this._delayB.connect(this._crossFade.b); - //connect the frequency - this._frequency.fan(this._lfoA.frequency, this._lfoB.frequency, this._crossFadeLFO.frequency); - //route the input - this.effectSend.fan(this._delayA, this._delayB); - this._crossFade.chain(this._feedbackDelay, this.effectReturn); - //start the LFOs at the same time - var now = this.now(); - this._lfoA.start(now); - this._lfoB.start(now); - this._crossFadeLFO.start(now); - //set the initial value - this.windowSize = this._windowSize; + this.mute = options.mute; + //set the initial values + this.playbackRate = options.playbackRate; }; - Tone.extend(Tone.PitchShift, Tone.FeedbackEffect); + Tone.extend(Tone.Event); /** - * default values - * @static - * @type {Object} + * The default values + * @type {Object} * @const */ - Tone.PitchShift.defaults = { - 'pitch': 0, - 'windowSize': 0.1, - 'delayTime': 0, - 'feedback': 0 + Tone.Event.defaults = { + 'callback': Tone.noOp, + 'loop': false, + 'loopEnd': '1m', + 'loopStart': 0, + 'playbackRate': 1, + 'value': null, + 'probability': 1, + 'mute': false, + 'humanize': false + }; + /** + * Reschedule all of the events along the timeline + * with the updated values. + * @param {Time} after Only reschedules events after the given time. + * @return {Tone.Event} this + * @private + */ + Tone.Event.prototype._rescheduleEvents = function (after) { + //if no argument is given, schedules all of the events + after = this.defaultArg(after, -1); + this._state.forEachFrom(after, function (event) { + var duration; + if (event.state === Tone.State.Started) { + if (!this.isUndef(event.id)) { + Tone.Transport.clear(event.id); + } + var startTick = event.time + Math.round(this.startOffset / this._playbackRate); + if (this._loop) { + duration = Infinity; + if (this.isNumber(this._loop)) { + duration = this._loop * this._getLoopDuration(); + } + var nextEvent = this._state.getEventAfter(startTick); + if (nextEvent !== null) { + duration = Math.min(duration, nextEvent.time - startTick); + } + if (duration !== Infinity) { + //schedule a stop since it's finite duration + this._state.setStateAtTime(Tone.State.Stopped, startTick + duration + 1); + duration = Tone.Time(duration, 'i'); + } + var interval = Tone.Time(this._getLoopDuration(), 'i'); + event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), interval, Tone.TransportTime(startTick, 'i'), duration); + } else { + event.id = Tone.Transport.schedule(this._tick.bind(this), startTick + 'i'); + } + } + }.bind(this)); + return this; + }; + /** + * Returns the playback state of the note, either "started" or "stopped". + * @type {String} + * @readOnly + * @memberOf Tone.Event# + * @name state + */ + Object.defineProperty(Tone.Event.prototype, 'state', { + get: function () { + return this._state.getStateAtTime(Tone.Transport.ticks); + } + }); + /** + * The start from the scheduled start time + * @type {Ticks} + * @memberOf Tone.Event# + * @name startOffset + * @private + */ + Object.defineProperty(Tone.Event.prototype, 'startOffset', { + get: function () { + return this._startOffset; + }, + set: function (offset) { + this._startOffset = offset; + } + }); + /** + * Start the note at the given time. + * @param {TimelinePosition} time When the note should start. + * @return {Tone.Event} this + */ + Tone.Event.prototype.start = function (time) { + time = this.toTicks(time); + if (this._state.getStateAtTime(time) === Tone.State.Stopped) { + this._state.addEvent({ + 'state': Tone.State.Started, + 'time': time, + 'id': undefined + }); + this._rescheduleEvents(time); + } + return this; + }; + /** + * Stop the Event at the given time. + * @param {TimelinePosition} time When the note should stop. + * @return {Tone.Event} this + */ + Tone.Event.prototype.stop = function (time) { + this.cancel(time); + time = this.toTicks(time); + if (this._state.getStateAtTime(time) === Tone.State.Started) { + this._state.setStateAtTime(Tone.State.Stopped, time); + var previousEvent = this._state.getEventBefore(time); + var reschedulTime = time; + if (previousEvent !== null) { + reschedulTime = previousEvent.time; + } + this._rescheduleEvents(reschedulTime); + } + return this; + }; + /** + * Cancel all scheduled events greater than or equal to the given time + * @param {TimelinePosition} [time=0] The time after which events will be cancel. + * @return {Tone.Event} this + */ + Tone.Event.prototype.cancel = function (time) { + time = this.defaultArg(time, -Infinity); + time = this.toTicks(time); + this._state.forEachFrom(time, function (event) { + Tone.Transport.clear(event.id); + }); + this._state.cancel(time); + return this; + }; + /** + * The callback function invoker. Also + * checks if the Event is done playing + * @param {Number} time The time of the event in seconds + * @private + */ + Tone.Event.prototype._tick = function (time) { + if (!this.mute && this._state.getStateAtTime(Tone.Transport.ticks) === Tone.State.Started) { + if (this.probability < 1 && Math.random() > this.probability) { + return; + } + if (this.humanize) { + var variation = 0.02; + if (!this.isBoolean(this.humanize)) { + variation = this.toSeconds(this.humanize); + } + time += (Math.random() * 2 - 1) * variation; + } + this.callback(time, this.value); + } + }; + /** + * Get the duration of the loop. + * @return {Ticks} + * @private + */ + Tone.Event.prototype._getLoopDuration = function () { + return Math.round((this._loopEnd - this._loopStart) / this._playbackRate); }; /** - * Repitch the incoming signal by some interval (measured - * in semi-tones). - * @memberOf Tone.PitchShift# - * @type {Interval} - * @name pitch - * @example - * pitchShift.pitch = -12; //down one octave - * pitchShift.pitch = 7; //up a fifth + * If the note should loop or not + * between Tone.Event.loopStart and + * Tone.Event.loopEnd. An integer + * value corresponds to the number of + * loops the Event does after it starts. + * @memberOf Tone.Event# + * @type {Boolean|Positive} + * @name loop */ - Object.defineProperty(Tone.PitchShift.prototype, 'pitch', { + Object.defineProperty(Tone.Event.prototype, 'loop', { get: function () { - return this._pitch; + return this._loop; }, - set: function (interval) { - this._pitch = interval; - var factor = 0; - if (interval < 0) { - this._lfoA.min = 0; - this._lfoA.max = this._windowSize; - this._lfoB.min = 0; - this._lfoB.max = this._windowSize; - factor = this.intervalToFrequencyRatio(interval - 1) + 1; - } else { - this._lfoA.min = this._windowSize; - this._lfoA.max = 0; - this._lfoB.min = this._windowSize; - this._lfoB.max = 0; - factor = this.intervalToFrequencyRatio(interval) - 1; - } - this._frequency.value = factor * (1.2 / this._windowSize); + set: function (loop) { + this._loop = loop; + this._rescheduleEvents(); } }); /** - * The window size corresponds roughly to the sample length in a looping sampler. - * Smaller values are desirable for a less noticeable delay time of the pitch shifted - * signal, but larger values will result in smoother pitch shifting for larger intervals. - * A nominal range of 0.03 to 0.1 is recommended. - * @memberOf Tone.PitchShift# - * @type {Time} - * @name windowSize - * @example - * pitchShift.windowSize = 0.1; + * The playback rate of the note. Defaults to 1. + * @memberOf Tone.Event# + * @type {Positive} + * @name playbackRate + * @example + * note.loop = true; + * //repeat the note twice as fast + * note.playbackRate = 2; */ - Object.defineProperty(Tone.PitchShift.prototype, 'windowSize', { + Object.defineProperty(Tone.Event.prototype, 'playbackRate', { get: function () { - return this._windowSize; + return this._playbackRate; }, - set: function (size) { - this._windowSize = this.toSeconds(size); - this.pitch = this._pitch; + set: function (rate) { + this._playbackRate = rate; + this._rescheduleEvents(); } }); /** - * Clean up. - * @return {Tone.PitchShift} this - */ - Tone.PitchShift.prototype.dispose = function () { - Tone.FeedbackEffect.prototype.dispose.call(this); - this._frequency.dispose(); - this._frequency = null; - this._delayA.disconnect(); - this._delayA = null; - this._delayB.disconnect(); - this._delayB = null; - this._lfoA.dispose(); - this._lfoA = null; - this._lfoB.dispose(); - this._lfoB = null; - this._crossFade.dispose(); - this._crossFade = null; - this._crossFadeLFO.dispose(); - this._crossFadeLFO = null; - this._writable('delayTime'); - this._feedbackDelay.dispose(); - this._feedbackDelay = null; - this.delayTime = null; - return this; - }; - return Tone.PitchShift; - }); - Module(function (Tone) { - - /** - * @class Base class for stereo feedback effects where the effectReturn - * is fed back into the same channel. - * - * @constructor - * @extends {Tone.FeedbackEffect} - */ - Tone.StereoFeedbackEffect = function () { - var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); - Tone.StereoEffect.call(this, options); - /** - * controls the amount of feedback - * @type {NormalRange} - * @signal - */ - this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); - /** - * the left side feeback - * @type {GainNode} - * @private - */ - this._feedbackL = this.context.createGain(); - /** - * the right side feeback - * @type {GainNode} - * @private - */ - this._feedbackR = this.context.createGain(); - //connect it up - this.effectReturnL.chain(this._feedbackL, this.effectSendL); - this.effectReturnR.chain(this._feedbackR, this.effectSendR); - this.feedback.fan(this._feedbackL.gain, this._feedbackR.gain); - this._readOnly(['feedback']); - }; - Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect); - /** - * clean up - * @returns {Tone.StereoFeedbackEffect} this + * The loopEnd point is the time the event will loop + * if Tone.Event.loop is true. + * @memberOf Tone.Event# + * @type {TransportTime} + * @name loopEnd */ - Tone.StereoFeedbackEffect.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - this._writable(['feedback']); - this.feedback.dispose(); - this.feedback = null; - this._feedbackL.disconnect(); - this._feedbackL = null; - this._feedbackR.disconnect(); - this._feedbackR = null; - return this; - }; - return Tone.StereoFeedbackEffect; - }); - Module(function (Tone) { - + Object.defineProperty(Tone.Event.prototype, 'loopEnd', { + get: function () { + return Tone.TransportTime(this._loopEnd, 'i').toNotation(); + }, + set: function (loopEnd) { + this._loopEnd = this.toTicks(loopEnd); + if (this._loop) { + this._rescheduleEvents(); + } + } + }); /** - * @class Applies a width factor to the mid/side seperation. - * 0 is all mid and 1 is all side. - * Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587). - *

- * - * Mid *= 2*(1-width)
- * Side *= 2*width - *
- * - * @extends {Tone.MidSideEffect} - * @constructor - * @param {NormalRange|Object} [width] The stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change. + * The time when the loop should start. + * @memberOf Tone.Event# + * @type {TransportTime} + * @name loopStart */ - Tone.StereoWidener = function () { - var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults); - Tone.MidSideEffect.call(this, options); - /** - * The width control. 0 = 100% mid. 1 = 100% side. 0.5 = no change. - * @type {NormalRange} - * @signal - */ - this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); - /** - * Mid multiplier - * @type {Tone.Expr} - * @private - */ - this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))'); - /** - * Side multiplier - * @type {Tone.Expr} - * @private - */ - this._sideMult = new Tone.Expr('$0 * ($1 * $2)'); - /** - * constant output of 2 - * @type {Tone} - * @private - */ - this._two = new Tone.Signal(2); - //the mid chain - this._two.connect(this._midMult, 0, 1); - this.width.connect(this._midMult, 0, 2); - //the side chain - this._two.connect(this._sideMult, 0, 1); - this.width.connect(this._sideMult, 0, 2); - //connect it to the effect send/return - this.midSend.chain(this._midMult, this.midReturn); - this.sideSend.chain(this._sideMult, this.sideReturn); - this._readOnly(['width']); - }; - Tone.extend(Tone.StereoWidener, Tone.MidSideEffect); + Object.defineProperty(Tone.Event.prototype, 'loopStart', { + get: function () { + return Tone.TransportTime(this._loopStart, 'i').toNotation(); + }, + set: function (loopStart) { + this._loopStart = this.toTicks(loopStart); + if (this._loop) { + this._rescheduleEvents(); + } + } + }); /** - * the default values - * @static - * @type {Object} + * The current progress of the loop interval. + * Returns 0 if the event is not started yet or + * it is not set to loop. + * @memberOf Tone.Event# + * @type {NormalRange} + * @name progress + * @readOnly */ - Tone.StereoWidener.defaults = { 'width': 0.5 }; + Object.defineProperty(Tone.Event.prototype, 'progress', { + get: function () { + if (this._loop) { + var ticks = Tone.Transport.ticks; + var lastEvent = this._state.getEvent(ticks); + if (lastEvent !== null && lastEvent.state === Tone.State.Started) { + var loopDuration = this._getLoopDuration(); + var progress = (ticks - lastEvent.time) % loopDuration; + return progress / loopDuration; + } else { + return 0; + } + } else { + return 0; + } + } + }); /** - * Clean up. - * @returns {Tone.StereoWidener} this + * Clean up + * @return {Tone.Event} this */ - Tone.StereoWidener.prototype.dispose = function () { - Tone.MidSideEffect.prototype.dispose.call(this); - this._writable(['width']); - this.width.dispose(); - this.width = null; - this._midMult.dispose(); - this._midMult = null; - this._sideMult.dispose(); - this._sideMult = null; - this._two.dispose(); - this._two = null; - return this; + Tone.Event.prototype.dispose = function () { + this.cancel(); + this._state.dispose(); + this._state = null; + this.callback = null; + this.value = null; }; - return Tone.StereoWidener; + return Tone.Event; }); Module(function (Tone) { - /** - * @class Tone.Tremolo modulates the amplitude of an incoming signal using a Tone.LFO. - * The type, frequency, and depth of the LFO is controllable. - * - * @extends {Tone.StereoEffect} - * @constructor - * @param {Frequency} [frequency] The rate of the effect. - * @param {NormalRange} [depth] The depth of the effect. + * @class Tone.Loop creates a looped callback at the + * specified interval. The callback can be + * started, stopped and scheduled along + * the Transport's timeline. * @example - * //create a tremolo and start it's LFO - * var tremolo = new Tone.Tremolo(9, 0.75).toMaster().start(); - * //route an oscillator through the tremolo and start it - * var oscillator = new Tone.Oscillator().connect(tremolo).start(); + * var loop = new Tone.Loop(function(time){ + * //triggered every eighth note. + * console.log(time); + * }, "8n").start(0); + * Tone.Transport.start(); + * @extends {Tone} + * @param {Function} callback The callback to invoke with the event. + * @param {Time} interval The time between successive callback calls. */ - Tone.Tremolo = function () { + Tone.Loop = function () { var options = this.optionsObject(arguments, [ - 'frequency', - 'depth' - ], Tone.Tremolo.defaults); - Tone.StereoEffect.call(this, options); - /** - * The tremelo LFO in the left channel - * @type {Tone.LFO} - * @private - */ - this._lfoL = new Tone.LFO({ - 'phase': options.spread, - 'min': 1, - 'max': 0 - }); + 'callback', + 'interval' + ], Tone.Loop.defaults); /** - * The tremelo LFO in the left channel - * @type {Tone.LFO} - * @private + * The event which produces the callbacks */ - this._lfoR = new Tone.LFO({ - 'phase': options.spread, - 'min': 1, - 'max': 0 + this._event = new Tone.Event({ + 'callback': this._tick.bind(this), + 'loop': true, + 'loopEnd': options.interval, + 'playbackRate': options.playbackRate, + 'probability': options.probability }); /** - * Where the gain is multiplied - * @type {Tone.Gain} - * @private - */ - this._amplitudeL = new Tone.Gain(); - /** - * Where the gain is multiplied - * @type {Tone.Gain} - * @private - */ - this._amplitudeR = new Tone.Gain(); - /** - * The frequency of the tremolo. - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); - /** - * The depth of the effect. A depth of 0, has no effect - * on the amplitude, and a depth of 1 makes the amplitude - * modulate fully between 0 and 1. - * @type {NormalRange} - * @signal + * The callback to invoke with the next event in the pattern + * @type {Function} */ - this.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange); - this._readOnly([ - 'frequency', - 'depth' - ]); - this.effectSendL.chain(this._amplitudeL, this.effectReturnL); - this.effectSendR.chain(this._amplitudeR, this.effectReturnR); - this._lfoL.connect(this._amplitudeL.gain); - this._lfoR.connect(this._amplitudeR.gain); - this.frequency.fan(this._lfoL.frequency, this._lfoR.frequency); - this.depth.fan(this._lfoR.amplitude, this._lfoL.amplitude); - this.type = options.type; - this.spread = options.spread; + this.callback = options.callback; + //set the iterations + this.iterations = options.iterations; }; - Tone.extend(Tone.Tremolo, Tone.StereoEffect); + Tone.extend(Tone.Loop); /** - * @static + * The defaults * @const - * @type {Object} + * @type {Object} */ - Tone.Tremolo.defaults = { - 'frequency': 10, - 'type': 'sine', - 'depth': 0.5, - 'spread': 180 + Tone.Loop.defaults = { + 'interval': '4n', + 'callback': Tone.noOp, + 'playbackRate': 1, + 'iterations': Infinity, + 'probability': true, + 'mute': false }; /** - * Start the tremolo. - * @param {Time} [time=now] When the tremolo begins. - * @returns {Tone.Tremolo} this + * Start the loop at the specified time along the Transport's + * timeline. + * @param {TimelinePosition=} time When to start the Loop. + * @return {Tone.Loop} this */ - Tone.Tremolo.prototype.start = function (time) { - this._lfoL.start(time); - this._lfoR.start(time); + Tone.Loop.prototype.start = function (time) { + this._event.start(time); return this; }; /** - * Stop the tremolo. - * @param {Time} [time=now] When the tremolo stops. - * @returns {Tone.Tremolo} this + * Stop the loop at the given time. + * @param {TimelinePosition=} time When to stop the Arpeggio + * @return {Tone.Loop} this */ - Tone.Tremolo.prototype.stop = function (time) { - this._lfoL.stop(time); - this._lfoR.stop(time); + Tone.Loop.prototype.stop = function (time) { + this._event.stop(time); return this; }; /** - * Sync the effect to the transport. - * @param {Time} [delay=0] Delay time before starting the effect after the - * Transport has started. - * @returns {Tone.AutoFilter} this + * Cancel all scheduled events greater than or equal to the given time + * @param {TimelinePosition} [time=0] The time after which events will be cancel. + * @return {Tone.Loop} this */ - Tone.Tremolo.prototype.sync = function (delay) { - this._lfoL.sync(delay); - this._lfoR.sync(delay); + Tone.Loop.prototype.cancel = function (time) { + this._event.cancel(time); return this; }; /** - * Unsync the filter from the transport - * @returns {Tone.Tremolo} this + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs + * @private */ - Tone.Tremolo.prototype.unsync = function () { - this._lfoL.unsync(); - this._lfoR.unsync(); - return this; + Tone.Loop.prototype._tick = function (time) { + this.callback(time); }; /** - * The Tremolo's oscillator type. - * @memberOf Tone.Tremolo# - * @type {string} - * @name type + * The state of the Loop, either started or stopped. + * @memberOf Tone.Loop# + * @type {String} + * @name state + * @readOnly + */ + Object.defineProperty(Tone.Loop.prototype, 'state', { + get: function () { + return this._event.state; + } + }); + /** + * The progress of the loop as a value between 0-1. 0, when + * the loop is stopped or done iterating. + * @memberOf Tone.Loop# + * @type {NormalRange} + * @name progress + * @readOnly + */ + Object.defineProperty(Tone.Loop.prototype, 'progress', { + get: function () { + return this._event.progress; + } + }); + /** + * The time between successive callbacks. + * @example + * loop.interval = "8n"; //loop every 8n + * @memberOf Tone.Loop# + * @type {Time} + * @name interval + */ + Object.defineProperty(Tone.Loop.prototype, 'interval', { + get: function () { + return this._event.loopEnd; + }, + set: function (interval) { + this._event.loopEnd = interval; + } + }); + /** + * The playback rate of the loop. The normal playback rate is 1 (no change). + * A `playbackRate` of 2 would be twice as fast. + * @memberOf Tone.Loop# + * @type {Time} + * @name playbackRate + */ + Object.defineProperty(Tone.Loop.prototype, 'playbackRate', { + get: function () { + return this._event.playbackRate; + }, + set: function (rate) { + this._event.playbackRate = rate; + } + }); + /** + * Random variation +/-0.01s to the scheduled time. + * Or give it a time value which it will randomize by. + * @type {Boolean|Time} + * @memberOf Tone.Loop# + * @name humanize + */ + Object.defineProperty(Tone.Loop.prototype, 'humanize', { + get: function () { + return this._event.humanize; + }, + set: function (variation) { + this._event.humanize = variation; + } + }); + /** + * The probably of the callback being invoked. + * @memberOf Tone.Loop# + * @type {NormalRange} + * @name probability */ - Object.defineProperty(Tone.Tremolo.prototype, 'type', { + Object.defineProperty(Tone.Loop.prototype, 'probability', { get: function () { - return this._lfoL.type; + return this._event.probability; }, - set: function (type) { - this._lfoL.type = type; - this._lfoR.type = type; + set: function (prob) { + this._event.probability = prob; } }); - /** - * Amount of stereo spread. When set to 0, both LFO's will be panned centrally. - * When set to 180, LFO's will be panned hard left and right respectively. - * @memberOf Tone.Tremolo# - * @type {Degrees} - * @name spread + /** + * Muting the Loop means that no callbacks are invoked. + * @memberOf Tone.Loop# + * @type {Boolean} + * @name mute */ - Object.defineProperty(Tone.Tremolo.prototype, 'spread', { + Object.defineProperty(Tone.Loop.prototype, 'mute', { get: function () { - return this._lfoR.phase - this._lfoL.phase; //180 + return this._event.mute; }, - set: function (spread) { - this._lfoL.phase = 90 - spread / 2; - this._lfoR.phase = spread / 2 + 90; + set: function (mute) { + this._event.mute = mute; } }); /** - * clean up - * @returns {Tone.Tremolo} this - */ - Tone.Tremolo.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - this._writable([ - 'frequency', - 'depth' - ]); - this._lfoL.dispose(); - this._lfoL = null; - this._lfoR.dispose(); - this._lfoR = null; - this._amplitudeL.dispose(); - this._amplitudeL = null; - this._amplitudeR.dispose(); - this._amplitudeR = null; - this.frequency = null; - this.depth = null; - return this; - }; - return Tone.Tremolo; - }); - Module(function (Tone) { - - /** - * @class A Vibrato effect composed of a Tone.Delay and a Tone.LFO. The LFO - * modulates the delayTime of the delay, causing the pitch to rise - * and fall. - * @extends {Tone.Effect} - * @param {Frequency} frequency The frequency of the vibrato. - * @param {NormalRange} depth The amount the pitch is modulated. - */ - Tone.Vibrato = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'depth' - ], Tone.Vibrato.defaults); - Tone.Effect.call(this, options); - /** - * The delay node used for the vibrato effect - * @type {Tone.Delay} - * @private - */ - this._delayNode = new Tone.Delay(0, options.maxDelay); - /** - * The LFO used to control the vibrato - * @type {Tone.LFO} - * @private - */ - this._lfo = new Tone.LFO({ - 'type': options.type, - 'min': 0, - 'max': options.maxDelay, - 'frequency': options.frequency, - 'phase': -90 //offse the phase so the resting position is in the center - }).start().connect(this._delayNode.delayTime); - /** - * The frequency of the vibrato - * @type {Frequency} - * @signal - */ - this.frequency = this._lfo.frequency; - /** - * The depth of the vibrato. - * @type {NormalRange} - * @signal - */ - this.depth = this._lfo.amplitude; - this.depth.value = options.depth; - this._readOnly([ - 'frequency', - 'depth' - ]); - this.effectSend.chain(this._delayNode, this.effectReturn); - }; - Tone.extend(Tone.Vibrato, Tone.Effect); - /** - * The defaults - * @type {Object} - * @const - */ - Tone.Vibrato.defaults = { - 'maxDelay': 0.005, - 'frequency': 5, - 'depth': 0.1, - 'type': 'sine' - }; - /** - * Type of oscillator attached to the Vibrato. - * @memberOf Tone.Vibrato# - * @type {string} - * @name type + * The number of iterations of the loop. The default + * value is Infinity (loop forever). + * @memberOf Tone.Loop# + * @type {Positive} + * @name iterations */ - Object.defineProperty(Tone.Vibrato.prototype, 'type', { + Object.defineProperty(Tone.Loop.prototype, 'iterations', { get: function () { - return this._lfo.type; + if (this._event.loop === true) { + return Infinity; + } else { + return this._event.loop; + } + return this._pattern.index; }, - set: function (type) { - this._lfo.type = type; + set: function (iters) { + if (iters === Infinity) { + this._event.loop = true; + } else { + this._event.loop = iters; + } } }); /** - * Clean up. - * @returns {Tone.Vibrato} this + * Clean up + * @return {Tone.Loop} this */ - Tone.Vibrato.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._delayNode.dispose(); - this._delayNode = null; - this._lfo.dispose(); - this._lfo = null; - this._writable([ - 'frequency', - 'depth' - ]); - this.frequency = null; - this.depth = null; + Tone.Loop.prototype.dispose = function () { + this._event.dispose(); + this._event = null; + this.callback = null; }; - return Tone.Vibrato; + return Tone.Loop; }); Module(function (Tone) { /** - * @class Tone.Event abstracts away Tone.Transport.schedule and provides a schedulable - * callback for a single or repeatable events along the timeline. + * @class Tone.Part is a collection Tone.Events which can be + * started/stoped and looped as a single unit. * - * @extends {Tone} - * @param {function} callback The callback to invoke at the time. - * @param {*} value The value or values which should be passed to - * the callback function on invocation. + * @extends {Tone.Event} + * @param {Function} callback The callback to invoke on each event + * @param {Array} events the array of events * @example - * var chord = new Tone.Event(function(time, chord){ - * //the chord as well as the exact time of the event - * //are passed in as arguments to the callback function - * }, ["D4", "E4", "F4"]); - * //start the chord at the beginning of the transport timeline - * chord.start(); - * //loop it every measure for 8 measures - * chord.loop = 8; - * chord.loopEnd = "1m"; + * var part = new Tone.Part(function(time, note){ + * //the notes given as the second element in the array + * //will be passed in as the second argument + * synth.triggerAttackRelease(note, "8n", time); + * }, [[0, "C2"], ["0:2", "C3"], ["0:3:2", "G2"]]); + * @example + * //use an array of objects as long as the object has a "time" attribute + * var part = new Tone.Part(function(time, value){ + * //the value is an object which contains both the note and the velocity + * synth.triggerAttackRelease(value.note, "8n", time, value.velocity); + * }, [{"time" : 0, "note" : "C3", "velocity": 0.9}, + * {"time" : "0:2", "note" : "C4", "velocity": 0.5} + * ]).start(0); */ - Tone.Event = function () { + Tone.Part = function () { var options = this.optionsObject(arguments, [ 'callback', - 'value' - ], Tone.Event.defaults); + 'events' + ], Tone.Part.defaults); /** - * Loop value + * If the part is looping or not * @type {Boolean|Positive} * @private */ this._loop = options.loop; - /** - * The callback to invoke. - * @type {Function} - */ - this.callback = options.callback; - /** - * The value which is passed to the - * callback function. - * @type {*} - * @private - */ - this.value = options.value; /** * When the note is scheduled to start. - * @type {Number} + * @type {Ticks} * @private */ this._loopStart = this.toTicks(options.loopStart); /** * When the note is scheduled to start. - * @type {Number} + * @type {Ticks} * @private */ this._loopEnd = this.toTicks(options.loopEnd); /** - * Tracks the scheduled events - * @type {Tone.TimelineState} + * The playback rate of the part + * @type {Positive} * @private */ - this._state = new Tone.TimelineState(Tone.State.Stopped); + this._playbackRate = options.playbackRate; /** - * The playback speed of the note. A speed of 1 - * is no change. + * private holder of probability value + * @type {NormalRange} * @private - * @type {Positive} */ - this._playbackRate = 1; + this._probability = options.probability; /** - * A delay time from when the event is scheduled to start + * the amount of variation from the + * given time. + * @type {Boolean|Time} + * @private + */ + this._humanize = options.humanize; + /** + * The start offset * @type {Ticks} * @private */ this._startOffset = 0; /** - * The probability that the callback will be invoked - * at the scheduled time. - * @type {NormalRange} - * @example - * //the callback will be invoked 50% of the time - * event.probability = 0.5; + * Keeps track of the current state + * @type {Tone.TimelineState} + * @private */ - this.probability = options.probability; + this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * If set to true, will apply small (+/-0.02 seconds) random variation - * to the callback time. If the value is given as a time, it will randomize - * by that amount. - * @example - * event.humanize = true; - * @type {Boolean|Time} + * An array of Objects. + * @type {Array} + * @private */ - this.humanize = options.humanize; + this._events = []; + /** + * The callback to invoke at all the scheduled events. + * @type {Function} + */ + this.callback = options.callback; /** * If mute is true, the callback won't be * invoked. * @type {Boolean} */ this.mute = options.mute; - //set the initial values - this.playbackRate = options.playbackRate; + //add the events + var events = this.defaultArg(options.events, []); + if (!this.isUndef(options.events)) { + for (var i = 0; i < events.length; i++) { + if (Array.isArray(events[i])) { + this.add(events[i][0], events[i][1]); + } else { + this.add(events[i]); + } + } + } }; - Tone.extend(Tone.Event); + Tone.extend(Tone.Part, Tone.Event); /** * The default values * @type {Object} * @const */ - Tone.Event.defaults = { + Tone.Part.defaults = { 'callback': Tone.noOp, 'loop': false, 'loopEnd': '1m', 'loopStart': 0, 'playbackRate': 1, - 'value': null, 'probability': 1, - 'mute': false, - 'humanize': false + 'humanize': false, + 'mute': false }; /** - * Reschedule all of the events along the timeline - * with the updated values. - * @param {Time} after Only reschedules events after the given time. - * @return {Tone.Event} this - * @private + * Start the part at the given time. + * @param {TransportTime} time When to start the part. + * @param {Time=} offset The offset from the start of the part + * to begin playing at. + * @return {Tone.Part} this */ - Tone.Event.prototype._rescheduleEvents = function (after) { - //if no argument is given, schedules all of the events - after = this.defaultArg(after, -1); - this._state.forEachFrom(after, function (event) { - var duration; - if (event.state === Tone.State.Started) { - if (!this.isUndef(event.id)) { - Tone.Transport.clear(event.id); - } - var startTick = event.time + Math.round(this.startOffset / this._playbackRate); - if (this._loop) { - duration = Infinity; - if (this.isNumber(this._loop)) { - duration = (this._loop - 1) * this._getLoopDuration(); - } - var nextEvent = this._state.getEventAfter(startTick); - if (nextEvent !== null) { - duration = Math.min(duration, nextEvent.time - startTick); - } - if (duration !== Infinity) { - //schedule a stop since it's finite duration - this._state.setStateAtTime(Tone.State.Stopped, startTick + duration + 1); - duration += 'i'; - } - event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), this._getLoopDuration().toString() + 'i', startTick + 'i', duration); - } else { - event.id = Tone.Transport.schedule(this._tick.bind(this), startTick + 'i'); - } + Tone.Part.prototype.start = function (time, offset) { + var ticks = this.toTicks(time); + if (this._state.getStateAtTime(ticks) !== Tone.State.Started) { + if (this._loop) { + offset = this.defaultArg(offset, this._loopStart); + } else { + offset = this.defaultArg(offset, 0); } - }.bind(this)); + offset = this.toTicks(offset); + this._state.addEvent({ + 'state': Tone.State.Started, + 'time': ticks, + 'offset': offset + }); + this._forEach(function (event) { + this._startNote(event, ticks, offset); + }); + } return this; }; /** - * Returns the playback state of the note, either "started" or "stopped". - * @type {String} - * @readOnly - * @memberOf Tone.Event# - * @name state + * Start the event in the given event at the correct time given + * the ticks and offset and looping. + * @param {Tone.Event} event + * @param {Ticks} ticks + * @param {Ticks} offset + * @private */ - Object.defineProperty(Tone.Event.prototype, 'state', { - get: function () { - return this._state.getStateAtTime(Tone.Transport.ticks); + Tone.Part.prototype._startNote = function (event, ticks, offset) { + ticks -= offset; + if (this._loop) { + if (event.startOffset >= this._loopStart && event.startOffset < this._loopEnd) { + if (event.startOffset < offset) { + //start it on the next loop + ticks += this._getLoopDuration(); + } + event.start(Tone.TransportTime(ticks, 'i')); + } else if (event.startOffset < this._loopStart && event.startOffset >= offset) { + event.loop = false; + event.start(Tone.TransportTime(ticks, 'i')); + } + } else { + if (event.startOffset >= offset) { + event.start(Tone.TransportTime(ticks, 'i')); + } } - }); + }; /** * The start from the scheduled start time * @type {Ticks} - * @memberOf Tone.Event# + * @memberOf Tone.Part# * @name startOffset * @private */ - Object.defineProperty(Tone.Event.prototype, 'startOffset', { + Object.defineProperty(Tone.Part.prototype, 'startOffset', { get: function () { return this._startOffset; }, set: function (offset) { this._startOffset = offset; - } - }); - /** - * Start the note at the given time. - * @param {Time} time When the note should start. - * @return {Tone.Event} this - */ - Tone.Event.prototype.start = function (time) { - time = this.toTicks(time); - if (this._state.getStateAtTime(time) === Tone.State.Stopped) { - this._state.addEvent({ - 'state': Tone.State.Started, - 'time': time, - 'id': undefined + this._forEach(function (event) { + event.startOffset += this._startOffset; }); - this._rescheduleEvents(time); - } - return this; - }; - /** - * Stop the Event at the given time. - * @param {Time} time When the note should stop. - * @return {Tone.Event} this - */ - Tone.Event.prototype.stop = function (time) { - this.cancel(time); - time = this.toTicks(time); - if (this._state.getStateAtTime(time) === Tone.State.Started) { - this._state.setStateAtTime(Tone.State.Stopped, time); - var previousEvent = this._state.getEventBefore(time); - var reschedulTime = time; - if (previousEvent !== null) { - reschedulTime = previousEvent.time; - } - this._rescheduleEvents(reschedulTime); } - return this; - }; + }); /** - * Cancel all scheduled events greater than or equal to the given time - * @param {Time} [time=0] The time after which events will be cancel. - * @return {Tone.Event} this + * Stop the part at the given time. + * @param {TimelinePosition} time When to stop the part. + * @return {Tone.Part} this */ - Tone.Event.prototype.cancel = function (time) { - time = this.defaultArg(time, -Infinity); - time = this.toTicks(time); - this._state.forEachFrom(time, function (event) { - Tone.Transport.clear(event.id); + Tone.Part.prototype.stop = function (time) { + var ticks = this.toTicks(time); + this._state.cancel(ticks); + this._state.setStateAtTime(Tone.State.Stopped, ticks); + this._forEach(function (event) { + event.stop(time); }); - this._state.cancel(time); return this; }; /** - * The callback function invoker. Also - * checks if the Event is done playing - * @param {Number} time The time of the event in seconds - * @private + * Get/Set an Event's value at the given time. + * If a value is passed in and no event exists at + * the given time, one will be created with that value. + * If two events are at the same time, the first one will + * be returned. + * @example + * part.at("1m"); //returns the part at the first measure + * + * part.at("2m", "C2"); //set the value at "2m" to C2. + * //if an event didn't exist at that time, it will be created. + * @param {TransportTime} time The time of the event to get or set. + * @param {*=} value If a value is passed in, the value of the + * event at the given time will be set to it. + * @return {Tone.Event} the event at the time */ - Tone.Event.prototype._tick = function (time) { - if (!this.mute && this._state.getStateAtTime(Tone.Transport.ticks) === Tone.State.Started) { - if (this.probability < 1 && Math.random() > this.probability) { - return; - } - if (this.humanize) { - var variation = 0.02; - if (!this.isBoolean(this.humanize)) { - variation = this.toSeconds(this.humanize); + Tone.Part.prototype.at = function (time, value) { + time = Tone.TransportTime(time); + var tickTime = Tone.Time(1, 'i').toSeconds(); + for (var i = 0; i < this._events.length; i++) { + var event = this._events[i]; + if (Math.abs(time.toTicks() - event.startOffset) < tickTime) { + if (!this.isUndef(value)) { + event.value = value; } - time += (Math.random() * 2 - 1) * variation; + return event; } - this.callback(time, this.value); - } - }; - /** - * Get the duration of the loop. - * @return {Ticks} - * @private - */ - Tone.Event.prototype._getLoopDuration = function () { - return Math.round((this._loopEnd - this._loopStart) / this._playbackRate); - }; - /** - * If the note should loop or not - * between Tone.Event.loopStart and - * Tone.Event.loopEnd. An integer - * value corresponds to the number of - * loops the Event does after it starts. - * @memberOf Tone.Event# - * @type {Boolean|Positive} - * @name loop - */ - Object.defineProperty(Tone.Event.prototype, 'loop', { - get: function () { - return this._loop; - }, - set: function (loop) { - this._loop = loop; - this._rescheduleEvents(); - } - }); - /** - * The playback rate of the note. Defaults to 1. - * @memberOf Tone.Event# - * @type {Positive} - * @name playbackRate - * @example - * note.loop = true; - * //repeat the note twice as fast - * note.playbackRate = 2; - */ - Object.defineProperty(Tone.Event.prototype, 'playbackRate', { - get: function () { - return this._playbackRate; - }, - set: function (rate) { - this._playbackRate = rate; - this._rescheduleEvents(); } - }); + //if there was no event at that time, create one + if (!this.isUndef(value)) { + this.add(time, value); + //return the new event + return this._events[this._events.length - 1]; + } else { + return null; + } + }; /** - * The loopEnd point is the time the event will loop. - * Note: only loops if Tone.Event.loop is true. - * @memberOf Tone.Event# - * @type {Boolean|Positive} - * @name loopEnd + * Add a an event to the part. + * @param {Time} time The time the note should start. + * If an object is passed in, it should + * have a 'time' attribute and the rest + * of the object will be used as the 'value'. + * @param {Tone.Event|*} value + * @returns {Tone.Part} this + * @example + * part.add("1m", "C#+11"); */ - Object.defineProperty(Tone.Event.prototype, 'loopEnd', { - get: function () { - return this.toNotation(this._loopEnd + 'i'); - }, - set: function (loopEnd) { - this._loopEnd = this.toTicks(loopEnd); - if (this._loop) { - this._rescheduleEvents(); - } + Tone.Part.prototype.add = function (time, value) { + //extract the parameters + if (this.isObject(time) && time.hasOwnProperty('time')) { + value = time; + time = value.time; + delete value.time; } - }); + time = this.toTicks(time); + var event; + if (value instanceof Tone.Event) { + event = value; + event.callback = this._tick.bind(this); + } else { + event = new Tone.Event({ + 'callback': this._tick.bind(this), + 'value': value + }); + } + //the start offset + event.startOffset = time; + //initialize the values + event.set({ + 'loopEnd': this.loopEnd, + 'loopStart': this.loopStart, + 'loop': this.loop, + 'humanize': this.humanize, + 'playbackRate': this.playbackRate, + 'probability': this.probability + }); + this._events.push(event); + //start the note if it should be played right now + this._restartEvent(event); + return this; + }; /** - * The time when the loop should start. - * @memberOf Tone.Event# - * @type {Boolean|Positive} - * @name loopStart + * Restart the given event + * @param {Tone.Event} event + * @private */ - Object.defineProperty(Tone.Event.prototype, 'loopStart', { - get: function () { - return this.toNotation(this._loopStart + 'i'); - }, - set: function (loopStart) { - this._loopStart = this.toTicks(loopStart); - if (this._loop) { - this._rescheduleEvents(); - } + Tone.Part.prototype._restartEvent = function (event) { + var stateEvent = this._state.getEvent(this.now()); + if (stateEvent && stateEvent.state === Tone.State.Started) { + this._startNote(event, stateEvent.time, stateEvent.offset); } - }); + }; /** - * The current progress of the loop interval. - * Returns 0 if the event is not started yet or - * it is not set to loop. - * @memberOf Tone.Event# - * @type {NormalRange} - * @name progress - * @readOnly + * Remove an event from the part. Will recursively iterate + * into nested parts to find the event. + * @param {Time} time The time of the event + * @param {*} value Optionally select only a specific event value */ - Object.defineProperty(Tone.Event.prototype, 'progress', { - get: function () { - if (this._loop) { - var ticks = Tone.Transport.ticks; - var lastEvent = this._state.getEvent(ticks); - if (lastEvent !== null && lastEvent.state === Tone.State.Started) { - var loopDuration = this._getLoopDuration(); - var progress = (ticks - lastEvent.time) % loopDuration; - return progress / loopDuration; - } else { - return 0; - } + Tone.Part.prototype.remove = function (time, value) { + //extract the parameters + if (this.isObject(time) && time.hasOwnProperty('time')) { + value = time; + time = value.time; + } + time = this.toTicks(time); + for (var i = this._events.length - 1; i >= 0; i--) { + var event = this._events[i]; + if (event instanceof Tone.Part) { + event.remove(time, value); } else { - return 0; + if (event.startOffset === time) { + if (this.isUndef(value) || !this.isUndef(value) && event.value === value) { + this._events.splice(i, 1); + event.dispose(); + } + } } } - }); - /** - * Clean up - * @return {Tone.Event} this - */ - Tone.Event.prototype.dispose = function () { - this.cancel(); - this._state.dispose(); - this._state = null; - this.callback = null; - this.value = null; + return this; }; - return Tone.Event; - }); - Module(function (Tone) { /** - * @class Tone.Loop creates a looped callback at the - * specified interval. The callback can be - * started, stopped and scheduled along - * the Transport's timeline. - * @example - * var loop = new Tone.Loop(function(time){ - * //triggered every eighth note. - * console.log(time); - * }, "8n").start(0); - * Tone.Transport.start(); - * @extends {Tone} - * @param {Function} callback The callback to invoke with the - * event. - * @param {Array} events The events to arpeggiate over. + * Remove all of the notes from the group. + * @return {Tone.Part} this */ - Tone.Loop = function () { - var options = this.optionsObject(arguments, [ - 'callback', - 'interval' - ], Tone.Loop.defaults); - /** - * The event which produces the callbacks - */ - this._event = new Tone.Event({ - 'callback': this._tick.bind(this), - 'loop': true, - 'loopEnd': options.interval, - 'playbackRate': options.playbackRate, - 'probability': options.probability + Tone.Part.prototype.removeAll = function () { + this._forEach(function (event) { + event.dispose(); }); - /** - * The callback to invoke with the next event in the pattern - * @type {Function} - */ - this.callback = options.callback; - //set the iterations - this.iterations = options.iterations; - }; - Tone.extend(Tone.Loop); - /** - * The defaults - * @const - * @type {Object} - */ - Tone.Loop.defaults = { - 'interval': '4n', - 'callback': Tone.noOp, - 'playbackRate': 1, - 'iterations': Infinity, - 'probability': true, - 'mute': false + this._events = []; + return this; }; /** - * Start the loop at the specified time along the Transport's - * timeline. - * @param {Time=} time When to start the Loop. - * @return {Tone.Loop} this + * Cancel scheduled state change events: i.e. "start" and "stop". + * @param {TimelinePosition} after The time after which to cancel the scheduled events. + * @return {Tone.Part} this */ - Tone.Loop.prototype.start = function (time) { - this._event.start(time); + Tone.Part.prototype.cancel = function (after) { + this._forEach(function (event) { + event.cancel(after); + }); + this._state.cancel(after); return this; }; /** - * Stop the loop at the given time. - * @param {Time=} time When to stop the Arpeggio - * @return {Tone.Loop} this + * Iterate over all of the events + * @param {Function} callback + * @param {Object} ctx The context + * @private */ - Tone.Loop.prototype.stop = function (time) { - this._event.stop(time); + Tone.Part.prototype._forEach = function (callback, ctx) { + ctx = this.defaultArg(ctx, this); + for (var i = this._events.length - 1; i >= 0; i--) { + var e = this._events[i]; + if (e instanceof Tone.Part) { + e._forEach(callback, ctx); + } else { + callback.call(ctx, e); + } + } return this; }; /** - * Cancel all scheduled events greater than or equal to the given time - * @param {Time} [time=0] The time after which events will be cancel. - * @return {Tone.Loop} this + * Set the attribute of all of the events + * @param {String} attr the attribute to set + * @param {*} value The value to set it to + * @private */ - Tone.Loop.prototype.cancel = function (time) { - this._event.cancel(time); - return this; + Tone.Part.prototype._setAll = function (attr, value) { + this._forEach(function (event) { + event[attr] = value; + }); }; /** - * Internal function called when the notes should be called - * @param {Number} time The time the event occurs + * Internal tick method + * @param {Number} time The time of the event in seconds * @private */ - Tone.Loop.prototype._tick = function (time) { - this.callback(time); + Tone.Part.prototype._tick = function (time, value) { + if (!this.mute) { + this.callback(time, value); + } }; /** - * The state of the Loop, either started or stopped. - * @memberOf Tone.Loop# - * @type {String} - * @name state - * @readOnly + * Determine if the event should be currently looping + * given the loop boundries of this Part. + * @param {Tone.Event} event The event to test + * @private */ - Object.defineProperty(Tone.Loop.prototype, 'state', { - get: function () { - return this._event.state; + Tone.Part.prototype._testLoopBoundries = function (event) { + if (event.startOffset < this._loopStart || event.startOffset >= this._loopEnd) { + event.cancel(); + } else { + //reschedule it if it's stopped + if (event.state === Tone.State.Stopped) { + this._restartEvent(event); + } } - }); + }; /** - * The progress of the loop as a value between 0-1. 0, when - * the loop is stopped or done iterating. - * @memberOf Tone.Loop# + * The probability of the notes being triggered. + * @memberOf Tone.Part# * @type {NormalRange} - * @name progress - * @readOnly + * @name probability */ - Object.defineProperty(Tone.Loop.prototype, 'progress', { + Object.defineProperty(Tone.Part.prototype, 'probability', { get: function () { - return this._event.progress; + return this._probability; + }, + set: function (prob) { + this._probability = prob; + this._setAll('probability', prob); } }); /** - * The time between successive callbacks. + * If set to true, will apply small random variation + * to the callback time. If the value is given as a time, it will randomize + * by that amount. * @example - * loop.interval = "8n"; //loop every 8n - * @memberOf Tone.Loop# - * @type {Time} - * @name interval + * event.humanize = true; + * @type {Boolean|Time} + * @name humanize */ - Object.defineProperty(Tone.Loop.prototype, 'interval', { + Object.defineProperty(Tone.Part.prototype, 'humanize', { get: function () { - return this._event.loopEnd; + return this._humanize; }, - set: function (interval) { - this._event.loopEnd = interval; + set: function (variation) { + this._humanize = variation; + this._setAll('humanize', variation); } }); /** - * The playback rate of the loop. The normal playback rate is 1 (no change). - * A `playbackRate` of 2 would be twice as fast. - * @memberOf Tone.Loop# - * @type {Time} - * @name playbackRate + * If the part should loop or not + * between Tone.Part.loopStart and + * Tone.Part.loopEnd. An integer + * value corresponds to the number of + * loops the Part does after it starts. + * @memberOf Tone.Part# + * @type {Boolean|Positive} + * @name loop + * @example + * //loop the part 8 times + * part.loop = 8; */ - Object.defineProperty(Tone.Loop.prototype, 'playbackRate', { + Object.defineProperty(Tone.Part.prototype, 'loop', { get: function () { - return this._event.playbackRate; + return this._loop; }, - set: function (rate) { - this._event.playbackRate = rate; + set: function (loop) { + this._loop = loop; + this._forEach(function (event) { + event._loopStart = this._loopStart; + event._loopEnd = this._loopEnd; + event.loop = loop; + this._testLoopBoundries(event); + }); } }); /** - * Random variation +/-0.01s to the scheduled time. - * Or give it a time value which it will randomize by. - * @type {Boolean|Time} - * @memberOf Tone.Loop# - * @name humanize + * The loopEnd point determines when it will + * loop if Tone.Part.loop is true. + * @memberOf Tone.Part# + * @type {TransportTime} + * @name loopEnd */ - Object.defineProperty(Tone.Loop.prototype, 'humanize', { + Object.defineProperty(Tone.Part.prototype, 'loopEnd', { get: function () { - return this._event.humanize; + return Tone.TransportTime(this._loopEnd, 'i').toNotation(); }, - set: function (variation) { - this._event.humanize = variation; + set: function (loopEnd) { + this._loopEnd = this.toTicks(loopEnd); + if (this._loop) { + this._forEach(function (event) { + event.loopEnd = this.loopEnd; + this._testLoopBoundries(event); + }); + } } }); /** - * The probably of the callback being invoked. - * @memberOf Tone.Loop# - * @type {NormalRange} - * @name probability + * The loopStart point determines when it will + * loop if Tone.Part.loop is true. + * @memberOf Tone.Part# + * @type {TransportTime} + * @name loopStart */ - Object.defineProperty(Tone.Loop.prototype, 'probability', { + Object.defineProperty(Tone.Part.prototype, 'loopStart', { get: function () { - return this._event.probability; + return Tone.TransportTime(this._loopStart, 'i').toNotation(); }, - set: function (prob) { - this._event.probability = prob; + set: function (loopStart) { + this._loopStart = this.toTicks(loopStart); + if (this._loop) { + this._forEach(function (event) { + event.loopStart = this.loopStart; + this._testLoopBoundries(event); + }); + } } }); /** - * Muting the Loop means that no callbacks are invoked. - * @memberOf Tone.Loop# - * @type {Boolean} - * @name mute + * The playback rate of the part + * @memberOf Tone.Part# + * @type {Positive} + * @name playbackRate */ - Object.defineProperty(Tone.Loop.prototype, 'mute', { + Object.defineProperty(Tone.Part.prototype, 'playbackRate', { get: function () { - return this._event.mute; + return this._playbackRate; }, - set: function (mute) { - this._event.mute = mute; + set: function (rate) { + this._playbackRate = rate; + this._setAll('playbackRate', rate); } }); /** - * The number of iterations of the loop. The default - * value is Infinity (loop forever). - * @memberOf Tone.Loop# + * The number of scheduled notes in the part. + * @memberOf Tone.Part# * @type {Positive} - * @name iterations + * @name length + * @readOnly */ - Object.defineProperty(Tone.Loop.prototype, 'iterations', { + Object.defineProperty(Tone.Part.prototype, 'length', { get: function () { - if (this._event.loop === true) { - return Infinity; - } else { - return this._event.loop; - } - return this._pattern.index; - }, - set: function (iters) { - if (iters === Infinity) { - this._event.loop = true; - } else { - this._event.loop = iters; - } + return this._events.length; } }); /** * Clean up - * @return {Tone.Loop} this + * @return {Tone.Part} this */ - Tone.Loop.prototype.dispose = function () { - this._event.dispose(); - this._event = null; + Tone.Part.prototype.dispose = function () { + this.removeAll(); + this._state.dispose(); + this._state = null; this.callback = null; - }; - return Tone.Loop; - }); - Module(function (Tone) { - - /** - * @class Tone.Part is a collection Tone.Events which can be - * started/stoped and looped as a single unit. - * - * @extends {Tone.Event} - * @param {Function} callback The callback to invoke on each event - * @param {Array} events the array of events - * @example - * var part = new Tone.Part(function(time, note){ - * //the notes given as the second element in the array - * //will be passed in as the second argument - * synth.triggerAttackRelease(note, "8n", time); - * }, [[0, "C2"], ["0:2", "C3"], ["0:3:2", "G2"]]); - * @example - * //use an array of objects as long as the object has a "time" attribute - * var part = new Tone.Part(function(time, value){ - * //the value is an object which contains both the note and the velocity - * synth.triggerAttackRelease(value.note, "8n", time, value.velocity); - * }, [{"time" : 0, "note" : "C3", "velocity": 0.9}, - * {"time" : "0:2", "note" : "C4", "velocity": 0.5} - * ]).start(0); - */ - Tone.Part = function () { - var options = this.optionsObject(arguments, [ - 'callback', - 'events' - ], Tone.Part.defaults); - /** - * If the part is looping or not - * @type {Boolean|Positive} - * @private - */ - this._loop = options.loop; - /** - * When the note is scheduled to start. - * @type {Ticks} - * @private - */ - this._loopStart = this.toTicks(options.loopStart); - /** - * When the note is scheduled to start. - * @type {Ticks} - * @private - */ - this._loopEnd = this.toTicks(options.loopEnd); - /** - * The playback rate of the part - * @type {Positive} - * @private - */ - this._playbackRate = options.playbackRate; - /** - * private holder of probability value - * @type {NormalRange} - * @private - */ - this._probability = options.probability; - /** - * the amount of variation from the - * given time. - * @type {Boolean|Time} - * @private - */ - this._humanize = options.humanize; - /** - * The start offset - * @type {Ticks} - * @private - */ - this._startOffset = 0; - /** - * Keeps track of the current state - * @type {Tone.TimelineState} - * @private - */ - this._state = new Tone.TimelineState(Tone.State.Stopped); + this._events = null; + return this; + }; + return Tone.Part; + }); + Module(function (Tone) { + /** + * @class Tone.Pattern arpeggiates between the given notes + * in a number of patterns. See Tone.CtrlPattern for + * a full list of patterns. + * @example + * var pattern = new Tone.Pattern(function(time, note){ + * //the order of the notes passed in depends on the pattern + * }, ["C2", "D4", "E5", "A6"], "upDown"); + * @extends {Tone.Loop} + * @param {Function} callback The callback to invoke with the + * event. + * @param {Array} values The values to arpeggiate over. + */ + Tone.Pattern = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'values', + 'pattern' + ], Tone.Pattern.defaults); + Tone.Loop.call(this, options); /** - * An array of Objects. - * @type {Array} + * The pattern manager + * @type {Tone.CtrlPattern} * @private */ - this._events = []; - /** - * The callback to invoke at all the scheduled events. - * @type {Function} - */ - this.callback = options.callback; - /** - * If mute is true, the callback won't be - * invoked. - * @type {Boolean} - */ - this.mute = options.mute; - //add the events - var events = this.defaultArg(options.events, []); - if (!this.isUndef(options.events)) { - for (var i = 0; i < events.length; i++) { - if (Array.isArray(events[i])) { - this.add(events[i][0], events[i][1]); - } else { - this.add(events[i]); - } - } - } + this._pattern = new Tone.CtrlPattern({ + 'values': options.values, + 'type': options.pattern, + 'index': options.index + }); }; - Tone.extend(Tone.Part, Tone.Event); + Tone.extend(Tone.Pattern, Tone.Loop); /** - * The default values - * @type {Object} + * The defaults * @const + * @type {Object} */ - Tone.Part.defaults = { - 'callback': Tone.noOp, - 'loop': false, - 'loopEnd': '1m', - 'loopStart': 0, - 'playbackRate': 1, - 'probability': 1, - 'humanize': false, - 'mute': false + Tone.Pattern.defaults = { + 'pattern': Tone.CtrlPattern.Type.Up, + 'values': [] }; /** - * Start the part at the given time. - * @param {Time} time When to start the part. - * @param {Time=} offset The offset from the start of the part - * to begin playing at. - * @return {Tone.Part} this + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs + * @private */ - Tone.Part.prototype.start = function (time, offset) { - var ticks = this.toTicks(time); - if (this._state.getStateAtTime(ticks) !== Tone.State.Started) { - offset = this.defaultArg(offset, 0); - offset = this.toTicks(offset); - this._state.addEvent({ - 'state': Tone.State.Started, - 'time': ticks, - 'offset': offset - }); - this._forEach(function (event) { - this._startNote(event, ticks, offset); - }); - } - return this; + Tone.Pattern.prototype._tick = function (time) { + this.callback(time, this._pattern.value); + this._pattern.next(); }; /** - * Start the event in the given event at the correct time given - * the ticks and offset and looping. - * @param {Tone.Event} event - * @param {Ticks} ticks - * @param {Ticks} offset - * @private + * The current index in the values array. + * @memberOf Tone.Pattern# + * @type {Positive} + * @name index */ - Tone.Part.prototype._startNote = function (event, ticks, offset) { - ticks -= offset; - if (this._loop) { - if (event.startOffset >= this._loopStart && event.startOffset < this._loopEnd) { - if (event.startOffset < offset) { - //start it on the next loop - ticks += this._getLoopDuration(); - } - event.start(ticks + 'i'); - } - } else { - if (event.startOffset >= offset) { - event.start(ticks + 'i'); - } + Object.defineProperty(Tone.Pattern.prototype, 'index', { + get: function () { + return this._pattern.index; + }, + set: function (i) { + this._pattern.index = i; } - }; + }); /** - * The start from the scheduled start time - * @type {Ticks} - * @memberOf Tone.Part# - * @name startOffset - * @private + * The array of events. + * @memberOf Tone.Pattern# + * @type {Array} + * @name values */ - Object.defineProperty(Tone.Part.prototype, 'startOffset', { + Object.defineProperty(Tone.Pattern.prototype, 'values', { get: function () { - return this._startOffset; + return this._pattern.values; }, - set: function (offset) { - this._startOffset = offset; - this._forEach(function (event) { - event.startOffset += this._startOffset; - }); + set: function (vals) { + this._pattern.values = vals; } }); /** - * Stop the part at the given time. - * @param {Time} time When to stop the part. - * @return {Tone.Part} this + * The current value of the pattern. + * @memberOf Tone.Pattern# + * @type {*} + * @name value + * @readOnly */ - Tone.Part.prototype.stop = function (time) { - var ticks = this.toTicks(time); - if (this._state.getStateAtTime(ticks) === Tone.State.Started) { - this._state.setStateAtTime(Tone.State.Stopped, ticks); - this._forEach(function (event) { - event.stop(time); - }); + Object.defineProperty(Tone.Pattern.prototype, 'value', { + get: function () { + return this._pattern.value; } - return this; + }); + /** + * The pattern type. See Tone.CtrlPattern for the full list of patterns. + * @memberOf Tone.Pattern# + * @type {String} + * @name pattern + */ + Object.defineProperty(Tone.Pattern.prototype, 'pattern', { + get: function () { + return this._pattern.type; + }, + set: function (pattern) { + this._pattern.type = pattern; + } + }); + /** + * Clean up + * @return {Tone.Pattern} this + */ + Tone.Pattern.prototype.dispose = function () { + Tone.Loop.prototype.dispose.call(this); + this._pattern.dispose(); + this._pattern = null; }; + return Tone.Pattern; + }); + Module(function (Tone) { + /** - * Get/Set an Event's value at the given time. - * If a value is passed in and no event exists at - * the given time, one will be created with that value. - * If two events are at the same time, the first one will - * be returned. + * @class A sequence is an alternate notation of a part. Instead + * of passing in an array of [time, event] pairs, pass + * in an array of events which will be spaced at the + * given subdivision. Sub-arrays will subdivide that beat + * by the number of items are in the array. + * Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/) + * @param {Function} callback The callback to invoke with every note + * @param {Array} events The sequence + * @param {Time} subdivision The subdivision between which events are placed. + * @extends {Tone.Part} * @example - * part.at("1m"); //returns the part at the first measure - * - * part.at("2m", "C2"); //set the value at "2m" to C2. - * //if an event didn't exist at that time, it will be created. - * @param {Time} time the time of the event to get or set - * @param {*=} value If a value is passed in, the value of the - * event at the given time will be set to it. - * @return {Tone.Event} the event at the time + * var seq = new Tone.Sequence(function(time, note){ + * console.log(note); + * //straight quater notes + * }, ["C4", "E4", "G4", "A4"], "4n"); + * @example + * var seq = new Tone.Sequence(function(time, note){ + * console.log(note); + * //subdivisions are given as subarrays + * }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]); */ - Tone.Part.prototype.at = function (time, value) { - time = this.toTicks(time); - var tickTime = this.ticksToSeconds(1); - for (var i = 0; i < this._events.length; i++) { - var event = this._events[i]; - if (Math.abs(time - event.startOffset) < tickTime) { - if (!this.isUndef(value)) { - event.value = value; - } - return event; - } + Tone.Sequence = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'events', + 'subdivision' + ], Tone.Sequence.defaults); + //remove the events + var events = options.events; + delete options.events; + Tone.Part.call(this, options); + /** + * The subdivison of each note + * @type {Ticks} + * @private + */ + this._subdivision = this.toTicks(options.subdivision); + //if no time was passed in, the loop end is the end of the cycle + if (this.isUndef(options.loopEnd) && !this.isUndef(events)) { + this._loopEnd = events.length * this._subdivision; } - //if there was no event at that time, create one - if (!this.isUndef(value)) { - this.add(time + 'i', value); - //return the new event - return this._events[this._events.length - 1]; - } else { - return null; + //defaults to looping + this._loop = true; + //add all of the events + if (!this.isUndef(events)) { + for (var i = 0; i < events.length; i++) { + this.add(i, events[i]); + } } }; + Tone.extend(Tone.Sequence, Tone.Part); /** - * Add a an event to the part. - * @param {Time} time The time the note should start. - * If an object is passed in, it should - * have a 'time' attribute and the rest - * of the object will be used as the 'value'. - * @param {Tone.Event|*} value - * @returns {Tone.Part} this - * @example - * part.add("1m", "C#+11"); + * The default values. + * @type {Object} */ - Tone.Part.prototype.add = function (time, value) { - //extract the parameters - if (this.isObject(time) && time.hasOwnProperty('time')) { - value = time; - time = value.time; - delete value.time; - } - time = this.toTicks(time); - var event; - if (value instanceof Tone.Event) { - event = value; - event.callback = this._tick.bind(this); - } else { - event = new Tone.Event({ - 'callback': this._tick.bind(this), - 'value': value - }); + Tone.Sequence.defaults = { 'subdivision': '4n' }; + /** + * The subdivision of the sequence. This can only be + * set in the constructor. The subdivision is the + * interval between successive steps. + * @type {Time} + * @memberOf Tone.Sequence# + * @name subdivision + * @readOnly + */ + Object.defineProperty(Tone.Sequence.prototype, 'subdivision', { + get: function () { + return Tone.Time(this._subdivision, 'i').toNotation(); } - //the start offset - event.startOffset = time; - //initialize the values - event.set({ - 'loopEnd': this.loopEnd, - 'loopStart': this.loopStart, - 'loop': this.loop, - 'humanize': this.humanize, - 'playbackRate': this.playbackRate, - 'probability': this.probability - }); - this._events.push(event); - //start the note if it should be played right now - this._restartEvent(event); - return this; - }; + }); /** - * Restart the given event - * @param {Tone.Event} event - * @private + * Get/Set an index of the sequence. If the index contains a subarray, + * a Tone.Sequence representing that sub-array will be returned. + * @example + * var sequence = new Tone.Sequence(playNote, ["E4", "C4", "F#4", ["A4", "Bb3"]]) + * sequence.at(0)// => returns "E4" + * //set a value + * sequence.at(0, "G3"); + * //get a nested sequence + * sequence.at(3).at(1)// => returns "Bb3" + * @param {Positive} index The index to get or set + * @param {*} value Optionally pass in the value to set at the given index. */ - Tone.Part.prototype._restartEvent = function (event) { - var stateEvent = this._state.getEvent(this.now()); - if (stateEvent && stateEvent.state === Tone.State.Started) { - this._startNote(event, stateEvent.time, stateEvent.offset); + Tone.Sequence.prototype.at = function (index, value) { + //if the value is an array, + if (this.isArray(value)) { + //remove the current event at that index + this.remove(index); } + //call the parent's method + return Tone.Part.prototype.at.call(this, this._indexTime(index), value); }; /** - * Remove an event from the part. Will recursively iterate - * into nested parts to find the event. - * @param {Time} time The time of the event - * @param {*} value Optionally select only a specific event value + * Add an event at an index, if there's already something + * at that index, overwrite it. If `value` is an array, + * it will be parsed as a subsequence. + * @param {Number} index The index to add the event to + * @param {*} value The value to add at that index + * @returns {Tone.Sequence} this */ - Tone.Part.prototype.remove = function (time, value) { - //extract the parameters - if (this.isObject(time) && time.hasOwnProperty('time')) { - value = time; - time = value.time; + Tone.Sequence.prototype.add = function (index, value) { + if (value === null) { + return this; } - time = this.toTicks(time); - for (var i = this._events.length - 1; i >= 0; i--) { - var event = this._events[i]; - if (event instanceof Tone.Part) { - event.remove(time, value); - } else { - if (event.startOffset === time) { - if (this.isUndef(value) || !this.isUndef(value) && event.value === value) { - this._events.splice(i, 1); - event.dispose(); - } - } - } + if (this.isArray(value)) { + //make a subsequence and add that to the sequence + var subSubdivision = Math.round(this._subdivision / value.length); + value = new Tone.Sequence(this._tick.bind(this), value, Tone.Time(subSubdivision, 'i')); } + Tone.Part.prototype.add.call(this, this._indexTime(index), value); return this; }; /** - * Remove all of the notes from the group. - * @return {Tone.Part} this + * Remove a value from the sequence by index + * @param {Number} index The index of the event to remove + * @returns {Tone.Sequence} this */ - Tone.Part.prototype.removeAll = function () { - this._forEach(function (event) { - event.dispose(); - }); - this._events = []; + Tone.Sequence.prototype.remove = function (index, value) { + Tone.Part.prototype.remove.call(this, this._indexTime(index), value); return this; }; /** - * Cancel scheduled state change events: i.e. "start" and "stop". - * @param {Time} after The time after which to cancel the scheduled events. - * @return {Tone.Part} this + * Get the time of the index given the Sequence's subdivision + * @param {Number} index + * @return {Time} The time of that index + * @private */ - Tone.Part.prototype.cancel = function (after) { - this._forEach(function (event) { - event.cancel(after); - }); - this._state.cancel(after); - return this; + Tone.Sequence.prototype._indexTime = function (index) { + if (index instanceof Tone.TransportTime) { + return index; + } else { + return Tone.TransportTime(index * this._subdivision + this.startOffset, 'i'); + } }; /** - * Iterate over all of the events - * @param {Function} callback - * @param {Object} ctx The context - * @private + * Clean up. + * @return {Tone.Sequence} this */ - Tone.Part.prototype._forEach = function (callback, ctx) { - ctx = this.defaultArg(ctx, this); - for (var i = this._events.length - 1; i >= 0; i--) { - var e = this._events[i]; - if (e instanceof Tone.Part) { - e._forEach(callback, ctx); - } else { - callback.call(ctx, e); - } - } + Tone.Sequence.prototype.dispose = function () { + Tone.Part.prototype.dispose.call(this); return this; }; + return Tone.Sequence; + }); + Module(function (Tone) { + /** - * Set the attribute of all of the events - * @param {String} attr the attribute to set - * @param {*} value The value to set it to - * @private + * @class Tone.PulseOscillator is a pulse oscillator with control over pulse width, + * also known as the duty cycle. At 50% duty cycle (width = 0.5) the wave is + * a square and only odd-numbered harmonics are present. At all other widths + * even-numbered harmonics are present. Read more + * [here](https://wigglewave.wordpress.com/2014/08/16/pulse-waveforms-and-harmonics/). + * + * @constructor + * @extends {Tone.Oscillator} + * @param {Frequency} [frequency] The frequency of the oscillator + * @param {NormalRange} [width] The width of the pulse + * @example + * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start(); */ - Tone.Part.prototype._setAll = function (attr, value) { - this._forEach(function (event) { - event[attr] = value; + Tone.PulseOscillator = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'width' + ], Tone.Oscillator.defaults); + Tone.Source.call(this, options); + /** + * The width of the pulse. + * @type {NormalRange} + * @signal + */ + this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); + /** + * gate the width amount + * @type {GainNode} + * @private + */ + this._widthGate = this.context.createGain(); + /** + * the sawtooth oscillator + * @type {Tone.Oscillator} + * @private + */ + this._sawtooth = new Tone.Oscillator({ + frequency: options.frequency, + detune: options.detune, + type: 'sawtooth', + phase: options.phase + }); + /** + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = this._sawtooth.frequency; + /** + * The detune in cents. + * @type {Cents} + * @signal + */ + this.detune = this._sawtooth.detune; + /** + * Threshold the signal to turn it into a square + * @type {Tone.WaveShaper} + * @private + */ + this._thresh = new Tone.WaveShaper(function (val) { + if (val < 0) { + return -1; + } else { + return 1; + } }); + //connections + this._sawtooth.chain(this._thresh, this.output); + this.width.chain(this._widthGate, this._thresh); + this._readOnly([ + 'width', + 'frequency', + 'detune' + ]); + }; + Tone.extend(Tone.PulseOscillator, Tone.Oscillator); + /** + * The default parameters. + * @static + * @const + * @type {Object} + */ + Tone.PulseOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'width': 0.2 }; /** - * Internal tick method - * @param {Number} time The time of the event in seconds + * start the oscillator + * @param {Time} time * @private */ - Tone.Part.prototype._tick = function (time, value) { - if (!this.mute) { - this.callback(time, value); - } + Tone.PulseOscillator.prototype._start = function (time) { + time = this.toSeconds(time); + this._sawtooth.start(time); + this._widthGate.gain.setValueAtTime(1, time); }; /** - * Determine if the event should be currently looping - * given the loop boundries of this Part. - * @param {Tone.Event} event The event to test + * stop the oscillator + * @param {Time} time * @private */ - Tone.Part.prototype._testLoopBoundries = function (event) { - if (event.startOffset < this._loopStart || event.startOffset >= this._loopEnd) { - event.cancel(); - } else { - //reschedule it if it's stopped - if (event.state === Tone.State.Stopped) { - this._restartEvent(event); - } - } + 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 probability of the notes being triggered. - * @memberOf Tone.Part# - * @type {NormalRange} - * @name probability - */ - Object.defineProperty(Tone.Part.prototype, 'probability', { - get: function () { - return this._probability; - }, - set: function (prob) { - this._probability = prob; - this._setAll('probability', prob); - } - }); - /** - * If set to true, will apply small random variation - * to the callback time. If the value is given as a time, it will randomize - * by that amount. - * @example - * event.humanize = true; - * @type {Boolean|Time} - * @name humanize - */ - Object.defineProperty(Tone.Part.prototype, 'humanize', { - get: function () { - return this._humanize; - }, - set: function (variation) { - this._humanize = variation; - this._setAll('humanize', variation); - } - }); - /** - * If the part should loop or not - * between Tone.Part.loopStart and - * Tone.Part.loopEnd. An integer - * value corresponds to the number of - * loops the Part does after it starts. - * @memberOf Tone.Part# - * @type {Boolean|Positive} - * @name loop - * @example - * //loop the part 8 times - * part.loop = 8; - */ - Object.defineProperty(Tone.Part.prototype, 'loop', { - get: function () { - return this._loop; - }, - set: function (loop) { - this._loop = loop; - this._forEach(function (event) { - event._loopStart = this._loopStart; - event._loopEnd = this._loopEnd; - event.loop = loop; - this._testLoopBoundries(event); - }); - } - }); - /** - * The loopEnd point determines when it will - * loop if Tone.Part.loop is true. - * @memberOf Tone.Part# - * @type {Boolean|Positive} - * @name loopEnd - */ - Object.defineProperty(Tone.Part.prototype, 'loopEnd', { - get: function () { - return this.toNotation(this._loopEnd + 'i'); - }, - set: function (loopEnd) { - this._loopEnd = this.toTicks(loopEnd); - if (this._loop) { - this._forEach(function (event) { - event.loopEnd = this.loopEnd; - this._testLoopBoundries(event); - }); - } - } - }); - /** - * The loopStart point determines when it will - * loop if Tone.Part.loop is true. - * @memberOf Tone.Part# - * @type {Boolean|Positive} - * @name loopStart + * The phase of the oscillator in degrees. + * @memberOf Tone.PulseOscillator# + * @type {Degrees} + * @name phase */ - Object.defineProperty(Tone.Part.prototype, 'loopStart', { + Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', { get: function () { - return this.toNotation(this._loopStart + 'i'); + return this._sawtooth.phase; }, - set: function (loopStart) { - this._loopStart = this.toTicks(loopStart); - if (this._loop) { - this._forEach(function (event) { - event.loopStart = this.loopStart; - this._testLoopBoundries(event); - }); - } + set: function (phase) { + this._sawtooth.phase = phase; } }); /** - * The playback rate of the part - * @memberOf Tone.Part# - * @type {Positive} - * @name playbackRate + * The type of the oscillator. Always returns "pulse". + * @readOnly + * @memberOf Tone.PulseOscillator# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Part.prototype, 'playbackRate', { + Object.defineProperty(Tone.PulseOscillator.prototype, 'type', { get: function () { - return this._playbackRate; - }, - set: function (rate) { - this._playbackRate = rate; - this._setAll('playbackRate', rate); + return 'pulse'; } }); /** - * The number of scheduled notes in the part. - * @memberOf Tone.Part# - * @type {Positive} - * @name length - * @readOnly + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PulseOscillator# + * @type {Array} + * @name partials + * @private */ - Object.defineProperty(Tone.Part.prototype, 'length', { + Object.defineProperty(Tone.PulseOscillator.prototype, 'partials', { get: function () { - return this._events.length; + return []; } }); /** - * Clean up - * @return {Tone.Part} this + * Clean up method. + * @return {Tone.PulseOscillator} this */ - Tone.Part.prototype.dispose = function () { - this.removeAll(); - this._state.dispose(); - this._state = null; - this.callback = null; - this._events = 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.Part; + return Tone.PulseOscillator; }); Module(function (Tone) { + /** - * @class Tone.Pattern arpeggiates between the given notes - * in a number of patterns. See Tone.CtrlPattern for - * a full list of patterns. + * @class Tone.PWMOscillator modulates the width of a Tone.PulseOscillator + * at the modulationFrequency. This has the effect of continuously + * changing the timbre of the oscillator by altering the harmonics + * generated. + * + * @extends {Tone.Oscillator} + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse. * @example - * var pattern = new Tone.Pattern(function(time, note){ - * //the order of the notes passed in depends on the pattern - * }, ["C2", "D4", "E5", "A6"], "upDown"); - * @extends {Tone.Loop} - * @param {Function} callback The callback to invoke with the - * event. - * @param {Array} events The events to arpeggiate over. + * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start(); */ - Tone.Pattern = function () { + Tone.PWMOscillator = function () { var options = this.optionsObject(arguments, [ - 'callback', - 'events', - 'pattern' - ], Tone.Pattern.defaults); - Tone.Loop.call(this, options); + 'frequency', + 'modulationFrequency' + ], Tone.PWMOscillator.defaults); + Tone.Source.call(this, options); /** - * The pattern manager - * @type {Tone.CtrlPattern} + * the pulse oscillator + * @type {Tone.PulseOscillator} + * @private + */ + this._pulse = new Tone.PulseOscillator(options.modulationFrequency); + //change the pulse oscillator type + this._pulse._sawtooth.type = 'sine'; + /** + * the modulator + * @type {Tone.Oscillator} + * @private + */ + this._modulator = new Tone.Oscillator({ + 'frequency': options.frequency, + 'detune': options.detune, + 'phase': options.phase + }); + /** + * Scale the oscillator so it doesn't go silent + * at the extreme values. + * @type {Tone.Multiply} * @private */ - this._pattern = new Tone.CtrlPattern({ - 'values': options.events, - 'type': options.pattern, - 'index': options.index - }); + this._scale = new Tone.Multiply(2); + /** + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = this._modulator.frequency; + /** + * The detune of the oscillator. + * @type {Cents} + * @signal + */ + this.detune = this._modulator.detune; + /** + * The modulation rate of the oscillator. + * @type {Frequency} + * @signal + */ + this.modulationFrequency = this._pulse.frequency; + //connections + this._modulator.chain(this._scale, this._pulse.width); + this._pulse.connect(this.output); + this._readOnly([ + 'modulationFrequency', + 'frequency', + 'detune' + ]); }; - Tone.extend(Tone.Pattern, Tone.Loop); + Tone.extend(Tone.PWMOscillator, Tone.Oscillator); /** - * The defaults + * default values + * @static + * @type {Object} * @const - * @type {Object} */ - Tone.Pattern.defaults = { - 'pattern': Tone.CtrlPattern.Type.Up, - 'events': [] + Tone.PWMOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'modulationFrequency': 0.4 }; /** - * Internal function called when the notes should be called - * @param {Number} time The time the event occurs + * start the oscillator + * @param {Time} [time=now] * @private */ - Tone.Pattern.prototype._tick = function (time) { - this.callback(time, this._pattern.value); - this._pattern.next(); + Tone.PWMOscillator.prototype._start = function (time) { + time = this.toSeconds(time); + this._modulator.start(time); + this._pulse.start(time); }; /** - * The current index in the events array. - * @memberOf Tone.Pattern# - * @type {Positive} - * @name index + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private */ - Object.defineProperty(Tone.Pattern.prototype, 'index', { - get: function () { - return this._pattern.index; - }, - set: function (i) { - this._pattern.index = i; - } - }); + Tone.PWMOscillator.prototype._stop = function (time) { + time = this.toSeconds(time); + this._modulator.stop(time); + this._pulse.stop(time); + }; /** - * The array of events. - * @memberOf Tone.Pattern# - * @type {Array} - * @name events + * The type of the oscillator. Always returns "pwm". + * @readOnly + * @memberOf Tone.PWMOscillator# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Pattern.prototype, 'events', { + Object.defineProperty(Tone.PWMOscillator.prototype, 'type', { get: function () { - return this._pattern.values; - }, - set: function (vals) { - this._pattern.values = vals; + return 'pwm'; } }); /** - * The current value of the pattern. - * @memberOf Tone.Pattern# - * @type {*} - * @name value - * @readOnly + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PWMOscillator# + * @type {Array} + * @name partials + * @private */ - Object.defineProperty(Tone.Pattern.prototype, 'value', { + Object.defineProperty(Tone.PWMOscillator.prototype, 'partials', { get: function () { - return this._pattern.value; + return []; } }); /** - * The pattern type. See Tone.CtrlPattern for the full list of patterns. - * @memberOf Tone.Pattern# - * @type {String} - * @name pattern + * The phase of the oscillator in degrees. + * @memberOf Tone.PWMOscillator# + * @type {number} + * @name phase */ - Object.defineProperty(Tone.Pattern.prototype, 'pattern', { + Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', { get: function () { - return this._pattern.type; + return this._modulator.phase; }, - set: function (pattern) { - this._pattern.type = pattern; + set: function (phase) { + this._modulator.phase = phase; } }); /** - * Clean up - * @return {Tone.Pattern} this + * Clean up. + * @return {Tone.PWMOscillator} this */ - Tone.Pattern.prototype.dispose = function () { - Tone.Loop.prototype.dispose.call(this); - this._pattern.dispose(); - this._pattern = null; + Tone.PWMOscillator.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + this._pulse.dispose(); + this._pulse = null; + this._scale.dispose(); + this._scale = null; + this._modulator.dispose(); + this._modulator = null; + this._writable([ + 'modulationFrequency', + 'frequency', + 'detune' + ]); + this.frequency = null; + this.detune = null; + this.modulationFrequency = null; + return this; }; - return Tone.Pattern; + return Tone.PWMOscillator; }); Module(function (Tone) { /** - * @class A sequence is an alternate notation of a part. Instead - * of passing in an array of [time, event] pairs, pass - * in an array of events which will be spaced at the - * given subdivision. Sub-arrays will subdivide that beat - * by the number of items are in the array. - * Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/) - * @param {Function} callback The callback to invoke with every note - * @param {Array} events The sequence - * @param {Time} subdivision The subdivision between which events are placed. - * @extends {Tone.Part} - * @example - * var seq = new Tone.Sequence(function(time, note){ - * console.log(note); - * //straight quater notes - * }, ["C4", "E4", "G4", "A4"], "4n"); + * @class Tone.FMOscillator + * + * @extends {Tone.Oscillator} + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. * @example - * var seq = new Tone.Sequence(function(time, note){ - * console.log(note); - * //subdivisions are given as subarrays - * }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]); + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.FMOscillator("Ab3", "sine", "square").toMaster().start(); */ - Tone.Sequence = function () { + Tone.FMOscillator = function () { var options = this.optionsObject(arguments, [ - 'callback', - 'events', - 'subdivision' - ], Tone.Sequence.defaults); - //remove the events - var events = options.events; - delete options.events; - Tone.Part.call(this, options); + 'frequency', + 'type', + 'modulationType' + ], Tone.FMOscillator.defaults); + Tone.Source.call(this, options); /** - * The subdivison of each note - * @type {Ticks} + * The carrier oscillator + * @type {Tone.Oscillator} * @private */ - this._subdivision = this.toTicks(options.subdivision); - //if no time was passed in, the loop end is the end of the cycle - if (this.isUndef(options.loopEnd) && !this.isUndef(events)) { - this._loopEnd = events.length * this._subdivision; - } - //defaults to looping - this._loop = true; - //add all of the events - if (!this.isUndef(events)) { - for (var i = 0; i < events.length; i++) { - this.add(i, events[i]); - } - } + this._carrier = new Tone.Oscillator(options.frequency, options.type); + /** + * The oscillator's frequency + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + /** + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = this._carrier.detune; + this.detune.value = options.detune; + /** + * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the + * ratio of the frequency of the modulating signal (mf) to the amplitude of the + * modulating signal (ma) -- as in ma/mf. + * @type {Positive} + * @signal + */ + this.modulationIndex = new Tone.Multiply(options.modulationIndex); + this.modulationIndex.units = Tone.Type.Positive; + /** + * The modulating oscillator + * @type {Tone.Oscillator} + * @private + */ + this._modulator = new Tone.Oscillator(options.frequency, options.modulationType); + /** + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. + * @type {Positive} + * @signal + * @example + * //pitch the modulator an octave below carrier + * synth.harmonicity.value = 0.5; + */ + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; + /** + * the node where the modulation happens + * @type {Tone.Gain} + * @private + */ + this._modulationNode = new Tone.Gain(0); + //connections + 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.connect(this._carrier.frequency); + this._carrier.connect(this.output); + this.detune.connect(this._modulator.detune); + this.phase = options.phase; + this._readOnly([ + 'modulationIndex', + 'frequency', + 'detune', + 'harmonicity' + ]); + }; + Tone.extend(Tone.FMOscillator, Tone.Oscillator); + /** + * default values + * @static + * @type {Object} + * @const + */ + Tone.FMOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'modulationIndex': 2, + 'modulationType': 'square', + 'harmonicity': 1 + }; + /** + * start the oscillator + * @param {Time} [time=now] + * @private + */ + Tone.FMOscillator.prototype._start = function (time) { + time = this.toSeconds(time); + this._modulator.start(time); + this._carrier.start(time); }; - Tone.extend(Tone.Sequence, Tone.Part); /** - * The default values. - * @type {Object} + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private */ - Tone.Sequence.defaults = { 'subdivision': '4n' }; + Tone.FMOscillator.prototype._stop = function (time) { + time = this.toSeconds(time); + this._modulator.stop(time); + this._carrier.stop(time); + }; /** - * The subdivision of the sequence. This can only be - * set in the constructor. The subdivision is the - * interval between successive steps. - * @type {Time} - * @memberOf Tone.Sequence# - * @name subdivision - * @readOnly + * The type of the carrier oscillator + * @memberOf Tone.FMOscillator# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Sequence.prototype, 'subdivision', { + Object.defineProperty(Tone.FMOscillator.prototype, 'type', { get: function () { - return this.toNotation(this._subdivision + 'i'); + return this._carrier.type; + }, + set: function (type) { + this._carrier.type = type; } }); /** - * Get/Set an index of the sequence. If the index contains a subarray, - * a Tone.Sequence representing that sub-array will be returned. - * @example - * var sequence = new Tone.Sequence(playNote, ["E4", "C4", "F#4", ["A4", "Bb3"]]) - * sequence.at(0)// => returns "E4" - * //set a value - * sequence.at(0, "G3"); - * //get a nested sequence - * sequence.at(3).at(1)// => returns "Bb3" - * @param {Positive} index The index to get or set - * @param {*} value Optionally pass in the value to set at the given index. + * The type of the modulator oscillator + * @memberOf Tone.FMOscillator# + * @type {String} + * @name modulationType */ - Tone.Sequence.prototype.at = function (index, value) { - //if the value is an array, - if (this.isArray(value)) { - //remove the current event at that index - this.remove(index); + Object.defineProperty(Tone.FMOscillator.prototype, 'modulationType', { + get: function () { + return this._modulator.type; + }, + set: function (type) { + this._modulator.type = type; } - //call the parent's method - return Tone.Part.prototype.at.call(this, this._indexTime(index), value); - }; + }); /** - * Add an event at an index, if there's already something - * at that index, overwrite it. If `value` is an array, - * it will be parsed as a subsequence. - * @param {Number} index The index to add the event to - * @param {*} value The value to add at that index - * @returns {Tone.Sequence} this + * The phase of the oscillator in degrees. + * @memberOf Tone.FMOscillator# + * @type {number} + * @name phase */ - Tone.Sequence.prototype.add = function (index, value) { - if (value === null) { - return this; - } - if (this.isArray(value)) { - //make a subsequence and add that to the sequence - var subSubdivision = Math.round(this._subdivision / value.length) + 'i'; - value = new Tone.Sequence(this._tick.bind(this), value, subSubdivision); + Object.defineProperty(Tone.FMOscillator.prototype, 'phase', { + get: function () { + return this._carrier.phase; + }, + set: function (phase) { + this._carrier.phase = phase; + this._modulator.phase = phase; } - Tone.Part.prototype.add.call(this, this._indexTime(index), value); - return this; - }; - /** - * Remove a value from the sequence by index - * @param {Number} index The index of the event to remove - * @returns {Tone.Sequence} this - */ - Tone.Sequence.prototype.remove = function (index, value) { - Tone.Part.prototype.remove.call(this, this._indexTime(index), value); - return this; - }; + }); /** - * Get the time of the index given the Sequence's subdivision - * @param {Number} index - * @return {Time} The time of that index - * @private + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.FMOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; */ - Tone.Sequence.prototype._indexTime = function (index) { - if (this.isTicks(index)) { - return index; - } else { - return index * this._subdivision + this.startOffset + 'i'; + Object.defineProperty(Tone.FMOscillator.prototype, 'partials', { + get: function () { + return this._carrier.partials; + }, + set: function (partials) { + this._carrier.partials = partials; } - }; + }); /** * Clean up. - * @return {Tone.Sequence} this + * @return {Tone.FMOscillator} this */ - Tone.Sequence.prototype.dispose = function () { - Tone.Part.prototype.dispose.call(this); + Tone.FMOscillator.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + this._writable([ + 'modulationIndex', + 'frequency', + 'detune', + 'harmonicity' + ]); + this.frequency.dispose(); + this.frequency = null; + this.detune = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; + this._modulationNode.dispose(); + this._modulationNode = null; + this.modulationIndex.dispose(); + this.modulationIndex = null; return this; }; - return Tone.Sequence; + return Tone.FMOscillator; }); Module(function (Tone) { /** - * @class Tone.PulseOscillator is a pulse oscillator with control over pulse width, - * also known as the duty cycle. At 50% duty cycle (width = 0.5) the wave is - * a square and only odd-numbered harmonics are present. At all other widths - * even-numbered harmonics are present. Read more - * [here](https://wigglewave.wordpress.com/2014/08/16/pulse-waveforms-and-harmonics/). + * @class Tone.AMOscillator * - * @constructor * @extends {Tone.Oscillator} - * @param {Frequency} [frequency] The frequency of the oscillator - * @param {NormalRange} [width] The width of the pulse + * @constructor + * @param {Frequency} frequency The starting frequency of the oscillator. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. * @example - * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start(); + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.AMOscillator("Ab3", "sine", "square").toMaster().start(); */ - Tone.PulseOscillator = function () { + Tone.AMOscillator = function () { var options = this.optionsObject(arguments, [ 'frequency', - 'width' - ], Tone.Oscillator.defaults); + 'type', + 'modulationType' + ], Tone.AMOscillator.defaults); Tone.Source.call(this, options); /** - * The width of the pulse. - * @type {NormalRange} + * The carrier oscillator + * @type {Tone.Oscillator} + * @private + */ + this._carrier = new Tone.Oscillator(options.frequency, options.type); + /** + * The oscillator's frequency + * @type {Frequency} * @signal */ - this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); + this.frequency = this._carrier.frequency; /** - * gate the width amount - * @type {GainNode} - * @private + * The detune control signal. + * @type {Cents} + * @signal */ - this._widthGate = this.context.createGain(); + this.detune = this._carrier.detune; + this.detune.value = options.detune; /** - * the sawtooth oscillator - * @type {Tone.Oscillator} + * The modulating oscillator + * @type {Tone.Oscillator} * @private */ - this._sawtooth = new Tone.Oscillator({ - frequency: options.frequency, - detune: options.detune, - type: 'sawtooth', - phase: options.phase - }); + this._modulator = new Tone.Oscillator(options.frequency, options.modulationType); /** - * The frequency control. - * @type {Frequency} - * @signal + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} + * @private */ - this.frequency = this._sawtooth.frequency; + this._modulationScale = new Tone.AudioToGain(); /** - * The detune in cents. - * @type {Cents} + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. + * @type {Positive} * @signal + * @example + * //pitch the modulator an octave below carrier + * synth.harmonicity.value = 0.5; */ - this.detune = this._sawtooth.detune; + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; /** - * Threshold the signal to turn it into a square - * @type {Tone.WaveShaper} + * the node where the modulation happens + * @type {Tone.Gain} * @private */ - this._thresh = new Tone.WaveShaper(function (val) { - if (val < 0) { - return -1; - } else { - return 1; - } - }); + this._modulationNode = new Tone.Gain(0); //connections - this._sawtooth.chain(this._thresh, this.output); - this.width.chain(this._widthGate, this._thresh); + this.frequency.chain(this.harmonicity, this._modulator.frequency); + this.detune.connect(this._modulator.detune); + this._modulator.chain(this._modulationScale, this._modulationNode.gain); + this._carrier.chain(this._modulationNode, this.output); + this.phase = options.phase; this._readOnly([ - 'width', 'frequency', - 'detune' + 'detune', + 'harmonicity' ]); }; - Tone.extend(Tone.PulseOscillator, Tone.Oscillator); + Tone.extend(Tone.AMOscillator, Tone.Oscillator); /** - * The default parameters. + * default values * @static - * @const * @type {Object} + * @const */ - Tone.PulseOscillator.defaults = { + Tone.AMOscillator.defaults = { 'frequency': 440, 'detune': 0, 'phase': 0, - 'width': 0.2 + 'modulationType': 'square', + 'harmonicity': 1 }; /** * start the oscillator - * @param {Time} time + * @param {Time} [time=now] * @private */ - Tone.PulseOscillator.prototype._start = function (time) { + Tone.AMOscillator.prototype._start = function (time) { time = this.toSeconds(time); - this._sawtooth.start(time); - this._widthGate.gain.setValueAtTime(1, time); + this._modulator.start(time); + this._carrier.start(time); }; /** * stop the oscillator - * @param {Time} time + * @param {Time} time (optional) timing parameter * @private */ - Tone.PulseOscillator.prototype._stop = function (time) { + Tone.AMOscillator.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); + this._modulator.stop(time); + this._carrier.stop(time); }; /** - * The phase of the oscillator in degrees. - * @memberOf Tone.PulseOscillator# - * @type {Degrees} - * @name phase + * The type of the carrier oscillator + * @memberOf Tone.AMOscillator# + * @type {string} + * @name type */ - Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', { + Object.defineProperty(Tone.AMOscillator.prototype, 'type', { get: function () { - return this._sawtooth.phase; + return this._carrier.type; }, - set: function (phase) { - this._sawtooth.phase = phase; + set: function (type) { + this._carrier.type = type; } }); /** - * The type of the oscillator. Always returns "pulse". - * @readOnly - * @memberOf Tone.PulseOscillator# + * The type of the modulator oscillator + * @memberOf Tone.AMOscillator# * @type {string} - * @name type + * @name modulationType */ - Object.defineProperty(Tone.PulseOscillator.prototype, 'type', { + Object.defineProperty(Tone.AMOscillator.prototype, 'modulationType', { get: function () { - return 'pulse'; + return this._modulator.type; + }, + set: function (type) { + this._modulator.type = type; } }); /** - * The partials of the waveform. Cannot set partials for this waveform type - * @memberOf Tone.PulseOscillator# + * The phase of the oscillator in degrees. + * @memberOf Tone.AMOscillator# + * @type {number} + * @name phase + */ + Object.defineProperty(Tone.AMOscillator.prototype, 'phase', { + get: function () { + return this._carrier.phase; + }, + set: function (phase) { + this._carrier.phase = phase; + this._modulator.phase = phase; + } + }); + /** + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.AMOscillator# * @type {Array} * @name partials - * @private + * @example + * osc.partials = [1, 0.2, 0.01]; */ - Object.defineProperty(Tone.PulseOscillator.prototype, 'partials', { + Object.defineProperty(Tone.AMOscillator.prototype, 'partials', { get: function () { - return []; + return this._carrier.partials; + }, + set: function (partials) { + this._carrier.partials = partials; } }); /** - * Clean up method. - * @return {Tone.PulseOscillator} this + * Clean up. + * @return {Tone.AMOscillator} this */ - Tone.PulseOscillator.prototype.dispose = function () { + Tone.AMOscillator.prototype.dispose = function () { Tone.Source.prototype.dispose.call(this); - this._sawtooth.dispose(); - this._sawtooth = null; this._writable([ - 'width', 'frequency', - 'detune' + 'detune', + 'harmonicity' ]); - 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.harmonicity.dispose(); + this.harmonicity = null; + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; + this._modulationNode.dispose(); + this._modulationNode = null; + this._modulationScale.dispose(); + this._modulationScale = null; return this; }; - return Tone.PulseOscillator; + return Tone.AMOscillator; }); Module(function (Tone) { /** - * @class Tone.PWMOscillator modulates the width of a Tone.PulseOscillator - * at the modulationFrequency. This has the effect of continuously - * changing the timbre of the oscillator by altering the harmonics - * generated. + * @class Tone.FatOscillator * * @extends {Tone.Oscillator} * @constructor * @param {Frequency} frequency The starting frequency of the oscillator. - * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse. + * @param {String} type The type of the carrier oscillator. + * @param {String} modulationType The type of the modulator oscillator. * @example - * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start(); + * //a sine oscillator frequency-modulated by a square wave + * var fmOsc = new Tone.FatOscillator("Ab3", "sine", "square").toMaster().start(); */ - Tone.PWMOscillator = function () { + Tone.FatOscillator = function () { var options = this.optionsObject(arguments, [ 'frequency', - 'modulationFrequency' - ], Tone.PWMOscillator.defaults); + 'type', + 'spread' + ], Tone.FatOscillator.defaults); Tone.Source.call(this, options); /** - * the pulse oscillator - * @type {Tone.PulseOscillator} - * @private + * The oscillator's frequency + * @type {Frequency} + * @signal */ - this._pulse = new Tone.PulseOscillator(options.modulationFrequency); - //change the pulse oscillator type - this._pulse._sawtooth.type = 'sine'; + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * the modulator - * @type {Tone.Oscillator} + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + /** + * The array of oscillators + * @type {Array} * @private */ - this._modulator = new Tone.Oscillator({ - 'frequency': options.frequency, - 'detune': options.detune, - 'phase': options.phase - }); + this._oscillators = []; /** - * Scale the oscillator so it doesn't go silent - * at the extreme values. - * @type {Tone.Multiply} + * The total spread of the oscillators + * @type {Cents} * @private */ - this._scale = new Tone.Multiply(1.01); + this._spread = options.spread; /** - * The frequency control. - * @type {Frequency} - * @signal + * The type of the oscillator + * @type {String} + * @private */ - this.frequency = this._modulator.frequency; + this._type = options.type; /** - * The detune of the oscillator. - * @type {Cents} - * @signal + * The phase of the oscillators + * @type {Degrees} + * @private */ - this.detune = this._modulator.detune; + this._phase = options.phase; /** - * The modulation rate of the oscillator. - * @type {Frequency} - * @signal + * The partials array + * @type {Array} + * @private */ - this.modulationFrequency = this._pulse.frequency; - //connections - this._modulator.chain(this._scale, this._pulse.width); - this._pulse.connect(this.output); + this._partials = this.defaultArg(options.partials, []); + //set the count initially + this.count = options.count; this._readOnly([ - 'modulationFrequency', 'frequency', 'detune' ]); }; - Tone.extend(Tone.PWMOscillator, Tone.Oscillator); + Tone.extend(Tone.FatOscillator, Tone.Oscillator); /** * default values * @static * @type {Object} * @const */ - Tone.PWMOscillator.defaults = { + Tone.FatOscillator.defaults = { 'frequency': 440, 'detune': 0, 'phase': 0, - 'modulationFrequency': 0.4 + 'spread': 20, + 'count': 3, + 'type': 'sawtooth' }; /** * start the oscillator * @param {Time} [time=now] * @private */ - Tone.PWMOscillator.prototype._start = function (time) { + Tone.FatOscillator.prototype._start = function (time) { time = this.toSeconds(time); - this._modulator.start(time); - this._pulse.start(time); + this._forEach(function (osc) { + osc.start(time); + }); }; /** * stop the oscillator * @param {Time} time (optional) timing parameter * @private */ - Tone.PWMOscillator.prototype._stop = function (time) { + Tone.FatOscillator.prototype._stop = function (time) { time = this.toSeconds(time); - this._modulator.stop(time); - this._pulse.stop(time); + this._forEach(function (osc) { + osc.stop(time); + }); }; /** - * The type of the oscillator. Always returns "pwm". - * @readOnly - * @memberOf Tone.PWMOscillator# + * Iterate over all of the oscillators + * @param {Function} iterator The iterator function + * @private + */ + Tone.FatOscillator.prototype._forEach = function (iterator) { + for (var i = 0; i < this._oscillators.length; i++) { + iterator.call(this, this._oscillators[i], i); + } + }; + /** + * The type of the carrier oscillator + * @memberOf Tone.FatOscillator# * @type {string} * @name type */ - Object.defineProperty(Tone.PWMOscillator.prototype, 'type', { + Object.defineProperty(Tone.FatOscillator.prototype, 'type', { get: function () { - return 'pwm'; + return this._type; + }, + set: function (type) { + this._type = type; + this._forEach(function (osc) { + osc.type = type; + }); } }); /** - * The partials of the waveform. Cannot set partials for this waveform type - * @memberOf Tone.PWMOscillator# - * @type {Array} - * @name partials - * @private + * The detune spread between the oscillators. If "count" is + * set to 3 oscillators and the "spread" is set to 40, + * the three oscillators would be detuned like this: [-20, 0, 20] + * for a total detune spread of 40 cents. + * @memberOf Tone.FatOscillator# + * @type {Cents} + * @name spread */ - Object.defineProperty(Tone.PWMOscillator.prototype, 'partials', { + Object.defineProperty(Tone.FatOscillator.prototype, 'spread', { get: function () { - return []; + return this._spread; + }, + set: function (spread) { + this._spread = spread; + if (this._oscillators.length > 1) { + var start = -spread / 2; + var step = spread / (this._oscillators.length - 1); + this._forEach(function (osc, i) { + osc.detune.value = start + step * i; + }); + } + } + }); + /** + * The number of detuned oscillators + * @memberOf Tone.FatOscillator# + * @type {Number} + * @name count + */ + Object.defineProperty(Tone.FatOscillator.prototype, 'count', { + get: function () { + return this._oscillators.length; + }, + set: function (count) { + count = Math.max(count, 1); + if (this._oscillators.length !== count) { + // var partials = this.partials; + // var type = this.type; + //dispose the previous oscillators + this._forEach(function (osc) { + osc.dispose(); + }); + this._oscillators = []; + for (var i = 0; i < count; i++) { + var osc = new Tone.Oscillator(); + if (this.type === Tone.Oscillator.Type.Custom) { + osc.partials = this._partials; + } else { + osc.type = this._type; + } + osc.phase = this._phase; + osc.volume.value = -6 - count; + this.frequency.connect(osc.frequency); + this.detune.connect(osc.detune); + osc.connect(this.output); + this._oscillators[i] = osc; + } + //set the spread + this.spread = this._spread; + if (this.state === Tone.State.Started) { + this._forEach(function (osc) { + osc.start(); + }); + } + } } }); /** * The phase of the oscillator in degrees. - * @memberOf Tone.PWMOscillator# - * @type {number} + * @memberOf Tone.FatOscillator# + * @type {Number} * @name phase */ - Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', { + Object.defineProperty(Tone.FatOscillator.prototype, 'phase', { get: function () { - return this._modulator.phase; + return this._phase; }, set: function (phase) { - this._modulator.phase = phase; + this._phase = phase; + this._forEach(function (osc) { + osc.phase = phase; + }); + } + }); + /** + * The partials of the carrier waveform. A partial represents + * the amplitude at a harmonic. The first harmonic is the + * fundamental frequency, the second is the octave and so on + * following the harmonic series. + * Setting this value will automatically set the type to "custom". + * The value is an empty array when the type is not "custom". + * @memberOf Tone.FatOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.FatOscillator.prototype, 'partials', { + get: function () { + return this._partials; + }, + set: function (partials) { + this._partials = partials; + this._type = Tone.Oscillator.Type.Custom; + this._forEach(function (osc) { + osc.partials = partials; + }); } }); /** * Clean up. - * @return {Tone.PWMOscillator} this + * @return {Tone.FatOscillator} this */ - Tone.PWMOscillator.prototype.dispose = function () { + Tone.FatOscillator.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.dispose(); this.frequency = null; + this.detune.dispose(); this.detune = null; - this.modulationFrequency = null; + this._forEach(function (osc) { + osc.dispose(); + }); + this._oscillators = null; + this._partials = null; return this; }; - return Tone.PWMOscillator; + return Tone.FatOscillator; }); Module(function (Tone) { /** * @class Tone.OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator, - * and Tone.PWMOscillator into one class, allowing it to have the - * types: sine, square, triangle, sawtooth, pulse or pwm. Additionally, - * OmniOscillator is capable of setting the first x number of partials - * of the oscillator. For example: "sine4" would set be the first 4 - * partials of the sine wave and "triangle8" would set the first - * 8 partials of the triangle wave. + * Tone.PWMOscillator, Tone.FMOscillator, Tone.AMOscillator, and Tone.FatOscillator + * into one class. The oscillator class can be changed by setting the `type`. + * `omniOsc.type = "pwm"` will set it to the Tone.PWMOscillator. Prefixing + * any of the basic types ("sine", "square4", etc.) with "fm", "am", or "fat" + * will use the FMOscillator, AMOscillator or FatOscillator respectively. + * For example: `omniOsc.type = "fatsawtooth"` will create set the oscillator + * to a FatOscillator of type "sawtooth". * * @extends {Tone.Oscillator} * @constructor * @param {Frequency} frequency The initial frequency of the oscillator. - * @param {string} type The type of the oscillator. + * @param {String} type The type of the oscillator. * @example * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); */ @@ -16090,26 +16846,24 @@ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); /** * the type of the oscillator source - * @type {string} + * @type {String} * @private */ this._sourceType = undefined; /** * the oscillator - * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} + * @type {Tone.Oscillator} * @private */ this._oscillator = null; //set the oscillator this.type = options.type; - this.phase = options.phase; this._readOnly([ 'frequency', 'detune' ]); - if (this.isArray(options.partials)) { - this.partials = options.partials; - } + //set the options + this.set(options); }; Tone.extend(Tone.OmniOscillator, Tone.Oscillator); /** @@ -16122,19 +16876,19 @@ 'frequency': 440, 'detune': 0, 'type': 'sine', - 'phase': 0, - 'width': 0.4, - //only applies if the oscillator is set to "pulse", - 'modulationFrequency': 0.4 + 'phase': 0 }; /** - * @enum {string} + * @enum {String} * @private */ var OmniOscType = { - PulseOscillator: 'PulseOscillator', - PWMOscillator: 'PWMOscillator', - Oscillator: 'Oscillator' + Pulse: 'PulseOscillator', + PWM: 'PWMOscillator', + Osc: 'Oscillator', + FM: 'FMOscillator', + AM: 'AMOscillator', + Fat: 'FatOscillator' }; /** * start the oscillator @@ -16153,34 +16907,54 @@ this._oscillator.stop(time); }; /** - * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse. + * The type of the oscillator. Can be any of the basic types: sine, square, triangle, sawtooth. Or + * prefix the basic types with "fm", "am", or "fat" to use the FMOscillator, AMOscillator or FatOscillator + * types. The oscillator could also be set to "pwm" or "pulse". All of the parameters of the + * oscillator's class are accessible when the oscillator is set to that type, but throws an error + * when it's not. + * * @memberOf Tone.OmniOscillator# - * @type {string} + * @type {String} * @name type + * @example + * omniOsc.type = "pwm"; + * //modulationFrequency is parameter which is available + * //only when the type is "pwm". + * omniOsc.modulationFrequency.value = 0.5; + * @example + * //an square wave frequency modulated by a sawtooth + * omniOsc.type = "fmsquare"; + * omniOsc.modulationType = "sawtooth"; */ Object.defineProperty(Tone.OmniOscillator.prototype, 'type', { get: function () { - return this._oscillator.type; + var prefix = ''; + if (this._sourceType === OmniOscType.FM) { + prefix = 'fm'; + } else if (this._sourceType === OmniOscType.AM) { + prefix = 'am'; + } else if (this._sourceType === OmniOscType.Fat) { + prefix = 'fat'; + } + return prefix + this._oscillator.type; }, set: function (type) { - if (type.indexOf('sine') === 0 || type.indexOf('square') === 0 || type.indexOf('triangle') === 0 || type.indexOf('sawtooth') === 0 || type === Tone.Oscillator.Type.Custom) { - if (this._sourceType !== OmniOscType.Oscillator) { - this._sourceType = OmniOscType.Oscillator; - this._createNewOscillator(Tone.Oscillator); - } - this._oscillator.type = type; + if (type.substr(0, 2) === 'fm') { + this._createNewOscillator(OmniOscType.FM); + this._oscillator.type = type.substr(2); + } else if (type.substr(0, 2) === 'am') { + this._createNewOscillator(OmniOscType.AM); + this._oscillator.type = type.substr(2); + } else if (type.substr(0, 3) === 'fat') { + this._createNewOscillator(OmniOscType.Fat); + this._oscillator.type = type.substr(3); } else if (type === 'pwm') { - if (this._sourceType !== OmniOscType.PWMOscillator) { - this._sourceType = OmniOscType.PWMOscillator; - this._createNewOscillator(Tone.PWMOscillator); - } + this._createNewOscillator(OmniOscType.PWM); } else if (type === 'pulse') { - if (this._sourceType !== OmniOscType.PulseOscillator) { - this._sourceType = OmniOscType.PulseOscillator; - this._createNewOscillator(Tone.PulseOscillator); - } + this._createNewOscillator(OmniOscType.Pulse); } else { - throw new Error('Tone.OmniOscillator does not support type ' + type); + this._createNewOscillator(OmniOscType.Osc); + this._oscillator.type = type; } } }); @@ -16191,6 +16965,7 @@ * following the harmonic series. * Setting this value will automatically set the type to "custom". * The value is an empty array when the type is not "custom". + * This is not available on "pwm" and "pulse" oscillator types. * @memberOf Tone.OmniOscillator# * @type {Array} * @name partials @@ -16202,34 +16977,53 @@ return this._oscillator.partials; }, set: function (partials) { - if (this._sourceType !== OmniOscType.Oscillator) { - this.type = Tone.Oscillator.Type.Custom; - } this._oscillator.partials = partials; } }); + /** + * Set a member/attribute of the oscillator. + * @param {Object|String} params + * @param {number=} value + * @param {Time=} rampTime + * @returns {Tone.OmniOscillator} this + */ + Tone.OmniOscillator.prototype.set = function (params, value) { + //make sure the type is set first + if (params === 'type') { + this.type = value; + } else if (this.isObject(params) && params.hasOwnProperty('type')) { + this.type = params.type; + } + //then set the rest + Tone.prototype.set.apply(this, arguments); + return this; + }; /** * 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.blockTime; - if (this._oscillator !== null) { - var oldOsc = this._oscillator; - oldOsc.stop(now); - //dispose the old one - setTimeout(function () { - oldOsc.dispose(); - oldOsc = null; - }, this.blockTime * 1000); - } - this._oscillator = new OscillatorConstructor(); - this.frequency.connect(this._oscillator.frequency); - this.detune.connect(this._oscillator.detune); - this._oscillator.connect(this.output); - if (this.state === Tone.State.Started) { - this._oscillator.start(now); + Tone.OmniOscillator.prototype._createNewOscillator = function (oscType) { + if (oscType !== this._sourceType) { + this._sourceType = oscType; + var OscillatorConstructor = Tone[oscType]; + //short delay to avoid clicks on the change + var now = this.now() + this.blockTime; + if (this._oscillator !== null) { + var oldOsc = this._oscillator; + oldOsc.stop(now); + //dispose the old one + setTimeout(function () { + oldOsc.dispose(); + oldOsc = null; + }, this.blockTime * 1000); + } + this._oscillator = new OscillatorConstructor(); + this.frequency.connect(this._oscillator.frequency); + this.detune.connect(this._oscillator.detune); + this._oscillator.connect(this.output); + if (this.state === Tone.State.Started) { + this._oscillator.start(now); + } } }; /** @@ -16247,7 +17041,7 @@ } }); /** - * The width of the oscillator (only if the oscillator is set to pulse) + * The width of the oscillator (only if the oscillator is set to "pulse") * @memberOf Tone.OmniOscillator# * @type {NormalRange} * @signal @@ -16257,16 +17051,110 @@ * //can access the width attribute only if type === "pulse" * omniOsc.width.value = 0.2; */ - Object.defineProperty(Tone.OmniOscillator.prototype, 'width', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'width', { + get: function () { + if (this._sourceType === OmniOscType.Pulse) { + return this._oscillator.width; + } + } + }); + /** + * The number of detuned oscillators + * @memberOf Tone.OmniOscillator# + * @type {Number} + * @name count + */ + Object.defineProperty(Tone.OmniOscillator.prototype, 'count', { + get: function () { + if (this._sourceType === OmniOscType.Fat) { + return this._oscillator.count; + } + }, + set: function (count) { + if (this._sourceType === OmniOscType.Fat) { + this._oscillator.count = count; + } + } + }); + /** + * The detune spread between the oscillators. If "count" is + * set to 3 oscillators and the "spread" is set to 40, + * the three oscillators would be detuned like this: [-20, 0, 20] + * for a total detune spread of 40 cents. See Tone.FatOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @type {Cents} + * @name spread + */ + Object.defineProperty(Tone.OmniOscillator.prototype, 'spread', { + get: function () { + if (this._sourceType === OmniOscType.Fat) { + return this._oscillator.spread; + } + }, + set: function (spread) { + if (this._sourceType === OmniOscType.Fat) { + this._oscillator.spread = spread; + } + } + }); + /** + * The type of the modulator oscillator. Only if the oscillator + * is set to "am" or "fm" types. see. Tone.AMOscillator or Tone.FMOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @type {String} + * @name modulationType + */ + Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationType', { + get: function () { + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) { + return this._oscillator.modulationType; + } + }, + set: function (mType) { + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) { + this._oscillator.modulationType = mType; + } + } + }); + /** + * 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. + * See Tone.FMOscillator for more info. + * @type {Positive} + * @signal + * @name modulationIndex + */ + Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationIndex', { + get: function () { + if (this._sourceType === OmniOscType.FM) { + return this._oscillator.modulationIndex; + } + } + }); + /** + * Harmonicity is the frequency ratio between the carrier and the modulator oscillators. + * A harmonicity of 1 gives both oscillators the same frequency. + * Harmonicity = 2 means a change of an octave. See Tone.AMOscillator or Tone.FMOscillator + * for more info. + * @memberOf Tone.OmniOscillator# + * @signal + * @type {Positive} + * @name harmonicity + */ + Object.defineProperty(Tone.OmniOscillator.prototype, 'harmonicity', { get: function () { - if (this._sourceType === OmniOscType.PulseOscillator) { - return this._oscillator.width; + if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) { + return this._oscillator.harmonicity; } } }); /** * The modulationFrequency Signal of the oscillator - * (only if the oscillator type is set to pwm). + * (only if the oscillator type is set to pwm). See + * Tone.PWMOscillator for more info. * @memberOf Tone.OmniOscillator# * @type {Frequency} * @signal @@ -16278,7 +17166,7 @@ */ Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationFrequency', { get: function () { - if (this._sourceType === OmniOscType.PWMOscillator) { + if (this._sourceType === OmniOscType.PWM) { return this._oscillator.modulationFrequency; } } @@ -16485,7 +17373,311 @@ } return this; }; - return Tone.Monophonic; + return Tone.Monophonic; + }); + Module(function (Tone) { + + /** + * @class Tone.Synth is composed simply of a Tone.OmniOscillator + * routed through a Tone.AmplitudeEnvelope. + * + * + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below + * @example + * var synth = new Tone.Synth().toMaster(); + * synth.triggerAttackRelease("C4", "8n"); + */ + Tone.Synth = function (options) { + //get the defaults + options = this.defaultArg(options, Tone.Synth.defaults); + Tone.Monophonic.call(this, options); + /** + * The oscillator. + * @type {Tone.OmniOscillator} + */ + this.oscillator = new Tone.OmniOscillator(options.oscillator); + /** + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = this.oscillator.frequency; + /** + * The detune control. + * @type {Cents} + * @signal + */ + this.detune = this.oscillator.detune; + /** + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} + */ + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); + //connect the oscillators to the output + this.oscillator.chain(this.envelope, this.output); + //start the oscillators + this.oscillator.start(); + this._readOnly([ + 'oscillator', + 'frequency', + 'detune', + 'envelope' + ]); + }; + Tone.extend(Tone.Synth, Tone.Monophonic); + /** + * @const + * @static + * @type {Object} + */ + Tone.Synth.defaults = { + 'oscillator': { 'type': 'triangle' }, + 'envelope': { + 'attack': 0.005, + 'decay': 0.1, + 'sustain': 0.3, + 'release': 1 + } + }; + /** + * start the attack portion of the envelope + * @param {Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.Synth} this + * @private + */ + Tone.Synth.prototype._triggerEnvelopeAttack = function (time, velocity) { + //the envelopes + this.envelope.triggerAttack(time, velocity); + return this; + }; + /** + * start the release portion of the envelope + * @param {Time} [time=now] the time the release should start + * @returns {Tone.Synth} this + * @private + */ + Tone.Synth.prototype._triggerEnvelopeRelease = function (time) { + this.envelope.triggerRelease(time); + return this; + }; + /** + * clean up + * @returns {Tone.Synth} this + */ + Tone.Synth.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.Synth; + }); + Module(function (Tone) { + + /** + * @class AMSynth uses the output of one Tone.Synth to modulate the + * amplitude of another Tone.Synth. The harmonicity (the ratio between + * the two signals) affects the timbre of the output signal greatly. + * Read more about Amplitude Modulation Synthesis on + * [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm). + * + * + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below + * @example + * var synth = new Tone.AMSynth().toMaster(); + * synth.triggerAttackRelease("C4", "4n"); + */ + Tone.AMSynth = function (options) { + options = this.defaultArg(options, Tone.AMSynth.defaults); + Tone.Monophonic.call(this, options); + /** + * The carrier voice. + * @type {Tone.Synth} + */ + this._carrier = new Tone.Synth(); + this._carrier.volume.value = -10; + /** + * The carrier's oscillator + * @type {Tone.Oscillator} + */ + this.oscillator = this._carrier.oscillator; + /** + * The carrier's envelope + * @type {Tone.Oscillator} + */ + this.envelope = this._carrier.envelope.set(options.envelope); + /** + * The modulator voice. + * @type {Tone.Synth} + */ + this._modulator = new Tone.Synth(); + this._modulator.volume.value = -10; + /** + * The modulator's oscillator which is applied + * to the amplitude of the oscillator + * @type {Tone.Oscillator} + */ + this.modulation = this._modulator.oscillator.set(options.modulation); + /** + * The modulator's envelope + * @type {Tone.Oscillator} + */ + this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope); + /** + * The frequency. + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + /** + * The detune in cents + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + /** + * Harmonicity is the ratio between the two voices. A harmonicity of + * 1 is no change. Harmonicity = 2 means a change of an octave. + * @type {Positive} + * @signal + * @example + * //pitch voice1 an octave below voice0 + * synth.harmonicity.value = 0.5; + */ + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; + /** + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} + * @private + */ + this._modulationScale = new Tone.AudioToGain(); + /** + * the node where the modulation happens + * @type {GainNode} + * @private + */ + this._modulationNode = this.context.createGain(); + //control the two voices frequency + this.frequency.connect(this._carrier.frequency); + this.frequency.chain(this.harmonicity, this._modulator.frequency); + this.detune.fan(this._carrier.detune, this._modulator.detune); + this._modulator.chain(this._modulationScale, this._modulationNode.gain); + this._carrier.chain(this._modulationNode, this.output); + this._readOnly([ + 'frequency', + 'harmonicity', + 'oscillator', + 'envelope', + 'modulation', + 'modulationEnvelope', + 'detune' + ]); + }; + Tone.extend(Tone.AMSynth, Tone.Monophonic); + /** + * @static + * @type {Object} + */ + Tone.AMSynth.defaults = { + 'harmonicity': 3, + 'detune': 0, + 'oscillator': { 'type': 'sine' }, + 'envelope': { + 'attack': 0.01, + 'decay': 0.01, + 'sustain': 1, + 'release': 0.5 + }, + 'moduation': { 'type': 'square' }, + 'modulationEnvelope': { + 'attack': 0.5, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 + } + }; + /** + * trigger the attack portion of the note + * + * @param {Time} [time=now] the time the note will occur + * @param {NormalRange} [velocity=1] the velocity of the note + * @private + * @returns {Tone.AMSynth} this + */ + Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + //the port glide + time = this.toSeconds(time); + //the envelopes + this.envelope.triggerAttack(time, velocity); + this.modulationEnvelope.triggerAttack(time, velocity); + return this; + }; + /** + * trigger the release portion of the note + * + * @param {Time} [time=now] the time the note will release + * @private + * @returns {Tone.AMSynth} this + */ + Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) { + this.envelope.triggerRelease(time); + this.modulationEnvelope.triggerRelease(time); + return this; + }; + /** + * clean up + * @returns {Tone.AMSynth} this + */ + Tone.AMSynth.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); + this._writable([ + 'frequency', + 'harmonicity', + 'oscillator', + 'envelope', + 'modulation', + 'modulationEnvelope', + 'detune' + ]); + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; + this.frequency.dispose(); + this.frequency = null; + this.detune.dispose(); + this.detune = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this._modulationScale.dispose(); + this._modulationScale = null; + this._modulationNode.disconnect(); + this._modulationNode = null; + this.oscillator = null; + this.envelope = null; + this.modulationEnvelope = null; + this.modulation = null; + return this; + }; + return Tone.AMSynth; }); Module(function (Tone) { @@ -16647,42 +17839,264 @@ Module(function (Tone) { /** - * @class AMSynth uses the output of one Tone.MonoSynth to modulate the - * amplitude of another Tone.MonoSynth. The harmonicity (the ratio between - * the two signals) affects the timbre of the output signal the most. - * Read more about Amplitude Modulation Synthesis on - * [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm). - * + * @class Tone.DuoSynth is a monophonic synth composed of two + * MonoSynths run in parallel with control over the + * frequency ratio between the two voices and vibrato effect. + * * * @constructor * @extends {Tone.Monophonic} * @param {Object} [options] the options available for the synth - * see defaults below + * see defaults below * @example - * var synth = new Tone.AMSynth().toMaster(); - * synth.triggerAttackRelease("C4", "4n"); + * var duoSynth = new Tone.DuoSynth().toMaster(); + * duoSynth.triggerAttackRelease("C4", "2n"); */ - Tone.AMSynth = function (options) { - options = this.defaultArg(options, Tone.AMSynth.defaults); + Tone.DuoSynth = function (options) { + options = this.defaultArg(options, Tone.DuoSynth.defaults); Tone.Monophonic.call(this, options); /** - * The carrier voice. + * the first voice * @type {Tone.MonoSynth} */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this.voice0 = new Tone.MonoSynth(options.voice0); + this.voice0.volume.value = -10; /** - * The modulator voice. + * the second voice * @type {Tone.MonoSynth} */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this.voice1 = new Tone.MonoSynth(options.voice1); + this.voice1.volume.value = -10; /** - * The frequency. + * The vibrato LFO. + * @type {Tone.LFO} + * @private + */ + this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50); + this._vibrato.start(); + /** + * the vibrato frequency + * @type {Frequency} + * @signal + */ + this.vibratoRate = this._vibrato.frequency; + /** + * the vibrato gain + * @type {GainNode} + * @private + */ + this._vibratoGain = this.context.createGain(); + /** + * The amount of vibrato + * @type {Positive} + * @signal + */ + this.vibratoAmount = new Tone.Param({ + 'param': this._vibratoGain.gain, + 'units': Tone.Type.Positive, + 'value': options.vibratoAmount + }); + /** + * the frequency control + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + /** + * Harmonicity is the ratio between the two voices. A harmonicity of + * 1 is no change. Harmonicity = 2 means a change of an octave. + * @type {Positive} + * @signal + * @example + * //pitch voice1 an octave below voice0 + * duoSynth.harmonicity.value = 0.5; + */ + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; + //control the two voices frequency + this.frequency.connect(this.voice0.frequency); + this.frequency.chain(this.harmonicity, this.voice1.frequency); + this._vibrato.connect(this._vibratoGain); + this._vibratoGain.fan(this.voice0.detune, this.voice1.detune); + this.voice0.connect(this.output); + this.voice1.connect(this.output); + this._readOnly([ + 'voice0', + 'voice1', + 'frequency', + 'vibratoAmount', + 'vibratoRate' + ]); + }; + Tone.extend(Tone.DuoSynth, Tone.Monophonic); + /** + * @static + * @type {Object} + */ + Tone.DuoSynth.defaults = { + 'vibratoAmount': 0.5, + 'vibratoRate': 5, + 'harmonicity': 1.5, + 'voice0': { + 'volume': -10, + 'portamento': 0, + 'oscillator': { 'type': 'sine' }, + 'filterEnvelope': { + 'attack': 0.01, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 + }, + 'envelope': { + 'attack': 0.01, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 + } + }, + 'voice1': { + 'volume': -10, + 'portamento': 0, + 'oscillator': { 'type': 'sine' }, + 'filterEnvelope': { + 'attack': 0.01, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 + }, + 'envelope': { + 'attack': 0.01, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 + } + } + }; + /** + * start the attack portion of the envelopes + * + * @param {Time} [time=now] the time the attack should start + * @param {NormalRange} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.DuoSynth} this + * @private + */ + Tone.DuoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + time = this.toSeconds(time); + this.voice0.envelope.triggerAttack(time, velocity); + this.voice1.envelope.triggerAttack(time, velocity); + this.voice0.filterEnvelope.triggerAttack(time); + this.voice1.filterEnvelope.triggerAttack(time); + return this; + }; + /** + * start the release portion of the envelopes + * + * @param {Time} [time=now] the time the release should start + * @returns {Tone.DuoSynth} this + * @private + */ + Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) { + this.voice0.triggerRelease(time); + this.voice1.triggerRelease(time); + return this; + }; + /** + * clean up + * @returns {Tone.DuoSynth} this + */ + Tone.DuoSynth.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); + this._writable([ + 'voice0', + 'voice1', + 'frequency', + 'vibratoAmount', + 'vibratoRate' + ]); + this.voice0.dispose(); + this.voice0 = null; + this.voice1.dispose(); + this.voice1 = null; + this.frequency.dispose(); + this.frequency = null; + this._vibrato.dispose(); + this._vibrato = null; + this._vibratoGain.disconnect(); + this._vibratoGain = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this.vibratoAmount.dispose(); + this.vibratoAmount = null; + this.vibratoRate = null; + return this; + }; + return Tone.DuoSynth; + }); + Module(function (Tone) { + + /** + * @class FMSynth is composed of two Tone.Synths where one Tone.Synth modulates + * the frequency of a second Tone.Synth. A lot of spectral content + * can be explored using the modulationIndex parameter. Read more about + * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm). + * + * + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below + * @example + * var fmSynth = new Tone.FMSynth().toMaster(); + * fmSynth.triggerAttackRelease("C5", "4n"); + */ + Tone.FMSynth = function (options) { + options = this.defaultArg(options, Tone.FMSynth.defaults); + Tone.Monophonic.call(this, options); + /** + * The carrier voice. + * @type {Tone.Synth} + */ + this._carrier = new Tone.Synth(options.carrier); + this._carrier.volume.value = -10; + /** + * The carrier's oscillator + * @type {Tone.Oscillator} + */ + this.oscillator = this._carrier.oscillator; + /** + * The carrier's envelope + * @type {Tone.Oscillator} + */ + this.envelope = this._carrier.envelope.set(options.envelope); + /** + * The modulator voice. + * @type {Tone.Synth} + */ + this._modulator = new Tone.Synth(options.modulator); + this._modulator.volume.value = -10; + /** + * The modulator's oscillator which is applied + * to the amplitude of the oscillator + * @type {Tone.Oscillator} + */ + this.modulation = this._modulator.oscillator.set(options.modulation); + /** + * The modulator's envelope + * @type {Tone.Oscillator} + */ + this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope); + /** + * The frequency control. * @type {Frequency} * @signal */ this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + /** + * The detune in cents + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); /** * Harmonicity is the ratio between the two voices. A harmonicity of * 1 is no change. Harmonicity = 2 means a change of an octave. @@ -16695,11 +18109,14 @@ this.harmonicity = new Tone.Multiply(options.harmonicity); this.harmonicity.units = Tone.Type.Positive; /** - * convert the -1,1 output to 0,1 - * @type {Tone.AudioToGain} - * @private + * The modulation index which essentially the depth or amount of the modulation. It is the + * ratio of the frequency of the modulating signal (mf) to the amplitude of the + * modulating signal (ma) -- as in ma/mf. + * @type {Positive} + * @signal */ - this._modulationScale = new Tone.AudioToGain(); + this.modulationIndex = new Tone.Multiply(options.modulationIndex); + this.modulationIndex.units = Tone.Type.Positive; /** * the node where the modulation happens * @type {GainNode} @@ -16707,137 +18124,123 @@ */ this._modulationNode = this.context.createGain(); //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.modulator.chain(this._modulationScale, this._modulationNode.gain); - this.carrier.chain(this._modulationNode, this.output); + this.frequency.connect(this._carrier.frequency); + this.frequency.chain(this.harmonicity, this._modulator.frequency); + this.frequency.chain(this.modulationIndex, this._modulationNode); + this.detune.fan(this._carrier.detune, this._modulator.detune); + this._modulator.connect(this._modulationNode.gain); + this._modulationNode.gain.value = 0; + this._modulationNode.connect(this._carrier.frequency); + this._carrier.connect(this.output); this._readOnly([ - 'carrier', - 'modulator', 'frequency', - 'harmonicity' + 'harmonicity', + 'modulationIndex', + 'oscillator', + 'envelope', + 'modulation', + 'modulationEnvelope', + 'detune' ]); }; - Tone.extend(Tone.AMSynth, Tone.Monophonic); + Tone.extend(Tone.FMSynth, Tone.Monophonic); /** * @static * @type {Object} */ - Tone.AMSynth.defaults = { + Tone.FMSynth.defaults = { 'harmonicity': 3, - 'carrier': { - 'volume': -10, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0.01, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'baseFrequency': 20000, - 'octaves': 0 - }, - 'filter': { - 'Q': 6, - 'type': 'lowpass', - 'rolloff': -24 - } + 'modulationIndex': 10, + 'detune': 0, + 'oscillator': { 'type': 'sine' }, + 'envelope': { + 'attack': 0.01, + 'decay': 0.01, + 'sustain': 1, + 'release': 0.5 }, - 'modulator': { - 'volume': -10, - 'oscillator': { 'type': 'square' }, - 'envelope': { - 'attack': 2, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 4, - 'decay': 0.2, - 'sustain': 0.5, - 'release': 0.5, - 'baseFrequency': 20, - 'octaves': 6 - }, - 'filter': { - 'Q': 6, - 'type': 'lowpass', - 'rolloff': -24 - } + 'moduation': { 'type': 'square' }, + 'modulationEnvelope': { + 'attack': 0.5, + 'decay': 0, + 'sustain': 1, + 'release': 0.5 } }; /** - * trigger the attack portion of the note + * trigger the attack portion of the note * * @param {Time} [time=now] the time the note will occur - * @param {NormalRange} [velocity=1] the velocity of the note + * @param {number} [velocity=1] the velocity of the note + * @returns {Tone.FMSynth} this * @private - * @returns {Tone.AMSynth} this */ - Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the port glide + Tone.FMSynth.prototype._triggerEnvelopeAttack = function (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.envelope.triggerAttack(time, velocity); + this.modulationEnvelope.triggerAttack(time); return this; }; /** * trigger the release portion of the note * * @param {Time} [time=now] the time the note will release + * @returns {Tone.FMSynth} this * @private - * @returns {Tone.AMSynth} this */ - Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) { + time = this.toSeconds(time); + this.envelope.triggerRelease(time); + this.modulationEnvelope.triggerRelease(time); return this; }; /** * clean up - * @returns {Tone.AMSynth} this + * @returns {Tone.FMSynth} this */ - Tone.AMSynth.prototype.dispose = function () { + Tone.FMSynth.prototype.dispose = function () { Tone.Monophonic.prototype.dispose.call(this); this._writable([ - 'carrier', - 'modulator', 'frequency', - 'harmonicity' + 'harmonicity', + 'modulationIndex', + 'oscillator', + 'envelope', + 'modulation', + 'modulationEnvelope', + 'detune' ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; + this._carrier.dispose(); + this._carrier = null; + this._modulator.dispose(); + this._modulator = null; this.frequency.dispose(); this.frequency = null; + this.detune.dispose(); + this.detune = null; + this.modulationIndex.dispose(); + this.modulationIndex = null; this.harmonicity.dispose(); this.harmonicity = null; - this._modulationScale.dispose(); - this._modulationScale = null; this._modulationNode.disconnect(); this._modulationNode = null; + this.oscillator = null; + this.envelope = null; + this.modulationEnvelope = null; + this.modulation = null; return this; }; - return Tone.AMSynth; + return Tone.FMSynth; }); Module(function (Tone) { /** - * @class Tone.DrumSynth makes kick and tom sounds using a single oscillator + * @class Tone.MembraneSynth makes kick and tom sounds using a single oscillator * with an amplitude envelope and frequency ramp. A Tone.Oscillator * is routed through a Tone.AmplitudeEnvelope to the output. The drum * quality of the sound comes from the frequency envelope applied - * during during Tone.DrumSynth.triggerAttack(note). The frequency + * during during Tone.MembraneSynth.triggerAttack(note). The frequency * envelope starts at note * .octaves and ramps to * note over the duration of .pitchDecay. * @@ -16846,11 +18249,11 @@ * @param {Object} [options] the options available for the synth * see defaults below * @example - * var synth = new Tone.DrumSynth().toMaster(); + * var synth = new Tone.MembraneSynth().toMaster(); * synth.triggerAttackRelease("C2", "8n"); */ - Tone.DrumSynth = function (options) { - options = this.defaultArg(options, Tone.DrumSynth.defaults); + Tone.MembraneSynth = function (options) { + options = this.defaultArg(options, Tone.MembraneSynth.defaults); Tone.Instrument.call(this, options); /** * The oscillator. @@ -16878,458 +18281,322 @@ 'envelope' ]); }; - Tone.extend(Tone.DrumSynth, Tone.Instrument); + Tone.extend(Tone.MembraneSynth, Tone.Instrument); /** * @static * @type {Object} */ - Tone.DrumSynth.defaults = { + Tone.MembraneSynth.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 note at the given time with the given velocity. - * - * @param {Frequency} note the note - * @param {Time} [time=now] the time, if not given is now - * @param {number} [velocity=1] velocity defaults to 1 - * @returns {Tone.DrumSynth} this - * @example - * kick.triggerAttack(60); - */ - Tone.DrumSynth.prototype.triggerAttack = function (note, time, velocity) { - time = this.toSeconds(time); - note = this.toFrequency(note); - var maxNote = note * this.octaves; - this.oscillator.frequency.setValueAtTime(maxNote, time); - this.oscillator.frequency.exponentialRampToValueAtTime(note, time + this.toSeconds(this.pitchDecay)); - this.envelope.triggerAttack(time, velocity); - return this; - }; - /** - * Trigger the release portion of the note. - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.DrumSynth} this - */ - Tone.DrumSynth.prototype.triggerRelease = function (time) { - this.envelope.triggerRelease(time); - return this; - }; - /** - * Clean up. - * @returns {Tone.DrumSynth} this - */ - Tone.DrumSynth.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(this); - this._writable([ - 'oscillator', - 'envelope' - ]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - return this; - }; - return Tone.DrumSynth; - }); - Module(function (Tone) { - - /** - * @class Tone.DuoSynth is a monophonic synth composed of two - * MonoSynths run in parallel with control over the - * frequency ratio between the two voices and vibrato effect. - * - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var duoSynth = new Tone.DuoSynth().toMaster(); - * duoSynth.triggerAttackRelease("C4", "2n"); - */ - Tone.DuoSynth = function (options) { - options = this.defaultArg(options, Tone.DuoSynth.defaults); - Tone.Monophonic.call(this, options); - /** - * the first voice - * @type {Tone.MonoSynth} - */ - this.voice0 = new Tone.MonoSynth(options.voice0); - this.voice0.volume.value = -10; - /** - * the second voice - * @type {Tone.MonoSynth} - */ - this.voice1 = new Tone.MonoSynth(options.voice1); - this.voice1.volume.value = -10; - /** - * The vibrato LFO. - * @type {Tone.LFO} - * @private - */ - this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50); - this._vibrato.start(); - /** - * the vibrato frequency - * @type {Frequency} - * @signal - */ - this.vibratoRate = this._vibrato.frequency; - /** - * the vibrato gain - * @type {GainNode} - * @private - */ - this._vibratoGain = this.context.createGain(); - /** - * The amount of vibrato - * @type {Positive} - * @signal - */ - this.vibratoAmount = new Tone.Param({ - 'param': this._vibratoGain.gain, - 'units': Tone.Type.Positive, - 'value': options.vibratoAmount - }); - /** - * the delay before the vibrato starts - * @type {number} - * @private - */ - this._vibratoDelay = this.toSeconds(options.vibratoDelay); - /** - * the frequency control - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); - /** - * Harmonicity is the ratio between the two voices. A harmonicity of - * 1 is no change. Harmonicity = 2 means a change of an octave. - * @type {Positive} - * @signal - * @example - * //pitch voice1 an octave below voice0 - * duoSynth.harmonicity.value = 0.5; - */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; - //control the two voices frequency - this.frequency.connect(this.voice0.frequency); - this.frequency.chain(this.harmonicity, this.voice1.frequency); - this._vibrato.connect(this._vibratoGain); - this._vibratoGain.fan(this.voice0.detune, this.voice1.detune); - this.voice0.connect(this.output); - this.voice1.connect(this.output); - this._readOnly([ - 'voice0', - 'voice1', - 'frequency', - 'vibratoAmount', - 'vibratoRate' - ]); - }; - Tone.extend(Tone.DuoSynth, Tone.Monophonic); - /** - * @static - * @type {Object} - */ - Tone.DuoSynth.defaults = { - 'vibratoAmount': 0.5, - 'vibratoRate': 5, - 'vibratoDelay': 1, - 'harmonicity': 1.5, - 'voice0': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - } - }, - 'voice1': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - } + 'attack': 0.001, + 'decay': 0.4, + 'sustain': 0.01, + 'release': 1.4, + 'attackCurve': 'exponential' } }; /** - * start the attack portion of the envelopes + * Trigger the note at the given time with the given velocity. * - * @param {Time} [time=now] the time the attack should start - * @param {NormalRange} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.DuoSynth} this - * @private + * @param {Frequency} note the note + * @param {Time} [time=now] the time, if not given is now + * @param {number} [velocity=1] velocity defaults to 1 + * @returns {Tone.MembraneSynth} this + * @example + * kick.triggerAttack(60); */ - Tone.DuoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + Tone.MembraneSynth.prototype.triggerAttack = function (note, 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); + 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; }; /** - * start the release portion of the envelopes + * Trigger the release portion of the note. * - * @param {Time} [time=now] the time the release should start - * @returns {Tone.DuoSynth} this - * @private + * @param {Time} [time=now] the time the note will release + * @returns {Tone.MembraneSynth} this */ - Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) { - this.voice0.triggerRelease(time); - this.voice1.triggerRelease(time); + Tone.MembraneSynth.prototype.triggerRelease = function (time) { + this.envelope.triggerRelease(time); return this; }; /** - * clean up - * @returns {Tone.DuoSynth} this + * Clean up. + * @returns {Tone.MembraneSynth} this */ - Tone.DuoSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); + Tone.MembraneSynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); this._writable([ - 'voice0', - 'voice1', - 'frequency', - 'vibratoAmount', - 'vibratoRate' + 'oscillator', + 'envelope' ]); - 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.oscillator.dispose(); + this.oscillator = null; + this.envelope.dispose(); + this.envelope = null; return this; }; - return Tone.DuoSynth; + return Tone.MembraneSynth; }); Module(function (Tone) { - /** - * @class FMSynth is composed of two Tone.MonoSynths where one Tone.MonoSynth modulates - * the frequency of a second Tone.MonoSynth. A lot of spectral content - * can be explored using the modulationIndex parameter. Read more about - * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm). - * + * Inharmonic ratio of frequencies based on the Roland TR-808 + * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model + * @private + * @static + * @type {Array} + */ + var inharmRatios = [ + 1, + 1.483, + 1.932, + 2.546, + 2.63, + 3.897 + ]; + /** + * @class A highly inharmonic and spectrally complex source with a highpass filter + * and amplitude envelope which is good for making metalophone sounds. Based + * on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic). + * Inspiration from [Sound on Sound](http://www.soundonsound.com/sos/jul02/articles/synthsecrets0702.asp). * * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var fmSynth = new Tone.FMSynth().toMaster(); - * fmSynth.triggerAttackRelease("C5", "4n"); + * @extends {Tone.Instrument} + * @param {Object} [options] The options availble for the synth + * see defaults below */ - Tone.FMSynth = function (options) { - options = this.defaultArg(options, Tone.FMSynth.defaults); - Tone.Monophonic.call(this, options); + Tone.MetalSynth = function (options) { + options = this.defaultArg(options, Tone.MetalSynth.defaults); + Tone.Instrument.call(this, options); /** - * The carrier voice. - * @type {Tone.MonoSynth} + * The frequency of the cymbal + * @type {Frequency} + * @signal */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * The modulator voice. - * @type {Tone.MonoSynth} + * The array of FMOscillators + * @type {Array} + * @private */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this._oscillators = []; /** - * The frequency control. - * @type {Frequency} - * @signal + * The frequency multipliers + * @type {Array} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + this._freqMultipliers = []; /** - * Harmonicity is the ratio between the two voices. A harmonicity of - * 1 is no change. Harmonicity = 2 means a change of an octave. - * @type {Positive} - * @signal - * @example - * //pitch voice1 an octave below voice0 - * synth.harmonicity.value = 0.5; + * The amplitude for the body + * @type {Tone.Gain} + * @private */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; + this._amplitue = new Tone.Gain(0).connect(this.output); /** - * The modulation index which essentially the depth or amount of the modulation. It is the - * ratio of the frequency of the modulating signal (mf) to the amplitude of the - * modulating signal (ma) -- as in ma/mf. - * @type {Positive} - * @signal + * highpass the output + * @type {Tone.Filter} + * @private */ - this.modulationIndex = new Tone.Multiply(options.modulationIndex); - this.modulationIndex.units = Tone.Type.Positive; + this._highpass = new Tone.Filter({ + 'type': 'highpass', + 'Q': 0 + }).connect(this._amplitue); /** - * the node where the modulation happens - * @type {GainNode} + * The number of octaves the highpass + * filter frequency ramps + * @type {Number} * @private */ - this._modulationNode = this.context.createGain(); - //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.frequency.chain(this.modulationIndex, this._modulationNode); - this.modulator.connect(this._modulationNode.gain); - this._modulationNode.gain.value = 0; - this._modulationNode.connect(this.carrier.frequency); - this.carrier.connect(this.output); - this._readOnly([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity', - 'modulationIndex' - ]); + this._octaves = options.octaves; + /** + * Scale the body envelope + * for the bandpass + * @type {Tone.Scale} + * @private + */ + this._filterFreqScaler = new Tone.Scale(options.resonance, 7000); + /** + * The envelope which is connected both to the + * amplitude and highpass filter's cutoff frequency + * @type {Tone.Envelope} + */ + this.envelope = new Tone.Envelope({ + 'attack': options.envelope.attack, + 'attackCurve': 'exponential', + 'decay': options.envelope.decay, + 'sustain': 0, + 'release': options.envelope.release + }).chain(this._filterFreqScaler, this._highpass.frequency); + this.envelope.connect(this._amplitue.gain); + for (var i = 0; i < inharmRatios.length; i++) { + var osc = new Tone.FMOscillator({ + 'type': 'square', + 'modulationType': 'square', + 'harmonicity': options.harmonicity, + 'modulationIndex': options.modulationIndex + }); + osc.connect(this._highpass).start(0); + this._oscillators[i] = osc; + var mult = new Tone.Multiply(inharmRatios[i]); + this._freqMultipliers[i] = mult; + this.frequency.chain(mult, osc.frequency); + } + //set the octaves + this.octaves = options.octaves; }; - Tone.extend(Tone.FMSynth, Tone.Monophonic); + Tone.extend(Tone.MetalSynth, Tone.Instrument); /** + * default values * @static + * @const * @type {Object} */ - Tone.FMSynth.defaults = { - 'harmonicity': 3, - 'modulationIndex': 10, - 'carrier': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'baseFrequency': 200, - 'octaves': 8 - } + Tone.MetalSynth.defaults = { + 'frequency': 200, + 'envelope': { + 'attack': 0.0015, + 'decay': 1.4, + 'release': 0.2 }, - 'modulator': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'triangle' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'baseFrequency': 600, - 'octaves': 5 - } - } + 'harmonicity': 5.1, + 'modulationIndex': 32, + 'resonance': 4000, + 'octaves': 1.5 }; /** - * trigger the attack portion of the note - * - * @param {Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.FMSynth} this - * @private + * Trigger the attack. + * @param {Time} time When the attack should be triggered. + * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at. + * @return {Tone.MetalSynth} this */ - Tone.FMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the port glide + Tone.MetalSynth.prototype.triggerAttack = function (time, vel) { 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); + vel = this.defaultArg(vel, 1); + this.envelope.triggerAttack(time, vel); return this; }; /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.FMSynth} this - * @private + * Trigger the release of the envelope. + * @param {Time} time When the release should be triggered. + * @return {Tone.MetalSynth} this */ - Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + Tone.MetalSynth.prototype.triggerRelease = function (time) { + time = this.toSeconds(time); + this.envelope.triggerRelease(time); return this; }; /** - * clean up - * @returns {Tone.FMSynth} this + * Trigger the attack and release of the envelope after the given + * duration. + * @param {Time} duration The duration before triggering the release + * @param {Time} time When the attack should be triggered. + * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at. + * @return {Tone.MetalSynth} this */ - Tone.FMSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity', - 'modulationIndex' - ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); - this.frequency = null; - this.modulationIndex.dispose(); - this.modulationIndex = null; - this.harmonicity.dispose(); - this.harmonicity = null; - this._modulationNode.disconnect(); - this._modulationNode = null; + Tone.MetalSynth.prototype.triggerAttackRelease = function (duration, time, velocity) { + var now = this.now(); + time = this.toSeconds(time, now); + duration = this.toSeconds(duration, now); + this.triggerAttack(time, velocity); + this.triggerRelease(time + duration); return this; }; - return Tone.FMSynth; + /** + * The modulationIndex of the oscillators which make up the source. + * see Tone.FMOscillator.modulationIndex + * @memberOf Tone.MetalSynth# + * @type {Positive} + * @name modulationIndex + */ + Object.defineProperty(Tone.MetalSynth.prototype, 'modulationIndex', { + get: function () { + return this._oscillators[0].modulationIndex.value; + }, + set: function (val) { + for (var i = 0; i < this._oscillators.length; i++) { + this._oscillators[i].modulationIndex.value = val; + } + } + }); + /** + * The harmonicity of the oscillators which make up the source. + * see Tone.FMOscillator.harmonicity + * @memberOf Tone.MetalSynth# + * @type {Positive} + * @name harmonicity + */ + Object.defineProperty(Tone.MetalSynth.prototype, 'harmonicity', { + get: function () { + return this._oscillators[0].harmonicity.value; + }, + set: function (val) { + for (var i = 0; i < this._oscillators.length; i++) { + this._oscillators[i].harmonicity.value = val; + } + } + }); + /** + * The frequency of the highpass filter attached to the envelope + * @memberOf Tone.MetalSynth# + * @type {Frequency} + * @name resonance + */ + Object.defineProperty(Tone.MetalSynth.prototype, 'resonance', { + get: function () { + return this._filterFreqScaler.min; + }, + set: function (val) { + this._filterFreqScaler.min = val; + this.octaves = this._octaves; + } + }); + /** + * The number of octaves above the "resonance" frequency + * that the filter ramps during the attack/decay envelope + * @memberOf Tone.MetalSynth# + * @type {Number} + * @name octaves + */ + Object.defineProperty(Tone.MetalSynth.prototype, 'octaves', { + get: function () { + return this._octaves; + }, + set: function (octs) { + this._octaves = octs; + this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, octs); + } + }); + /** + * Clean up + * @returns {Tone.MetalSynth} this + */ + Tone.MetalSynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); + for (var i = 0; i < this._oscillators.length; i++) { + this._oscillators[i].dispose(); + this._freqMultipliers[i].dispose(); + } + this._oscillators = null; + this._freqMultipliers = null; + this.frequency.dispose(); + this.frequency = null; + this._filterFreqScaler.dispose(); + this._filterFreqScaler = null; + this._amplitue.dispose(); + this._amplitue = null; + this.envelope.dispose(); + this.envelope = null; + this._highpass.dispose(); + this._highpass = null; + }; + return Tone.MetalSynth; }); Module(function (Tone) { @@ -17423,7 +18690,7 @@ this._buffer = _brownNoise; break; default: - throw new Error('invalid noise type: ' + type); + throw new TypeError('Tone.Noise: invalid type: ' + type); } //if it's playing, stop and restart it if (this.state === Tone.State.Started) { @@ -17464,7 +18731,7 @@ this._source.loop = true; this._source.playbackRate.value = this._playbackRate; this._source.connect(this.output); - this._source.start(this.toSeconds(time)); + this._source.start(this.toSeconds(time), Math.random() * (this._buffer.duration - 0.001)); }; /** * internal stop method @@ -17583,31 +18850,17 @@ * noiseSynth.set("noise.type", "brown"); */ this.noise = new Tone.Noise(); - /** - * The filter. - * @type {Tone.Filter} - */ - this.filter = new Tone.Filter(options.filter); - /** - * The filter envelope. - * @type {Tone.FrequencyEnvelope} - */ - this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** * The amplitude envelope. * @type {Tone.AmplitudeEnvelope} */ this.envelope = new Tone.AmplitudeEnvelope(options.envelope); //connect the noise to the output - this.noise.chain(this.filter, this.envelope, this.output); + this.noise.chain(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' ]); }; @@ -17619,23 +18872,10 @@ */ Tone.NoiseSynth.defaults = { 'noise': { 'type': 'white' }, - 'filter': { - 'Q': 6, - 'type': 'highpass', - 'rolloff': -24 - }, 'envelope': { 'attack': 0.005, 'decay': 0.1, 'sustain': 0 - }, - 'filterEnvelope': { - 'attack': 0.06, - 'decay': 0.2, - 'sustain': 0, - 'release': 2, - 'baseFrequency': 20, - 'octaves': 5 } }; /** @@ -17650,7 +18890,6 @@ Tone.NoiseSynth.prototype.triggerAttack = function (time, velocity) { //the envelopes this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); return this; }; /** @@ -17660,7 +18899,6 @@ */ Tone.NoiseSynth.prototype.triggerRelease = function (time) { this.envelope.triggerRelease(time); - this.filterEnvelope.triggerRelease(time); return this; }; /** @@ -17685,18 +18923,12 @@ Tone.Instrument.prototype.dispose.call(this); this._writable([ 'noise', - 'filter', - 'filterEnvelope', 'envelope' ]); this.noise.dispose(); this.noise = null; this.envelope.dispose(); this.envelope = null; - this.filterEnvelope.dispose(); - this.filterEnvelope = null; - this.filter.dispose(); - this.filter = null; return this; }; return Tone.NoiseSynth; @@ -17816,11 +19048,11 @@ * @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. + * @param {function} [voice=Tone.Synth] The constructor of the voices + * uses Tone.Synth by default. * @example - * //a polysynth composed of 6 Voices of MonoSynth - * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster(); + * //a polysynth composed of 6 Voices of Synth + * var synth = new Tone.PolySynth(6, Tone.Synth).toMaster(); * //set the attributes using the set interface * synth.set("detune", -1200); * //play a chord @@ -17832,37 +19064,44 @@ 'polyphony', 'voice' ], Tone.PolySynth.defaults); + options = this.defaultArg(options, Tone.Instrument.defaults); + //max polyphony + options.polyphony = Math.min(Tone.PolySynth.MAX_POLYPHONY, options.polyphony); /** * the array of voices * @type {Array} */ this.voices = new Array(options.polyphony); /** - * If there are no more voices available, - * should an active voice be stolen to play the new note? - * @type {Boolean} - */ - this.stealVoices = true; - /** - * the queue of free voices + * The queue of voices with data about last trigger + * and the triggered note * @private * @type {Array} */ - this._freeVoices = []; + this._triggers = new Array(options.polyphony); /** - * keeps track of which notes are down - * @private - * @type {Object} + * The detune in cents + * @type {Cents} + * @signal */ - this._activeVoices = {}; + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + this._readOnly('detune'); //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); + if (v.hasOwnProperty('detune')) { + this.detune.connect(v.detune); + } + this._triggers[i] = { + release: -1, + note: null, + voice: v + }; } - //make a copy of the voices - this._freeVoices = this.voices.slice(0); //get the prototypes and properties + //set the volume initially + this.volume.value = options.volume; }; Tone.extend(Tone.PolySynth, Tone.Instrument); /** @@ -17873,7 +19112,9 @@ */ Tone.PolySynth.defaults = { 'polyphony': 4, - 'voice': Tone.MonoSynth + 'volume': 0, + 'detune': 0, + 'voice': Tone.Synth }; /** * Trigger the attack portion of the note @@ -17890,24 +19131,21 @@ if (!Array.isArray(notes)) { notes = [notes]; } + time = this.toSeconds(time); for (var i = 0; i < notes.length; i++) { var val = notes[i]; - var stringified = JSON.stringify(val); - //retrigger the same note if possible - if (this._activeVoices.hasOwnProperty(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; - } else if (this.stealVoices) { - //steal a voice - //take the first voice - for (var voiceName in this._activeVoices) { - this._activeVoices[voiceName].triggerAttack(val, time, velocity); - break; + //trigger the oldest voice + var oldest = this._triggers[0]; + var oldestIndex = 0; + for (var j = 1; j < this._triggers.length; j++) { + if (this._triggers[j].release < oldest.release) { + oldest = this._triggers[j]; + oldestIndex = j; } } + oldest.release = Infinity; + oldest.note = JSON.stringify(val); + oldest.voice.triggerAttack(val, time, velocity); } return this; }; @@ -17923,11 +19161,21 @@ * @example * //trigger a chord for a duration of a half note * poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n"); + * @example + * //can pass in an array of durations as well + * poly.triggerAttackRelease(["Eb3", "G4", "C5"], ["2n", "4n", "4n"]); */ Tone.PolySynth.prototype.triggerAttackRelease = function (notes, duration, time, velocity) { time = this.toSeconds(time); this.triggerAttack(notes, time, velocity); - this.triggerRelease(notes, time + this.toSeconds(duration)); + if (this.isArray(duration) && this.isArray(notes)) { + for (var i = 0; i < notes.length; i++) { + var d = duration[Math.min(i, duration.length - 1)]; + this.triggerRelease(notes[i], time + this.toSeconds(d)); + } + } else { + this.triggerRelease(notes, time + this.toSeconds(duration)); + } return this; }; /** @@ -17944,15 +19192,16 @@ if (!Array.isArray(notes)) { notes = [notes]; } + time = this.toSeconds(time); for (var i = 0; i < notes.length; i++) { //get the voice var stringified = JSON.stringify(notes[i]); - var voice = this._activeVoices[stringified]; - if (voice) { - voice.triggerRelease(time); - this._freeVoices.push(voice); - delete this._activeVoices[stringified]; - voice = null; + for (var v = 0; v < this._triggers.length; v++) { + var desc = this._triggers[v]; + if (desc.note === stringified && desc.release > time) { + desc.voice.triggerRelease(time); + desc.release = time; + } } } return this; @@ -17997,8 +19246,13 @@ * @return {Tone.PolySynth} this */ Tone.PolySynth.prototype.releaseAll = function (time) { - for (var i = 0; i < this.voices.length; i++) { - this.voices[i].triggerRelease(time); + time = this.toSeconds(time); + for (var i = 0; i < this._triggers.length; i++) { + var desc = this._triggers[i]; + if (desc.release > time) { + desc.release = time; + desc.voice.triggerRelease(time); + } } return this; }; @@ -18012,11 +19266,20 @@ this.voices[i].dispose(); this.voices[i] = null; } + this._writable('detune'); + this.detune.dispose(); + this.detune = null; this.voices = null; - this._activeVoices = null; - this._freeVoices = null; + this._triggers = null; return this; }; + /** + * The maximum number of notes that can be allocated + * to a polysynth. + * @type {Number} + * @static + */ + Tone.PolySynth.MAX_POLYPHONY = 20; return Tone.PolySynth; }); Module(function (Tone) { @@ -18032,9 +19295,8 @@ * Recommended to use Tone.Buffer.onload instead. * @example * var player = new Tone.Player("./path/to/sample.mp3").toMaster(); - * Tone.Buffer.onload = function(){ - * player.start(); - * } + * //play as soon as the buffer is loaded + * player.autostart = true; */ Tone.Player = function (url) { var options; @@ -18157,16 +19419,24 @@ } }; /** - * play the buffer between the desired positions + * Play the buffer at the given startTime. Optionally add an offset + * and/or duration which will play the buffer from a position + * within the buffer for the given duration. * - * @private - * @param {Time} [startTime=now] when the player should start. - * @param {Time} [offset=0] the offset from the beginning of the sample + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample * to start at. - * @param {Time=} duration how long the sample should play. If no duration + * @param {Time=} duration How long the sample should play. If no duration * is given, it will default to the full length * of the sample (minus any offset) * @returns {Tone.Player} this + * @memberOf Tone.Player# + * @method start + * @name start + */ + /** + * Internal start method + * @private */ Tone.Player.prototype._start = function (startTime, offset, duration) { if (this._buffer.loaded) { @@ -18204,7 +19474,7 @@ this._source.start(startTime, offset, duration); } } else { - throw Error('tried to start Player before the buffer was loaded'); + throw Error('Tone.Player: tried to start Player before the buffer was loaded'); } return this; }; @@ -18353,84 +19623,42 @@ Module(function (Tone) { /** - * @class A sampler instrument which plays an audio buffer - * through an amplitude envelope and a filter envelope. The sampler takes - * an Object in the constructor which maps a sample name to the URL - * of the sample. Nested Objects will be flattened and can be accessed using - * a dot notation (see the example). - * + * @class Sampler wraps Tone.Player in an AmplitudeEnvelope. * * @constructor * @extends {Tone.Instrument} - * @param {Object|string} urls the urls of the audio file - * @param {Object} [options] the options object for the synth + * @param {String} url the url of the audio file + * @param {Function=} onload The callback to invoke when the sample is loaded. * @example - * var sampler = new Sampler({ - * A : { - * 1 : "./audio/casio/A1.mp3", - * 2 : "./audio/casio/A2.mp3", - * }, - * "B.1" : "./audio/casio/B1.mp3", + * var sampler = new Sampler("./audio/casio/A1.mp3", function(){ + * //repitch the sample down a half step + * sampler.triggerAttack(-1); * }).toMaster(); - * - * //listen for when all the samples have loaded - * Tone.Buffer.onload = function(){ - * sampler.triggerAttack("A.1", time, velocity); - * }; */ - Tone.Sampler = function (urls, options) { - options = this.defaultArg(options, Tone.Sampler.defaults); + Tone.Sampler = function () { + var options = this.optionsObject(arguments, [ + 'url', + 'onload' + ], Tone.Sampler.defaults); Tone.Instrument.call(this, options); /** * The sample player. * @type {Tone.Player} */ - this.player = new Tone.Player(options.player); + this.player = new Tone.Player(options.url, options.onload); this.player.retrigger = true; - /** - * the buffers - * @type {Object} - * @private - */ - this._buffers = {}; /** * The amplitude envelope. * @type {Tone.AmplitudeEnvelope} */ this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - /** - * The filter envelope. - * @type {Tone.FrequencyEnvelope} - */ - this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); - /** - * The name of the current sample. - * @type {string} - * @private - */ - this._sample = options.sample; - /** - * the private reference to the pitch - * @type {number} - * @private - */ - this._pitch = options.pitch; - /** - * The filter. - * @type {Tone.Filter} - */ - this.filter = new Tone.Filter(options.filter); - //connections / setup - this._loadBuffers(urls); - this.pitch = options.pitch; - this.player.chain(this.filter, this.envelope, this.output); - this.filterEnvelope.connect(this.filter.frequency); + this.player.chain(this.envelope, this.output); this._readOnly([ 'player', - 'filterEnvelope', - 'envelope', - 'filter' + 'envelope' ]); + this.loop = options.loop; + this.reverse = options.reverse; }; Tone.extend(Tone.Sampler, Tone.Instrument); /** @@ -18438,87 +19666,32 @@ * @static */ Tone.Sampler.defaults = { - 'sample': 0, - 'pitch': 0, - 'player': { 'loop': false }, + 'onload': Tone.noOp, + 'loop': false, + 'reverse': false, 'envelope': { 'attack': 0.001, 'decay': 0, 'sustain': 1, 'release': 0.1 - }, - 'filterEnvelope': { - 'attack': 0.001, - 'decay': 0.001, - 'sustain': 1, - 'release': 0.5, - 'baseFrequency': 20, - 'octaves': 10 - }, - 'filter': { 'type': 'lowpass' } - }; - /** - * load the buffers - * @param {Object} urls the urls - * @private - */ - Tone.Sampler.prototype._loadBuffers = function (urls) { - if (this.isString(urls)) { - this._buffers['0'] = new Tone.Buffer(urls, function () { - this.sample = '0'; - }.bind(this)); - } else { - urls = this._flattenUrls(urls); - for (var buffName in urls) { - this._sample = buffName; - var urlString = urls[buffName]; - this._buffers[buffName] = new Tone.Buffer(urlString); - } - } - }; - /** - * Flatten an object into a single depth object. - * thanks to https://gist.github.com/penguinboy/762197 - * @param {Object} ob - * @return {Object} - * @private - */ - Tone.Sampler.prototype._flattenUrls = function (ob) { - var toReturn = {}; - for (var i in ob) { - if (!ob.hasOwnProperty(i)) - continue; - if (this.isObject(ob[i])) { - var flatObject = this._flattenUrls(ob[i]); - for (var x in flatObject) { - if (!flatObject.hasOwnProperty(x)) - continue; - toReturn[i + '.' + x] = flatObject[x]; - } - } else { - toReturn[i] = ob[i]; - } } - return toReturn; }; /** - * Start the sample and simultaneously trigger the envelopes. - * @param {string=} sample The name of the sample to trigger, defaults to - * the last sample used. + * Trigger the start of the sample. + * @param {Interval} [pitch=0] The amount the sample should + * be repitched. * @param {Time} [time=now] The time when the sample should start - * @param {number} [velocity=1] The velocity of the note + * @param {NormalRange} [velocity=1] The velocity of the note * @returns {Tone.Sampler} this * @example - * sampler.triggerAttack("B.1"); + * sampler.triggerAttack(0, "+0.1", 0.5); */ - Tone.Sampler.prototype.triggerAttack = function (name, time, velocity) { + Tone.Sampler.prototype.triggerAttack = function (pitch, time, velocity) { time = this.toSeconds(time); - if (name) { - this.sample = name; - } + pitch = this.defaultArg(pitch, 0); + this.player.playbackRate = this.intervalToFrequencyRatio(pitch); this.player.start(time); this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); return this; }; /** @@ -18532,31 +19705,22 @@ */ Tone.Sampler.prototype.triggerRelease = function (time) { time = this.toSeconds(time); - this.filterEnvelope.triggerRelease(time); this.envelope.triggerRelease(time); this.player.stop(this.toSeconds(this.envelope.release) + time); return this; }; /** - * The name of the sample to trigger. + * If the output sample should loop or not. * @memberOf Tone.Sampler# * @type {number|string} - * @name sample - * @example - * //set the sample to "A.2" for next time the sample is triggered - * sampler.sample = "A.2"; + * @name loop */ - Object.defineProperty(Tone.Sampler.prototype, 'sample', { + Object.defineProperty(Tone.Sampler.prototype, 'loop', { get: function () { - return this._sample; + return this.player.loop; }, - set: function (name) { - if (this._buffers.hasOwnProperty(name)) { - this._sample = name; - this.player.buffer = this._buffers[name]; - } else { - throw new Error('Sampler does not have a sample named ' + name); - } + set: function (loop) { + this.player.loop = loop; } }); /** @@ -18567,33 +19731,10 @@ */ Object.defineProperty(Tone.Sampler.prototype, 'reverse', { get: function () { - for (var i in this._buffers) { - return this._buffers[i].reverse; - } + return this.player.reverse; }, set: function (rev) { - for (var i in this._buffers) { - this._buffers[i].reverse = rev; - } - } - }); - /** - * Repitch the sampled note by some interval (measured - * in semi-tones). - * @memberOf Tone.Sampler# - * @type {Interval} - * @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); + this.player.reverse = rev; } }); /** @@ -18604,23 +19745,12 @@ Tone.Instrument.prototype.dispose.call(this); this._writable([ 'player', - 'filterEnvelope', - 'envelope', - 'filter' + 'envelope' ]); this.player.dispose(); - this.filterEnvelope.dispose(); - this.envelope.dispose(); - this.filter.dispose(); this.player = null; - this.filterEnvelope = null; + this.envelope.dispose(); 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; @@ -18628,440 +19758,412 @@ Module(function (Tone) { /** - * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator - * routed through a Tone.AmplitudeEnvelope. - * - * + * @class Now called Tone.Synth * @constructor * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var synth = new Tone.SimpleSynth().toMaster(); - * synth.triggerAttackRelease("C4", "8n"); */ Tone.SimpleSynth = function (options) { - //get the defaults - options = this.defaultArg(options, Tone.SimpleSynth.defaults); - Tone.Monophonic.call(this, options); - /** - * The oscillator. - * @type {Tone.OmniOscillator} - */ - this.oscillator = new Tone.OmniOscillator(options.oscillator); - /** - * The frequency control. - * @type {Frequency} - * @signal - */ - this.frequency = this.oscillator.frequency; - /** - * The detune control. - * @type {Cents} - * @signal - */ - this.detune = this.oscillator.detune; - /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} - */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - //connect the oscillators to the output - this.oscillator.chain(this.envelope, this.output); - //start the oscillators - this.oscillator.start(); - this._readOnly([ - 'oscillator', - 'frequency', - 'detune', - 'envelope' - ]); - }; - Tone.extend(Tone.SimpleSynth, Tone.Monophonic); - /** - * @const - * @static - * @type {Object} - */ - Tone.SimpleSynth.defaults = { - 'oscillator': { 'type': 'triangle' }, - 'envelope': { - 'attack': 0.005, - 'decay': 0.1, - 'sustain': 0.3, - 'release': 1 - } - }; - /** - * start the attack portion of the envelope - * @param {Time} [time=now] the time the attack should start - * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.SimpleSynth} this - * @private - */ - Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the envelopes - this.envelope.triggerAttack(time, velocity); - return this; + console.warn('Tone.SimpleSynth is now called Tone.Synth'); + Tone.Synth.call(this, options); }; + Tone.extend(Tone.SimpleSynth, Tone.Synth); + return Tone.SimpleSynth; + }); + Module(function (Tone) { + /** - * start the release portion of the envelope - * @param {Time} [time=now] the time the release should start - * @returns {Tone.SimpleSynth} this - * @private + * @class Maps a NormalRange [0, 1] to an AudioRange [-1, 1]. + * See also Tone.AudioToGain. + * + * @extends {Tone.SignalBase} + * @constructor + * @example + * var g2a = new Tone.GainToAudio(); */ - Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function (time) { - this.envelope.triggerRelease(time); - return this; + 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.SimpleSynth} this + * @returns {Tone.GainToAudio} this */ - Tone.SimpleSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'oscillator', - 'frequency', - 'detune', - 'envelope' - ]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - this.frequency = null; - this.detune = null; + Tone.GainToAudio.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._norm.dispose(); + this._norm = null; return this; }; - return Tone.SimpleSynth; + return Tone.GainToAudio; }); Module(function (Tone) { /** - * @class AMSynth uses the output of one Tone.SimpleSynth to modulate the - * amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between - * the two signals) affects the timbre of the output signal the most. - * Read more about Amplitude Modulation Synthesis on [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm). - * + * @class Normalize takes an input min and max and maps it linearly to NormalRange [0,1] * + * @extends {Tone.SignalBase} * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below + * @param {number} inputMin the min input value + * @param {number} inputMax the max input value * @example - * var synth = new Tone.SimpleAM().toMaster(); - * synth.triggerAttackRelease("C4", "8n"); + * var norm = new Tone.Normalize(2, 4); + * var sig = new Tone.Signal(3).connect(norm); + * //output of norm is 0.5. */ - Tone.SimpleAM = function (options) { - options = this.defaultArg(options, Tone.SimpleAM.defaults); - Tone.Monophonic.call(this, options); - /** - * The carrier voice. - * @type {Tone.SimpleSynth} - */ - this.carrier = new Tone.SimpleSynth(options.carrier); - /** - * The modulator voice. - * @type {Tone.SimpleSynth} - */ - this.modulator = new Tone.SimpleSynth(options.modulator); + Tone.Normalize = function (inputMin, inputMax) { /** - * the frequency control - * @type {Frequency} - * @signal + * the min input value + * @type {number} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + this._inputMin = this.defaultArg(inputMin, 0); /** - * The ratio between the carrier and the modulator frequencies. A value of 1 - * makes both voices in unison, a value of 0.5 puts the modulator an octave below - * the carrier. - * @type {Positive} - * @signal - * @example - * //set the modulator an octave above the carrier frequency - * simpleAM.harmonicity.value = 2; + * the max input value + * @type {number} + * @private */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; + this._inputMax = this.defaultArg(inputMax, 1); /** - * convert the -1,1 output to 0,1 - * @type {Tone.AudioToGain} + * subtract the min from the input + * @type {Tone.Add} * @private */ - this._modulationScale = new Tone.AudioToGain(); + this._sub = this.input = new Tone.Add(0); /** - * the node where the modulation happens - * @type {GainNode} + * divide by the difference between the input and output + * @type {Tone.Multiply} * @private */ - this._modulationNode = this.context.createGain(); - //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.modulator.chain(this._modulationScale, this._modulationNode.gain); - this.carrier.chain(this._modulationNode, this.output); - this._readOnly([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity' - ]); + this._div = this.output = new Tone.Multiply(1); + this._sub.connect(this._div); + this._setRange(); }; - Tone.extend(Tone.SimpleAM, Tone.Monophonic); + Tone.extend(Tone.Normalize, Tone.SignalBase); /** - * @static - * @type {Object} + * The minimum value the input signal will reach. + * @memberOf Tone.Normalize# + * @type {number} + * @name min */ - 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 - } + Object.defineProperty(Tone.Normalize.prototype, 'min', { + get: function () { + return this._inputMin; }, - 'modulator': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.5, - 'decay': 0.1, - 'sustain': 1, - 'release': 0.5 - } + set: function (min) { + this._inputMin = min; + this._setRange(); } - }; + }); /** - * trigger the attack portion of the note - * - * @param {Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.SimpleAM} this - * @private + * The maximum value the input signal will reach. + * @memberOf Tone.Normalize# + * @type {number} + * @name max */ - Tone.SimpleAM.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the port glide - time = this.toSeconds(time); - //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); - return this; - }; + Object.defineProperty(Tone.Normalize.prototype, 'max', { + get: function () { + return this._inputMax; + }, + set: function (max) { + this._inputMax = max; + this._setRange(); + } + }); /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.SimpleAM} this + * set the values * @private */ - Tone.SimpleAM.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); - return this; + Tone.Normalize.prototype._setRange = function () { + this._sub.value = -this._inputMin; + this._div.value = 1 / (this._inputMax - this._inputMin); }; /** * clean up - * @returns {Tone.SimpleAM} this + * @returns {Tone.Normalize} this */ - Tone.SimpleAM.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity' - ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); - this.frequency = null; - this.harmonicity.dispose(); - this.harmonicity = null; - this._modulationScale.dispose(); - this._modulationScale = null; - this._modulationNode.disconnect(); - this._modulationNode = null; + Tone.Normalize.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._sub.dispose(); + this._sub = null; + this._div.dispose(); + this._div = null; return this; }; - return Tone.SimpleAM; + return Tone.Normalize; }); Module(function (Tone) { - /** - * @class SimpleFM is composed of two Tone.SimpleSynths where one Tone.SimpleSynth modulates - * the frequency of a second Tone.SimpleSynth. A lot of spectral content - * can be explored using the Tone.FMSynth.modulationIndex parameter. Read more about - * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm). - * - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var fmSynth = new Tone.SimpleFM().toMaster(); - * fmSynth.triggerAttackRelease("C4", "8n"); + * @class Wrapper around the native BufferSourceNode. + * @param {AudioBuffer|Tone.Buffer} buffer The buffer to play + * @param {Function} onended The callback to invoke when the + * buffer is done playing. */ - Tone.SimpleFM = function (options) { - options = this.defaultArg(options, Tone.SimpleFM.defaults); - Tone.Monophonic.call(this, options); + Tone.BufferSource = function () { + var options = this.optionsObject(arguments, [ + 'buffer', + 'onended' + ], Tone.BufferSource.defaults); /** - * The carrier voice. - * @type {Tone.SimpleSynth} + * The callback to invoke after the + * buffer source is done playing. + * @type {Function} */ - this.carrier = new Tone.SimpleSynth(options.carrier); - this.carrier.volume.value = -10; + this.onended = options.onended; /** - * The modulator voice. - * @type {Tone.SimpleSynth} + * The time that the buffer was started. + * @type {Number} + * @private */ - this.modulator = new Tone.SimpleSynth(options.modulator); - this.modulator.volume.value = -10; + this._startTime = -1; /** - * the frequency control - * @type {Frequency} - * @signal + * The gain node which envelopes the BufferSource + * @type {GainNode} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + this._gainNode = this.output = this.context.createGain(); /** - * Harmonicity is the ratio between the two voices. A harmonicity of - * 1 is no change. Harmonicity = 2 means a change of an octave. - * @type {Positive} - * @signal - * @example - * //pitch voice1 an octave below voice0 - * synth.harmonicity.value = 0.5; + * The buffer source + * @type {AudioBufferSourceNode} + * @private */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; + this._source = this.context.createBufferSource(); + this._source.connect(this._gainNode); + this._source.onended = this._onended.bind(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. - * @type {Positive} - * @signal + * The playbackRate of the buffer + * @type {AudioParam} */ - this.modulationIndex = new Tone.Multiply(options.modulationIndex); - this.modulationIndex.units = Tone.Type.Positive; + this.playbackRate = this._source.playbackRate; /** - * the node where the modulation happens - * @type {GainNode} + * The fadeIn time of the amplitude envelope. + * @type {Time} + */ + this.fadeIn = options.fadeIn; + /** + * The fadeOut time of the amplitude envelope. + * @type {Time} + */ + this.fadeOut = options.fadeOut; + /** + * The value that the buffer ramps to + * @type {Gain} * @private */ - this._modulationNode = this.context.createGain(); - //control the two voices frequency - this.frequency.connect(this.carrier.frequency); - this.frequency.chain(this.harmonicity, this.modulator.frequency); - this.frequency.chain(this.modulationIndex, this._modulationNode); - this.modulator.connect(this._modulationNode.gain); - this._modulationNode.gain.value = 0; - this._modulationNode.connect(this.carrier.frequency); - this.carrier.connect(this.output); - this._readOnly([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity', - 'modulationIndex' - ]); - ; + this._gain = 1; + //set the buffer initially + if (!this.isUndef(options.buffer)) { + this.buffer = options.buffer; + } + this.loop = options.loop; }; - Tone.extend(Tone.SimpleFM, Tone.Monophonic); + Tone.extend(Tone.BufferSource); /** - * @static - * @type {Object} + * The defaults + * @const + * @type {Object} */ - Tone.SimpleFM.defaults = { - 'harmonicity': 3, - 'modulationIndex': 10, - 'carrier': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - } - }, - 'modulator': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'triangle' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 + Tone.BufferSource.defaults = { + 'onended': Tone.noOp, + 'fadeIn': 0, + 'fadeOut': 0 + }; + /** + * Returns the playback state of the source, either "started" or "stopped". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.BufferSource# + * @name state + */ + Object.defineProperty(Tone.BufferSource.prototype, 'state', { + get: function () { + var now = this.now(); + if (this._startTime !== -1 && now > this._startTime) { + return Tone.State.Started; + } else { + return Tone.State.Stopped; } } + }); + /** + * Start the buffer + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample + * to start at. + * @param {Time=} duration How long the sample should play. If no duration + * is given, it will default to the full length + * of the sample (minus any offset) + * @param {Gain} [gain=1] The gain to play the buffer back at. + * @param {Time=} fadeInTime The optional fadeIn ramp time. + * @return {Tone.BufferSource} this + */ + Tone.BufferSource.prototype.start = function (time, offset, duration, gain, fadeInTime) { + if (this._startTime !== -1) { + throw new Error('Tone.BufferSource: can only be started once.'); + } + if (!this.buffer) { + throw new Error('Tone.BufferSource: no buffer set.'); + } + time = this.toSeconds(time); + //if it's a loop the default offset is the loopstart point + if (this.loop) { + offset = this.defaultArg(offset, this.loopStart); + } else { + //otherwise the default offset is 0 + offset = this.defaultArg(offset, 0); + } + offset = this.toSeconds(offset); + //the values in seconds + time = this.toSeconds(time); + this._source.start(time, offset); + gain = this.defaultArg(gain, 1); + this._gain = gain; + //the fadeIn time + if (this.isUndef(fadeInTime)) { + fadeInTime = this.toSeconds(this.fadeIn); + } else { + fadeInTime = this.toSeconds(fadeInTime); + } + if (fadeInTime > 0) { + this._gainNode.gain.setValueAtTime(0, time); + this._gainNode.gain.linearRampToValueAtTime(this._gain, time + fadeInTime); + } else { + this._gainNode.gain.setValueAtTime(gain, time); + } + this._startTime = time + fadeInTime; + if (!this.isUndef(duration)) { + duration = this.defaultArg(duration, this.buffer.duration - offset); + duration = this.toSeconds(duration); + this.stop(time + duration + fadeInTime, fadeInTime); + } + return this; }; /** - * trigger the attack portion of the note - * - * @param {Time} [time=now] the time the note will occur - * @param {number} [velocity=1] the velocity of the note - * @returns {Tone.SimpleFM} this - * @private + * Stop the buffer. Optionally add a ramp time to fade the + * buffer out. + * @param {Time=} time The time the buffer should stop. + * @param {Time=} fadeOutTime How long the gain should fade out for + * @return {Tone.BufferSource} this */ - Tone.SimpleFM.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the port glide + Tone.BufferSource.prototype.stop = function (time, fadeOutTime) { + if (!this.buffer) { + throw new Error('Tone.BufferSource: no buffer set.'); + } time = this.toSeconds(time); - //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); + //the fadeOut time + if (this.isUndef(fadeOutTime)) { + fadeOutTime = this.toSeconds(this.fadeOut); + } else { + fadeOutTime = this.toSeconds(fadeOutTime); + } + //cancel the end curve + this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime); + //set a new one + if (fadeOutTime > 0) { + this._gainNode.gain.setValueAtTime(this._gain, time); + this._gainNode.gain.linearRampToValueAtTime(0, time + fadeOutTime); + time += fadeOutTime; + } else { + this._gainNode.gain.setValueAtTime(0, time); + } + this._source.stop(time); return this; }; /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.SimpleFM} this + * Internal callback when the buffer is ended. + * Invokes `onended` and disposes the node. * @private */ - Tone.SimpleFM.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); - return this; + Tone.BufferSource.prototype._onended = function () { + this.onended(this); + this.dispose(); }; /** - * clean up - * @returns {Tone.SimpleFM} this + * If loop is true, the loop will start at this position. + * @memberOf Tone.BufferSource# + * @type {Time} + * @name loopStart */ - Tone.SimpleFM.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'carrier', - 'modulator', - 'frequency', - 'harmonicity', - 'modulationIndex' - ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); - this.frequency = null; - this.modulationIndex.dispose(); - this.modulationIndex = null; - this.harmonicity.dispose(); - this.harmonicity = null; - this._modulationNode.disconnect(); - this._modulationNode = null; + Object.defineProperty(Tone.BufferSource.prototype, 'loopStart', { + get: function () { + return this._source.loopStart; + }, + set: function (loopStart) { + this._source.loopStart = this.toSeconds(loopStart); + } + }); + /** + * If loop is true, the loop will end at this position. + * @memberOf Tone.BufferSource# + * @type {Time} + * @name loopEnd + */ + Object.defineProperty(Tone.BufferSource.prototype, 'loopEnd', { + get: function () { + return this._source.loopEnd; + }, + set: function (loopEnd) { + this._source.loopEnd = this.toSeconds(loopEnd); + } + }); + /** + * The audio buffer belonging to the player. + * @memberOf Tone.BufferSource# + * @type {AudioBuffer} + * @name buffer + */ + Object.defineProperty(Tone.BufferSource.prototype, 'buffer', { + get: function () { + return this._source.buffer; + }, + set: function (buffer) { + if (buffer instanceof Tone.Buffer) { + this._source.buffer = buffer.get(); + } else { + this._source.buffer = buffer; + } + } + }); + /** + * If the buffer should loop once it's over. + * @memberOf Tone.BufferSource# + * @type {boolean} + * @name loop + */ + Object.defineProperty(Tone.BufferSource.prototype, 'loop', { + get: function () { + return this._source.loop; + }, + set: function (loop) { + this._source.loop = loop; + } + }); + /** + * Clean up. + * @return {Tone.BufferSource} this + */ + Tone.BufferSource.prototype.dispose = function () { + this.onended = null; + if (this._source) { + this._source.onended = null; + this._source.disconnect(); + this._source = null; + } + if (this._gainNode) { + this._gainNode.disconnect(); + this._gainNode = null; + } + this._startTime = -1; + this.playbackRate = null; + this.output = null; return this; }; - return Tone.SimpleFM; + return Tone.BufferSource; }); Module(function (Tone) { @@ -19137,11 +20239,12 @@ /** * wrapper for getUserMedia function * @param {function} callback + * @param {function} error * @private */ - Tone.ExternalInput.prototype._getUserMedia = function (callback) { + Tone.ExternalInput.prototype._getUserMedia = function (callback, error) { if (!Tone.ExternalInput.supported) { - throw new Error('browser does not support \'getUserMedia\''); + error('browser does not support \'getUserMedia\''); } if (Tone.ExternalInput.sources[this._inputNum]) { this._constraints = { audio: { optional: [{ sourceId: Tone.ExternalInput.sources[this._inputNum].id }] } }; @@ -19150,7 +20253,7 @@ this._onStream(stream); callback(); }.bind(this), function (err) { - callback(err); + error(err); }); }; /** @@ -19160,7 +20263,7 @@ */ Tone.ExternalInput.prototype._onStream = function (stream) { if (!this.isFunction(this.context.createMediaStreamSource)) { - throw new Error('browser does not support the \'MediaStreamSourceNode\''); + throw new Error('Tone.ExternalInput: browser does not support the \'MediaStreamSourceNode\''); } //can only start a new source if the previous one is closed if (!this._stream) { @@ -19175,12 +20278,18 @@ * Open the media stream * @param {function=} callback The callback function to * execute when the stream is open + * @param {function=} error The callback function to execute + * when the media stream can't open. + * This is fired either because the browser + * doesn't support the media stream, + * or the user blocked opening the microphone. * @return {Tone.ExternalInput} this */ - Tone.ExternalInput.prototype.open = function (callback) { + Tone.ExternalInput.prototype.open = function (callback, error) { callback = this.defaultArg(callback, Tone.noOp); + error = this.defaultArg(error, Tone.noOp); Tone.ExternalInput.getSources(function () { - this._getUserMedia(callback); + this._getUserMedia(callback, error); }.bind(this)); return this; }; @@ -19300,384 +20409,560 @@ return Tone.ExternalInput; }); Module(function (Tone) { - /** - * @class Opens up the default source (typically the microphone). - * - * @constructor - * @extends {Tone.ExternalInput} + * @class Tone.MultiPlayer is well suited for one-shots, multi-sampled istruments + * or any time you need to play a bunch of audio buffers. + * @param {Object|Array|Tone.Buffers} buffers The buffers which are available + * to the MultiPlayer + * @param {Function} onload The callback to invoke when all of the buffers are loaded. + * @extends {Tone} * @example - * //mic will feedback if played through master - * var mic = new Tone.Microphone(); - * mic.open(function(){ - * //start the mic at ten seconds - * mic.start(10); - * }); - * //stop the mic - * mic.stop(20); + * var multiPlayer = new MultiPlayer({ + * "kick" : "path/to/kick.mp3", + * "snare" : "path/to/snare.mp3", + * }, function(){ + * multiPlayer.start("kick"); + * }); + * @example + * //can also store the values in an array + * var multiPlayer = new MultiPlayer(["path/to/kick.mp3", "path/to/snare.mp3"], + * function(){ + * //if an array is passed in, the samples are referenced to by index + * multiPlayer.start(1); + * }); */ - Tone.Microphone = function () { - Tone.ExternalInput.call(this, 0); + Tone.MultiPlayer = function () { + var options = this.optionsObject(arguments, [ + 'urls', + 'onload' + ], Tone.MultiPlayer.defaults); + if (options.urls instanceof Tone.Buffers) { + /** + * All the buffers belonging to the player. + * @type {Tone.Buffers} + */ + this.buffers = options.urls; + } else { + this.buffers = new Tone.Buffers(options.urls, options.onload); + } + /** + * Keeps track of the currently playing sources. + * @type {Array} + * @private + */ + this._activeSources = []; + /** + * The fade in envelope which is applied + * to the beginning of the BufferSource + * @type {Time} + */ + this.fadeIn = options.fadeIn; + /** + * The fade out envelope which is applied + * to the end of the BufferSource + * @type {Time} + */ + this.fadeOut = options.fadeOut; + /** + * The output volume node + * @type {Tone.Volume} + * @private + */ + this._volume = this.output = new Tone.Volume(options.volume); + /** + * The volume of the output in decibels. + * @type {Decibels} + * @signal + * @example + * source.volume.value = -6; + */ + this.volume = this._volume.volume; + this._readOnly('volume'); + //make the output explicitly stereo + this._volume.output.output.channelCount = 2; + this._volume.output.output.channelCountMode = 'explicit'; + //mute initially + this.mute = options.mute; }; - Tone.extend(Tone.Microphone, Tone.ExternalInput); + Tone.extend(Tone.MultiPlayer, Tone.Source); /** - * If getUserMedia is supported by the browser. - * @type {Boolean} - * @memberOf Tone.Microphone# - * @name supported - * @static - * @readOnly + * The defaults + * @type {Object} */ - Object.defineProperty(Tone.Microphone, 'supported', { - get: function () { - return Tone.ExternalInput.supported; + Tone.MultiPlayer.defaults = { + 'onload': Tone.noOp, + 'fadeIn': 0, + 'fadeOut': 0 + }; + /** + * Get the given buffer. + * @param {String|Number|AudioBuffer|Tone.Buffer} buffer + * @return {AudioBuffer} The requested buffer. + * @private + */ + Tone.MultiPlayer.prototype._getBuffer = function (buffer) { + if (this.isNumber(buffer) || this.isString(buffer)) { + return this.buffers.get(buffer).get(); + } else if (buffer instanceof Tone.Buffer) { + return buffer.get(); + } else { + return buffer; } - }); - return Tone.Microphone; - }); - Module(function (Tone) { - + }; /** - * @class Clip the incoming signal so that the output is always between min and max. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number} min the minimum value of the outgoing signal - * @param {number} max the maximum value of the outgoing signal - * @example - * var clip = new Tone.Clip(0.5, 1); - * var osc = new Tone.Oscillator().connect(clip); - * //clips the output of the oscillator to between 0.5 and 1. + * Start a buffer by name. The `start` method allows a number of options + * to be passed in such as offset, interval, and gain. This is good for multi-sampled + * instruments and sound sprites where samples are repitched played back at different velocities. + * @param {String|AudioBuffer} buffer The name of the buffer to start. + * Or pass in a buffer which will be started. + * @param {Time} time When to start the buffer. + * @param {Time} [offset=0] The offset into the buffer to play from. + * @param {Time=} duration How long to play the buffer for. + * @param {Interval} [interval=0] The interval to repitch the buffer. + * @param {Gain} [gain=1] The gain to play the sample at. + * @return {Tone.MultiPlayer} this */ - Tone.Clip = function (min, max) { - //make sure the args are in the right order - if (min > max) { - var tmp = min; - min = max; - max = tmp; + Tone.MultiPlayer.prototype.start = function (buffer, time, offset, duration, interval, gain) { + buffer = this._getBuffer(buffer); + var source = new Tone.BufferSource(buffer).connect(this.output); + this._activeSources.push(source); + time = this.toSeconds(time); + source.start(time, offset, duration, this.defaultArg(gain, 1), this.fadeIn); + if (duration) { + source.stop(time + this.toSeconds(duration), this.fadeOut); } - /** - * The min clip value - * @type {Number} - * @signal - */ - this.min = this.input = new Tone.Min(max); - this._readOnly('min'); - /** - * The max clip value - * @type {Number} - * @signal - */ - this.max = this.output = new Tone.Max(min); - this._readOnly('max'); - this.min.connect(this.max); + source.onended = this._onended.bind(this); + interval = this.defaultArg(interval, 0); + source.playbackRate.value = this.intervalToFrequencyRatio(interval); + return this; }; - Tone.extend(Tone.Clip, Tone.SignalBase); /** - * clean up - * @returns {Tone.Clip} this + * Internal callback when a buffer is done playing. + * @param {Tone.BufferSource} source The stopped source + * @private */ - 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; + Tone.MultiPlayer.prototype._onended = function (source) { + var index = this._activeSources.indexOf(source); + this._activeSources.splice(index, 1); + }; + /** + * Stop all instances of the currently playing buffer at the given time. + * @param {String|AudioBuffer} buffer The buffer to stop. + * @param {Time=} time When to stop the buffer + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.stop = function (buffer, time) { + buffer = this._getBuffer(buffer); + time = this.toSeconds(time); + for (var i = 0; i < this._activeSources.length; i++) { + if (this._activeSources[i].buffer === buffer) { + this._activeSources[i].stop(time, this.fadeOut); + } + } return this; }; - return Tone.Clip; - }); - Module(function (Tone) { - /** - * @class Normalize takes an input min and max and maps it linearly to NormalRange [0,1] - * - * @extends {Tone.SignalBase} - * @constructor - * @param {number} inputMin the min input value - * @param {number} inputMax the max input value - * @example - * var norm = new Tone.Normalize(2, 4); - * var sig = new Tone.Signal(3).connect(norm); - * //output of norm is 0.5. + * Stop all currently playing buffers at the given time. + * @param {Time=} time When to stop the buffers. + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.stopAll = function (time) { + time = this.toSeconds(time); + for (var i = 0; i < this._activeSources.length; i++) { + this._activeSources[i].stop(time, this.fadeOut); + } + return this; + }; + /** + * Add another buffer to the available buffers. + * @param {String} name The name to that the buffer is refered + * to in start/stop methods. + * @param {String|Tone.Buffer} url The url of the buffer to load + * or the buffer. + * @param {Function} callback The function to invoke after the buffer is loaded. + */ + Tone.MultiPlayer.prototype.add = function (name, url, callback) { + this.buffers.add(name, url, callback); + return this; + }; + /** + * Returns the playback state of the source. "started" + * if there are any buffers playing. "stopped" otherwise. + * @type {Tone.State} + * @readOnly + * @memberOf Tone.MultiPlayer# + * @name state + */ + Object.defineProperty(Tone.MultiPlayer.prototype, 'state', { + get: function () { + return this._activeSources.length > 0 ? Tone.State.Started : Tone.State.Stopped; + } + }); + /** + * Mute the output. + * @memberOf Tone.MultiPlayer# + * @type {boolean} + * @name mute + * @example + * //mute the output + * source.mute = true; + */ + Object.defineProperty(Tone.MultiPlayer.prototype, 'mute', { + get: function () { + return this._volume.mute; + }, + set: function (mute) { + this._volume.mute = mute; + } + }); + /** + * Clean up. + * @return {Tone.MultiPlayer} this + */ + Tone.MultiPlayer.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._volume.dispose(); + this._volume = null; + this._writable('volume'); + this.volume = null; + this.buffers.dispose(); + this.buffers = null; + for (var i = 0; i < this._activeSources.length; i++) { + this._activeSources[i].dispose(); + } + this._activeSources = null; + return this; + }; + return Tone.MultiPlayer; + }); + Module(function (Tone) { + /** + * @class Tone.GrainPlayer implements [granular synthesis](https://en.wikipedia.org/wiki/Granular_synthesis). + * Granular Synthesis enables you to adjust pitch and playback rate independently. The grainSize is the + * amount of time each small chunk of audio is played for and the overlap is the + * amount of crossfading transition time between successive grains. + * @extends {Tone} + * @param {String|Tone.Buffer} url The url to load, or the Tone.Buffer to play. + * @param {Function=} callback The callback to invoke after the url is loaded. */ - Tone.Normalize = function (inputMin, inputMax) { + Tone.GrainPlayer = function () { + var options = this.optionsObject(arguments, [ + 'url', + 'onload' + ], Tone.GrainPlayer.defaults); + Tone.Source.call(this); /** - * the min input value - * @type {number} + * The audio buffer belonging to the player. + * @type {Tone.Buffer} + */ + this.buffer = new Tone.Buffer(options.url, options.onload); + /** + * Plays the buffer with a small envelope + * @type {Tone.MultiPlayer} * @private */ - this._inputMin = this.defaultArg(inputMin, 0); + this._player = this.output = new Tone.MultiPlayer(); /** - * the max input value - * @type {number} + * Create a repeating tick to schedule + * the grains. + * @type {Tone.Clock} * @private */ - this._inputMax = this.defaultArg(inputMax, 1); + this._clock = new Tone.Clock(this._tick.bind(this), 1); /** - * subtract the min from the input - * @type {Tone.Add} + * @type {Number} * @private */ - this._sub = this.input = new Tone.Add(0); + this._loopStart = 0; /** - * divide by the difference between the input and output - * @type {Tone.Multiply} + * @type {Number} * @private */ - this._div = this.output = new Tone.Multiply(1); - this._sub.connect(this._div); - this._setRange(); + this._loopEnd = 0; + /** + * @type {Number} + * @private + */ + this._playbackRate = options.playbackRate; + /** + * @type {Number} + * @private + */ + this._grainSize = options.grainSize; + /** + * @private + * @type {Number} + */ + this._overlap = options.overlap; + /** + * Adjust the pitch independently of the playbackRate. + * @type {Cents} + */ + this.detune = options.detune; + /** + * The amount of time randomly added + * or subtracted from the grain's offset + * @type {Time} + */ + this.drift = options.drift; + //setup + this.overlap = options.overlap; + this.loop = options.loop; + this.playbackRate = options.playbackRate; + this.grainSize = options.grainSize; + this.loopStart = options.loopStart; + this.loopEnd = options.loopEnd; + this.reverse = options.reverse; }; - Tone.extend(Tone.Normalize, Tone.SignalBase); + Tone.extend(Tone.GrainPlayer, Tone.Source); /** - * The minimum value the input signal will reach. - * @memberOf Tone.Normalize# - * @type {number} - * @name min + * the default parameters + * @static + * @const + * @type {Object} */ - Object.defineProperty(Tone.Normalize.prototype, 'min', { - get: function () { - return this._inputMin; - }, - set: function (min) { - this._inputMin = min; - this._setRange(); - } - }); + Tone.GrainPlayer.defaults = { + 'onload': Tone.noOp, + 'overlap': 0.1, + 'grainSize': 0.2, + 'drift': 0, + 'playbackRate': 1, + 'detune': 0, + 'loop': false, + 'loopStart': 0, + 'loopEnd': 0, + 'reverse': false + }; /** - * The maximum value the input signal will reach. - * @memberOf Tone.Normalize# - * @type {number} - * @name max + * Play the buffer at the given startTime. Optionally add an offset + * from the start of the buffer to play from. + * + * @param {Time} [startTime=now] When the player should start. + * @param {Time} [offset=0] The offset from the beginning of the sample + * to start at. + * @return {Tone.GrainPlayer} this */ - Object.defineProperty(Tone.Normalize.prototype, 'max', { - get: function () { - return this._inputMax; - }, - set: function (max) { - this._inputMax = max; - this._setRange(); - } - }); /** - * set the values + * Internal start method + * @param {Time} time + * @param {Time} offset * @private */ - Tone.Normalize.prototype._setRange = function () { - this._sub.value = -this._inputMin; - this._div.value = 1 / (this._inputMax - this._inputMin); + Tone.GrainPlayer.prototype._start = function (time, offset) { + offset = this.defaultArg(offset, 0); + offset = this.toSeconds(offset); + time = this.toSeconds(time); + this._offset = offset; + this._clock.start(time); }; /** - * clean up - * @returns {Tone.Normalize} this + * Internal start method + * @param {Time} time + * @private */ - Tone.Normalize.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._sub.dispose(); - this._sub = null; - this._div.dispose(); - this._div = null; - return this; + Tone.GrainPlayer.prototype._stop = function (time) { + this._clock.stop(time); + this._player.stop(this.buffer, time); + this._offset = 0; }; - return Tone.Normalize; - }); - Module(function (Tone) { - /** - * @class Route a single input to the specified output. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number} [outputCount=2] the number of inputs the switch accepts - * @example - * var route = new Tone.Route(4); - * var signal = new Tone.Signal(3).connect(route); - * route.select(0); - * //signal is routed through output 0 - * route.select(3); - * //signal is now routed through output 3 - */ - Tone.Route = function (outputCount) { - outputCount = this.defaultArg(outputCount, 2); - Tone.call(this, 1, outputCount); - /** - * The control signal. - * @type {Number} - * @signal - */ - this.gate = new Tone.Signal(0); - this._readOnly('gate'); - //make all the inputs and connect them - for (var i = 0; i < outputCount; i++) { - var routeGate = new RouteGate(i); - this.output[i] = routeGate; - this.gate.connect(routeGate.selecter); - this.input.connect(routeGate); + * Invoked on each clock tick. scheduled a new + * grain at this time. + * @param {Time} time + * @private + */ + Tone.GrainPlayer.prototype._tick = function (time) { + var bufferDuration = this.buffer.duration; + if (this.loop && this._loopEnd > 0) { + bufferDuration = this._loopEnd; + } + var drift = (Math.random() * 2 - 1) * this.drift; + var offset = this._offset - this._overlap + drift; + var detune = this.detune / 100; + var originalFadeIn = this._player.fadeIn; + if (this.loop && this._offset > bufferDuration) { + //play the end + var endSegmentDuration = this._offset - bufferDuration; + this._player.start(this.buffer, time, offset, endSegmentDuration + this._overlap, detune); + //and play the beginning + offset = this._offset % bufferDuration; + this._offset = this._loopStart; + this._player.fadeIn = 0; + this._player.start(this.buffer, time + endSegmentDuration, this._offset, offset + this._overlap, detune); + } else if (this._offset > bufferDuration) { + //set the state to stopped. + this.stop(time); + } else { + if (offset < 0) { + this._player.fadeIn = Math.max(this._player.fadeIn + offset, 0); + offset = 0; + } + this._player.start(this.buffer, time, offset, this.grainSize + this._overlap, detune); } + this._player.fadeIn = originalFadeIn; + //increment the offset + var duration = this._clock._nextTick - time; + this._offset += duration * this._playbackRate; }; - Tone.extend(Tone.Route, Tone.SignalBase); /** - * Routes the signal to one of the outputs and close the others. - * @param {number} [which=0] Open one of the gates (closes the other). - * @param {Time} [time=now] The time when the switch will open. - * @returns {Tone.Route} this + * Jump to a specific time and play it. + * @param {Time} offset The offset to jump to. + * @param {Time=} time When to make the jump. + * @return {[type]} [description] */ - Tone.Route.prototype.select = function (which, time) { - //make sure it's an integer - which = Math.floor(which); - this.gate.setValueAtTime(which, this.toSeconds(time)); + Tone.GrainPlayer.prototype.scrub = function (offset, time) { + this._offset = this.toSeconds(offset); + this._tick(this.toSeconds(time)); return this; }; /** - * Clean up. - * @returns {Tone.Route} this + * The playback rate of the sample + * @memberOf Tone.GrainPlayer# + * @type {Positive} + * @name playbackRate */ - 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; + Object.defineProperty(Tone.GrainPlayer.prototype, 'playbackRate', { + get: function () { + return this._playbackRate; + }, + set: function (rate) { + this._playbackRate = rate; + this.grainSize = this._grainSize; } - Tone.prototype.dispose.call(this); - return this; - }; - ////////////START HELPER//////////// + }); /** - * helper class for Tone.Route representing a single gate - * @constructor - * @extends {Tone} - * @private + * The loop start time. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name loopStart */ - var RouteGate = function (num) { - /** - * the selector - * @type {Tone.Equal} - */ - this.selecter = new Tone.Equal(num); - /** - * the gate - * @type {GainNode} - */ - this.gate = this.input = this.output = this.context.createGain(); - //connect the selecter to the gate gain - this.selecter.connect(this.gate.gain); - }; - Tone.extend(RouteGate); + Object.defineProperty(Tone.GrainPlayer.prototype, 'loopStart', { + get: function () { + return this._loopStart; + }, + set: function (time) { + this._loopStart = this.toSeconds(time); + } + }); /** - * clean up - * @private + * The loop end time. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name loopEnd */ - RouteGate.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this.selecter.dispose(); - this.selecter = null; - this.gate.disconnect(); - this.gate = null; - }; - ////////////END HELPER//////////// - //return Tone.Route - return Tone.Route; - }); - Module(function (Tone) { - + Object.defineProperty(Tone.GrainPlayer.prototype, 'loopEnd', { + get: function () { + return this._loopEnd; + }, + set: function (time) { + this._loopEnd = this.toSeconds(time); + } + }); /** - * @class When the gate is set to 0, the input signal does not pass through to the output. - * If the gate is set to 1, the input signal passes through. - * the gate is initially closed. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {Boolean} [open=false] If the gate is initially open or closed. - * @example - * var sigSwitch = new Tone.Switch(); - * var signal = new Tone.Signal(2).connect(sigSwitch); - * //initially no output from sigSwitch - * sigSwitch.gate.value = 1; - * //open the switch and allow the signal through - * //the output of sigSwitch is now 2. - */ - Tone.Switch = function (open) { - open = this.defaultArg(open, false); - Tone.call(this); - /** - * The control signal for the switch. - * When this value is 0, the input signal will NOT pass through, - * when it is high (1), the input signal will pass through. - * - * @type {Number} - * @signal - */ - this.gate = new Tone.Signal(0); - this._readOnly('gate'); - /** - * thresh the control signal to either 0 or 1 - * @type {Tone.GreaterThan} - * @private - */ - this._thresh = new Tone.GreaterThan(0.5); - this.input.connect(this.output); - this.gate.chain(this._thresh, this.output.gain); - //initially open - if (open) { - this.open(); + * The direction the buffer should play in + * @memberOf Tone.GrainPlayer# + * @type {boolean} + * @name reverse + */ + Object.defineProperty(Tone.GrainPlayer.prototype, 'reverse', { + get: function () { + return this.buffer.reverse; + }, + set: function (rev) { + this.buffer.reverse = rev; } - }; - Tone.extend(Tone.Switch, Tone.SignalBase); + }); /** - * Open the switch at a specific time. - * - * @param {Time} [time=now] The time when the switch will be open. - * @returns {Tone.Switch} this - * @example - * //open the switch to let the signal through - * sigSwitch.open(); + * The size of each chunk of audio that the + * buffer is chopped into and played back at. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name grainSize + */ + Object.defineProperty(Tone.GrainPlayer.prototype, 'grainSize', { + get: function () { + return this._grainSize; + }, + set: function (size) { + this._grainSize = this.toSeconds(size); + this._clock.frequency.value = this._playbackRate / this._grainSize; + } + }); + /** + * This is the duration of the cross-fade between + * sucessive grains. + * @memberOf Tone.GrainPlayer# + * @type {Time} + * @name overlap + */ + Object.defineProperty(Tone.GrainPlayer.prototype, 'overlap', { + get: function () { + return this._overlap; + }, + set: function (time) { + time = this.toSeconds(time); + this._overlap = time; + if (this._overlap < 0) { + this._player.fadeIn = 0.01; + this._player.fadeOut = 0.01; + } else { + this._player.fadeIn = time; + this._player.fadeOut = time; + } + } + }); + /** + * Clean up + * @return {Tone.GrainPlayer} this */ - Tone.Switch.prototype.open = function (time) { - this.gate.setValueAtTime(1, this.toSeconds(time)); + Tone.GrainPlayer.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + this.buffer.dispose(); + this.buffer = null; + this._player.dispose(); + this._player = null; + this._clock.dispose(); + this._clock = null; return this; }; + return Tone.GrainPlayer; + }); + Module(function (Tone) { + /** - * Close the switch at a specific time. + * @class Opens up the default source (typically the microphone). * - * @param {Time} [time=now] The time when the switch will be closed. - * @returns {Tone.Switch} this + * @constructor + * @extends {Tone.ExternalInput} * @example - * //close the switch a half second from now - * sigSwitch.close("+0.5"); + * //mic will feedback if played through master + * var mic = new Tone.Microphone(); + * mic.open(function(){ + * //start the mic at ten seconds + * mic.start(10); + * }); + * //stop the mic + * mic.stop(20); */ - Tone.Switch.prototype.close = function (time) { - this.gate.setValueAtTime(0, this.toSeconds(time)); - return this; + Tone.Microphone = function () { + Tone.ExternalInput.call(this, 0); }; + Tone.extend(Tone.Microphone, Tone.ExternalInput); /** - * Clean up. - * @returns {Tone.Switch} this + * If getUserMedia is supported by the browser. + * @type {Boolean} + * @memberOf Tone.Microphone# + * @name supported + * @static + * @readOnly */ - Tone.Switch.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable('gate'); - this.gate.dispose(); - this.gate = null; - this._thresh.dispose(); - this._thresh = null; - return this; - }; - return Tone.Switch; + Object.defineProperty(Tone.Microphone, 'supported', { + get: function () { + return Tone.ExternalInput.supported; + } + }); + return Tone.Microphone; }); - //UMD - if ( typeof define === "function" && define.amd ) { - define(function() { - return Tone; - }); - } else if (typeof module === "object") { - module.exports = Tone; - } else { - root.Tone = Tone; - } -} (this)); \ No newline at end of file + return Tone; +})); \ No newline at end of file diff --git a/build/Tone.min.js b/build/Tone.min.js index a74dcb5ec..9a5017194 100644 --- a/build/Tone.min.js +++ b/build/Tone.min.js @@ -1,13 +1,15 @@ -!function(root){"use strict";function Main(t){Tone=t()}function Module(t){t(Tone)}var Tone;/** +!function(t,e){"function"==typeof define&&define.amd?define(function(){return e()}):"object"==typeof module?module.exports=e():t.Tone=e()}(this,function(){"use strict";function t(t){i=t()}function e(t){t(i)}var i;/** * Tone.js * @author Yotam Mann * @license http://opensource.org/licenses/MIT MIT License - * @copyright 2014-2015 Yotam Mann + * @copyright 2014-2016 Yotam Mann */ -Main(function(){function t(t){return void 0===t}function e(t){return"function"==typeof t}var i,s,n,o;if(t(window.AudioContext)&&(window.AudioContext=window.webkitAudioContext),t(window.OfflineAudioContext)&&(window.OfflineAudioContext=window.webkitOfflineAudioContext),t(AudioContext))throw new Error("Web Audio is not supported in this browser");return i=new AudioContext,e(AudioContext.prototype.createGain)||(AudioContext.prototype.createGain=AudioContext.prototype.createGainNode),e(AudioContext.prototype.createDelay)||(AudioContext.prototype.createDelay=AudioContext.prototype.createDelayNode),e(AudioContext.prototype.createPeriodicWave)||(AudioContext.prototype.createPeriodicWave=AudioContext.prototype.createWaveTable),e(AudioBufferSourceNode.prototype.start)||(AudioBufferSourceNode.prototype.start=AudioBufferSourceNode.prototype.noteGrainOn),e(AudioBufferSourceNode.prototype.stop)||(AudioBufferSourceNode.prototype.stop=AudioBufferSourceNode.prototype.noteOff),e(OscillatorNode.prototype.start)||(OscillatorNode.prototype.start=OscillatorNode.prototype.noteOn),e(OscillatorNode.prototype.stop)||(OscillatorNode.prototype.stop=OscillatorNode.prototype.noteOff),e(OscillatorNode.prototype.setPeriodicWave)||(OscillatorNode.prototype.setPeriodicWave=OscillatorNode.prototype.setWaveTable),AudioNode.prototype._nativeConnect=AudioNode.prototype.connect,AudioNode.prototype.connect=function(e,i,s){if(e.input)Array.isArray(e.input)?(t(s)&&(s=0),this.connect(e.input[s])):this.connect(e.input,i,s);else try{e instanceof AudioNode?this._nativeConnect(e,i,s):this._nativeConnect(e,i)}catch(n){throw new Error("error connecting to node: "+e)}},s=function(e,i){t(e)||1===e?this.input=this.context.createGain():e>1&&(this.input=new Array(e)),t(i)||1===i?this.output=this.context.createGain():i>1&&(this.output=new Array(e))},s.prototype.set=function(e,i,n){var o,r,a,l,h,u;this.isObject(e)?n=i:this.isString(e)&&(o={},o[e]=i,e=o);for(r in e){if(i=e[r],a=this,-1!==r.indexOf(".")){for(l=r.split("."),h=0;h1)for(t=arguments[0],e=1;e1)for(t=1;t0)for(t=this,e=0;e0)for(var t=0;te;e++)s=e/i*2-1,this._curve[e]=t(s,e);return this._shaper.curve=this._curve,this},Object.defineProperty(t.WaveShaper.prototype,"curve",{get:function(){return this._shaper.curve},set:function(t){this._curve=new Float32Array(t),this._shaper.curve=this._curve}}),Object.defineProperty(t.WaveShaper.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){if(-1===["none","2x","4x"].indexOf(t))throw new Error("invalid oversampling: "+t);this._shaper.oversample=t}}),t.WaveShaper.prototype.dispose=function(){return t.prototype.dispose.call(this),this._shaper.disconnect(),this._shaper=null,this._curve=null,this},t.WaveShaper}),Module(function(Tone){function getTransportBpm(){return Tone.Transport&&Tone.Transport.bpm?Tone.Transport.bpm.value:120}function getTransportTimeSignature(){return Tone.Transport&&Tone.Transport.timeSignature?Tone.Transport.timeSignature:4}function toNotationHelper(t,e,i,s){var n,o,r,a,l=this.toSeconds(t),h=this.notationToSeconds(s[s.length-1],e,i),u="";for(n=0;n1-r%1&&(r+=a),r=Math.floor(r),r>0){if(u+=1===r?s[n]:r.toString()+"*"+s[n],l-=r*o,h>l)break;u+=" + "}return""===u&&(u="0"),u}var noteToScaleIndex,scaleIndexToNote;return Tone.Type={Default:"number",Time:"time",Frequency:"frequency",NormalRange:"normalRange",AudioRange:"audioRange",Decibels:"db",Interval:"interval",BPM:"bpm",Positive:"positive",Cents:"cents",Degrees:"degrees",MIDI:"midi",TransportTime:"transportTime",Ticks:"tick",Note:"note",Milliseconds:"milliseconds",Notation:"notation"},Tone.prototype.isNowRelative=function(){var t=new RegExp(/^\s*\+(.)+/i);return function(e){return t.test(e)}}(),Tone.prototype.isTicks=function(){var t=new RegExp(/^\d+i$/i);return function(e){return t.test(e)}}(),Tone.prototype.isNotation=function(){var t=new RegExp(/^[0-9]+[mnt]$/i);return function(e){return t.test(e)}}(),Tone.prototype.isTransportTime=function(){var t=new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i);return function(e){return t.test(e)}}(),Tone.prototype.isNote=function(){var t=new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i);return function(e){return t.test(e)}}(),Tone.prototype.isFrequency=function(){var t=new RegExp(/^\d*\.?\d+hz$/i);return function(e){return t.test(e)}}(),Tone.prototype.notationToSeconds=function(t,e,i){var s,n,o,r;return e=this.defaultArg(e,getTransportBpm()),i=this.defaultArg(i,getTransportTimeSignature()),s=60/e,"1n"===t&&(t="1m"),n=parseInt(t,10),o=0,0===n&&(o=0),r=t.slice(-1),o="t"===r?4/n*2/3:"n"===r?4/n:"m"===r?n*i:0,s*o},Tone.prototype.transportTimeToSeconds=function(t,e,i){var s,n,o,r,a;return e=this.defaultArg(e,getTransportBpm()),i=this.defaultArg(i,getTransportTimeSignature()),s=0,n=0,o=0,r=t.split(":"),2===r.length?(s=parseFloat(r[0]),n=parseFloat(r[1])):1===r.length?n=parseFloat(r[0]):3===r.length&&(s=parseFloat(r[0]),n=parseFloat(r[1]),o=parseFloat(r[2])),a=s*i+n+o/4,a*(60/e)},Tone.prototype.ticksToSeconds=function(t,e){if(this.isUndef(Tone.Transport))return 0;t=parseFloat(t),e=this.defaultArg(e,getTransportBpm());var i=60/e/Tone.Transport.PPQ;return i*t},Tone.prototype.frequencyToSeconds=function(t){return 1/parseFloat(t)},Tone.prototype.samplesToSeconds=function(t){return t/this.context.sampleRate},Tone.prototype.secondsToSamples=function(t){return t*this.context.sampleRate},Tone.prototype.secondsToTransportTime=function(t,e,i){var s,n,o,r,a;return e=this.defaultArg(e,getTransportBpm()),i=this.defaultArg(i,getTransportTimeSignature()),s=60/e,n=t/s,o=Math.floor(n/i),r=n%1*4,n=Math.floor(n)%i,a=[o,n,r],a.join(":")},Tone.prototype.secondsToFrequency=function(t){return 1/t},Tone.prototype.toTransportTime=function(t,e,i){var s=this.toSeconds(t);return this.secondsToTransportTime(s,e,i)},Tone.prototype.toFrequency=function(t,e){return this.isFrequency(t)?parseFloat(t):this.isNotation(t)||this.isTransportTime(t)?this.secondsToFrequency(this.toSeconds(t,e)):this.isNote(t)?this.noteToFrequency(t):t},Tone.prototype.toTicks=function(t){var e,i,s,n,o,r;if(this.isUndef(Tone.Transport))return 0;if(e=Tone.Transport.bpm.value,i=0,this.isNowRelative(t))t=t.replace("+",""),i=Tone.Transport.ticks;else if(this.isUndef(t))return Tone.Transport.ticks;return s=this.toSeconds(t),n=60/e,o=s/n,r=o*Tone.Transport.PPQ,Math.round(r+i)},Tone.prototype.toSamples=function(t){var e=this.toSeconds(t);return Math.round(e*this.context.sampleRate)},Tone.prototype.toSeconds=function(time,now){var plusTime,betweenParens,j,symbol,symbolVal,quantizationSplit,toQuantize,subdivision,components,originalTime,i,symb,val;if(now=this.defaultArg(now,this.now()),this.isNumber(time))return time;if(this.isString(time)){if(plusTime=0,this.isNowRelative(time)&&(time=time.replace("+",""),plusTime=now),betweenParens=time.match(/\(([^)(]+)\)/g))for(j=0;j0&&(toQuantize="+"+toQuantize,plusTime=0),subdivision=quantizationSplit[1].trim(),time=Tone.Transport.quantize(toQuantize,subdivision)}else if(components=time.split(/[\(\)\-\+\/\*]/),components.length>1){for(originalTime=time,i=0;in&&(s+=-12*n),e=scaleIndexToNote[s%12],e+n.toString()},Tone.prototype.intervalToFrequencyRatio=function(t){return Math.pow(2,t/12)},Tone.prototype.midiToNote=function(t){var e=Math.floor(t/12)-1,i=t%12;return scaleIndexToNote[i]+e},Tone.prototype.noteToMidi=function(t){var e,i,s=t.split(/(\d+)/);return 3===s.length?(e=noteToScaleIndex[s[0].toLowerCase()],i=s[1],e+12*(parseInt(i,10)+1)):0},Tone.prototype.midiToFrequency=function(t){return Tone.A4*Math.pow(2,(t-69)/12)},Tone}),Module(function(t){return t.Param=function(){var e=this.optionsObject(arguments,["param","units","convert"],t.Param.defaults);this._param=this.input=e.param,this.units=e.units,this.convert=e.convert,this.overridden=!1,this.isUndef(e.value)||(this.value=e.value)},t.extend(t.Param),t.Param.defaults={units:t.Type.Default,convert:!0,param:void 0},Object.defineProperty(t.Param.prototype,"value",{get:function(){return this._toUnits(this._param.value)},set:function(t){var e=this._fromUnits(t);this._param.value=e}}),t.Param.prototype._fromUnits=function(e){if(!this.convert&&!this.isUndef(this.convert))return e;switch(this.units){case t.Type.Time:return this.toSeconds(e);case t.Type.Frequency:return this.toFrequency(e);case t.Type.Decibels:return this.dbToGain(e);case t.Type.NormalRange:return Math.min(Math.max(e,0),1);case t.Type.AudioRange:return Math.min(Math.max(e,-1),1);case t.Type.Positive:return Math.max(e,0);default:return e}},t.Param.prototype._toUnits=function(e){if(!this.convert&&!this.isUndef(this.convert))return e;switch(this.units){case t.Type.Decibels:return this.gainToDb(e);default:return e}},t.Param.prototype._minOutput=1e-5,t.Param.prototype.setValueAtTime=function(t,e){return t=this._fromUnits(t),this._param.setValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.setRampPoint=function(t){t=this.defaultArg(t,this.now());var e=this._param.value;return this._param.setValueAtTime(e,t),this},t.Param.prototype.linearRampToValueAtTime=function(t,e){return t=this._fromUnits(t),this._param.linearRampToValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.exponentialRampToValueAtTime=function(t,e){return t=this._fromUnits(t),t=Math.max(this._minOutput,t),this._param.exponentialRampToValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.exponentialRampToValue=function(t,e){var i=this.now(),s=this.value;return this.setValueAtTime(Math.max(s,this._minOutput),i),this.exponentialRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Param.prototype.linearRampToValue=function(t,e){var i=this.now();return this.setRampPoint(i),this.linearRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Param.prototype.setTargetAtTime=function(t,e,i){return t=this._fromUnits(t),t=Math.max(this._minOutput,t),i=Math.max(this._minOutput,i),this._param.setTargetAtTime(t,this.toSeconds(e),i),this},t.Param.prototype.setValueCurveAtTime=function(t,e,i){for(var s=0;sthis.memory&&(i=this.length-this.memory,this._timeline.splice(0,i)),this},t.Timeline.prototype.removeEvent=function(t){if(this._iterating)this._toRemove.push(t);else{var e=this._timeline.indexOf(t);-1!==e&&this._timeline.splice(e,1)}return this},t.Timeline.prototype.getEvent=function(t){t=this.toSeconds(t);var e=this._search(t);return-1!==e?this._timeline[e]:null},t.Timeline.prototype.getEventAfter=function(t){t=this.toSeconds(t);var e=this._search(t);return e+1=0?this._timeline[e-1]:null},t.Timeline.prototype.cancel=function(t){if(this._timeline.length>1){t=this.toSeconds(t);var e=this._search(t);this._timeline=e>=0?this._timeline.slice(0,e):[]}else 1===this._timeline.length&&this._timeline[0].time>=t&&(this._timeline=[]);return this},t.Timeline.prototype.cancelBefore=function(t){if(this._timeline.length){t=this.toSeconds(t);var e=this._search(t);e>=0&&(this._timeline=this._timeline.slice(e+1))}return this},t.Timeline.prototype._search=function(t){for(var e,i,s,n,o=0,r=this._timeline.length,a=r;a>=o&&r>o;){if(e=Math.floor(o+(a-o)/2),i=this._timeline[e],i.time===t){for(s=e;st?a=e-1:i.time=s;s++)t(this._timeline[s]);if(this._iterating=!1,this._toRemove.length>0){for(n=0;n=0&&this._timeline[i].time>=t;)i--;return this._iterate(e,i+1),this},t.Timeline.prototype.forEachAtTime=function(t,e){t=this.toSeconds(t);var i=this._search(t);return-1!==i&&this._iterate(function(i){i.time===t&&e(i)},0,i),this},t.Timeline.prototype.dispose=function(){t.prototype.dispose.call(this),this._timeline=null,this._toRemove=null},t.Timeline}),Module(function(t){return t.TimelineSignal=function(){var e=this.optionsObject(arguments,["value","units"],t.Signal.defaults);t.Signal.apply(this,e),e.param=this._param,t.Param.call(this,e),this._events=new t.Timeline(10),this._initial=this._fromUnits(this._param.value)},t.extend(t.TimelineSignal,t.Param),t.TimelineSignal.Type={Linear:"linear",Exponential:"exponential",Target:"target",Set:"set"},Object.defineProperty(t.TimelineSignal.prototype,"value",{get:function(){return this._toUnits(this._param.value)},set:function(t){var e=this._fromUnits(t);this._initial=e,this._param.value=e}}),t.TimelineSignal.prototype.setValueAtTime=function(e,i){return e=this._fromUnits(e),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Set,value:e,time:i}),this._param.setValueAtTime(e,i),this},t.TimelineSignal.prototype.linearRampToValueAtTime=function(e,i){return e=this._fromUnits(e),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Linear,value:e,time:i}),this._param.linearRampToValueAtTime(e,i),this},t.TimelineSignal.prototype.exponentialRampToValueAtTime=function(e,i){return e=this._fromUnits(e),e=Math.max(this._minOutput,e),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Exponential,value:e,time:i}),this._param.exponentialRampToValueAtTime(e,i),this},t.TimelineSignal.prototype.setTargetAtTime=function(e,i,s){return e=this._fromUnits(e),e=Math.max(this._minOutput,e),s=Math.max(this._minOutput,s),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Target,value:e,time:i,constant:s}),this._param.setTargetAtTime(e,i,s),this},t.TimelineSignal.prototype.cancelScheduledValues=function(t){return this._events.cancel(t),this._param.cancelScheduledValues(this.toSeconds(t)),this},t.TimelineSignal.prototype.setRampPoint=function(e){var i,s;return e=this.toSeconds(e),i=this.getValueAtTime(e),s=this._searchAfter(e),s&&(this.cancelScheduledValues(e),s.type===t.TimelineSignal.Type.Linear?this.linearRampToValueAtTime(i,e):s.type===t.TimelineSignal.Type.Exponential&&this.exponentialRampToValueAtTime(i,e)),this.setValueAtTime(i,e),this},t.TimelineSignal.prototype.linearRampToValueBetween=function(t,e,i){return this.setRampPoint(e),this.linearRampToValueAtTime(t,i),this},t.TimelineSignal.prototype.exponentialRampToValueBetween=function(t,e,i){return this.setRampPoint(e),this.exponentialRampToValueAtTime(t,i),this},t.TimelineSignal.prototype._searchBefore=function(t){return this._events.getEvent(t)},t.TimelineSignal.prototype._searchAfter=function(t){return this._events.getEventAfter(t)},t.TimelineSignal.prototype.getValueAtTime=function(e){var i,s,n=this._searchAfter(e),o=this._searchBefore(e),r=this._initial;return null===o?r=this._initial:o.type===t.TimelineSignal.Type.Target?(i=this._events.getEventBefore(o.time),s=null===i?this._initial:i.value,r=this._exponentialApproach(o.time,s,o.value,o.constant,e)):r=null===n?o.value:n.type===t.TimelineSignal.Type.Linear?this._linearInterpolate(o.time,o.value,n.time,n.value,e):n.type===t.TimelineSignal.Type.Exponential?this._exponentialInterpolate(o.time,o.value,n.time,n.value,e):o.value,r},t.TimelineSignal.prototype.connect=t.SignalBase.prototype.connect,t.TimelineSignal.prototype._exponentialApproach=function(t,e,i,s,n){return i+(e-i)*Math.exp(-(n-t)/s)},t.TimelineSignal.prototype._linearInterpolate=function(t,e,i,s,n){return e+(s-e)*((n-t)/(i-t))},t.TimelineSignal.prototype._exponentialInterpolate=function(t,e,i,s,n){return e=Math.max(this._minOutput,e),e*Math.pow(s/e,(n-t)/(i-t))},t.TimelineSignal.prototype.dispose=function(){t.Signal.prototype.dispose.call(this),t.Param.prototype.dispose.call(this),this._events.dispose(),this._events=null},t.TimelineSignal}),Module(function(t){return t.Pow=function(e){this._exp=this.defaultArg(e,1),this._expScaler=this.input=this.output=new t.WaveShaper(this._expFunc(this._exp),8192)},t.extend(t.Pow,t.SignalBase),Object.defineProperty(t.Pow.prototype,"value",{get:function(){return this._exp},set:function(t){this._exp=t,this._expScaler.setMap(this._expFunc(this._exp))}}),t.Pow.prototype._expFunc=function(t){return function(e){return Math.pow(Math.abs(e),t)}},t.Pow.prototype.dispose=function(){return t.prototype.dispose.call(this),this._expScaler.dispose(),this._expScaler=null,this},t.Pow}),Module(function(t){return t.Envelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);this.attack=e.attack,this.decay=e.decay,this.sustain=e.sustain,this.release=e.release,this._attackCurve=t.Envelope.Type.Linear,this._releaseCurve=t.Envelope.Type.Exponential,this._minOutput=1e-5,this._sig=this.output=new t.TimelineSignal,this._sig.setValueAtTime(this._minOutput,0),this.attackCurve=e.attackCurve,this.releaseCurve=e.releaseCurve},t.extend(t.Envelope),t.Envelope.defaults={attack:.01,decay:.1,sustain:.5,release:1,attackCurve:"linear",releaseCurve:"exponential"},t.Envelope.prototype._timeMult=.25,Object.defineProperty(t.Envelope.prototype,"value",{get:function(){return this._sig.value}}),Object.defineProperty(t.Envelope.prototype,"attackCurve",{get:function(){return this._attackCurve},set:function(e){if(e!==t.Envelope.Type.Linear&&e!==t.Envelope.Type.Exponential)throw Error('attackCurve must be either "linear" or "exponential". Invalid type: ',e);this._attackCurve=e}}),Object.defineProperty(t.Envelope.prototype,"releaseCurve",{get:function(){return this._releaseCurve},set:function(e){if(e!==t.Envelope.Type.Linear&&e!==t.Envelope.Type.Exponential)throw Error('releaseCurve must be either "linear" or "exponential". Invalid type: ',e);this._releaseCurve=e}}),t.Envelope.prototype.triggerAttack=function(e,i){var s,n,o=this.now()+this.blockTime;return e=this.toSeconds(e,o),s=this.toSeconds(this.attack)+e,n=this.toSeconds(this.decay),i=this.defaultArg(i,1),this._attackCurve===t.Envelope.Type.Linear?this._sig.linearRampToValueBetween(i,e,s):this._sig.exponentialRampToValueBetween(i,e,s),this._sig.setValueAtTime(i,s),this._sig.exponentialRampToValueAtTime(this.sustain*i,s+n),this},t.Envelope.prototype.triggerRelease=function(e){var i,s=this.now()+this.blockTime;return e=this.toSeconds(e,s),i=this.toSeconds(this.release),this._releaseCurve===t.Envelope.Type.Linear?this._sig.linearRampToValueBetween(this._minOutput,e,e+i):this._sig.exponentialRampToValueBetween(this._minOutput,e,i+e),this},t.Envelope.prototype.triggerAttackRelease=function(t,e,i){return e=this.toSeconds(e),this.triggerAttack(e,i),this.triggerRelease(e+this.toSeconds(t)),this},t.Envelope.prototype.connect=t.Signal.prototype.connect,t.Envelope.prototype.dispose=function(){return t.prototype.dispose.call(this),this._sig.dispose(),this._sig=null,this},t.Envelope.Phase={Attack:"attack",Decay:"decay",Sustain:"sustain",Release:"release",Standby:"standby"},t.Envelope.Type={Linear:"linear",Exponential:"exponential"},t.Envelope}),Module(function(t){return t.AmplitudeEnvelope=function(){t.Envelope.apply(this,arguments),this.input=this.output=new t.Gain,this._sig.connect(this.output.gain)},t.extend(t.AmplitudeEnvelope,t.Envelope),t.AmplitudeEnvelope.prototype.dispose=function(){return this.input.dispose(),this.input=null,t.Envelope.prototype.dispose.call(this),this},t.AmplitudeEnvelope}),Module(function(t){return t.Analyser=function(){var e=this.optionsObject(arguments,["size","type"],t.Analyser.defaults);this._analyser=this.input=this.context.createAnalyser(),this._type=e.type,this._returnType=e.returnType,this._buffer=null,this.size=e.size,this.type=e.type,this.returnType=e.returnType,this.minDecibels=e.minDecibels,this.maxDecibels=e.maxDecibels},t.extend(t.Analyser),t.Analyser.defaults={size:2048,returnType:"byte",type:"fft",smoothing:.8,maxDecibels:-30,minDecibels:-100},t.Analyser.Type={Waveform:"waveform",FFT:"fft"},t.Analyser.ReturnType={Byte:"byte",Float:"float"},t.Analyser.prototype.analyse=function(){return this._type===t.Analyser.Type.FFT?this._returnType===t.Analyser.ReturnType.Byte?this._analyser.getByteFrequencyData(this._buffer):this._analyser.getFloatFrequencyData(this._buffer):this._type===t.Analyser.Type.Waveform&&(this._returnType===t.Analyser.ReturnType.Byte?this._analyser.getByteTimeDomainData(this._buffer):this._analyser.getFloatTimeDomainData(this._buffer)),this._buffer},Object.defineProperty(t.Analyser.prototype,"size",{get:function(){return this._analyser.frequencyBinCount},set:function(t){this._analyser.fftSize=2*t,this.type=this._type}}),Object.defineProperty(t.Analyser.prototype,"returnType",{get:function(){return this._returnType},set:function(e){if(e===t.Analyser.ReturnType.Byte)this._buffer=new Uint8Array(this._analyser.frequencyBinCount);else{if(e!==t.Analyser.ReturnType.Float)throw new Error("Invalid Return Type: "+e);this._buffer=new Float32Array(this._analyser.frequencyBinCount)}this._returnType=e}}),Object.defineProperty(t.Analyser.prototype,"type",{get:function(){ -return this._type},set:function(e){if(e!==t.Analyser.Type.Waveform&&e!==t.Analyser.Type.FFT)throw new Error("Invalid Type: "+e);this._type=e}}),Object.defineProperty(t.Analyser.prototype,"smoothing",{get:function(){return this._analyser.smoothingTimeConstant},set:function(t){this._analyser.smoothingTimeConstant=t}}),Object.defineProperty(t.Analyser.prototype,"minDecibels",{get:function(){return this._analyser.minDecibels},set:function(t){this._analyser.minDecibels=t}}),Object.defineProperty(t.Analyser.prototype,"maxDecibels",{get:function(){return this._analyser.maxDecibels},set:function(t){this._analyser.maxDecibels=t}}),t.Analyser.prototype.dispose=function(){t.prototype.dispose.call(this),this._analyser.disconnect(),this._analyser=null,this._buffer=null},t.Analyser}),Module(function(t){return t.Compressor=function(){var e=this.optionsObject(arguments,["threshold","ratio"],t.Compressor.defaults);this._compressor=this.input=this.output=this.context.createDynamicsCompressor(),this.threshold=this._compressor.threshold,this.attack=new t.Param(this._compressor.attack,t.Type.Time),this.release=new t.Param(this._compressor.release,t.Type.Time),this.knee=this._compressor.knee,this.ratio=this._compressor.ratio,this._readOnly(["knee","release","attack","ratio","threshold"]),this.set(e)},t.extend(t.Compressor),t.Compressor.defaults={ratio:12,threshold:-24,release:.25,attack:.003,knee:30},t.Compressor.prototype.dispose=function(){return t.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},t.Compressor}),Module(function(t){return t.Add=function(e){t.call(this,2,0),this._sum=this.input[0]=this.input[1]=this.output=this.context.createGain(),this._param=this.input[1]=new t.Signal(e),this._param.connect(this._sum)},t.extend(t.Add,t.Signal),t.Add.prototype.dispose=function(){return t.prototype.dispose.call(this),this._sum.disconnect(),this._sum=null,this._param.dispose(),this._param=null,this},t.Add}),Module(function(t){return t.Multiply=function(e){t.call(this,2,0),this._mult=this.input[0]=this.output=this.context.createGain(),this._param=this.input[1]=this.output.gain,this._param.value=this.defaultArg(e,0)},t.extend(t.Multiply,t.Signal),t.Multiply.prototype.dispose=function(){return t.prototype.dispose.call(this),this._mult.disconnect(),this._mult=null,this._param=null,this},t.Multiply}),Module(function(t){return t.Negate=function(){this._multiply=this.input=this.output=new t.Multiply(-1)},t.extend(t.Negate,t.SignalBase),t.Negate.prototype.dispose=function(){return t.prototype.dispose.call(this),this._multiply.dispose(),this._multiply=null,this},t.Negate}),Module(function(t){return t.Subtract=function(e){t.call(this,2,0),this._sum=this.input[0]=this.output=this.context.createGain(),this._neg=new t.Negate,this._param=this.input[1]=new t.Signal(e),this._param.chain(this._neg,this._sum)},t.extend(t.Subtract,t.Signal),t.Subtract.prototype.dispose=function(){return t.prototype.dispose.call(this),this._neg.dispose(),this._neg=null,this._sum.disconnect(),this._sum=null,this._param.dispose(),this._param=null,this},t.Subtract}),Module(function(t){return t.GreaterThanZero=function(){this._thresh=this.output=new t.WaveShaper(function(t){return 0>=t?0:1}),this._scale=this.input=new t.Multiply(1e4),this._scale.connect(this._thresh)},t.extend(t.GreaterThanZero,t.SignalBase),t.GreaterThanZero.prototype.dispose=function(){return t.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},t.GreaterThanZero}),Module(function(t){return t.EqualZero=function(){this._scale=this.input=new t.Multiply(1e4),this._thresh=new t.WaveShaper(function(t){return 0===t?1:0},128),this._gtz=this.output=new t.GreaterThanZero,this._scale.chain(this._thresh,this._gtz)},t.extend(t.EqualZero,t.SignalBase),t.EqualZero.prototype.dispose=function(){return t.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},t.EqualZero}),Module(function(t){return t.Equal=function(e){t.call(this,2,0),this._sub=this.input[0]=new t.Subtract(e),this._equals=this.output=new t.EqualZero,this._sub.connect(this._equals),this.input[1]=this._sub.input[1]},t.extend(t.Equal,t.SignalBase),Object.defineProperty(t.Equal.prototype,"value",{get:function(){return this._sub.value},set:function(t){this._sub.value=t}}),t.Equal.prototype.dispose=function(){return t.prototype.dispose.call(this),this._equals.dispose(),this._equals=null,this._sub.dispose(),this._sub=null,this},t.Equal}),Module(function(t){t.Select=function(i){var s,n;for(i=this.defaultArg(i,2),t.call(this,i,1),this.gate=new t.Signal(0),this._readOnly("gate"),s=0;i>s;s++)n=new e(s),this.input[s]=n,this.gate.connect(n.selecter),n.connect(this.output)},t.extend(t.Select,t.SignalBase),t.Select.prototype.select=function(t,e){return t=Math.floor(t),this.gate.setValueAtTime(t,this.toSeconds(e)),this},t.Select.prototype.dispose=function(){this._writable("gate"),this.gate.dispose(),this.gate=null;for(var e=0;ei;i++)this.input[i]=this._sum;this._sum.connect(this._gtz)},t.extend(t.OR,t.SignalBase),t.OR.prototype.dispose=function(){return t.prototype.dispose.call(this),this._gtz.dispose(),this._gtz=null,this._sum.disconnect(),this._sum=null,this},t.OR}),Module(function(t){return t.AND=function(e){e=this.defaultArg(e,2),t.call(this,e,0),this._equals=this.output=new t.Equal(e);for(var i=0;e>i;i++)this.input[i]=this._equals},t.extend(t.AND,t.SignalBase),t.AND.prototype.dispose=function(){return t.prototype.dispose.call(this),this._equals.dispose(),this._equals=null,this},t.AND}),Module(function(t){return t.NOT=t.EqualZero,t.NOT}),Module(function(t){return t.GreaterThan=function(e){t.call(this,2,0),this._param=this.input[0]=new t.Subtract(e),this.input[1]=this._param.input[1],this._gtz=this.output=new t.GreaterThanZero,this._param.connect(this._gtz)},t.extend(t.GreaterThan,t.Signal),t.GreaterThan.prototype.dispose=function(){return t.prototype.dispose.call(this),this._param.dispose(),this._param=null,this._gtz.dispose(),this._gtz=null,this},t.GreaterThan}),Module(function(t){return t.LessThan=function(e){t.call(this,2,0),this._neg=this.input[0]=new t.Negate,this._gt=this.output=new t.GreaterThan,this._rhNeg=new t.Negate,this._param=this.input[1]=new t.Signal(e),this._neg.connect(this._gt),this._param.connect(this._rhNeg),this._rhNeg.connect(this._gt,0,1)},t.extend(t.LessThan,t.Signal),t.LessThan.prototype.dispose=function(){return t.prototype.dispose.call(this),this._neg.dispose(),this._neg=null,this._gt.dispose(),this._gt=null,this._rhNeg.dispose(),this._rhNeg=null,this._param.dispose(),this._param=null,this},t.LessThan}),Module(function(t){return t.Abs=function(){t.call(this,1,0),this._ltz=new t.LessThan(0),this._switch=this.output=new t.Select(2),this._negate=new t.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)},t.extend(t.Abs,t.SignalBase),t.Abs.prototype.dispose=function(){return t.prototype.dispose.call(this),this._switch.dispose(),this._switch=null,this._ltz.dispose(),this._ltz=null,this._negate.dispose(),this._negate=null,this},t.Abs}),Module(function(t){return t.Max=function(e){t.call(this,2,0),this.input[0]=this.context.createGain(),this._param=this.input[1]=new t.Signal(e),this._ifThenElse=this.output=new t.IfThenElse,this._gt=new t.GreaterThan,this.input[0].chain(this._gt,this._ifThenElse["if"]),this.input[0].connect(this._ifThenElse.then),this._param.connect(this._ifThenElse["else"]),this._param.connect(this._gt,0,1)},t.extend(t.Max,t.Signal),t.Max.prototype.dispose=function(){return t.prototype.dispose.call(this),this._param.dispose(),this._ifThenElse.dispose(),this._gt.dispose(),this._param=null,this._ifThenElse=null,this._gt=null,this},t.Max}),Module(function(t){return t.Min=function(e){t.call(this,2,0),this.input[0]=this.context.createGain(),this._ifThenElse=this.output=new t.IfThenElse,this._lt=new t.LessThan,this._param=this.input[1]=new t.Signal(e),this.input[0].chain(this._lt,this._ifThenElse["if"]),this.input[0].connect(this._ifThenElse.then),this._param.connect(this._ifThenElse["else"]),this._param.connect(this._lt,0,1)},t.extend(t.Min,t.Signal),t.Min.prototype.dispose=function(){return t.prototype.dispose.call(this),this._param.dispose(),this._ifThenElse.dispose(),this._lt.dispose(),this._param=null,this._ifThenElse=null,this._lt=null,this},t.Min}),Module(function(t){return t.Modulo=function(e){t.call(this,1,1),this._shaper=new t.WaveShaper(Math.pow(2,16)),this._multiply=new t.Multiply,this._subtract=this.output=new t.Subtract,this._modSignal=new t.Signal(e),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(e)},t.extend(t.Modulo,t.SignalBase),t.Modulo.prototype._setWaveShaper=function(t){this._shaper.setMap(function(e){var i=Math.floor((e+1e-4)/t);return i})},Object.defineProperty(t.Modulo.prototype,"value",{get:function(){return this._modSignal.value},set:function(t){this._modSignal.value=t,this._setWaveShaper(t)}}),t.Modulo.prototype.dispose=function(){return t.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},t.Modulo}),Module(function(t){return t.AudioToGain=function(){this._norm=this.input=this.output=new t.WaveShaper(function(t){return(t+1)/2})},t.extend(t.AudioToGain,t.SignalBase),t.AudioToGain.prototype.dispose=function(){return t.prototype.dispose.call(this),this._norm.dispose(),this._norm=null,this},t.AudioToGain}),Module(function(t){function e(t,e,i){var s=new t;return i._eval(e[0]).connect(s,0,0),i._eval(e[1]).connect(s,0,1),s}function i(t,e,i){var s=new t;return i._eval(e[0]).connect(s,0,0),s}function s(t){return t?parseFloat(t):void 0}function n(t){return t&&t.args?parseFloat(t.args):void 0}return t.Expr=function(){var t,e,i,s=this._replacements(Array.prototype.slice.call(arguments)),n=this._parseInputs(s);for(this._nodes=[],this.input=new Array(n),t=0;n>t;t++)this.input[t]=this.context.createGain();e=this._parseTree(s);try{i=this._eval(e)}catch(o){throw this._disposeNodes(),new Error("Could evaluate expression: "+s)}this.output=i},t.extend(t.Expr,t.SignalBase),t.Expr._Expressions={value:{signal:{regexp:/^\d+\.\d+|^\d+/,method:function(e){var i=new t.Signal(s(e));return i}},input:{regexp:/^\$\d/,method:function(t,e){return e.input[s(t.substr(1))]}}},glue:{"(":{regexp:/^\(/},")":{regexp:/^\)/},",":{regexp:/^,/}},func:{abs:{regexp:/^abs/,method:i.bind(this,t.Abs)},min:{regexp:/^min/,method:e.bind(this,t.Min)},max:{regexp:/^max/,method:e.bind(this,t.Max)},"if":{regexp:/^if/,method:function(e,i){var s=new t.IfThenElse;return i._eval(e[0]).connect(s["if"]),i._eval(e[1]).connect(s.then),i._eval(e[2]).connect(s["else"]),s}},gt0:{regexp:/^gt0/,method:i.bind(this,t.GreaterThanZero)},eq0:{regexp:/^eq0/,method:i.bind(this,t.EqualZero)},mod:{regexp:/^mod/,method:function(e,i){var s=n(e[1]),o=new t.Modulo(s);return i._eval(e[0]).connect(o),o}},pow:{regexp:/^pow/,method:function(e,i){var s=n(e[1]),o=new t.Pow(s);return i._eval(e[0]).connect(o),o}},a2g:{regexp:/^a2g/,method:function(e,i){var s=new t.AudioToGain;return i._eval(e[0]).connect(s),s}}},binary:{"+":{regexp:/^\+/,precedence:1,method:e.bind(this,t.Add)},"-":{regexp:/^\-/,precedence:1,method:function(s,n){return 1===s.length?i(t.Negate,s,n):e(t.Subtract,s,n)}},"*":{regexp:/^\*/,precedence:0,method:e.bind(this,t.Multiply)},">":{regexp:/^\>/,precedence:2,method:e.bind(this,t.GreaterThan)},"<":{regexp:/^0;)e=e.trim(),s=i(e),o.push(s),e=e.substr(s.value.length);return{next:function(){return o[++n]},peek:function(){return o[n+1]}}},t.Expr.prototype._parseTree=function(e){function i(t,e){return!u(t)&&"glue"===t.type&&t.value===e}function s(e,i,s){var n,o,r=!1,a=t.Expr._Expressions[i];if(!u(e))for(n in a)if(o=a[n],o.regexp.test(e.value)){if(u(s))return!0;if(o.precedence===s)return!0}return r}function n(t){var e,i;for(u(t)&&(t=5),e=0>t?o():n(t-1),i=h.peek();s(i,"binary",t);)i=h.next(),e={operator:i.value,method:i.method,args:[e,n(t)]},i=h.peek();return e}function o(){var t,e;return t=h.peek(),s(t,"unary")?(t=h.next(),e=o(),{operator:t.value,method:t.method,args:[e]}):r()}function r(){var t,e;if(t=h.peek(),u(t))throw new SyntaxError("Unexpected termination of expression");if("func"===t.type)return t=h.next(),a(t);if("value"===t.type)return t=h.next(),{method:t.method,args:t.value};if(i(t,"(")){if(h.next(),e=n(),t=h.next(),!i(t,")"))throw new SyntaxError("Expected )");return e}throw new SyntaxError("Parse error, cannot process token "+t.value)}function a(t){var e,s=[];if(e=h.next(),!i(e,"("))throw new SyntaxError('Expected ( in a function call "'+t.value+'"');if(e=h.peek(),i(e,")")||(s=l()),e=h.next(),!i(e,")"))throw new SyntaxError('Expected ) in a function call "'+t.value+'"');return{method:t.method,args:s,name:name}}function l(){for(var t,e,s=[];;){if(e=n(),u(e))break;if(s.push(e),t=h.peek(),!i(t,","))break;h.next()}return s}var h=this._tokenize(e),u=this.isUndef.bind(this);return n()},t.Expr.prototype._eval=function(t){if(!this.isUndef(t)){var e=t.method(t.args,this);return this._nodes.push(e),e}},t.Expr.prototype._disposeNodes=function(){var t,e;for(t=0;tn;n++)o=this.context.createBiquadFilter(),o.type=this._type,this.frequency.connect(o.frequency),this.detune.connect(o.detune),this.Q.connect(o.Q),this.gain.connect(o.gain),this._filters[n]=o;r=[this.input].concat(this._filters).concat([this.output]),this.connectSeries.apply(this,r)}}),t.Filter.prototype.dispose=function(){t.prototype.dispose.call(this);for(var e=0;e=i?t:e})},Object.defineProperty(t.Follower.prototype,"attack",{get:function(){return this._attack},set:function(t){this._attack=t,this._setAttackRelease(this._attack,this._release)}}),Object.defineProperty(t.Follower.prototype,"release",{get:function(){return this._release},set:function(t){this._release=t,this._setAttackRelease(this._attack,this._release)}}),t.Follower.prototype.connect=t.Signal.prototype.connect,t.Follower.prototype.dispose=function(){return t.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},t.Follower}),Module(function(t){return t.ScaledEnvelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);t.Envelope.call(this,e),e=this.defaultArg(e,t.ScaledEnvelope.defaults),this._exp=this.output=new t.Pow(e.exponent),this._scale=this.output=new t.Scale(e.min,e.max),this._sig.chain(this._exp,this._scale)},t.extend(t.ScaledEnvelope,t.Envelope),t.ScaledEnvelope.defaults={min:0,max:1,exponent:1},Object.defineProperty(t.ScaledEnvelope.prototype,"min",{get:function(){return this._scale.min},set:function(t){this._scale.min=t}}),Object.defineProperty(t.ScaledEnvelope.prototype,"max",{get:function(){return this._scale.max},set:function(t){this._scale.max=t}}),Object.defineProperty(t.ScaledEnvelope.prototype,"exponent",{get:function(){return this._exp.value},set:function(t){this._exp.value=t}}),t.ScaledEnvelope.prototype.dispose=function(){return t.Envelope.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._exp.dispose(),this._exp=null,this},t.ScaledEnvelope}),Module(function(t){return t.FrequencyEnvelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);t.ScaledEnvelope.call(this,e),e=this.defaultArg(e,t.FrequencyEnvelope.defaults),this._octaves=e.octaves,this.baseFrequency=e.baseFrequency,this.octaves=e.octaves},t.extend(t.FrequencyEnvelope,t.Envelope),t.FrequencyEnvelope.defaults={baseFrequency:200,octaves:4,exponent:2},Object.defineProperty(t.FrequencyEnvelope.prototype,"baseFrequency",{get:function(){return this._scale.min},set:function(t){this._scale.min=this.toFrequency(t)}}),Object.defineProperty(t.FrequencyEnvelope.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t,this._scale.max=this.baseFrequency*Math.pow(2,t)}}),Object.defineProperty(t.FrequencyEnvelope.prototype,"exponent",{get:function(){return this._exp.value},set:function(t){this._exp.value=t}}),t.FrequencyEnvelope.prototype.dispose=function(){return t.ScaledEnvelope.prototype.dispose.call(this),this},t.FrequencyEnvelope}),Module(function(t){return t.Gate=function(){t.call(this);var e=this.optionsObject(arguments,["threshold","attack","release"],t.Gate.defaults);this._follower=new t.Follower(e.attack,e.release),this._gt=new t.GreaterThan(this.dbToGain(e.threshold)),this.input.connect(this.output),this.input.chain(this._gt,this._follower,this.output.gain)},t.extend(t.Gate),t.Gate.defaults={attack:.1,release:.1,threshold:-40},Object.defineProperty(t.Gate.prototype,"threshold",{get:function(){return this.gainToDb(this._gt.value)},set:function(t){this._gt.value=this.dbToGain(t)}}),Object.defineProperty(t.Gate.prototype,"attack",{get:function(){return this._follower.attack},set:function(t){this._follower.attack=t}}),Object.defineProperty(t.Gate.prototype,"release",{get:function(){return this._follower.release},set:function(t){this._follower.release=t}}),t.Gate.prototype.dispose=function(){return t.prototype.dispose.call(this),this._follower.dispose(),this._gt.dispose(),this._follower=null,this._gt=null,this},t.Gate}),Module(function(t){return t.TimelineState=function(e){t.Timeline.call(this),this._initial=e},t.extend(t.TimelineState,t.Timeline),t.TimelineState.prototype.getStateAtTime=function(t){var e=this.getEvent(t);return null!==e?e.state:this._initial},t.TimelineState.prototype.setStateAtTime=function(t,e){this.addEvent({state:t,time:this.toSeconds(e)})},t.TimelineState}),Module(function(t){return t.Clock=function(){var e=this.optionsObject(arguments,["callback","frequency"],t.Clock.defaults);this.callback=e.callback,this._lookAhead="auto",this._computedLookAhead=1/60,this._threshold=.5,this._nextTick=-1,this._lastUpdate=0,this._loopID=-1,this.frequency=new t.TimelineSignal(e.frequency,t.Type.Frequency),this.ticks=0,this._state=new t.TimelineState(t.State.Stopped),this._boundLoop=this._loop.bind(this),this._readOnly("frequency"),this._loop()},t.extend(t.Clock),t.Clock.defaults={callback:t.noOp,frequency:1,lookAhead:"auto"},Object.defineProperty(t.Clock.prototype,"state",{get:function(){return this._state.getStateAtTime(this.now())}}),Object.defineProperty(t.Clock.prototype,"lookAhead",{get:function(){return this._lookAhead},set:function(t){this._lookAhead="auto"===t?"auto":this.toSeconds(t)}}),t.Clock.prototype.start=function(e,i){return e=this.toSeconds(e),this._state.getStateAtTime(e)!==t.State.Started&&this._state.addEvent({state:t.State.Started,time:e,offset:i}),this},t.Clock.prototype.stop=function(e){return e=this.toSeconds(e),this._state.getStateAtTime(e)!==t.State.Stopped&&this._state.setStateAtTime(t.State.Stopped,e),this},t.Clock.prototype.pause=function(e){return e=this.toSeconds(e),this._state.getStateAtTime(e)===t.State.Started&&this._state.setStateAtTime(t.State.Paused,e),this},t.Clock.prototype._loop=function(e){var i,s,n,o,r,a;if(this._loopID=requestAnimationFrame(this._boundLoop),"auto"===this._lookAhead?this.isUndef(e)||(i=(e-this._lastUpdate)/1e3,this._lastUpdate=e,ithis._nextTick;)s>this._nextTick+this._threshold&&(this._nextTick=s), -a=this._nextTick,this._nextTick+=1/this.frequency.getValueAtTime(this._nextTick),this.callback(a),this.ticks++;else r===t.State.Stopped&&(this._nextTick=-1,this.ticks=0)},t.Clock.prototype.getStateAtTime=function(t){return this._state.getStateAtTime(t)},t.Clock.prototype.dispose=function(){cancelAnimationFrame(this._loopID),t.TimelineState.prototype.dispose.call(this),this._writable("frequency"),this.frequency.dispose(),this.frequency=null,this._boundLoop=t.noOp,this._nextTick=1/0,this.callback=null,this._state.dispose(),this._state=null},t.Clock}),Module(function(t){return t.Emitter=function(){this._events={}},t.extend(t.Emitter),t.Emitter.prototype.on=function(t,e){var i,s,n=t.split(/\W+/);for(i=0;is;s++)i[s].apply(this,e);return this},t.Emitter.mixin=function(e){var i,s,n,o=["on","off","trigger"];for(e._events={},i=0;i0)if(null===t.left.right)i=t.left,i.right=t.right,s=i;else{for(i=t.left.right;null!==i.right;)i=i.right;i.parent.right=i.left,s=i.parent,i.left=t.left,i.right=t.right}else if(null===t.right.left)i=t.right,i.left=t.left,s=i;else{for(i=t.right.left;null!==i.left;)i=i.left;i.parent=i.parent,i.parent.left=i.right,s=i.parent,i.left=t.left,i.right=t.right}null!==t.parent?t.isLeftChild()?t.parent.left=i:t.parent.right=i:this._setRoot(i),this._rebalance(s)}t.dispose()},t.IntervalTimeline.prototype._rotateLeft=function(t){var e=t.parent,i=t.isLeftChild(),s=t.right;t.right=s.left,s.left=t,null!==e?i?e.left=s:e.right=s:this._setRoot(s)},t.IntervalTimeline.prototype._rotateRight=function(t){var e=t.parent,i=t.isLeftChild(),s=t.left;t.left=s.right,s.right=t,null!==e?i?e.left=s:e.right=s:this._setRoot(s)},t.IntervalTimeline.prototype._rebalance=function(t){var e=t.getBalance();e>1?t.left.getBalance()<0?this._rotateLeft(t.left):this._rotateRight(t):-1>e&&(t.right.getBalance()>0?this._rotateRight(t.right):this._rotateLeft(t))},t.IntervalTimeline.prototype.getEvent=function(t){var e,i,s;if(null!==this._root&&(e=[],this._root.search(t,e),e.length>0)){for(i=e[0],s=1;si.low&&(i=e[s]);return i.event}return null},t.IntervalTimeline.prototype.forEach=function(t){var e,i,s;if(null!==this._root)for(e=[],null!==this._root&&this._root.traverse(function(t){e.push(t)}),i=0;i=0;s--)n=i[s].event,n&&e(n);return this},t.IntervalTimeline.prototype.forEachAfter=function(t,e){var i,s,n;if(t=this.toSeconds(t),null!==this._root)for(i=[],this._root.searchAfter(t,i),s=i.length-1;s>=0;s--)n=i[s].event,n&&e(n);return this},t.IntervalTimeline.prototype.dispose=function(){var t,e=[];for(null!==this._root&&this._root.traverse(function(t){e.push(t)}),t=0;tthis.max||(null!==this.left&&this.left.search(t,e),this.low<=t&&this.high>=t&&e.push(this),this.low>t||null!==this.right&&this.right.search(t,e))},e.prototype.searchAfter=function(t,e){this.low>=t&&(e.push(this),null!==this.left&&this.left.searchAfter(t,e)),null!==this.right&&this.right.searchAfter(t,e)},e.prototype.traverse=function(t){t(this),null!==this.left&&this.left.traverse(t),null!==this.right&&this.right.traverse(t)},e.prototype.updateHeight=function(){this.height=null!==this.left&&null!==this.right?Math.max(this.left.height,this.right.height)+1:null!==this.right?this.right.height+1:null!==this.left?this.left.height+1:0},e.prototype.updateMax=function(){this.max=this.high,null!==this.left&&(this.max=Math.max(this.max,this.left.max)),null!==this.right&&(this.max=Math.max(this.max,this.right.max))},e.prototype.getBalance=function(){var t=0;return null!==this.left&&null!==this.right?t=this.left.height-this.right.height:null!==this.left?t=this.left.height+1:null!==this.right&&(t=-(this.right.height+1)),t},e.prototype.isLeftChild=function(){return null!==this.parent&&this.parent.left===this},Object.defineProperty(e.prototype,"left",{get:function(){return this._left},set:function(t){this._left=t,null!==t&&(t.parent=this),this.updateHeight(),this.updateMax()}}),Object.defineProperty(e.prototype,"right",{get:function(){return this._right},set:function(t){this._right=t,null!==t&&(t.parent=this),this.updateHeight(),this.updateMax()}}),e.prototype.dispose=function(){this.parent=null,this._left=null,this._right=null,this.event=null},t.IntervalTimeline}),Module(function(t){t.Transport=function(){t.Emitter.call(this),this.loop=!1,this._loopStart=0,this._loopEnd=0,this._ppq=e.defaults.PPQ,this._clock=new t.Clock({callback:this._processTick.bind(this),frequency:0}),this.bpm=this._clock.frequency,this.bpm._toUnits=this._toUnits.bind(this),this.bpm._fromUnits=this._fromUnits.bind(this),this.bpm.units=t.Type.BPM,this.bpm.value=e.defaults.bpm,this._readOnly("bpm"),this._timeSignature=e.defaults.timeSignature,this._scheduledEvents={},this._eventID=0,this._timeline=new t.Timeline,this._repeatedEvents=new t.IntervalTimeline,this._onceEvents=new t.Timeline,this._syncedSignals=[];var i=this.notationToSeconds(e.defaults.swingSubdivision,e.defaults.bpm,e.defaults.timeSignature);this._swingTicks=i/(60/e.defaults.bpm)*this._ppq,this._swingAmount=0},t.extend(t.Transport,t.Emitter),t.Transport.defaults={bpm:120,swing:0,swingSubdivision:"16n",timeSignature:4,loopStart:0,loopEnd:"4m",PPQ:48},t.Transport.prototype._processTick=function(t){this._swingAmount>0&&this._clock.ticks%this._ppq!==0&&this._clock.ticks%this._swingTicks===0&&(t+=this.ticksToSeconds(this._swingTicks)*this._swingAmount),this.loop&&this._clock.ticks===this._loopEnd&&(this.ticks=this._loopStart,this.trigger("loop",t));var e=this._clock.ticks;this._timeline.forEachAtTime(e,function(e){e.callback(t)}),this._repeatedEvents.forEachOverlap(e,function(i){(e-i.time)%i.interval===0&&i.callback(t)}),this._onceEvents.forEachBefore(e,function(e){e.callback(t)}),this._onceEvents.cancelBefore(e)},t.Transport.prototype.schedule=function(t,e){var i={time:this.toTicks(e),callback:t},s=this._eventID++;return this._scheduledEvents[s.toString()]={event:i,timeline:this._timeline},this._timeline.addEvent(i),s},t.Transport.prototype.scheduleRepeat=function(t,e,i,s){var n,o;if(0>=e)throw new Error("repeat events must have an interval larger than 0");return n={time:this.toTicks(i),duration:this.toTicks(this.defaultArg(s,1/0)),interval:this.toTicks(e),callback:t},o=this._eventID++,this._scheduledEvents[o.toString()]={event:n,timeline:this._repeatedEvents},this._repeatedEvents.addEvent(n),o},t.Transport.prototype.scheduleOnce=function(t,e){var i={time:this.toTicks(e),callback:t},s=this._eventID++;return this._scheduledEvents[s.toString()]={event:i,timeline:this._onceEvents},this._onceEvents.addEvent(i),s},t.Transport.prototype.clear=function(t){if(this._scheduledEvents.hasOwnProperty(t)){var e=this._scheduledEvents[t.toString()];e.timeline.removeEvent(e.event),delete this._scheduledEvents[t.toString()]}return this},t.Transport.prototype.cancel=function(t){return t=this.defaultArg(t,0),t=this.toTicks(t),this._timeline.cancel(t),this._onceEvents.cancel(t),this._repeatedEvents.cancel(t),this},t.Transport.prototype.quantize=function(e,i){var s,n,o;return i=this.defaultArg(i,"4n"),s=this.toTicks(e),i=this.toTicks(i),n=i-s%i,n===i&&(n=0),o=this.now(),this.state===t.State.Started&&(o=this._clock._nextTick),this.toSeconds(e,o)+this.ticksToSeconds(n)},Object.defineProperty(t.Transport.prototype,"state",{get:function(){return this._clock.getStateAtTime(this.now())}}),t.Transport.prototype.start=function(t,e){return t=this.toSeconds(t),e=this.isUndef(e)?this.defaultArg(e,this._clock.ticks):this.toTicks(e),this._clock.start(t,e),this.trigger("start",t,this.ticksToSeconds(e)),this},t.Transport.prototype.stop=function(t){return t=this.toSeconds(t),this._clock.stop(t),this.trigger("stop",t),this},t.Transport.prototype.pause=function(t){return t=this.toSeconds(t),this._clock.pause(t),this.trigger("pause",t),this},Object.defineProperty(t.Transport.prototype,"timeSignature",{get:function(){return this._timeSignature},set:function(t){this.isArray(t)&&(t=t[0]/t[1]*4),this._timeSignature=t}}),Object.defineProperty(t.Transport.prototype,"loopStart",{get:function(){return this.ticksToSeconds(this._loopStart)},set:function(t){this._loopStart=this.toTicks(t)}}),Object.defineProperty(t.Transport.prototype,"loopEnd",{get:function(){return this.ticksToSeconds(this._loopEnd)},set:function(t){this._loopEnd=this.toTicks(t)}}),t.Transport.prototype.setLoopPoints=function(t,e){return this.loopStart=t,this.loopEnd=e,this},Object.defineProperty(t.Transport.prototype,"swing",{get:function(){return 2*this._swingAmount},set:function(t){this._swingAmount=.5*t}}),Object.defineProperty(t.Transport.prototype,"swingSubdivision",{get:function(){return this.toNotation(this._swingTicks+"i")},set:function(t){this._swingTicks=this.toTicks(t)}}),Object.defineProperty(t.Transport.prototype,"position",{get:function(){var t,e=this.ticks/this._ppq,i=Math.floor(e/this._timeSignature),s=e%1*4;return s%1>0&&(s=s.toFixed(3)),e=Math.floor(e)%this._timeSignature,t=[i,e,s],t.join(":")},set:function(t){var e=this.toTicks(t);this.ticks=e}}),Object.defineProperty(t.Transport.prototype,"progress",{get:function(){return this.loop?(this.ticks-this._loopStart)/(this._loopEnd-this._loopStart):0}}),Object.defineProperty(t.Transport.prototype,"ticks",{get:function(){return this._clock.ticks},set:function(t){this._clock.ticks=t}}),Object.defineProperty(t.Transport.prototype,"PPQ",{get:function(){return this._ppq},set:function(t){this._ppq=t,this.bpm.value=this.bpm.value}}),t.Transport.prototype._fromUnits=function(t){return 1/(60/t/this.PPQ)},t.Transport.prototype._toUnits=function(t){return t/this.PPQ*60},t.Transport.prototype.syncSignal=function(e,i){i||(i=0!==e._param.value?e._param.value/this.bpm._param.value:0);var s=new t.Gain(i);return this.bpm.chain(s,e._param),this._syncedSignals.push({ratio:s,signal:e,initial:e._param.value}),e._param.value=0,this},t.Transport.prototype.unsyncSignal=function(t){var e,i;for(e=this._syncedSignals.length-1;e>=0;e--)i=this._syncedSignals[e],i.signal===t&&(i.ratio.dispose(),i.signal._param.value=i.initial,this._syncedSignals.splice(e,1));return this},t.Transport.prototype.dispose=function(){return t.Emitter.prototype.dispose.call(this),this._clock.dispose(),this._clock=null,this._writable("bpm"),this.bpm=null,this._timeline.dispose(),this._timeline=null,this._onceEvents.dispose(),this._onceEvents=null,this._repeatedEvents.dispose(),this._repeatedEvents=null,this},t.Transport.prototype.setInterval=function(e,i){return console.warn("This method is deprecated. Use Tone.Transport.scheduleRepeat instead."),t.Transport.scheduleRepeat(e,i)},t.Transport.prototype.clearInterval=function(e){return console.warn("This method is deprecated. Use Tone.Transport.clear instead."),t.Transport.clear(e)},t.Transport.prototype.setTimeout=function(e,i){return console.warn("This method is deprecated. Use Tone.Transport.scheduleOnce instead."),t.Transport.scheduleOnce(e,i)},t.Transport.prototype.clearTimeout=function(e){return console.warn("This method is deprecated. Use Tone.Transport.clear instead."),t.Transport.clear(e)},t.Transport.prototype.setTimeline=function(e,i){return console.warn("This method is deprecated. Use Tone.Transport.schedule instead."),t.Transport.schedule(e,i)},t.Transport.prototype.clearTimeline=function(e){return console.warn("This method is deprecated. Use Tone.Transport.clear instead."),t.Transport.clear(e)};var e=t.Transport;return t._initAudioContext(function(){if("function"==typeof t.Transport)t.Transport=new t.Transport;else{t.Transport.stop();var i=t.Transport.get();t.Transport.dispose(),e.call(t.Transport),t.Transport.set(i)}}),t.Transport}),Module(function(t){return t.Volume=function(){var e=this.optionsObject(arguments,["volume"],t.Volume.defaults);this.output=this.input=new t.Gain(e.volume,t.Type.Decibels),this.volume=this.output.gain,this._readOnly("volume")},t.extend(t.Volume),t.Volume.defaults={volume:0},t.Volume.prototype.dispose=function(){return this.input.dispose(),t.prototype.dispose.call(this),this._writable("volume"),this.volume.dispose(),this.volume=null,this},t.Volume}),Module(function(t){return t.Source=function(e){t.call(this),e=this.defaultArg(e,t.Source.defaults),this._volume=this.output=new t.Volume(e.volume),this.volume=this._volume.volume,this._readOnly("volume"),this._state=new t.TimelineState(t.State.Stopped),this._syncStart=function(t,e){t=this.toSeconds(t),t+=this.toSeconds(this._startDelay),this.start(t,e)}.bind(this),this._syncStop=this.stop.bind(this),this._startDelay=0,this._volume.output.output.channelCount=2,this._volume.output.output.channelCountMode="explicit"},t.extend(t.Source),t.Source.defaults={volume:0},Object.defineProperty(t.Source.prototype,"state",{get:function(){return this._state.getStateAtTime(this.now())}}),t.Source.prototype.start=function(e){return e=this.toSeconds(e),(this._state.getStateAtTime(e)!==t.State.Started||this.retrigger)&&(this._state.setStateAtTime(t.State.Started,e),this._start&&this._start.apply(this,arguments)),this},t.Source.prototype.stop=function(e){return e=this.toSeconds(e),this._state.getStateAtTime(e)===t.State.Started&&(this._state.setStateAtTime(t.State.Stopped,e),this._stop&&this._stop.apply(this,arguments)),this},t.Source.prototype.sync=function(e){return this._startDelay=this.defaultArg(e,0),t.Transport.on("start",this._syncStart),t.Transport.on("stop pause",this._syncStop),this},t.Source.prototype.unsync=function(){return this._startDelay=0,t.Transport.off("start",this._syncStart),t.Transport.off("stop pause",this._syncStop),this},t.Source.prototype.dispose=function(){this.stop(),t.prototype.dispose.call(this),this.unsync(),this._writable("volume"),this._volume.dispose(),this._volume=null,this.volume=null,this._state.dispose(),this._state=null,this._syncStart=null,this._syncStart=null},t.Source}),Module(function(t){return t.Oscillator=function(){var e=this.optionsObject(arguments,["frequency","type"],t.Oscillator.defaults);t.Source.call(this,e),this._oscillator=null,this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this._wave=null,this._partials=this.defaultArg(e.partials,[1]),this._phase=e.phase,this._type=null,this.type=e.type,this.phase=this._phase,this._readOnly(["frequency","detune"])},t.extend(t.Oscillator,t.Source),t.Oscillator.defaults={type:"sine",frequency:440,detune:0,phase:0,partials:[]},t.Oscillator.Type={Sine:"sine",Triangle:"triangle",Sawtooth:"sawtooth",Square:"square",Custom:"custom"},t.Oscillator.prototype._start=function(t){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(t))},t.Oscillator.prototype._stop=function(t){return this._oscillator&&(this._oscillator.stop(this.toSeconds(t)),this._oscillator=null),this},t.Oscillator.prototype.syncFrequency=function(){return t.Transport.syncSignal(this.frequency),this},t.Oscillator.prototype.unsyncFrequency=function(){return t.Transport.unsyncSignal(this.frequency),this},Object.defineProperty(t.Oscillator.prototype,"type",{get:function(){return this._type},set:function(t){var e=this._getRealImaginary(t,this._phase),i=this.context.createPeriodicWave(e[0],e[1]);this._wave=i,null!==this._oscillator&&this._oscillator.setPeriodicWave(this._wave),this._type=t}}),t.Oscillator.prototype._getRealImaginary=function(e,i){var s,n,o,r,a=4096,l=a/2,h=new Float32Array(l),u=new Float32Array(l),c=1;for(e===t.Oscillator.Type.Custom?(c=this._partials.length+1,l=c):(s=/^(sine|triangle|square|sawtooth)(\d+)$/.exec(e),s&&(c=parseInt(s[2])+1,e=s[1],c=Math.max(c,2),l=c)),n=1;l>n;++n){switch(o=2/(n*Math.PI),e){case t.Oscillator.Type.Sine:r=c>=n?1:0;break;case t.Oscillator.Type.Square:r=1&n?2*o:0;break;case t.Oscillator.Type.Sawtooth:r=o*(1&n?1:-1);break;case t.Oscillator.Type.Triangle:r=1&n?2*o*o*(n-1>>1&1?-1:1):0;break;case t.Oscillator.Type.Custom:r=this._partials[n-1];break;default:throw new Error("invalid oscillator type: "+e)}0!==r?(h[n]=-r*Math.sin(i*n),u[n]=r*Math.cos(i*n)):(h[n]=0,u[n]=0)}return[h,u]},t.Oscillator.prototype._inverseFFT=function(t,e,i){var s,n=0,o=t.length;for(s=0;o>s;s++)n+=t[s]*Math.cos(s*i)+e[s]*Math.sin(s*i);return n},t.Oscillator.prototype._getInitialValue=function(){var t,e=this._getRealImaginary(this._type,0),i=e[0],s=e[1],n=0,o=2*Math.PI;for(t=0;8>t;t++)n=Math.max(this._inverseFFT(i,s,t/8*o),n);return-this._inverseFFT(i,s,this._phase)/n},Object.defineProperty(t.Oscillator.prototype,"partials",{get:function(){return this._type!==t.Oscillator.Type.Custom?[]:this._partials},set:function(e){this._partials=e,this.type=t.Oscillator.Type.Custom}}),Object.defineProperty(t.Oscillator.prototype,"phase",{get:function(){return this._phase*(180/Math.PI)},set:function(t){this._phase=t*Math.PI/180,this.type=this._type}}),t.Oscillator.prototype.dispose=function(){return t.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._partials=null,this},t.Oscillator}),Module(function(t){return t.LFO=function(){var e=this.optionsObject(arguments,["frequency","min","max"],t.LFO.defaults);this._oscillator=new t.Oscillator({frequency:e.frequency,type:e.type}),this.frequency=this._oscillator.frequency,this.amplitude=this._oscillator.volume,this.amplitude.units=t.Type.NormalRange,this.amplitude.value=e.amplitude,this._stoppedSignal=new t.Signal(0,t.Type.AudioRange),this._stoppedValue=0,this._a2g=new t.AudioToGain,this._scaler=this.output=new t.Scale(e.min,e.max),this._units=t.Type.Default,this.units=e.units,this._oscillator.chain(this._a2g,this._scaler),this._stoppedSignal.connect(this._a2g),this._readOnly(["amplitude","frequency"]),this.phase=e.phase},t.extend(t.LFO,t.Oscillator),t.LFO.defaults={type:"sine",min:0,max:1,phase:0,frequency:"4n",amplitude:1,units:t.Type.Default},t.LFO.prototype.start=function(t){return t=this.toSeconds(t),this._stoppedSignal.setValueAtTime(0,t),this._oscillator.start(t),this},t.LFO.prototype.stop=function(t){return t=this.toSeconds(t),this._stoppedSignal.setValueAtTime(this._stoppedValue,t),this._oscillator.stop(t),this},t.LFO.prototype.sync=function(t){return this._oscillator.sync(t),this._oscillator.syncFrequency(),this},t.LFO.prototype.unsync=function(){return this._oscillator.unsync(),this._oscillator.unsyncFrequency(),this},Object.defineProperty(t.LFO.prototype,"min",{get:function(){return this._toUnits(this._scaler.min)},set:function(t){t=this._fromUnits(t),this._scaler.min=t}}),Object.defineProperty(t.LFO.prototype,"max",{get:function(){return this._toUnits(this._scaler.max)},set:function(t){t=this._fromUnits(t),this._scaler.max=t}}),Object.defineProperty(t.LFO.prototype,"type",{get:function(){return this._oscillator.type},set:function(t){this._oscillator.type=t,this._stoppedValue=this._oscillator._getInitialValue(),this._stoppedSignal.value=this._stoppedValue}}),Object.defineProperty(t.LFO.prototype,"phase",{get:function(){return this._oscillator.phase},set:function(t){this._oscillator.phase=t,this._stoppedValue=this._oscillator._getInitialValue(),this._stoppedSignal.value=this._stoppedValue}}),Object.defineProperty(t.LFO.prototype,"units",{get:function(){return this._units},set:function(t){var e=this.min,i=this.max;this._units=t,this.min=e,this.max=i}}),Object.defineProperty(t.LFO.prototype,"state",{get:function(){return this._oscillator.state}}),t.LFO.prototype.connect=function(e){return(e.constructor===t.Signal||e.constructor===t.Param||e.constructor===t.TimelineSignal)&&(this.convert=e.convert,this.units=e.units),t.Signal.prototype.connect.apply(this,arguments),this},t.LFO.prototype._fromUnits=t.Param.prototype._fromUnits,t.LFO.prototype._toUnits=t.Param.prototype._toUnits,t.LFO.prototype.dispose=function(){return t.prototype.dispose.call(this),this._writable(["amplitude","frequency"]),this._oscillator.dispose(),this._oscillator=null,this._stoppedSignal.dispose(),this._stoppedSignal=null,this._scaler.dispose(),this._scaler=null,this._a2g.dispose(),this._a2g=null,this.frequency=null,this.amplitude=null,this},t.LFO}),Module(function(t){return t.Limiter=function(){var e=this.optionsObject(arguments,["threshold"],t.Limiter.defaults);this._compressor=this.input=this.output=new t.Compressor({attack:.001,decay:.001,threshold:e.threshold}),this.threshold=this._compressor.threshold,this._readOnly("threshold")},t.extend(t.Limiter),t.Limiter.defaults={threshold:-12},t.Limiter.prototype.dispose=function(){return t.prototype.dispose.call(this),this._compressor.dispose(),this._compressor=null,this._writable("threshold"),this.threshold=null,this},t.Limiter}),Module(function(t){return t.LowpassCombFilter=function(){t.call(this);var e=this.optionsObject(arguments,["delayTime","resonance","dampening"],t.LowpassCombFilter.defaults);this._delay=this.input=this.context.createDelay(1),this.delayTime=new t.Signal(e.delayTime,t.Type.Time),this._lowpass=this.output=this.context.createBiquadFilter(),this._lowpass.Q.value=0,this._lowpass.type="lowpass",this.dampening=new t.Param({param:this._lowpass.frequency,units:t.Type.Frequency,value:e.dampening}),this._feedback=this.context.createGain(),this.resonance=new t.Param({param:this._feedback.gain,units:t.Type.NormalRange,value:e.resonance}),this._delay.chain(this._lowpass,this._feedback,this._delay),this.delayTime.connect(this._delay.delayTime),this._readOnly(["dampening","resonance","delayTime"])},t.extend(t.LowpassCombFilter),t.LowpassCombFilter.defaults={delayTime:.1,resonance:.5,dampening:3e3},t.LowpassCombFilter.prototype.dispose=function(){return t.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},t.LowpassCombFilter}),Module(function(t){return t.Merge=function(){t.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),this.left.channelCount=1,this.right.channelCount=1,this.left.channelCountMode="explicit",this.right.channelCountMode="explicit"},t.extend(t.Merge),t.Merge.prototype.dispose=function(){return t.prototype.dispose.call(this),this.left.disconnect(),this.left=null,this.right.disconnect(),this.right=null,this._merger.disconnect(),this._merger=null,this},t.Merge}),Module(function(t){return t.Meter=function(){var e,i,s=this.optionsObject(arguments,["channels","smoothing"],t.Meter.defaults);for(t.call(this),this._channels=s.channels,this.smoothing=s.smoothing,this.clipMemory=s.clipMemory,this.clipLevel=s.clipLevel,this._volume=new Array(this._channels),this._values=new Array(this._channels),e=0;er;r++)o=i[r],n+=o,s+=o*o;a=n/h,l=Math.sqrt(s/h),l>.9&&(this._lastClip[e]=Date.now()),this._volume[e]=Math.max(l,this._volume[e]*u),this._values[e]=a}},t.Meter.prototype.getLevel=function(t){t=this.defaultArg(t,0);var e=this._volume[t];return 1e-5>e?0:e},t.Meter.prototype.getValue=function(t){return t=this.defaultArg(t,0),this._values[t]},t.Meter.prototype.getDb=function(t){return this.gainToDb(this.getLevel(t))},t.Meter.prototype.isClipped=function(t){return t=this.defaultArg(t,0),Date.now()-this._lastClip[t]<1e3*this._clipMemory},t.Meter.prototype.dispose=function(){return t.prototype.dispose.call(this),this._jsNode.disconnect(),this._jsNode.onaudioprocess=null,this._jsNode=null,this._volume=null,this._values=null,this._lastClip=null,this},t.Meter}),Module(function(t){return t.Split=function(){t.call(this,0,2),this._splitter=this.input=this.context.createChannelSplitter(2),this.left=this.output[0]=this.context.createGain(),this.right=this.output[1]=this.context.createGain(),this._splitter.connect(this.left,0,0),this._splitter.connect(this.right,1,0)},t.extend(t.Split),t.Split.prototype.dispose=function(){return t.prototype.dispose.call(this),this._splitter.disconnect(),this.left.disconnect(),this.right.disconnect(),this.left=null,this.right=null,this._splitter=null,this},t.Split}),Module(function(t){t.MidSideSplit=function(){t.call(this,0,2),this._split=this.input=new t.Split,this.mid=this.output[0]=new t.Expr("($0 + $1) * $2"),this.side=this.output[1]=new t.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),e.connect(this.mid,0,2),e.connect(this.side,0,2)},t.extend(t.MidSideSplit);var e=null;return t._initAudioContext(function(){e=new t.Signal(1/Math.sqrt(2))}),t.MidSideSplit.prototype.dispose=function(){return t.prototype.dispose.call(this),this.mid.dispose(),this.mid=null,this.side.dispose(),this.side=null,this._split.dispose(),this._split=null,this},t.MidSideSplit}),Module(function(t){t.MidSideMerge=function(){t.call(this,2,0),this.mid=this.input[0]=this.context.createGain(),this._left=new t.Expr("($0 + $1) * $2"),this.side=this.input[1]=this.context.createGain(),this._right=new t.Expr("($0 - $1) * $2"),this._merge=this.output=new t.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),e.connect(this._left,0,2),e.connect(this._right,0,2)},t.extend(t.MidSideMerge);var e=null;return t._initAudioContext(function(){e=new t.Signal(1/Math.sqrt(2))}),t.MidSideMerge.prototype.dispose=function(){return t.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,this},t.MidSideMerge}),Module(function(t){return t.MidSideCompressor=function(e){e=this.defaultArg(e,t.MidSideCompressor.defaults),this._midSideSplit=this.input=new t.MidSideSplit,this._midSideMerge=this.output=new t.MidSideMerge,this.mid=new t.Compressor(e.mid),this.side=new t.Compressor(e.side),this._midSideSplit.mid.chain(this.mid,this._midSideMerge.mid),this._midSideSplit.side.chain(this.side,this._midSideMerge.side),this._readOnly(["mid","side"])},t.extend(t.MidSideCompressor),t.MidSideCompressor.defaults={mid:{ratio:3,threshold:-24,release:.03,attack:.02,knee:16},side:{ratio:6,threshold:-30,release:.25,attack:.03,knee:10}},t.MidSideCompressor.prototype.dispose=function(){return t.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,this},t.MidSideCompressor}),Module(function(t){return t.Mono=function(){t.call(this,1,0),this._merge=this.output=new t.Merge,this.input.connect(this._merge,0,0),this.input.connect(this._merge,0,1),this.input.gain.value=this.dbToGain(-10)},t.extend(t.Mono),t.Mono.prototype.dispose=function(){return t.prototype.dispose.call(this),this._merge.dispose(),this._merge=null,this},t.Mono}),Module(function(t){return t.MultibandCompressor=function(e){e=this.defaultArg(arguments,t.MultibandCompressor.defaults),this._splitter=this.input=new t.MultibandSplit({lowFrequency:e.lowFrequency,highFrequency:e.highFrequency}),this.lowFrequency=this._splitter.lowFrequency,this.highFrequency=this._splitter.highFrequency,this.output=this.context.createGain(),this.low=new t.Compressor(e.low),this.mid=new t.Compressor(e.mid),this.high=new t.Compressor(e.high),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"])},t.extend(t.MultibandCompressor),t.MultibandCompressor.defaults={low:t.Compressor.defaults,mid:t.Compressor.defaults,high:t.Compressor.defaults,lowFrequency:250,highFrequency:2e3},t.MultibandCompressor.prototype.dispose=function(){return t.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,this},t.MultibandCompressor}),Module(function(t){ -return t.GainToAudio=function(){this._norm=this.input=this.output=new t.WaveShaper(function(t){return 2*Math.abs(t)-1})},t.extend(t.GainToAudio,t.SignalBase),t.GainToAudio.prototype.dispose=function(){return t.prototype.dispose.call(this),this._norm.dispose(),this._norm=null,this},t.GainToAudio}),Module(function(t){return t.Panner=function(e){t.call(this),this._hasStereoPanner=this.isFunction(this.context.createStereoPanner),this._hasStereoPanner?(this._panner=this.input=this.output=this.context.createStereoPanner(),this.pan=new t.Signal(0,t.Type.NormalRange),this._scalePan=new t.GainToAudio,this.pan.chain(this._scalePan,this._panner.pan)):(this._crossFade=new t.CrossFade,this._merger=this.output=new t.Merge,this._splitter=this.input=new t.Split,this.pan=this._crossFade.fade,this._splitter.connect(this._crossFade,0,0),this._splitter.connect(this._crossFade,1,1),this._crossFade.a.connect(this._merger,0,0),this._crossFade.b.connect(this._merger,0,1)),this.pan.value=this.defaultArg(e,.5),this._readOnly("pan")},t.extend(t.Panner),t.Panner.prototype.dispose=function(){return t.prototype.dispose.call(this),this._writable("pan"),this._hasStereoPanner?(this._panner.disconnect(),this._panner=null,this.pan.dispose(),this.pan=null,this._scalePan.dispose(),this._scalePan=null):(this._crossFade.dispose(),this._crossFade=null,this._splitter.dispose(),this._splitter=null,this._merger.dispose(),this._merger=null,this.pan=null),this},t.Panner}),Module(function(t){return t.PanVol=function(){var e=this.optionsObject(arguments,["pan","volume"],t.PanVol.defaults);this._panner=this.input=new t.Panner(e.pan),this.pan=this._panner.pan,this._volume=this.output=new t.Volume(e.volume),this.volume=this._volume.volume,this._panner.connect(this._volume),this._readOnly(["pan","volume"])},t.extend(t.PanVol),t.PanVol.defaults={pan:.5,volume:0},t.PanVol.prototype.dispose=function(){return t.prototype.dispose.call(this),this._writable(["pan","volume"]),this._panner.dispose(),this._panner=null,this.pan=null,this._volume.dispose(),this._volume=null,this.volume=null,this},t.PanVol}),Module(function(t){return t.CtrlInterpolate=function(){var e=this.optionsObject(arguments,["values","index"],t.CtrlInterpolate.defaults);this.values=e.values,this.index=e.index},t.extend(t.CtrlInterpolate),t.CtrlInterpolate.defaults={index:0,values:[]},Object.defineProperty(t.CtrlInterpolate.prototype,"value",{get:function(){var t,e,i,s=this.index;return s=Math.min(s,this.values.length-1),t=Math.floor(s),e=this.values[t],i=this.values[Math.ceil(s)],this._interpolate(s-t,e,i)}}),t.CtrlInterpolate.prototype._interpolate=function(t,e,i){var s,n,o,r;if(this.isArray(e)){for(s=[],n=0;ns&&s+o>i&&(r=t[n],this.value=this.isObject(r)?r.value:r),s+=o;else this.value=t;return this.value},t.CtrlMarkov.prototype._getProbDistribution=function(t){var e,i,s,n=[],o=0,r=!1;for(e=0;e=this.values.length&&(this.index=0)):e===t.CtrlPattern.Type.Down?(this.index--,this.index<0&&(this.index=this.values.length-1)):e===t.CtrlPattern.Type.UpDown||e===t.CtrlPattern.Type.DownUp?(this._direction===t.CtrlPattern.Type.Up?this.index++:this.index--,this.index<0?(this.index=1,this._direction=t.CtrlPattern.Type.Up):this.index>=this.values.length&&(this.index=this.values.length-2,this._direction=t.CtrlPattern.Type.Down)):e===t.CtrlPattern.Type.Random?this.index=Math.floor(Math.random()*this.values.length):e===t.CtrlPattern.Type.RandomWalk?Math.random()<.5?(this.index--,this.index=Math.max(this.index,0)):(this.index++,this.index=Math.min(this.index,this.values.length-1)):e===t.CtrlPattern.Type.RandomOnce?(this.index++,this.index>=this.values.length&&(this.index=0,this._shuffleValues())):e===t.CtrlPattern.Type.AlternateUp?(this._direction===t.CtrlPattern.Type.Up?(this.index+=2,this._direction=t.CtrlPattern.Type.Down):(this.index-=1,this._direction=t.CtrlPattern.Type.Up),this.index>=this.values.length&&(this.index=0,this._direction=t.CtrlPattern.Type.Up)):e===t.CtrlPattern.Type.AlternateDown&&(this._direction===t.CtrlPattern.Type.Up?(this.index+=1,this._direction=t.CtrlPattern.Type.Down):(this.index-=2,this._direction=t.CtrlPattern.Type.Up),this.index<0&&(this.index=this.values.length-1,this._direction=t.CtrlPattern.Type.Down)),this.value},t.CtrlPattern.prototype._shuffleValues=function(){var t,e,i=[];for(this._shuffled=[],t=0;t0;)e=i.splice(Math.floor(i.length*Math.random()),1),this._shuffled.push(e[0])},t.CtrlPattern.prototype.dispose=function(){this._shuffled=null,this.values=null},t.CtrlPattern}),Module(function(t){return t.CtrlRandom=function(){var e=this.optionsObject(arguments,["min","max"],t.CtrlRandom.defaults);this.min=e.min,this.max=e.max,this.integer=e.integer},t.extend(t.CtrlRandom),t.CtrlRandom.defaults={min:0,max:1,integer:!1},Object.defineProperty(t.CtrlRandom.prototype,"value",{get:function(){var t=this.toSeconds(this.min),e=this.toSeconds(this.max),i=Math.random(),s=i*t+(1-i)*e;return this.integer&&(s=Math.floor(s)),s}}),t.CtrlRandom}),Module(function(t){return t.Buffer=function(){var e=this.optionsObject(arguments,["url","onload"],t.Buffer.defaults);this._buffer=null,this._reversed=e.reverse,this.url=void 0,this.loaded=!1,this.onload=e.onload.bind(this,this),e.url instanceof AudioBuffer||e.url instanceof t.Buffer?(this.set(e.url),this.onload(this)):this.isString(e.url)&&(this.url=e.url,t.Buffer._addToQueue(e.url,this))},t.extend(t.Buffer),t.Buffer.defaults={url:void 0,onload:t.noOp,reverse:!1},t.Buffer.prototype.set=function(e){return this._buffer=e instanceof t.Buffer?e.get():e,this.loaded=!0,this},t.Buffer.prototype.get=function(){return this._buffer},t.Buffer.prototype.load=function(e,i){return this.url=e,this.onload=this.defaultArg(i,this.onload),t.Buffer._addToQueue(e,this),this},t.Buffer.prototype.dispose=function(){return t.prototype.dispose.call(this),t.Buffer._removeFromQueue(this),this._buffer=null,this.onload=t.Buffer.defaults.onload,this},Object.defineProperty(t.Buffer.prototype,"duration",{get:function(){return this._buffer?this._buffer.duration:0}}),t.Buffer.prototype._reverse=function(){if(this.loaded)for(var t=0;t0){if(t.Buffer._currentDownloads.length0){for(e=0;r>e;e++)i=t.Buffer._currentDownloads[e],o+=i.progress;a=o}s=r-a,n=t.Buffer._totalDownloads-t.Buffer._queue.length-s,t.Buffer.trigger("progress",n/t.Buffer._totalDownloads)},t.Buffer.load=function(e,i){var s=new XMLHttpRequest;return s.open("GET",e,!0),s.responseType="arraybuffer",s.onload=function(){t.context.decodeAudioData(s.response,function(t){if(!t)throw new Error("could not decode audio data:"+e);i(t)})},s.send(),s},Object.defineProperty(t.Buffer,"onload",{set:function(e){console.warn("Tone.Buffer.onload is deprecated, use Tone.Buffer.on('load', callback)"),t.Buffer.on("load",e)}}),Object.defineProperty(t.Buffer,"onprogress",{set:function(e){console.warn("Tone.Buffer.onprogress is deprecated, use Tone.Buffer.on('progress', callback)"),t.Buffer.on("progress",e)}}),Object.defineProperty(t.Buffer,"onerror",{set:function(e){console.warn("Tone.Buffer.onerror is deprecated, use Tone.Buffer.on('error', callback)"),t.Buffer.on("error",e)}}),t.Buffer}),Module(function(t){var e={};return t.prototype.send=function(t,i){e.hasOwnProperty(t)||(e[t]=this.context.createGain());var s=this.context.createGain();return s.gain.value=this.dbToGain(this.defaultArg(i,1)),this.output.chain(s,e[t]),s},t.prototype.receive=function(t,i){return e.hasOwnProperty(t)||(e[t]=this.context.createGain()),this.isUndef(i)&&(i=this.input),e[t].connect(i),this},t}),Module(function(t){return t.Delay=function(){var e=this.optionsObject(arguments,["delayTime","maxDelay"],t.Delay.defaults);this._delayNode=this.input=this.output=this.context.createDelay(this.toSeconds(e.maxDelay)),this.delayTime=new t.Param({param:this._delayNode.delayTime,units:t.Type.Time,value:e.delayTime}),this._readOnly("delayTime")},t.extend(t.Delay),t.Delay.defaults={maxDelay:1,delayTime:0},t.Delay.prototype.dispose=function(){return t.Param.prototype.dispose.call(this),this._delayNode.disconnect(),this._delayNode=null,this._writable("delayTime"),this.delayTime=null,this},t.Delay}),Module(function(t){t.Master=function(){t.call(this),this._unmutedVolume=1,this._muted=!1,this._volume=this.output=new t.Volume,this.volume=this._volume.volume,this._readOnly("volume"),this.input.chain(this.output,this.context.destination)},t.extend(t.Master),t.Master.defaults={volume:0,mute:!1},Object.defineProperty(t.Master.prototype,"mute",{get:function(){return this._muted},set:function(t){!this._muted&&t?(this._unmutedVolume=this.volume.value,this.volume.value=-(1/0)):this._muted&&!t&&(this.volume.value=this._unmutedVolume),this._muted=t}}),t.Master.prototype.chain=function(){this.input.disconnect(),this.input.chain.apply(this.input,arguments),arguments[arguments.length-1].connect(this.output)},t.Master.prototype.dispose=function(){t.prototype.dispose.call(this),this._writable("volume"),this._volume.dispose(),this._volume=null,this.volume=null},t.prototype.toMaster=function(){return this.connect(t.Master),this},AudioNode.prototype.toMaster=function(){return this.connect(t.Master),this};var e=t.Master;return t._initAudioContext(function(){t.prototype.isUndef(t.Master)?(e.prototype.dispose.call(t.Master),e.call(t.Master)):t.Master=new e}),t.Master}),Module(function(t){function e(t,e,s){var n,o,r,a;if(i.hasOwnProperty(t))for(n=i[t],o=0,r=n.length;r>o;o++)a=n[o],Array.isArray(s)?a.apply(window,[e].concat(s)):a(e,s)}t.Note=function(e,i,s){this.value=s,this._channel=e,this._timelineID=t.Transport.setTimeline(this._trigger.bind(this),i)},t.Note.prototype._trigger=function(t){e(this._channel,t,this.value)},t.Note.prototype.dispose=function(){return t.Transport.clearTimeline(this._timelineID),this.value=null,this};var i={};return t.Note.route=function(t,e){i.hasOwnProperty(t)?i[t].push(e):i[t]=[e]},t.Note.unroute=function(t,e){var s,n;i.hasOwnProperty(t)&&(s=i[t],n=s.indexOf(e),-1!==n&&i[t].splice(n,1))},t.Note.parseScore=function(e){var i,s,n,o,r,a,l,h=[];for(i in e)if(s=e[i],"tempo"===i)t.Transport.bpm.value=s;else if("timeSignature"===i)t.Transport.timeSignature=s[0]/(s[1]/4);else{if(!Array.isArray(s))throw new TypeError("score parts must be Arrays");for(n=0;ns;++s)n=2*s/i-1,e[s]=0===n?0:this._getCoefficient(n,t,{});this._shaper.curve=e}}),Object.defineProperty(t.Chebyshev.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){this._shaper.oversample=t}}),t.Chebyshev.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},t.Chebyshev}),Module(function(t){return t.StereoEffect=function(){t.call(this);var e=this.optionsObject(arguments,["wet"],t.Effect.defaults);this._dryWet=new t.CrossFade(e.wet),this.wet=this._dryWet.fade,this._split=new t.Split,this.effectSendL=this._split.left,this.effectSendR=this._split.right,this._merge=new t.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"])},t.extend(t.StereoEffect,t.Effect),t.StereoEffect.prototype.dispose=function(){return t.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},t.StereoEffect}),Module(function(t){return t.FeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"]);e=this.defaultArg(e,t.FeedbackEffect.defaults),t.Effect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),this._feedbackGain=this.context.createGain(),this.effectReturn.chain(this._feedbackGain,this.effectSend),this.feedback.connect(this._feedbackGain.gain),this._readOnly(["feedback"])},t.extend(t.FeedbackEffect,t.Effect),t.FeedbackEffect.defaults={feedback:.125},t.FeedbackEffect.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._writable(["feedback"]),this.feedback.dispose(),this.feedback=null,this._feedbackGain.disconnect(),this._feedbackGain=null,this},t.FeedbackEffect}),Module(function(t){return t.StereoXFeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"],t.FeedbackEffect.defaults);t.StereoEffect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),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"])},t.extend(t.StereoXFeedbackEffect,t.FeedbackEffect),t.StereoXFeedbackEffect.prototype.dispose=function(){return t.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},t.StereoXFeedbackEffect}),Module(function(t){return t.Chorus=function(){var e=this.optionsObject(arguments,["frequency","delayTime","depth"],t.Chorus.defaults);t.StereoXFeedbackEffect.call(this,e),this._depth=e.depth,this._delayTime=e.delayTime/1e3,this._lfoL=new t.LFO({frequency:e.frequency,min:0,max:1}),this._lfoR=new t.LFO({frequency:e.frequency,min:0,max:1,phase:180}),this._delayNodeL=this.context.createDelay(),this._delayNodeR=this.context.createDelay(),this.frequency=this._lfoL.frequency,this.effectSendL.chain(this._delayNodeL,this.effectReturnL),this.effectSendR.chain(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=e.frequency,this.type=e.type,this._readOnly(["frequency"]),this.spread=e.spread},t.extend(t.Chorus,t.StereoXFeedbackEffect),t.Chorus.defaults={frequency:1.5,delayTime:3.5,depth:.7,feedback:.1,type:"sine",spread:180},Object.defineProperty(t.Chorus.prototype,"depth",{get:function(){return this._depth},set:function(t){this._depth=t;var e=this._delayTime*t;this._lfoL.min=Math.max(this._delayTime-e,0),this._lfoL.max=this._delayTime+e,this._lfoR.min=Math.max(this._delayTime-e,0),this._lfoR.max=this._delayTime+e}}),Object.defineProperty(t.Chorus.prototype,"delayTime",{get:function(){return 1e3*this._delayTime},set:function(t){this._delayTime=t/1e3,this.depth=this._depth}}),Object.defineProperty(t.Chorus.prototype,"type",{get:function(){return this._lfoL.type},set:function(t){this._lfoL.type=t,this._lfoR.type=t}}),Object.defineProperty(t.Chorus.prototype,"spread",{get:function(){return this._lfoR.phase-this._lfoL.phase},set:function(t){this._lfoL.phase=90-t/2,this._lfoR.phase=t/2+90}}),t.Chorus.prototype.dispose=function(){return t.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},t.Chorus}),Module(function(t){return t.Convolver=function(){var e=this.optionsObject(arguments,["url"],t.Convolver.defaults);t.Effect.call(this,e),this._convolver=this.context.createConvolver(),this._buffer=new t.Buffer(e.url,function(t){this.buffer=t,e.onload()}.bind(this)),this.connectEffect(this._convolver)},t.extend(t.Convolver,t.Effect),t.Convolver.defaults={url:"",onload:t.noOp},Object.defineProperty(t.Convolver.prototype,"buffer",{get:function(){return this._buffer.get()},set:function(t){this._buffer.set(t),this._convolver.buffer=this._buffer.get()}}),t.Convolver.prototype.load=function(t,e){return this._buffer.load(t,function(t){this.buffer=t,e&&e()}.bind(this)),this},t.Convolver.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._convolver.disconnect(),this._convolver=null,this._buffer.dispose(),this._buffer=null,this},t.Convolver}),Module(function(t){return t.Distortion=function(){var e=this.optionsObject(arguments,["distortion"],t.Distortion.defaults);t.Effect.call(this,e),this._shaper=new t.WaveShaper(4096),this._distortion=e.distortion,this.connectEffect(this._shaper),this.distortion=e.distortion,this.oversample=e.oversample},t.extend(t.Distortion,t.Effect),t.Distortion.defaults={distortion:.4,oversample:"none"},Object.defineProperty(t.Distortion.prototype,"distortion",{get:function(){return this._distortion},set:function(t){var e,i;this._distortion=t,e=100*t,i=Math.PI/180,this._shaper.setMap(function(t){return Math.abs(t)<.001?0:(3+e)*t*20*i/(Math.PI+e*Math.abs(t))})}}),Object.defineProperty(t.Distortion.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){this._shaper.oversample=t}}),t.Distortion.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},t.Distortion}),Module(function(t){return t.FeedbackDelay=function(){var e=this.optionsObject(arguments,["delayTime","feedback"],t.FeedbackDelay.defaults);t.FeedbackEffect.call(this,e),this.delayTime=new t.Signal(e.delayTime,t.Type.Time),this._delayNode=this.context.createDelay(4),this.connectEffect(this._delayNode),this.delayTime.connect(this._delayNode.delayTime),this._readOnly(["delayTime"])},t.extend(t.FeedbackDelay,t.FeedbackEffect),t.FeedbackDelay.defaults={delayTime:.25},t.FeedbackDelay.prototype.dispose=function(){return t.FeedbackEffect.prototype.dispose.call(this),this.delayTime.dispose(),this._delayNode.disconnect(),this._delayNode=null,this._writable(["delayTime"]),this.delayTime=null,this},t.FeedbackDelay}),Module(function(t){var e=[1557/44100,1617/44100,1491/44100,1422/44100,1277/44100,1356/44100,1188/44100,1116/44100],i=[225,556,441,341];return t.Freeverb=function(){var s,n,o,r,a,l,h=this.optionsObject(arguments,["roomSize","dampening"],t.Freeverb.defaults);for(t.StereoEffect.call(this,h),this.roomSize=new t.Signal(h.roomSize,t.Type.NormalRange),this.dampening=new t.Signal(h.dampening,t.Type.Frequency),this._combFilters=[],this._allpassFiltersL=[],this._allpassFiltersR=[],s=0;ss;s++)n=this.context.createBiquadFilter(),n.type="allpass",i.connect(n.Q),e.connect(n.frequency),o[s]=n;return this.connectSeries.apply(this,o),o},Object.defineProperty(t.Phaser.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t;var e=this._baseFrequency*Math.pow(2,t);this._lfoL.max=e,this._lfoR.max=e}}),Object.defineProperty(t.Phaser.prototype,"baseFrequency",{get:function(){return this._baseFrequency},set:function(t){this._baseFrequency=t,this._lfoL.min=t,this._lfoR.min=t,this.octaves=this._octaves}}),t.Phaser.prototype.dispose=function(){var e,i;for(t.StereoEffect.prototype.dispose.call(this),this._writable(["frequency","Q"]),this.Q.dispose(),this.Q=null,this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,e=0;et?(this._lfoA.min=0,this._lfoA.max=this._windowSize,this._lfoB.min=0,this._lfoB.max=this._windowSize,e=this.intervalToFrequencyRatio(t-1)+1):(this._lfoA.min=this._windowSize,this._lfoA.max=0,this._lfoB.min=this._windowSize,this._lfoB.max=0,e=this.intervalToFrequencyRatio(t)-1),this._frequency.value=e*(1.2/this._windowSize)}}),Object.defineProperty(t.PitchShift.prototype,"windowSize",{get:function(){return this._windowSize},set:function(t){this._windowSize=this.toSeconds(t),this.pitch=this._pitch}}),t.PitchShift.prototype.dispose=function(){return t.FeedbackEffect.prototype.dispose.call(this),this._frequency.dispose(),this._frequency=null,this._delayA.disconnect(),this._delayA=null,this._delayB.disconnect(),this._delayB=null,this._lfoA.dispose(),this._lfoA=null,this._lfoB.dispose(),this._lfoB=null,this._crossFade.dispose(),this._crossFade=null,this._crossFadeLFO.dispose(),this._crossFadeLFO=null,this._writable("delayTime"),this._feedbackDelay.dispose(),this._feedbackDelay=null,this.delayTime=null,this},t.PitchShift}),Module(function(t){return t.StereoFeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"],t.FeedbackEffect.defaults);t.StereoEffect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),this._feedbackL=this.context.createGain(),this._feedbackR=this.context.createGain(),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"])},t.extend(t.StereoFeedbackEffect,t.FeedbackEffect),t.StereoFeedbackEffect.prototype.dispose=function(){return t.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,this},t.StereoFeedbackEffect}),Module(function(t){return t.StereoWidener=function(){var e=this.optionsObject(arguments,["width"],t.StereoWidener.defaults);t.MidSideEffect.call(this,e),this.width=new t.Signal(e.width,t.Type.NormalRange),this._midMult=new t.Expr("$0 * ($1 * (1 - $2))"),this._sideMult=new t.Expr("$0 * ($1 * $2)"),this._two=new t.Signal(2),this._two.connect(this._midMult,0,1),this.width.connect(this._midMult,0,2),this._two.connect(this._sideMult,0,1),this.width.connect(this._sideMult,0,2),this.midSend.chain(this._midMult,this.midReturn),this.sideSend.chain(this._sideMult,this.sideReturn),this._readOnly(["width"])},t.extend(t.StereoWidener,t.MidSideEffect),t.StereoWidener.defaults={width:.5},t.StereoWidener.prototype.dispose=function(){return t.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,this},t.StereoWidener}),Module(function(t){return t.Tremolo=function(){var e=this.optionsObject(arguments,["frequency","depth"],t.Tremolo.defaults);t.StereoEffect.call(this,e),this._lfoL=new t.LFO({phase:e.spread,min:1,max:0}),this._lfoR=new t.LFO({phase:e.spread,min:1,max:0}),this._amplitudeL=new t.Gain,this._amplitudeR=new t.Gain,this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.depth=new t.Signal(e.depth,t.Type.NormalRange),this._readOnly(["frequency","depth"]),this.effectSendL.chain(this._amplitudeL,this.effectReturnL),this.effectSendR.chain(this._amplitudeR,this.effectReturnR),this._lfoL.connect(this._amplitudeL.gain),this._lfoR.connect(this._amplitudeR.gain),this.frequency.fan(this._lfoL.frequency,this._lfoR.frequency),this.depth.fan(this._lfoR.amplitude,this._lfoL.amplitude),this.type=e.type,this.spread=e.spread},t.extend(t.Tremolo,t.StereoEffect),t.Tremolo.defaults={frequency:10,type:"sine",depth:.5,spread:180},t.Tremolo.prototype.start=function(t){return this._lfoL.start(t),this._lfoR.start(t),this},t.Tremolo.prototype.stop=function(t){return this._lfoL.stop(t),this._lfoR.stop(t),this},t.Tremolo.prototype.sync=function(t){return this._lfoL.sync(t),this._lfoR.sync(t),this},t.Tremolo.prototype.unsync=function(){return this._lfoL.unsync(),this._lfoR.unsync(),this},Object.defineProperty(t.Tremolo.prototype,"type",{get:function(){return this._lfoL.type},set:function(t){this._lfoL.type=t,this._lfoR.type=t}}),Object.defineProperty(t.Tremolo.prototype,"spread",{get:function(){return this._lfoR.phase-this._lfoL.phase},set:function(t){this._lfoL.phase=90-t/2,this._lfoR.phase=t/2+90}}),t.Tremolo.prototype.dispose=function(){return t.StereoEffect.prototype.dispose.call(this),this._writable(["frequency","depth"]),this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,this._amplitudeL.dispose(),this._amplitudeL=null,this._amplitudeR.dispose(),this._amplitudeR=null,this.frequency=null,this.depth=null,this},t.Tremolo}),Module(function(t){return t.Vibrato=function(){var e=this.optionsObject(arguments,["frequency","depth"],t.Vibrato.defaults);t.Effect.call(this,e),this._delayNode=new t.Delay(0,e.maxDelay),this._lfo=new t.LFO({type:e.type,min:0,max:e.maxDelay,frequency:e.frequency,phase:-90}).start().connect(this._delayNode.delayTime),this.frequency=this._lfo.frequency,this.depth=this._lfo.amplitude,this.depth.value=e.depth,this._readOnly(["frequency","depth"]),this.effectSend.chain(this._delayNode,this.effectReturn)},t.extend(t.Vibrato,t.Effect),t.Vibrato.defaults={maxDelay:.005,frequency:5,depth:.1,type:"sine"},Object.defineProperty(t.Vibrato.prototype,"type",{get:function(){return this._lfo.type},set:function(t){this._lfo.type=t}}),t.Vibrato.prototype.dispose=function(){t.Effect.prototype.dispose.call(this),this._delayNode.dispose(),this._delayNode=null,this._lfo.dispose(),this._lfo=null,this._writable(["frequency","depth"]),this.frequency=null,this.depth=null},t.Vibrato}),Module(function(t){return t.Event=function(){var e=this.optionsObject(arguments,["callback","value"],t.Event.defaults);this._loop=e.loop,this.callback=e.callback,this.value=e.value,this._loopStart=this.toTicks(e.loopStart),this._loopEnd=this.toTicks(e.loopEnd),this._state=new t.TimelineState(t.State.Stopped),this._playbackRate=1,this._startOffset=0,this.probability=e.probability,this.humanize=e.humanize,this.mute=e.mute,this.playbackRate=e.playbackRate},t.extend(t.Event),t.Event.defaults={callback:t.noOp,loop:!1,loopEnd:"1m",loopStart:0,playbackRate:1,value:null,probability:1,mute:!1,humanize:!1},t.Event.prototype._rescheduleEvents=function(e){return e=this.defaultArg(e,-1),this._state.forEachFrom(e,function(e){var i,s,n;e.state===t.State.Started&&(this.isUndef(e.id)||t.Transport.clear(e.id),s=e.time+Math.round(this.startOffset/this._playbackRate),this._loop?(i=1/0,this.isNumber(this._loop)&&(i=(this._loop-1)*this._getLoopDuration()),n=this._state.getEventAfter(s),null!==n&&(i=Math.min(i,n.time-s)),i!==1/0&&(this._state.setStateAtTime(t.State.Stopped,s+i+1),i+="i"),e.id=t.Transport.scheduleRepeat(this._tick.bind(this),this._getLoopDuration().toString()+"i",s+"i",i)):e.id=t.Transport.schedule(this._tick.bind(this),s+"i"))}.bind(this)),this},Object.defineProperty(t.Event.prototype,"state",{get:function(){return this._state.getStateAtTime(t.Transport.ticks)}}),Object.defineProperty(t.Event.prototype,"startOffset",{get:function(){return this._startOffset},set:function(t){this._startOffset=t}}),t.Event.prototype.start=function(e){return e=this.toTicks(e),this._state.getStateAtTime(e)===t.State.Stopped&&(this._state.addEvent({state:t.State.Started,time:e,id:void 0}),this._rescheduleEvents(e)),this},t.Event.prototype.stop=function(e){var i,s;return this.cancel(e),e=this.toTicks(e),this._state.getStateAtTime(e)===t.State.Started&&(this._state.setStateAtTime(t.State.Stopped,e),i=this._state.getEventBefore(e),s=e,null!==i&&(s=i.time),this._rescheduleEvents(s)),this},t.Event.prototype.cancel=function(e){return e=this.defaultArg(e,-(1/0)),e=this.toTicks(e),this._state.forEachFrom(e,function(e){t.Transport.clear(e.id)}),this._state.cancel(e),this},t.Event.prototype._tick=function(e){if(!this.mute&&this._state.getStateAtTime(t.Transport.ticks)===t.State.Started){if(this.probability<1&&Math.random()>this.probability)return;if(this.humanize){var i=.02;this.isBoolean(this.humanize)||(i=this.toSeconds(this.humanize)),e+=(2*Math.random()-1)*i}this.callback(e,this.value)}},t.Event.prototype._getLoopDuration=function(){return Math.round((this._loopEnd-this._loopStart)/this._playbackRate)},Object.defineProperty(t.Event.prototype,"loop",{get:function(){return this._loop},set:function(t){this._loop=t,this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"loopEnd",{get:function(){return this.toNotation(this._loopEnd+"i")},set:function(t){this._loopEnd=this.toTicks(t),this._loop&&this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"loopStart",{get:function(){return this.toNotation(this._loopStart+"i")},set:function(t){this._loopStart=this.toTicks(t),this._loop&&this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"progress",{get:function(){var e,i,s,n;return this._loop?(e=t.Transport.ticks,i=this._state.getEvent(e),null!==i&&i.state===t.State.Started?(s=this._getLoopDuration(),n=(e-i.time)%s,n/s):0):0}}),t.Event.prototype.dispose=function(){this.cancel(),this._state.dispose(),this._state=null,this.callback=null,this.value=null},t.Event}),Module(function(t){return t.Loop=function(){var e=this.optionsObject(arguments,["callback","interval"],t.Loop.defaults);this._event=new t.Event({callback:this._tick.bind(this),loop:!0,loopEnd:e.interval,playbackRate:e.playbackRate,probability:e.probability}),this.callback=e.callback,this.iterations=e.iterations},t.extend(t.Loop),t.Loop.defaults={interval:"4n",callback:t.noOp,playbackRate:1,iterations:1/0,probability:!0,mute:!1},t.Loop.prototype.start=function(t){return this._event.start(t),this},t.Loop.prototype.stop=function(t){return this._event.stop(t),this},t.Loop.prototype.cancel=function(t){return this._event.cancel(t),this},t.Loop.prototype._tick=function(t){this.callback(t)},Object.defineProperty(t.Loop.prototype,"state",{get:function(){return this._event.state}}),Object.defineProperty(t.Loop.prototype,"progress",{get:function(){return this._event.progress}}),Object.defineProperty(t.Loop.prototype,"interval",{get:function(){return this._event.loopEnd},set:function(t){this._event.loopEnd=t}}),Object.defineProperty(t.Loop.prototype,"playbackRate",{get:function(){return this._event.playbackRate},set:function(t){this._event.playbackRate=t}}),Object.defineProperty(t.Loop.prototype,"humanize",{get:function(){return this._event.humanize},set:function(t){this._event.humanize=t}}),Object.defineProperty(t.Loop.prototype,"probability",{get:function(){return this._event.probability},set:function(t){this._event.probability=t}}),Object.defineProperty(t.Loop.prototype,"mute",{get:function(){return this._event.mute},set:function(t){this._event.mute=t}}),Object.defineProperty(t.Loop.prototype,"iterations",{get:function(){return this._event.loop===!0?1/0:this._event.loop},set:function(t){this._event.loop=t===1/0?!0:t}}),t.Loop.prototype.dispose=function(){this._event.dispose(),this._event=null,this.callback=null},t.Loop}),Module(function(t){return t.Part=function(){var e,i,s=this.optionsObject(arguments,["callback","events"],t.Part.defaults);if(this._loop=s.loop,this._loopStart=this.toTicks(s.loopStart),this._loopEnd=this.toTicks(s.loopEnd),this._playbackRate=s.playbackRate,this._probability=s.probability,this._humanize=s.humanize,this._startOffset=0,this._state=new t.TimelineState(t.State.Stopped),this._events=[],this.callback=s.callback,this.mute=s.mute,e=this.defaultArg(s.events,[]),!this.isUndef(s.events))for(i=0;i=this._loopStart&&t.startOffset=i&&t.start(e+"i")},Object.defineProperty(t.Part.prototype,"startOffset",{get:function(){return this._startOffset},set:function(t){this._startOffset=t,this._forEach(function(t){t.startOffset+=this._startOffset})}}),t.Part.prototype.stop=function(e){var i=this.toTicks(e);return this._state.getStateAtTime(i)===t.State.Started&&(this._state.setStateAtTime(t.State.Stopped,i),this._forEach(function(t){t.stop(e)})),this},t.Part.prototype.at=function(t,e){var i,s,n;for(t=this.toTicks(t),i=this.ticksToSeconds(1),s=0;s=0;s--)n=this._events[s],n instanceof t.Part?n.remove(e,i):n.startOffset===e&&(this.isUndef(i)||!this.isUndef(i)&&n.value===i)&&(this._events.splice(s,1),n.dispose());return this},t.Part.prototype.removeAll=function(){return this._forEach(function(t){t.dispose()}),this._events=[],this},t.Part.prototype.cancel=function(t){return this._forEach(function(e){e.cancel(t)}),this._state.cancel(t),this},t.Part.prototype._forEach=function(e,i){var s,n;for(i=this.defaultArg(i,this),s=this._events.length-1;s>=0;s--)n=this._events[s],n instanceof t.Part?n._forEach(e,i):e.call(i,n);return this},t.Part.prototype._setAll=function(t,e){this._forEach(function(i){i[t]=e})},t.Part.prototype._tick=function(t,e){this.mute||this.callback(t,e)},t.Part.prototype._testLoopBoundries=function(e){e.startOffset=this._loopEnd?e.cancel():e.state===t.State.Stopped&&this._restartEvent(e)},Object.defineProperty(t.Part.prototype,"probability",{get:function(){return this._probability},set:function(t){this._probability=t,this._setAll("probability",t)}}),Object.defineProperty(t.Part.prototype,"humanize",{get:function(){return this._humanize},set:function(t){this._humanize=t,this._setAll("humanize",t)}}),Object.defineProperty(t.Part.prototype,"loop",{get:function(){return this._loop},set:function(t){this._loop=t,this._forEach(function(e){e._loopStart=this._loopStart,e._loopEnd=this._loopEnd,e.loop=t,this._testLoopBoundries(e)})}}),Object.defineProperty(t.Part.prototype,"loopEnd",{get:function(){return this.toNotation(this._loopEnd+"i")},set:function(t){this._loopEnd=this.toTicks(t),this._loop&&this._forEach(function(t){t.loopEnd=this.loopEnd,this._testLoopBoundries(t)})}}),Object.defineProperty(t.Part.prototype,"loopStart",{get:function(){return this.toNotation(this._loopStart+"i")},set:function(t){this._loopStart=this.toTicks(t),this._loop&&this._forEach(function(t){t.loopStart=this.loopStart,this._testLoopBoundries(t)})}}),Object.defineProperty(t.Part.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this._setAll("playbackRate",t)}}),Object.defineProperty(t.Part.prototype,"length",{get:function(){return this._events.length}}),t.Part.prototype.dispose=function(){return this.removeAll(),this._state.dispose(),this._state=null,this.callback=null,this._events=null,this},t.Part}),Module(function(t){return t.Pattern=function(){var e=this.optionsObject(arguments,["callback","events","pattern"],t.Pattern.defaults);t.Loop.call(this,e),this._pattern=new t.CtrlPattern({values:e.events,type:e.pattern,index:e.index})},t.extend(t.Pattern,t.Loop),t.Pattern.defaults={pattern:t.CtrlPattern.Type.Up,events:[]},t.Pattern.prototype._tick=function(t){this.callback(t,this._pattern.value),this._pattern.next()},Object.defineProperty(t.Pattern.prototype,"index",{get:function(){return this._pattern.index},set:function(t){this._pattern.index=t}}),Object.defineProperty(t.Pattern.prototype,"events",{get:function(){return this._pattern.values},set:function(t){this._pattern.values=t}}),Object.defineProperty(t.Pattern.prototype,"value",{get:function(){return this._pattern.value}}),Object.defineProperty(t.Pattern.prototype,"pattern",{get:function(){return this._pattern.type},set:function(t){this._pattern.type=t}}),t.Pattern.prototype.dispose=function(){t.Loop.prototype.dispose.call(this),this._pattern.dispose(),this._pattern=null},t.Pattern}),Module(function(t){return t.Sequence=function(){var e,i=this.optionsObject(arguments,["callback","events","subdivision"],t.Sequence.defaults),s=i.events;if(delete i.events,t.Part.call(this,i),this._subdivision=this.toTicks(i.subdivision),this.isUndef(i.loopEnd)&&!this.isUndef(s)&&(this._loopEnd=s.length*this._subdivision),this._loop=!0,!this.isUndef(s))for(e=0;et?-1:1}),this._sawtooth.chain(this._thresh,this.output),this.width.chain(this._widthGate,this._thresh),this._readOnly(["width","frequency","detune"])},t.extend(t.PulseOscillator,t.Oscillator),t.PulseOscillator.defaults={frequency:440,detune:0,phase:0,width:.2},t.PulseOscillator.prototype._start=function(t){t=this.toSeconds(t),this._sawtooth.start(t),this._widthGate.gain.setValueAtTime(1,t)},t.PulseOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._sawtooth.stop(t),this._widthGate.gain.setValueAtTime(0,t)},Object.defineProperty(t.PulseOscillator.prototype,"phase",{get:function(){return this._sawtooth.phase},set:function(t){this._sawtooth.phase=t}}),Object.defineProperty(t.PulseOscillator.prototype,"type",{get:function(){return"pulse"}}),Object.defineProperty(t.PulseOscillator.prototype,"partials",{get:function(){return[]}}),t.PulseOscillator.prototype.dispose=function(){return t.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},t.PulseOscillator}),Module(function(t){return t.PWMOscillator=function(){var e=this.optionsObject(arguments,["frequency","modulationFrequency"],t.PWMOscillator.defaults);t.Source.call(this,e),this._pulse=new t.PulseOscillator(e.modulationFrequency),this._pulse._sawtooth.type="sine",this._modulator=new t.Oscillator({frequency:e.frequency,detune:e.detune,phase:e.phase}),this._scale=new t.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"])},t.extend(t.PWMOscillator,t.Oscillator),t.PWMOscillator.defaults={frequency:440,detune:0,phase:0,modulationFrequency:.4},t.PWMOscillator.prototype._start=function(t){t=this.toSeconds(t),this._modulator.start(t),this._pulse.start(t)},t.PWMOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._modulator.stop(t),this._pulse.stop(t)},Object.defineProperty(t.PWMOscillator.prototype,"type",{get:function(){return"pwm"}}),Object.defineProperty(t.PWMOscillator.prototype,"partials",{get:function(){return[]}}),Object.defineProperty(t.PWMOscillator.prototype,"phase",{get:function(){return this._modulator.phase},set:function(t){this._modulator.phase=t}}),t.PWMOscillator.prototype.dispose=function(){return t.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},t.PWMOscillator}),Module(function(t){t.OmniOscillator=function(){var e=this.optionsObject(arguments,["frequency","type"],t.OmniOscillator.defaults);t.Source.call(this,e),this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this._sourceType=void 0,this._oscillator=null,this.type=e.type,this.phase=e.phase,this._readOnly(["frequency","detune"]),this.isArray(e.partials)&&(this.partials=e.partials)},t.extend(t.OmniOscillator,t.Oscillator),t.OmniOscillator.defaults={frequency:440,detune:0,type:"sine",phase:0,width:.4,modulationFrequency:.4};var e={PulseOscillator:"PulseOscillator",PWMOscillator:"PWMOscillator",Oscillator:"Oscillator"};return t.OmniOscillator.prototype._start=function(t){this._oscillator.start(t)},t.OmniOscillator.prototype._stop=function(t){this._oscillator.stop(t)},Object.defineProperty(t.OmniOscillator.prototype,"type",{get:function(){return this._oscillator.type},set:function(i){if(0===i.indexOf("sine")||0===i.indexOf("square")||0===i.indexOf("triangle")||0===i.indexOf("sawtooth")||i===t.Oscillator.Type.Custom)this._sourceType!==e.Oscillator&&(this._sourceType=e.Oscillator,this._createNewOscillator(t.Oscillator)),this._oscillator.type=i;else if("pwm"===i)this._sourceType!==e.PWMOscillator&&(this._sourceType=e.PWMOscillator,this._createNewOscillator(t.PWMOscillator));else{if("pulse"!==i)throw new Error("Tone.OmniOscillator does not support type "+i);this._sourceType!==e.PulseOscillator&&(this._sourceType=e.PulseOscillator,this._createNewOscillator(t.PulseOscillator))}}}),Object.defineProperty(t.OmniOscillator.prototype,"partials",{get:function(){return this._oscillator.partials},set:function(i){this._sourceType!==e.Oscillator&&(this.type=t.Oscillator.Type.Custom),this._oscillator.partials=i}}),t.OmniOscillator.prototype._createNewOscillator=function(e){var i,s=this.now()+this.blockTime;null!==this._oscillator&&(i=this._oscillator,i.stop(s),setTimeout(function(){i.dispose(),i=null},1e3*this.blockTime)),this._oscillator=new e,this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.connect(this.output),this.state===t.State.Started&&this._oscillator.start(s)},Object.defineProperty(t.OmniOscillator.prototype,"phase",{get:function(){return this._oscillator.phase},set:function(t){this._oscillator.phase=t}}),Object.defineProperty(t.OmniOscillator.prototype,"width",{get:function(){return this._sourceType===e.PulseOscillator?this._oscillator.width:void 0}}),Object.defineProperty(t.OmniOscillator.prototype,"modulationFrequency",{get:function(){return this._sourceType===e.PWMOscillator?this._oscillator.modulationFrequency:void 0}}),t.OmniOscillator.prototype.dispose=function(){return t.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},t.OmniOscillator}),Module(function(t){return t.Instrument=function(e){e=this.defaultArg(e,t.Instrument.defaults),this._volume=this.output=new t.Volume(e.volume),this.volume=this._volume.volume,this._readOnly("volume")},t.extend(t.Instrument),t.Instrument.defaults={volume:0},t.Instrument.prototype.triggerAttack=t.noOp,t.Instrument.prototype.triggerRelease=t.noOp,t.Instrument.prototype.triggerAttackRelease=function(t,e,i,s){return i=this.toSeconds(i),e=this.toSeconds(e),this.triggerAttack(t,i,s),this.triggerRelease(i+e),this},t.Instrument.prototype.dispose=function(){return t.prototype.dispose.call(this),this._volume.dispose(),this._volume=null,this._writable(["volume"]),this.volume=null,this},t.Instrument}),Module(function(t){return t.Monophonic=function(e){e=this.defaultArg(e,t.Monophonic.defaults),t.Instrument.call(this,e),this.portamento=e.portamento},t.extend(t.Monophonic,t.Instrument),t.Monophonic.defaults={portamento:0},t.Monophonic.prototype.triggerAttack=function(t,e,i){return e=this.toSeconds(e),this._triggerEnvelopeAttack(e,i),this.setNote(t,e),this},t.Monophonic.prototype.triggerRelease=function(t){return this._triggerEnvelopeRelease(t),this},t.Monophonic.prototype._triggerEnvelopeAttack=function(){},t.Monophonic.prototype._triggerEnvelopeRelease=function(){},t.Monophonic.prototype.setNote=function(t,e){var i,s;return e=this.toSeconds(e),this.portamento>0?(i=this.frequency.value,this.frequency.setValueAtTime(i,e),s=this.toSeconds(this.portamento),this.frequency.exponentialRampToValueAtTime(t,e+s)):this.frequency.setValueAtTime(t,e),this},t.Monophonic}),Module(function(t){return t.MonoSynth=function(e){e=this.defaultArg(e,t.MonoSynth.defaults),t.Monophonic.call(this,e),this.oscillator=new t.OmniOscillator(e.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.filter=new t.Filter(e.filter),this.filterEnvelope=new t.FrequencyEnvelope(e.filterEnvelope),this.envelope=new t.AmplitudeEnvelope(e.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"])},t.extend(t.MonoSynth,t.Monophonic),t.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,baseFrequency:200,octaves:7,exponent:2}},t.MonoSynth.prototype._triggerEnvelopeAttack=function(t,e){return this.envelope.triggerAttack(t,e),this.filterEnvelope.triggerAttack(t),this},t.MonoSynth.prototype._triggerEnvelopeRelease=function(t){return this.envelope.triggerRelease(t),this.filterEnvelope.triggerRelease(t),this},t.MonoSynth.prototype.dispose=function(){return t.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},t.MonoSynth}),Module(function(t){return t.AMSynth=function(e){e=this.defaultArg(e,t.AMSynth.defaults),t.Monophonic.call(this,e),this.carrier=new t.MonoSynth(e.carrier),this.carrier.volume.value=-10,this.modulator=new t.MonoSynth(e.modulator),this.modulator.volume.value=-10,this.frequency=new t.Signal(440,t.Type.Frequency),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this._modulationScale=new t.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","harmonicity"])},t.extend(t.AMSynth,t.Monophonic),t.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,baseFrequency:2e4,octaves:0},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,baseFrequency:20,octaves:6},filter:{Q:6,type:"lowpass",rolloff:-24}}},t.AMSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.carrier.envelope.triggerAttack(t,e),this.modulator.envelope.triggerAttack(t),this.carrier.filterEnvelope.triggerAttack(t),this.modulator.filterEnvelope.triggerAttack(t),this},t.AMSynth.prototype._triggerEnvelopeRelease=function(t){return this.carrier.triggerRelease(t),this.modulator.triggerRelease(t),this},t.AMSynth.prototype.dispose=function(){return t.Monophonic.prototype.dispose.call(this),this._writable(["carrier","modulator","frequency","harmonicity"]),this.carrier.dispose(),this.carrier=null,this.modulator.dispose(),this.modulator=null,this.frequency.dispose(),this.frequency=null,this.harmonicity.dispose(),this.harmonicity=null,this._modulationScale.dispose(),this._modulationScale=null,this._modulationNode.disconnect(),this._modulationNode=null,this},t.AMSynth}),Module(function(t){return t.DrumSynth=function(e){e=this.defaultArg(e,t.DrumSynth.defaults),t.Instrument.call(this,e),this.oscillator=new t.Oscillator(e.oscillator).start(),this.envelope=new t.AmplitudeEnvelope(e.envelope),this.octaves=e.octaves,this.pitchDecay=e.pitchDecay,this.oscillator.chain(this.envelope,this.output),this._readOnly(["oscillator","envelope"])},t.extend(t.DrumSynth,t.Instrument),t.DrumSynth.defaults={pitchDecay:.05,octaves:10,oscillator:{type:"sine"},envelope:{attack:.001,decay:.4,sustain:.01,release:1.4,attackCurve:"exponential"}},t.DrumSynth.prototype.triggerAttack=function(t,e,i){e=this.toSeconds(e),t=this.toFrequency(t);var s=t*this.octaves;return this.oscillator.frequency.setValueAtTime(s,e),this.oscillator.frequency.exponentialRampToValueAtTime(t,e+this.toSeconds(this.pitchDecay)),this.envelope.triggerAttack(e,i),this},t.DrumSynth.prototype.triggerRelease=function(t){return this.envelope.triggerRelease(t),this},t.DrumSynth.prototype.dispose=function(){return t.Instrument.prototype.dispose.call(this),this._writable(["oscillator","envelope"]),this.oscillator.dispose(),this.oscillator=null,this.envelope.dispose(),this.envelope=null,this},t.DrumSynth}),Module(function(t){return t.DuoSynth=function(e){e=this.defaultArg(e,t.DuoSynth.defaults),t.Monophonic.call(this,e),this.voice0=new t.MonoSynth(e.voice0),this.voice0.volume.value=-10,this.voice1=new t.MonoSynth(e.voice1),this.voice1.volume.value=-10,this._vibrato=new t.LFO(e.vibratoRate,-50,50),this._vibrato.start(),this.vibratoRate=this._vibrato.frequency,this._vibratoGain=this.context.createGain(),this.vibratoAmount=new t.Param({param:this._vibratoGain.gain,units:t.Type.Positive,value:e.vibratoAmount}),this._vibratoDelay=this.toSeconds(e.vibratoDelay),this.frequency=new t.Signal(440,t.Type.Frequency),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,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"])},t.extend(t.DuoSynth,t.Monophonic),t.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}}},t.DuoSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.voice0.envelope.triggerAttack(t,e),this.voice1.envelope.triggerAttack(t,e),this.voice0.filterEnvelope.triggerAttack(t),this.voice1.filterEnvelope.triggerAttack(t),this},t.DuoSynth.prototype._triggerEnvelopeRelease=function(t){return this.voice0.triggerRelease(t),this.voice1.triggerRelease(t),this},t.DuoSynth.prototype.dispose=function(){return t.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},t.DuoSynth}),Module(function(t){return t.FMSynth=function(e){e=this.defaultArg(e,t.FMSynth.defaults),t.Monophonic.call(this,e),this.carrier=new t.MonoSynth(e.carrier),this.carrier.volume.value=-10,this.modulator=new t.MonoSynth(e.modulator),this.modulator.volume.value=-10,this.frequency=new t.Signal(440,t.Type.Frequency),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this.modulationIndex=new t.Multiply(e.modulationIndex),this.modulationIndex.units=t.Type.Positive,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","harmonicity","modulationIndex"])},t.extend(t.FMSynth,t.Monophonic),t.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,baseFrequency:200,octaves:8}},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,baseFrequency:600,octaves:5}}},t.FMSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.carrier.envelope.triggerAttack(t,e),this.modulator.envelope.triggerAttack(t),this.carrier.filterEnvelope.triggerAttack(t),this.modulator.filterEnvelope.triggerAttack(t),this},t.FMSynth.prototype._triggerEnvelopeRelease=function(t){return this.carrier.triggerRelease(t),this.modulator.triggerRelease(t),this},t.FMSynth.prototype.dispose=function(){return t.Monophonic.prototype.dispose.call(this),this._writable(["carrier","modulator","frequency","harmonicity","modulationIndex"]),this.carrier.dispose(),this.carrier=null,this.modulator.dispose(),this.modulator=null,this.frequency.dispose(),this.frequency=null,this.modulationIndex.dispose(),this.modulationIndex=null,this.harmonicity.dispose(),this.harmonicity=null,this._modulationNode.disconnect(),this._modulationNode=null,this},t.FMSynth}),Module(function(t){t.Noise=function(){var e=this.optionsObject(arguments,["type"],t.Noise.defaults);t.Source.call(this,e),this._source=null,this._buffer=null,this._playbackRate=e.playbackRate,this.type=e.type},t.extend(t.Noise,t.Source),t.Noise.defaults={type:"white",playbackRate:1},Object.defineProperty(t.Noise.prototype,"type",{get:function(){return this._buffer===s?"white":this._buffer===i?"brown":this._buffer===e?"pink":void 0},set:function(n){if(this.type!==n){switch(n){case"white":this._buffer=s;break;case"pink":this._buffer=e;break;case"brown":this._buffer=i;break;default:throw new Error("invalid noise type: "+n)}if(this.state===t.State.Started){var o=this.now()+this.blockTime;this._stop(o),this._start(o)}}}}),Object.defineProperty(t.Noise.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this._source&&(this._source.playbackRate.value=t)}}),t.Noise.prototype._start=function(t){this._source=this.context.createBufferSource(),this._source.buffer=this._buffer,this._source.loop=!0,this._source.playbackRate.value=this._playbackRate,this._source.connect(this.output),this._source.start(this.toSeconds(t))},t.Noise.prototype._stop=function(t){this._source&&this._source.stop(this.toSeconds(t))},t.Noise.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),null!==this._source&&(this._source.disconnect(),this._source=null),this._buffer=null,this};var e=null,i=null,s=null;return t._initAudioContext(function(t){var n=t.sampleRate,o=4*n;e=function(){var e,i,s,r,a,l,h,u,c,p,f,d=t.createBuffer(2,o,n);for(e=0;ep;p++)f=2*Math.random()-1,s=.99886*s+.0555179*f,r=.99332*r+.0750759*f,a=.969*a+.153852*f,l=.8665*l+.3104856*f,h=.55*h+.5329522*f,u=-.7616*u-.016898*f,i[p]=s+r+a+l+h+u+c+.5362*f,i[p]*=.11,c=.115926*f;return d}(),i=function(){var e,i,s,r,a,l=t.createBuffer(2,o,n);for(e=0;er;r++)a=2*Math.random()-1,i[r]=(s+.02*a)/1.02,s=i[r],i[r]*=3.5;return l}(),s=function(){var e,i,s,r=t.createBuffer(2,o,n);for(e=0;es;s++)i[s]=2*Math.random()-1;return r}()}),t.Noise}),Module(function(t){return t.NoiseSynth=function(e){e=this.defaultArg(e,t.NoiseSynth.defaults),t.Instrument.call(this,e),this.noise=new t.Noise,this.filter=new t.Filter(e.filter),this.filterEnvelope=new t.FrequencyEnvelope(e.filterEnvelope),this.envelope=new t.AmplitudeEnvelope(e.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"])},t.extend(t.NoiseSynth,t.Instrument),t.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,baseFrequency:20,octaves:5}},t.NoiseSynth.prototype.triggerAttack=function(t,e){return this.envelope.triggerAttack(t,e),this.filterEnvelope.triggerAttack(t),this},t.NoiseSynth.prototype.triggerRelease=function(t){return this.envelope.triggerRelease(t),this.filterEnvelope.triggerRelease(t),this},t.NoiseSynth.prototype.triggerAttackRelease=function(t,e,i){return e=this.toSeconds(e),t=this.toSeconds(t),this.triggerAttack(e,i),this.triggerRelease(e+t),this},t.NoiseSynth.prototype.dispose=function(){return t.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},t.NoiseSynth}),Module(function(t){return t.PluckSynth=function(e){e=this.defaultArg(e,t.PluckSynth.defaults),t.Instrument.call(this,e),this._noise=new t.Noise("pink"),this.attackNoise=1,this._lfcf=new t.LowpassCombFilter({resonance:e.resonance,dampening:e.dampening}),this.resonance=this._lfcf.resonance,this.dampening=this._lfcf.dampening,this._noise.connect(this._lfcf),this._lfcf.connect(this.output),this._readOnly(["resonance","dampening"])},t.extend(t.PluckSynth,t.Instrument),t.PluckSynth.defaults={attackNoise:1,dampening:4e3,resonance:.9},t.PluckSynth.prototype.triggerAttack=function(t,e){t=this.toFrequency(t),e=this.toSeconds(e);var i=1/t;return this._lfcf.delayTime.setValueAtTime(i,e),this._noise.start(e),this._noise.stop(e+i*this.attackNoise),this},t.PluckSynth.prototype.dispose=function(){return t.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},t.PluckSynth}),Module(function(t){return t.PolySynth=function(){var e,i,s;for(t.Instrument.call(this),e=this.optionsObject(arguments,["polyphony","voice"],t.PolySynth.defaults),this.voices=new Array(e.polyphony),this.stealVoices=!0,this._freeVoices=[],this._activeVoices={},i=0;i0)r=this._freeVoices.shift(),r.triggerAttack(n,e,i),this._activeVoices[o]=r;else if(this.stealVoices)for(a in this._activeVoices){this._activeVoices[a].triggerAttack(n,e,i);break}return this},t.PolySynth.prototype.triggerAttackRelease=function(t,e,i,s){return i=this.toSeconds(i),this.triggerAttack(t,i,s),this.triggerRelease(t,i+this.toSeconds(e)),this},t.PolySynth.prototype.triggerRelease=function(t,e){var i,s,n;for(Array.isArray(t)||(t=[t]),i=0;ii){var s=e;e=i,i=s}this.min=this.input=new t.Min(i),this._readOnly("min"),this.max=this.output=new t.Max(e),this._readOnly("max"),this.min.connect(this.max)},t.extend(t.Clip,t.SignalBase), -t.Clip.prototype.dispose=function(){return t.prototype.dispose.call(this),this._writable("min"),this.min.dispose(),this.min=null,this._writable("max"),this.max.dispose(),this.max=null,this},t.Clip}),Module(function(t){return t.Normalize=function(e,i){this._inputMin=this.defaultArg(e,0),this._inputMax=this.defaultArg(i,1),this._sub=this.input=new t.Add(0),this._div=this.output=new t.Multiply(1),this._sub.connect(this._div),this._setRange()},t.extend(t.Normalize,t.SignalBase),Object.defineProperty(t.Normalize.prototype,"min",{get:function(){return this._inputMin},set:function(t){this._inputMin=t,this._setRange()}}),Object.defineProperty(t.Normalize.prototype,"max",{get:function(){return this._inputMax},set:function(t){this._inputMax=t,this._setRange()}}),t.Normalize.prototype._setRange=function(){this._sub.value=-this._inputMin,this._div.value=1/(this._inputMax-this._inputMin)},t.Normalize.prototype.dispose=function(){return t.prototype.dispose.call(this),this._sub.dispose(),this._sub=null,this._div.dispose(),this._div=null,this},t.Normalize}),Module(function(t){t.Route=function(i){var s,n;for(i=this.defaultArg(i,2),t.call(this,1,i),this.gate=new t.Signal(0),this._readOnly("gate"),s=0;i>s;s++)n=new e(s),this.output[s]=n,this.gate.connect(n.selecter),this.input.connect(n)},t.extend(t.Route,t.SignalBase),t.Route.prototype.select=function(t,e){return t=Math.floor(t),this.gate.setValueAtTime(t,this.toSeconds(e)),this},t.Route.prototype.dispose=function(){this._writable("gate"),this.gate.dispose(),this.gate=null;for(var e=0;e1&&(this.input=new Array(e)),t(i)||1===i?this.output=this.context.createGain():i>1&&(this.output=new Array(e))},s.prototype.set=function(e,i,n){var o,r,a,h,l,u;this.isObject(e)?n=i:this.isString(e)&&(o={},o[e]=i,e=o);for(r in e){if(i=e[r],a=this,-1!==r.indexOf(".")){for(h=r.split("."),l=0;l1)for(t=arguments[0],e=1;e0)for(t=this,e=0;e0)for(var t=0;te;e++)s=e/(i-1)*2-1,this._curve[e]=t(s,e);return this._shaper.curve=this._curve,this},Object.defineProperty(t.WaveShaper.prototype,"curve",{get:function(){return this._shaper.curve},set:function(t){this._curve=new Float32Array(t),this._shaper.curve=this._curve}}),Object.defineProperty(t.WaveShaper.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){if(-1===["none","2x","4x"].indexOf(t))throw new RangeError("Tone.WaveShaper: oversampling must be either 'none', '2x', or '4x'");this._shaper.oversample=t}}),t.WaveShaper.prototype.dispose=function(){return t.prototype.dispose.call(this),this._shaper.disconnect(),this._shaper=null,this._curve=null,this},t.WaveShaper}),e(function(t){return t.TimeBase=function(e,i){if(!(this instanceof t.TimeBase))return new t.TimeBase(e,i);if(this._expr=this._noOp,i=this.defaultArg(i,this._defaultUnits),this.isString(e))this._expr=this._parseExprString(e);else if(this.isNumber(e)){var s=this._primaryExpressions[i].method;this._expr=s.bind(this,e)}else this.isUndef(e)?this._expr=this._defaultExpr():e instanceof t.TimeBase&&(this._expr=e._expr)},t.extend(t.TimeBase),t.TimeBase.prototype.set=function(t){return this._expr=this._parseExprString(t),this},t.TimeBase.prototype._primaryExpressions={n:{regexp:/^(\d+)n/i,method:function(t){return t=parseInt(t),this._beatsToUnits(1===t?this._timeSignature():4/t)}},t:{regexp:/^(\d+)t/i,method:function(t){return t=parseInt(t),this._beatsToUnits(8/(3*parseInt(t)))}},m:{regexp:/^(\d+)m/i,method:function(t){return this._beatsToUnits(parseInt(t)*this._timeSignature())}},i:{regexp:/^(\d+)i/i,method:function(t){return this._ticksToUnits(parseInt(t))}},hz:{regexp:/^(\d+(?:\.\d+)?)hz/i,method:function(t){return this._frequencyToUnits(parseFloat(t))}},tr:{regexp:/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/,method:function(t,e,i){var s=0;return t&&"0"!==t&&(s+=this._beatsToUnits(this._timeSignature()*parseFloat(t))),e&&"0"!==e&&(s+=this._beatsToUnits(parseFloat(e))),i&&"0"!==i&&(s+=this._beatsToUnits(parseFloat(i)/4)),s}},s:{regexp:/^(\d+(?:\.\d+)?s)/,method:function(t){return this._secondsToUnits(parseFloat(t))}},samples:{regexp:/^(\d+)samples/,method:function(t){return parseInt(t)/this.context.sampleRate}},"default":{regexp:/^(\d+(?:\.\d+)?)/,method:function(t){return this._primaryExpressions[this._defaultUnits].method.call(this,t)}}},t.TimeBase.prototype._binaryExpressions={"+":{regexp:/^\+/,precedence:2,method:function(t,e){return t()+e()}},"-":{regexp:/^\-/,precedence:2,method:function(t,e){return t()-e()}},"*":{regexp:/^\*/,precedence:1,method:function(t,e){return t()*e()}},"/":{regexp:/^\//,precedence:1,method:function(t,e){return t()/e()}}},t.TimeBase.prototype._unaryExpressions={neg:{regexp:/^\-/,method:function(t){return-t()}}},t.TimeBase.prototype._syntaxGlue={"(":{regexp:/^\(/},")":{regexp:/^\)/}},t.TimeBase.prototype._tokenize=function(t){function e(t,e){var i,s,n,o,r,a,h=["_binaryExpressions","_unaryExpressions","_primaryExpressions","_syntaxGlue"];for(i=0;i0;)t=t.trim(),i=e(t,this),n.push(i),t=t.substr(i.value.length);return{next:function(){return n[++s]},peek:function(){return n[s+1]}}},t.TimeBase.prototype._matchGroup=function(t,e,i){var s,n,o=!1;if(!this.isUndef(t))for(s in e)if(n=e[s],n.regexp.test(t.value)){if(this.isUndef(i))return n;if(n.precedence===i)return n}return o},t.TimeBase.prototype._parseBinary=function(t,e){var i,s;for(this.isUndef(e)&&(e=2),i=0>e?this._parseUnary(t):this._parseBinary(t,e-1),s=t.peek();s&&this._matchGroup(s,this._binaryExpressions,e);)s=t.next(),i=s.method.bind(this,i,this._parseBinary(t,e-1)),s=t.peek();return i},t.TimeBase.prototype._parseUnary=function(t){var e,i=t.peek(),s=this._matchGroup(i,this._unaryExpressions);return s?(i=t.next(),e=this._parseUnary(t),s.method.bind(this,e)):this._parsePrimary(t)},t.TimeBase.prototype._parsePrimary=function(t){var e,i,s=t.peek();if(this.isUndef(s))throw new SyntaxError("Tone.TimeBase: Unexpected end of expression");if(this._matchGroup(s,this._primaryExpressions))return s=t.next(),i=s.value.match(s.regexp),s.method.bind(this,i[1],i[2],i[3]);if(s&&"("===s.value){if(t.next(),e=this._parseBinary(t),s=t.next(),!s||")"!==s.value)throw new SyntaxError("Expected )");return e}throw new SyntaxError("Tone.TimeBase: Cannot process token "+s.value)},t.TimeBase.prototype._parseExprString=function(t){var e=this._tokenize(t),i=this._parseBinary(e);return i},t.TimeBase.prototype._noOp=function(){return 0},t.TimeBase.prototype._defaultExpr=function(){return this._noOp},t.TimeBase.prototype._defaultUnits="s",t.TimeBase.prototype._frequencyToUnits=function(t){return 1/t},t.TimeBase.prototype._beatsToUnits=function(e){return 60/t.Transport.bpm.value*e},t.TimeBase.prototype._secondsToUnits=function(t){return t},t.TimeBase.prototype._ticksToUnits=function(e){return e*(this._beatsToUnits(1)/t.Transport.PPQ)},t.TimeBase.prototype._timeSignature=function(){return t.Transport.timeSignature},t.TimeBase.prototype._pushExpr=function(e,i,s){return e instanceof t.TimeBase||(e=new t.TimeBase(e,s)),this._expr=this._binaryExpressions[i].method.bind(this,this._expr,e._expr),this},t.TimeBase.prototype.add=function(t,e){return this._pushExpr(t,"+",e)},t.TimeBase.prototype.sub=function(t,e){return this._pushExpr(t,"-",e)},t.TimeBase.prototype.mult=function(t,e){return this._pushExpr(t,"*",e)},t.TimeBase.prototype.div=function(t,e){return this._pushExpr(t,"/",e)},t.TimeBase.prototype.eval=function(){return this._expr()},t.TimeBase.prototype.dispose=function(){this._expr=null},t.TimeBase}),e(function(t){return t.Time=function(e,i){return this instanceof t.Time?(this._plusNow=!1,void t.TimeBase.call(this,e,i)):new t.Time(e,i)},t.extend(t.Time,t.TimeBase),t.Time.prototype._unaryExpressions=Object.create(t.TimeBase.prototype._unaryExpressions),t.Time.prototype._unaryExpressions.quantize={regexp:/^@/,method:function(e){return t.Transport.nextSubdivision(e())}},t.Time.prototype._unaryExpressions.now={regexp:/^\+/,method:function(t){return this._plusNow=!0,t()}},t.Time.prototype.quantize=function(t,e){return e=this.defaultArg(e,1),this._expr=function(t,e,i){var s,n,o;return t=t(),e=e.toSeconds(),s=Math.round(t/e),n=s*e,o=n-t,t+o*i}.bind(this,this._expr,new this.constructor(t),e),this},t.Time.prototype.addNow=function(){return this._plusNow=!0,this},t.Time.prototype._defaultExpr=function(){return this._plusNow=!0,this._noOp},t.Time.prototype.toNotation=function(){var t=this.toSeconds(),e=["1m","2n","4n","8n","16n","32n","64n","128n"],i=this._toNotationHelper(t,e),s=["1m","2n","2t","4n","4t","8n","8t","16n","16t","32n","32t","64n","64t","128n"],n=this._toNotationHelper(t,s);return n.split("+").length1-n%1&&(n+=o),n=Math.floor(n),n>0){if(a+=1===n?e[i]:n.toString()+"*"+e[i],t-=n*s,r>t)break;a+=" + "}return""===a&&(a="0"),a},t.Time.prototype._notationToUnits=function(t){var e,i,s,n=this._primaryExpressions,o=[n.n,n.t,n.m];for(e=0;e3&&(n=parseFloat(n).toFixed(3)),t=[s,i,n],t.join(":")},t.Time.prototype.toTicks=function(){var e=this._beatsToUnits(1),i=this.eval()/e;return Math.floor(i*t.Transport.PPQ)},t.Time.prototype.toSamples=function(){return this.toSeconds()*this.context.sampleRate},t.Time.prototype.toFrequency=function(){return 1/this.eval()},t.Time.prototype.toSeconds=function(){return this._expr()},t.Time.prototype.eval=function(){var t=this._expr();return t+(this._plusNow?this.now():0)},t.Time}),e(function(t){var e,i;return t.Frequency=function(e,i){return this instanceof t.Frequency?void t.TimeBase.call(this,e,i):new t.Frequency(e,i)},t.extend(t.Frequency,t.TimeBase),t.Frequency.prototype._primaryExpressions=Object.create(t.TimeBase.prototype._primaryExpressions),t.Frequency.prototype._primaryExpressions.midi={regexp:/^(\d+(?:\.\d+)?midi)/,method:function(t){return this.midiToFrequency(t)}},t.Frequency.prototype._primaryExpressions.note={regexp:/^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i,method:function(t,i){var s=e[t.toLowerCase()],n=s+12*(parseInt(i)+1);return this.midiToFrequency(n)}},t.Frequency.prototype._primaryExpressions.tr={regexp:/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/,method:function(t,e,i){var s=1;return t&&"0"!==t&&(s*=this._beatsToUnits(this._timeSignature()*parseFloat(t))),e&&"0"!==e&&(s*=this._beatsToUnits(parseFloat(e))),i&&"0"!==i&&(s*=this._beatsToUnits(parseFloat(i)/4)),s}},t.Frequency.prototype.transpose=function(t){return this._expr=function(t,e){var i=t();return i*this.intervalToFrequencyRatio(e)}.bind(this,this._expr,t),this},t.Frequency.prototype.harmonize=function(t){return this._expr=function(t,e){var i,s=t(),n=[];for(i=0;ir&&(o+=-12*r),e=i[o%12],e+r.toString()},t.Frequency.prototype.toSeconds=function(){return 1/this.eval()},t.Frequency.prototype.toTicks=function(){var e=this._beatsToUnits(1),i=this.eval()/e;return Math.floor(i*t.Transport.PPQ)},t.Frequency.prototype._frequencyToUnits=function(t){return t},t.Frequency.prototype._ticksToUnits=function(e){return 1/(60*e/(t.Transport.bpm.value*t.Transport.PPQ))},t.Frequency.prototype._beatsToUnits=function(e){return 1/t.TimeBase.prototype._beatsToUnits.call(this,e)},t.Frequency.prototype._secondsToUnits=function(t){return 1/t},t.Frequency.prototype._defaultUnits="hz",e={cbb:-2,cb:-1,c:0,"c#":1,cx:2,dbb:0,db:1,d:2,"d#":3,dx:4,ebb:2,eb:3,e:4,"e#":5,ex:6,fbb:3,fb:4,f:5,"f#":6,fx:7,gbb:5,gb:6,g:7,"g#":8,gx:9,abb:7,ab:8,a:9,"a#":10,ax:11,bbb:9,bb:10,b:11,"b#":12,bx:13},i=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"],t.Frequency.A4=440,t.Frequency.prototype.midiToFrequency=function(e){return t.Frequency.A4*Math.pow(2,(e-69)/12)},t.Frequency.prototype.frequencyToMidi=function(e){return 69+12*Math.log(e/t.Frequency.A4)/Math.LN2},t.Frequency}),e(function(t){return t.TransportTime=function(e,i){return this instanceof t.TransportTime?void t.Time.call(this,e,i):new t.TransportTime(e,i)},t.extend(t.TransportTime,t.Time),t.TransportTime.prototype._unaryExpressions=Object.create(t.Time.prototype._unaryExpressions),t.TransportTime.prototype._unaryExpressions.quantize={regexp:/^@/,method:function(e){var i=this._secondsToTicks(e()),s=Math.ceil(t.Transport.ticks/i);return this._ticksToUnits(s*i)}},t.TransportTime.prototype._secondsToTicks=function(e){var i=this._beatsToUnits(1),s=e/i;return Math.round(s*t.Transport.PPQ)},t.TransportTime.prototype.eval=function(){var e=this._secondsToTicks(this._expr());return e+(this._plusNow?t.Transport.ticks:0)},t.TransportTime.prototype.toTicks=function(){return this.eval()},t.TransportTime.prototype.toFrequency=function(){return 1/this.toSeconds()},t.TransportTime}),e(function(t){return t.Type={Default:"number",Time:"time",Frequency:"frequency",TransportTime:"transportTime",Ticks:"ticks",NormalRange:"normalRange",AudioRange:"audioRange",Decibels:"db",Interval:"interval",BPM:"bpm",Positive:"positive",Cents:"cents",Degrees:"degrees",MIDI:"midi",BarsBeatsSixteenths:"barsBeatsSixteenths",Samples:"samples",Hertz:"hertz",Note:"note",Milliseconds:"milliseconds",Seconds:"seconds",Notation:"notation"},t.prototype.toSeconds=function(e){return this.isNumber(e)?e:this.isString(e)||this.isUndef(e)?new t.Time(e).eval():e instanceof t.TransportTime?e.toSeconds():e instanceof t.Time?e.eval():e instanceof t.Frequency?e.toSeconds():void 0},t.prototype.toFrequency=function(e){return this.isNumber(e)?e:this.isString(e)||this.isUndef(e)?new t.Frequency(e).eval():e instanceof t.Frequency?e.eval():e instanceof t.Time?e.toFrequency():void 0},t.prototype.toTicks=function(e){return this.isNumber(e)||this.isString(e)||this.isUndef(e)?new t.TransportTime(e).eval():e instanceof t.Frequency?e.toTicks():e instanceof t.TransportTime?e.eval():e instanceof t.Time?e.toTicks():void 0},t}),e(function(t){return t.Param=function(){var e=this.optionsObject(arguments,["param","units","convert"],t.Param.defaults);this._param=this.input=e.param,this.units=e.units,this.convert=e.convert,this.overridden=!1,this.isUndef(e.value)||(this.value=e.value)},t.extend(t.Param),t.Param.defaults={units:t.Type.Default,convert:!0,param:void 0},Object.defineProperty(t.Param.prototype,"value",{get:function(){return this._toUnits(this._param.value)},set:function(t){var e=this._fromUnits(t);this._param.cancelScheduledValues(0),this._param.value=e}}),t.Param.prototype._fromUnits=function(e){if(!this.convert&&!this.isUndef(this.convert))return e;switch(this.units){case t.Type.Time:return this.toSeconds(e);case t.Type.Frequency:return this.toFrequency(e);case t.Type.Decibels:return this.dbToGain(e);case t.Type.NormalRange:return Math.min(Math.max(e,0),1);case t.Type.AudioRange:return Math.min(Math.max(e,-1),1);case t.Type.Positive:return Math.max(e,0);default:return e}},t.Param.prototype._toUnits=function(e){if(!this.convert&&!this.isUndef(this.convert))return e;switch(this.units){case t.Type.Decibels:return this.gainToDb(e);default:return e}},t.Param.prototype._minOutput=1e-5,t.Param.prototype.setValueAtTime=function(t,e){return t=this._fromUnits(t),this._param.setValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.setRampPoint=function(t){t=this.defaultArg(t,this.now());var e=this._param.value;return 0===e&&(e=this._minOutput),this._param.setValueAtTime(e,t),this},t.Param.prototype.linearRampToValueAtTime=function(t,e){return t=this._fromUnits(t),this._param.linearRampToValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.exponentialRampToValueAtTime=function(t,e){return t=this._fromUnits(t),t=Math.max(this._minOutput,t),this._param.exponentialRampToValueAtTime(t,this.toSeconds(e)),this},t.Param.prototype.exponentialRampToValue=function(t,e,i){return i=this.toSeconds(i),this.setRampPoint(i),this.exponentialRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Param.prototype.linearRampToValue=function(t,e,i){return i=this.toSeconds(i),this.setRampPoint(i),this.linearRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Param.prototype.setTargetAtTime=function(t,e,i){return t=this._fromUnits(t),t=Math.max(this._minOutput,t),i=Math.max(this._minOutput,i),this._param.setTargetAtTime(t,this.toSeconds(e),i),this},t.Param.prototype.setValueCurveAtTime=function(t,e,i){for(var s=0;sthis.memory&&(i=this.length-this.memory,this._timeline.splice(0,i)),this},t.Timeline.prototype.removeEvent=function(t){if(this._iterating)this._toRemove.push(t);else{var e=this._timeline.indexOf(t);-1!==e&&this._timeline.splice(e,1)}return this},t.Timeline.prototype.getEvent=function(t){t=this.toSeconds(t);var e=this._search(t);return-1!==e?this._timeline[e]:null},t.Timeline.prototype.getEventAfter=function(t){t=this.toSeconds(t);var e=this._search(t);return e+10&&this._timeline[e-1].time=0?this._timeline[i-1]:null)},t.Timeline.prototype.cancel=function(t){var e,i;if(this._timeline.length>1)if(t=this.toSeconds(t),e=this._search(t),e>=0)if(this._timeline[e].time===t){for(i=e;i>=0&&this._timeline[i].time===t;i--)e=i;this._timeline=this._timeline.slice(0,e)}else this._timeline=this._timeline.slice(0,e+1);else this._timeline=[];else 1===this._timeline.length&&this._timeline[0].time>=t&&(this._timeline=[]);return this},t.Timeline.prototype.cancelBefore=function(t){if(this._timeline.length){t=this.toSeconds(t);var e=this._search(t);e>=0&&(this._timeline=this._timeline.slice(e+1))}return this},t.Timeline.prototype._search=function(t){var e,i,s,n,o,r=0,a=this._timeline.length,h=a;if(a>0&&this._timeline[a-1].time<=t)return a-1;for(;h>r;){if(e=Math.floor(r+(h-r)/2),i=this._timeline[e],s=this._timeline[e+1],i.time===t){for(n=e;nt)return e;i.time>t?h=e:i.time=s;s++)t(this._timeline[s]);if(this._iterating=!1,this._toRemove.length>0){for(n=0;n=0&&this._timeline[i].time>=t;)i--;return this._iterate(e,i+1),this},t.Timeline.prototype.forEachAtTime=function(t,e){t=this.toSeconds(t);var i=this._search(t);return-1!==i&&this._iterate(function(i){i.time===t&&e(i)},0,i),this},t.Timeline.prototype.dispose=function(){t.prototype.dispose.call(this),this._timeline=null,this._toRemove=null},t.Timeline}),e(function(t){return t.TimelineSignal=function(){var e=this.optionsObject(arguments,["value","units"],t.Signal.defaults);this._events=new t.Timeline(10),t.Signal.apply(this,e),e.param=this._param,t.Param.call(this,e),this._initial=this._fromUnits(this._param.value)},t.extend(t.TimelineSignal,t.Param),t.TimelineSignal.Type={Linear:"linear",Exponential:"exponential",Target:"target",Curve:"curve",Set:"set"},Object.defineProperty(t.TimelineSignal.prototype,"value",{get:function(){var t=this.now(),e=this.getValueAtTime(t);return this._toUnits(e)},set:function(t){var e=this._fromUnits(t);this._initial=e,this.cancelScheduledValues(),this._param.value=e}}),t.TimelineSignal.prototype.setValueAtTime=function(e,i){return e=this._fromUnits(e),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Set,value:e,time:i}),this._param.setValueAtTime(e,i),this},t.TimelineSignal.prototype.linearRampToValueAtTime=function(e,i){return e=this._fromUnits(e),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Linear,value:e,time:i}),this._param.linearRampToValueAtTime(e,i),this},t.TimelineSignal.prototype.exponentialRampToValueAtTime=function(e,i){var s,n=this._searchBefore(i);return n&&0===n.value&&this.setValueAtTime(this._minOutput,n.time),e=this._fromUnits(e),s=Math.max(e,this._minOutput),i=this.toSeconds(i),this._events.addEvent({type:t.TimelineSignal.Type.Exponential,value:s,time:i}),ee?(this.cancelScheduledValues(e),this.linearRampToValueAtTime(i,e)):(n=this._searchAfter(e),n&&(this.cancelScheduledValues(e),n.type===t.TimelineSignal.Type.Linear?this.linearRampToValueAtTime(i,e):n.type===t.TimelineSignal.Type.Exponential&&this.exponentialRampToValueAtTime(i,e)),this.setValueAtTime(i,e)),this},t.TimelineSignal.prototype.linearRampToValueBetween=function(t,e,i){return this.setRampPoint(e),this.linearRampToValueAtTime(t,i),this},t.TimelineSignal.prototype.exponentialRampToValueBetween=function(t,e,i){return this.setRampPoint(e),this.exponentialRampToValueAtTime(t,i),this},t.TimelineSignal.prototype._searchBefore=function(t){return this._events.getEvent(t)},t.TimelineSignal.prototype._searchAfter=function(t){return this._events.getEventAfter(t)},t.TimelineSignal.prototype.getValueAtTime=function(e){var i,s,n=this._searchAfter(e),o=this._searchBefore(e),r=this._initial;return null===o?r=this._initial:o.type===t.TimelineSignal.Type.Target?(i=this._events.getEventBefore(o.time), +s=null===i?this._initial:i.value,r=this._exponentialApproach(o.time,s,o.value,o.constant,e)):r=o.type===t.TimelineSignal.Type.Curve?this._curveInterpolate(o.time,o.value,o.duration,e):null===n?o.value:n.type===t.TimelineSignal.Type.Linear?this._linearInterpolate(o.time,o.value,n.time,n.value,e):n.type===t.TimelineSignal.Type.Exponential?this._exponentialInterpolate(o.time,o.value,n.time,n.value,e):o.value,r},t.TimelineSignal.prototype.connect=t.SignalBase.prototype.connect,t.TimelineSignal.prototype._exponentialApproach=function(t,e,i,s,n){return i+(e-i)*Math.exp(-(n-t)/s)},t.TimelineSignal.prototype._linearInterpolate=function(t,e,i,s,n){return e+(s-e)*((n-t)/(i-t))},t.TimelineSignal.prototype._exponentialInterpolate=function(t,e,i,s,n){return e=Math.max(this._minOutput,e),e*Math.pow(s/e,(n-t)/(i-t))},t.TimelineSignal.prototype._curveInterpolate=function(t,e,i,s){var n,o,r,a,h,l=e.length;return s>=t+i?e[l-1]:t>=s?e[0]:(n=(s-t)/i,o=Math.floor((l-1)*n),r=Math.ceil((l-1)*n),a=e[o],h=e[r],r===o?a:this._linearInterpolate(o,a,r,h,n*(l-1)))},t.TimelineSignal.prototype.dispose=function(){t.Signal.prototype.dispose.call(this),t.Param.prototype.dispose.call(this),this._events.dispose(),this._events=null},t.TimelineSignal}),e(function(t){return t.Pow=function(e){this._exp=this.defaultArg(e,1),this._expScaler=this.input=this.output=new t.WaveShaper(this._expFunc(this._exp),8192)},t.extend(t.Pow,t.SignalBase),Object.defineProperty(t.Pow.prototype,"value",{get:function(){return this._exp},set:function(t){this._exp=t,this._expScaler.setMap(this._expFunc(this._exp))}}),t.Pow.prototype._expFunc=function(t){return function(e){return Math.pow(Math.abs(e),t)}},t.Pow.prototype.dispose=function(){return t.prototype.dispose.call(this),this._expScaler.dispose(),this._expScaler=null,this},t.Pow}),e(function(t){return t.Envelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);this.attack=e.attack,this.decay=e.decay,this.sustain=e.sustain,this.release=e.release,this._attackCurve="linear",this._releaseCurve="exponential",this._sig=this.output=new t.TimelineSignal,this._sig.setValueAtTime(0,0),this.attackCurve=e.attackCurve,this.releaseCurve=e.releaseCurve},t.extend(t.Envelope),t.Envelope.defaults={attack:.01,decay:.1,sustain:.5,release:1,attackCurve:"linear",releaseCurve:"exponential"},Object.defineProperty(t.Envelope.prototype,"value",{get:function(){return this.getValueAtTime(this.now())}}),Object.defineProperty(t.Envelope.prototype,"attackCurve",{get:function(){if(this.isString(this._attackCurve))return this._attackCurve;if(this.isArray(this._attackCurve)){for(var e in t.Envelope.Type)if(t.Envelope.Type[e].In===this._attackCurve)return e;return this._attackCurve}},set:function(e){if(t.Envelope.Type.hasOwnProperty(e)){var i=t.Envelope.Type[e];this._attackCurve=this.isObject(i)?i.In:i}else{if(!this.isArray(e))throw new Error("Tone.Envelope: invalid curve: "+e);this._attackCurve=e}}}),Object.defineProperty(t.Envelope.prototype,"releaseCurve",{get:function(){if(this.isString(this._releaseCurve))return this._releaseCurve;if(this.isArray(this._releaseCurve)){for(var e in t.Envelope.Type)if(t.Envelope.Type[e].Out===this._releaseCurve)return e;return this._releaseCurve}},set:function(e){if(t.Envelope.Type.hasOwnProperty(e)){var i=t.Envelope.Type[e];this._releaseCurve=this.isObject(i)?i.Out:i}else{if(!this.isArray(e))throw new Error("Tone.Envelope: invalid curve: "+e);this._releaseCurve=e}}}),t.Envelope.prototype.triggerAttack=function(t,e){var i,s,n,o,r,a,h,l,u,c=this.now()+this.blockTime;return t=this.toSeconds(t,c),i=this.toSeconds(this.attack),s=i,n=this.toSeconds(this.decay),e=this.defaultArg(e,1),o=this.getValueAtTime(t),o>0&&(r=1/s,a=1-o,s=a/r),"linear"===this._attackCurve?this._sig.linearRampToValue(e,s,t):"exponential"===this._attackCurve?this._sig.exponentialRampToValue(e,s,t):s>0&&(this._sig.setRampPoint(t),h=this._attackCurve,i>s&&(l=1-s/i,u=Math.floor(l*this._attackCurve.length),h=this._attackCurve.slice(u),h[0]=o),this._sig.setValueCurveAtTime(h,t,s,e)),this._sig.exponentialRampToValue(e*this.sustain,n,s+t),this},t.Envelope.prototype.triggerRelease=function(t){var e,i,s,n=this.now()+this.blockTime;return t=this.toSeconds(t,n),e=this.getValueAtTime(t),e>0&&(i=this.toSeconds(this.release),"linear"===this._releaseCurve?this._sig.linearRampToValue(0,i,t):"exponential"===this._releaseCurve?this._sig.exponentialRampToValue(0,i,t):(s=this._releaseCurve,this.isArray(s)&&(this._sig.setRampPoint(t),this._sig.setValueCurveAtTime(s,t,i,e)))),this},t.Envelope.prototype.getValueAtTime=function(t){return this._sig.getValueAtTime(t)},t.Envelope.prototype.triggerAttackRelease=function(t,e,i){return e=this.toSeconds(e),this.triggerAttack(e,i),this.triggerRelease(e+this.toSeconds(t)),this},t.Envelope.prototype.cancel=function(t){return this._sig.cancelScheduledValues(t),this},t.Envelope.prototype.connect=t.Signal.prototype.connect,function(){function e(t){var e,i=new Array(t.length);for(e=0;es;s++)_[s]=Math.sin(s/(d-1)*(Math.PI/2));for(o=[],r=6.4,s=0;d-1>s;s++)n=s/(d-1),a=Math.sin(2*n*Math.PI*r-Math.PI/2)+1,o[s]=a/10+.83*n;for(o[d-1]=1,h=[],l=5,s=0;d>s;s++)h[s]=Math.ceil(s/(d-1)*l)/l;for(u=[],s=0;d>s;s++)n=s/(d-1),u[s]=.5*(1-Math.cos(Math.PI*n));for(c=[],s=0;d>s;s++)n=s/(d-1),p=4*Math.pow(n,3)+.2,f=Math.cos(p*Math.PI*2*n),c[s]=Math.abs(f*(1-n));t.Envelope.Type={linear:"linear",exponential:"exponential",bounce:{In:e(c),Out:c},cosine:{In:_,Out:i(_)},step:{In:h,Out:e(h)},ripple:{In:o,Out:e(o)},sine:{In:u,Out:e(u)}}}(),t.Envelope.prototype.dispose=function(){return t.prototype.dispose.call(this),this._sig.dispose(),this._sig=null,this._attackCurve=null,this._releaseCurve=null,this},t.Envelope}),e(function(t){return t.AmplitudeEnvelope=function(){t.Envelope.apply(this,arguments),this.input=this.output=new t.Gain,this._sig.connect(this.output.gain)},t.extend(t.AmplitudeEnvelope,t.Envelope),t.AmplitudeEnvelope.prototype.dispose=function(){return this.input.dispose(),this.input=null,t.Envelope.prototype.dispose.call(this),this},t.AmplitudeEnvelope}),e(function(t){return t.Analyser=function(){var e=this.optionsObject(arguments,["type","size"],t.Analyser.defaults);this._analyser=this.input=this.output=this.context.createAnalyser(),this._type=e.type,this._returnType=e.returnType,this._buffer=null,this.size=e.size,this.type=e.type,this.returnType=e.returnType,this.minDecibels=e.minDecibels,this.maxDecibels=e.maxDecibels},t.extend(t.Analyser),t.Analyser.defaults={size:1024,returnType:"byte",type:"fft",smoothing:.8,maxDecibels:-30,minDecibels:-100},t.Analyser.Type={Waveform:"waveform",FFT:"fft"},t.Analyser.ReturnType={Byte:"byte",Float:"float"},t.Analyser.prototype.analyse=function(){var e,i;if(this._type===t.Analyser.Type.FFT)this._returnType===t.Analyser.ReturnType.Byte?this._analyser.getByteFrequencyData(this._buffer):this._analyser.getFloatFrequencyData(this._buffer);else if(this._type===t.Analyser.Type.Waveform)if(this._returnType===t.Analyser.ReturnType.Byte)this._analyser.getByteTimeDomainData(this._buffer);else if(this.isFunction(AnalyserNode.prototype.getFloatTimeDomainData))this._analyser.getFloatTimeDomainData(this._buffer);else for(e=new Uint8Array(this._buffer.length),this._analyser.getByteTimeDomainData(e),i=0;i=t?0:1},127),this._scale=this.input=new t.Multiply(1e4),this._scale.connect(this._thresh)},t.extend(t.GreaterThanZero,t.SignalBase),t.GreaterThanZero.prototype.dispose=function(){return t.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._thresh.dispose(),this._thresh=null,this},t.GreaterThanZero}),e(function(t){return t.GreaterThan=function(e){t.call(this,2,0),this._param=this.input[0]=new t.Subtract(e),this.input[1]=this._param.input[1],this._gtz=this.output=new t.GreaterThanZero,this._param.connect(this._gtz)},t.extend(t.GreaterThan,t.Signal),t.GreaterThan.prototype.dispose=function(){return t.prototype.dispose.call(this),this._param.dispose(),this._param=null,this._gtz.dispose(),this._gtz=null,this},t.GreaterThan}),e(function(t){return t.Abs=function(){t.call(this,1,0),this._abs=this.input=this.output=new t.WaveShaper(function(t){return 0===t?0:Math.abs(t)},127)},t.extend(t.Abs,t.SignalBase),t.Abs.prototype.dispose=function(){return t.prototype.dispose.call(this),this._abs.dispose(),this._abs=null,this},t.Abs}),e(function(t){return t.Modulo=function(e){t.call(this,1,1),this._shaper=new t.WaveShaper(Math.pow(2,16)),this._multiply=new t.Multiply,this._subtract=this.output=new t.Subtract,this._modSignal=new t.Signal(e),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(e)},t.extend(t.Modulo,t.SignalBase),t.Modulo.prototype._setWaveShaper=function(t){this._shaper.setMap(function(e){var i=Math.floor((e+1e-4)/t);return i})},Object.defineProperty(t.Modulo.prototype,"value",{get:function(){return this._modSignal.value},set:function(t){this._modSignal.value=t,this._setWaveShaper(t)}}),t.Modulo.prototype.dispose=function(){return t.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},t.Modulo}),e(function(t){return t.AudioToGain=function(){this._norm=this.input=this.output=new t.WaveShaper(function(t){return(t+1)/2})},t.extend(t.AudioToGain,t.SignalBase),t.AudioToGain.prototype.dispose=function(){return t.prototype.dispose.call(this),this._norm.dispose(),this._norm=null,this},t.AudioToGain}),e(function(t){function e(t,e,i){var s=new t;return i._eval(e[0]).connect(s,0,0),i._eval(e[1]).connect(s,0,1),s}function i(t,e,i){var s=new t;return i._eval(e[0]).connect(s,0,0),s}function s(t){return t?parseFloat(t):void 0}function n(t){return t&&t.args?parseFloat(t.args):void 0}return t.Expr=function(){var t,e,i,s=this._replacements(Array.prototype.slice.call(arguments)),n=this._parseInputs(s);for(this._nodes=[],this.input=new Array(n),t=0;n>t;t++)this.input[t]=this.context.createGain();e=this._parseTree(s);try{i=this._eval(e)}catch(o){throw this._disposeNodes(),new Error("Tone.Expr: Could evaluate expression: "+s)}this.output=i},t.extend(t.Expr,t.SignalBase),t.Expr._Expressions={value:{signal:{regexp:/^\d+\.\d+|^\d+/,method:function(e){var i=new t.Signal(s(e));return i}},input:{regexp:/^\$\d/,method:function(t,e){return e.input[s(t.substr(1))]}}},glue:{"(":{regexp:/^\(/},")":{regexp:/^\)/},",":{regexp:/^,/}},func:{abs:{regexp:/^abs/,method:i.bind(this,t.Abs)},mod:{regexp:/^mod/,method:function(e,i){var s=n(e[1]),o=new t.Modulo(s);return i._eval(e[0]).connect(o),o}},pow:{regexp:/^pow/,method:function(e,i){var s=n(e[1]),o=new t.Pow(s);return i._eval(e[0]).connect(o),o}},a2g:{regexp:/^a2g/,method:function(e,i){var s=new t.AudioToGain;return i._eval(e[0]).connect(s),s}}},binary:{"+":{regexp:/^\+/,precedence:1,method:e.bind(this,t.Add)},"-":{regexp:/^\-/,precedence:1,method:function(s,n){return 1===s.length?i(t.Negate,s,n):e(t.Subtract,s,n)}},"*":{regexp:/^\*/,precedence:0,method:e.bind(this,t.Multiply)}},unary:{"-":{regexp:/^\-/,method:i.bind(this,t.Negate)},"!":{regexp:/^\!/,method:i.bind(this,t.NOT)}}},t.Expr.prototype._parseInputs=function(t){var e,i,s=t.match(/\$\d/g),n=0;if(null!==s)for(e=0;e0;)e=e.trim(),s=i(e),o.push(s),e=e.substr(s.value.length);return{next:function(){return o[++n]},peek:function(){return o[n+1]}}},t.Expr.prototype._parseTree=function(e){function i(t,e){return!u(t)&&"glue"===t.type&&t.value===e}function s(e,i,s){var n,o,r=!1,a=t.Expr._Expressions[i];if(!u(e))for(n in a)if(o=a[n],o.regexp.test(e.value)){if(u(s))return!0;if(o.precedence===s)return!0}return r}function n(t){var e,i;for(u(t)&&(t=5),e=0>t?o():n(t-1),i=l.peek();s(i,"binary",t);)i=l.next(),e={operator:i.value,method:i.method,args:[e,n(t-1)]},i=l.peek();return e}function o(){var t,e;return t=l.peek(),s(t,"unary")?(t=l.next(),e=o(),{operator:t.value,method:t.method,args:[e]}):r()}function r(){var t,e;if(t=l.peek(),u(t))throw new SyntaxError("Tone.Expr: Unexpected termination of expression");if("func"===t.type)return t=l.next(),a(t);if("value"===t.type)return t=l.next(),{method:t.method,args:t.value};if(i(t,"(")){if(l.next(),e=n(),t=l.next(),!i(t,")"))throw new SyntaxError("Expected )");return e}throw new SyntaxError("Tone.Expr: Parse error, cannot process token "+t.value)}function a(t){var e,s=[];if(e=l.next(),!i(e,"("))throw new SyntaxError('Tone.Expr: Expected ( in a function call "'+t.value+'"');if(e=l.peek(),i(e,")")||(s=h()),e=l.next(),!i(e,")"))throw new SyntaxError('Tone.Expr: Expected ) in a function call "'+t.value+'"');return{method:t.method,args:s,name:name}}function h(){for(var t,e,s=[];;){if(e=n(),u(e))break;if(s.push(e),t=l.peek(),!i(t,","))break;l.next()}return s}var l=this._tokenize(e),u=this.isUndef.bind(this);return n()},t.Expr.prototype._eval=function(t){if(!this.isUndef(t)){var e=t.method(t.args,this);return this._nodes.push(e),e}},t.Expr.prototype._disposeNodes=function(){var t,e;for(t=0;tn;n++)o=this.context.createBiquadFilter(),o.type=this._type,this.frequency.connect(o.frequency),this.detune.connect(o.detune),this.Q.connect(o.Q),this.gain.connect(o.gain),this._filters[n]=o;r=[this.input].concat(this._filters).concat([this.output]),this.connectSeries.apply(this,r)}}),t.Filter.prototype.dispose=function(){t.prototype.dispose.call(this);for(var e=0;e=t?e:i})},Object.defineProperty(t.Follower.prototype,"attack",{get:function(){return this._attack},set:function(t){this._attack=t,this._setAttackRelease(this._attack,this._release)}}),Object.defineProperty(t.Follower.prototype,"release",{get:function(){return this._release},set:function(t){this._release=t,this._setAttackRelease(this._attack,this._release)}}),t.Follower.prototype.connect=t.Signal.prototype.connect,t.Follower.prototype.dispose=function(){return t.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},t.Follower}),e(function(t){return t.ScaledEnvelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);t.Envelope.call(this,e),e=this.defaultArg(e,t.ScaledEnvelope.defaults),this._exp=this.output=new t.Pow(e.exponent),this._scale=this.output=new t.Scale(e.min,e.max),this._sig.chain(this._exp,this._scale)},t.extend(t.ScaledEnvelope,t.Envelope),t.ScaledEnvelope.defaults={min:0,max:1,exponent:1},Object.defineProperty(t.ScaledEnvelope.prototype,"min",{get:function(){return this._scale.min},set:function(t){this._scale.min=t}}),Object.defineProperty(t.ScaledEnvelope.prototype,"max",{get:function(){return this._scale.max},set:function(t){this._scale.max=t}}),Object.defineProperty(t.ScaledEnvelope.prototype,"exponent",{get:function(){return this._exp.value},set:function(t){this._exp.value=t}}),t.ScaledEnvelope.prototype.dispose=function(){return t.Envelope.prototype.dispose.call(this),this._scale.dispose(),this._scale=null,this._exp.dispose(),this._exp=null,this},t.ScaledEnvelope}),e(function(t){return t.FrequencyEnvelope=function(){var e=this.optionsObject(arguments,["attack","decay","sustain","release"],t.Envelope.defaults);t.ScaledEnvelope.call(this,e),e=this.defaultArg(e,t.FrequencyEnvelope.defaults),this._octaves=e.octaves,this.baseFrequency=e.baseFrequency,this.octaves=e.octaves},t.extend(t.FrequencyEnvelope,t.Envelope),t.FrequencyEnvelope.defaults={baseFrequency:200,octaves:4,exponent:2},Object.defineProperty(t.FrequencyEnvelope.prototype,"baseFrequency",{get:function(){return this._scale.min},set:function(t){this._scale.min=this.toFrequency(t)}}),Object.defineProperty(t.FrequencyEnvelope.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t,this._scale.max=this.baseFrequency*Math.pow(2,t)}}),Object.defineProperty(t.FrequencyEnvelope.prototype,"exponent",{get:function(){return this._exp.value},set:function(t){this._exp.value=t}}),t.FrequencyEnvelope.prototype.dispose=function(){return t.ScaledEnvelope.prototype.dispose.call(this),this},t.FrequencyEnvelope}),e(function(t){return t.Gate=function(){t.call(this);var e=this.optionsObject(arguments,["threshold","attack","release"],t.Gate.defaults);this._follower=new t.Follower(e.attack,e.release),this._gt=new t.GreaterThan(this.dbToGain(e.threshold)),this.input.connect(this.output),this.input.chain(this._gt,this._follower,this.output.gain)},t.extend(t.Gate),t.Gate.defaults={attack:.1,release:.1,threshold:-40},Object.defineProperty(t.Gate.prototype,"threshold",{get:function(){return this.gainToDb(this._gt.value)},set:function(t){this._gt.value=this.dbToGain(t)}}),Object.defineProperty(t.Gate.prototype,"attack",{get:function(){return this._follower.attack},set:function(t){this._follower.attack=t}}),Object.defineProperty(t.Gate.prototype,"release",{get:function(){return this._follower.release},set:function(t){this._follower.release=t}}),t.Gate.prototype.dispose=function(){return t.prototype.dispose.call(this),this._follower.dispose(),this._gt.dispose(),this._follower=null,this._gt=null,this},t.Gate}),e(function(t){return t.TimelineState=function(e){t.Timeline.call(this),this._initial=e},t.extend(t.TimelineState,t.Timeline),t.TimelineState.prototype.getStateAtTime=function(t){var e=this.getEvent(t);return null!==e?e.state:this._initial; + +},t.TimelineState.prototype.setStateAtTime=function(t,e){this.addEvent({state:t,time:this.toSeconds(e)})},t.TimelineState}),e(function(t){var e,i,s;return t.Clock=function(){var i=this.optionsObject(arguments,["callback","frequency"],t.Clock.defaults);this.callback=i.callback,this._lookAhead="auto",this._computedLookAhead=e/1e3,this._nextTick=-1,this._lastUpdate=-1,this._loopID=-1,this.frequency=new t.TimelineSignal(i.frequency,t.Type.Frequency),this.ticks=0,this._state=new t.TimelineState(t.State.Stopped),this._boundLoop=this._loop.bind(this),t.Clock._worker.addEventListener("message",this._boundLoop),this._readOnly("frequency")},t.extend(t.Clock),t.Clock.defaults={callback:t.noOp,frequency:1,lookAhead:"auto"},Object.defineProperty(t.Clock.prototype,"state",{get:function(){return this._state.getStateAtTime(this.now())}}),Object.defineProperty(t.Clock.prototype,"lookAhead",{get:function(){return this._lookAhead},set:function(t){this._lookAhead="auto"===t?"auto":this.toSeconds(t)}}),t.Clock.prototype.start=function(e,i){return e=this.toSeconds(e),this._state.getStateAtTime(e)!==t.State.Started&&this._state.addEvent({state:t.State.Started,time:e,offset:i}),this},t.Clock.prototype.stop=function(e){return e=this.toSeconds(e),this._state.cancel(e),this._state.setStateAtTime(t.State.Stopped,e),this},t.Clock.prototype.pause=function(e){return e=this.toSeconds(e),this._state.getStateAtTime(e)===t.State.Started&&this._state.setStateAtTime(t.State.Paused,e),this},t.Clock.prototype._loop=function(){var i,s,n,o,r,a,h;if("auto"===this._lookAhead?(i=this.now(),-1!==this._lastUpdate&&(s=i-this._lastUpdate,s=Math.min(10*e/1e3,s),this._computedLookAhead=(9*this._computedLookAhead+s)/10),this._lastUpdate=i):this._computedLookAhead=this._lookAhead,n=this.now(),o=2*this._computedLookAhead,r=this._state.getEvent(n+o),a=t.State.Stopped,r&&(a=r.state,-1===this._nextTick&&a===t.State.Started&&(this._nextTick=r.time,this.isUndef(r.offset)||(this.ticks=r.offset))),a===t.State.Started)for(;n+o>this._nextTick;)h=this._nextTick,this._nextTick+=1/this.frequency.getValueAtTime(this._nextTick),this.callback(h),this.ticks++;else a===t.State.Stopped&&(this._nextTick=-1,this.ticks=0)},t.Clock.prototype.getStateAtTime=function(t){return this._state.getStateAtTime(t)},t.Clock.prototype.dispose=function(){cancelAnimationFrame(this._loopID),t.TimelineState.prototype.dispose.call(this),t.Clock._worker.removeEventListener("message",this._boundLoop),this._writable("frequency"),this.frequency.dispose(),this.frequency=null,this._boundLoop=null,this._nextTick=1/0,this.callback=null,this._state.dispose(),this._state=null},window.URL=window.URL||window.webkitURL,e=20,i=new Blob(["setInterval(function(){self.postMessage('tick')}, "+e+")"]),s=URL.createObjectURL(i),t.Clock._worker=new Worker(s),t.Clock}),e(function(t){return t.Emitter=function(){this._events={}},t.extend(t.Emitter),t.Emitter.prototype.on=function(t,e){var i,s,n=t.split(/\W+/);for(i=0;is;s++)i[s].apply(this,e);return this},t.Emitter.mixin=function(e){var i,s,n,o=["on","off","trigger"];for(e._events={},i=0;i0)if(null===t.left.right)i=t.left,i.right=t.right,s=i;else{for(i=t.left.right;null!==i.right;)i=i.right;i.parent.right=i.left,s=i.parent,i.left=t.left,i.right=t.right}else if(null===t.right.left)i=t.right,i.left=t.left,s=i;else{for(i=t.right.left;null!==i.left;)i=i.left;i.parent=i.parent,i.parent.left=i.right,s=i.parent,i.left=t.left,i.right=t.right}null!==t.parent?t.isLeftChild()?t.parent.left=i:t.parent.right=i:this._setRoot(i),this._rebalance(s)}t.dispose()},t.IntervalTimeline.prototype._rotateLeft=function(t){var e=t.parent,i=t.isLeftChild(),s=t.right;t.right=s.left,s.left=t,null!==e?i?e.left=s:e.right=s:this._setRoot(s)},t.IntervalTimeline.prototype._rotateRight=function(t){var e=t.parent,i=t.isLeftChild(),s=t.left;t.left=s.right,s.right=t,null!==e?i?e.left=s:e.right=s:this._setRoot(s)},t.IntervalTimeline.prototype._rebalance=function(t){var e=t.getBalance();e>1?t.left.getBalance()<0?this._rotateLeft(t.left):this._rotateRight(t):-1>e&&(t.right.getBalance()>0?this._rotateRight(t.right):this._rotateLeft(t))},t.IntervalTimeline.prototype.getEvent=function(t){var e,i,s;if(null!==this._root&&(e=[],this._root.search(t,e),e.length>0)){for(i=e[0],s=1;si.low&&(i=e[s]);return i.event}return null},t.IntervalTimeline.prototype.forEach=function(t){var e,i,s;if(null!==this._root)for(e=[],null!==this._root&&this._root.traverse(function(t){e.push(t)}),i=0;i=0;s--)n=i[s].event,n&&e(n);return this},t.IntervalTimeline.prototype.forEachAfter=function(t,e){var i,s,n;if(t=this.toSeconds(t),null!==this._root)for(i=[],this._root.searchAfter(t,i),s=i.length-1;s>=0;s--)n=i[s].event,n&&e(n);return this},t.IntervalTimeline.prototype.dispose=function(){var t,e=[];for(null!==this._root&&this._root.traverse(function(t){e.push(t)}),t=0;tthis.max||(null!==this.left&&this.left.search(t,e),this.low<=t&&this.high>t&&e.push(this),this.low>t||null!==this.right&&this.right.search(t,e))},e.prototype.searchAfter=function(t,e){this.low>=t&&(e.push(this),null!==this.left&&this.left.searchAfter(t,e)),null!==this.right&&this.right.searchAfter(t,e)},e.prototype.traverse=function(t){t(this),null!==this.left&&this.left.traverse(t),null!==this.right&&this.right.traverse(t)},e.prototype.updateHeight=function(){this.height=null!==this.left&&null!==this.right?Math.max(this.left.height,this.right.height)+1:null!==this.right?this.right.height+1:null!==this.left?this.left.height+1:0},e.prototype.updateMax=function(){this.max=this.high,null!==this.left&&(this.max=Math.max(this.max,this.left.max)),null!==this.right&&(this.max=Math.max(this.max,this.right.max))},e.prototype.getBalance=function(){var t=0;return null!==this.left&&null!==this.right?t=this.left.height-this.right.height:null!==this.left?t=this.left.height+1:null!==this.right&&(t=-(this.right.height+1)),t},e.prototype.isLeftChild=function(){return null!==this.parent&&this.parent.left===this},Object.defineProperty(e.prototype,"left",{get:function(){return this._left},set:function(t){this._left=t,null!==t&&(t.parent=this),this.updateHeight(),this.updateMax()}}),Object.defineProperty(e.prototype,"right",{get:function(){return this._right},set:function(t){this._right=t,null!==t&&(t.parent=this),this.updateHeight(),this.updateMax()}}),e.prototype.dispose=function(){this.parent=null,this._left=null,this._right=null,this.event=null},t.IntervalTimeline}),e(function(t){t.Transport=function(){t.Emitter.call(this),this.loop=!1,this._loopStart=0,this._loopEnd=0,this._ppq=e.defaults.PPQ,this._clock=new t.Clock({callback:this._processTick.bind(this),frequency:0}),this.bpm=this._clock.frequency,this.bpm._toUnits=this._toUnits.bind(this),this.bpm._fromUnits=this._fromUnits.bind(this),this.bpm.units=t.Type.BPM,this.bpm.value=e.defaults.bpm,this._readOnly("bpm"),this._timeSignature=e.defaults.timeSignature,this._scheduledEvents={},this._eventID=0,this._timeline=new t.Timeline,this._repeatedEvents=new t.IntervalTimeline,this._onceEvents=new t.Timeline,this._syncedSignals=[],this._swingTicks=e.defaults.PPQ/2,this._swingAmount=0},t.extend(t.Transport,t.Emitter),t.Transport.defaults={bpm:120,swing:0,swingSubdivision:"8n",timeSignature:4,loopStart:0,loopEnd:"4m",PPQ:192},t.Transport.prototype._processTick=function(e){var i,s,n=this._clock.ticks;this._swingAmount>0&&n%this._ppq!==0&&n%(2*this._swingTicks)!==0&&(i=n%(2*this._swingTicks)/(2*this._swingTicks),s=Math.sin(i*Math.PI)*this._swingAmount,e+=t.Time(2*this._swingTicks/3,"i").eval()*s),this.loop&&n===this._loopEnd&&(this.ticks=this._loopStart,n=this._loopStart,this.trigger("loop",e)),this._onceEvents.forEachBefore(n,function(t){t.callback(e)}),this._onceEvents.cancelBefore(n),this._timeline.forEachAtTime(n,function(t){t.callback(e)}),this._repeatedEvents.forEachAtTime(n,function(t){(n-t.time)%t.interval===0&&t.callback(e)})},t.Transport.prototype.schedule=function(t,e){var i={time:this.toTicks(e),callback:t},s=this._eventID++;return this._scheduledEvents[s.toString()]={event:i,timeline:this._timeline},this._timeline.addEvent(i),s},t.Transport.prototype.scheduleRepeat=function(t,e,i,s){var n,o;if(0>=e)throw new Error("Tone.Transport: repeat events must have an interval larger than 0");return n={time:this.toTicks(i),duration:this.toTicks(this.defaultArg(s,1/0)),interval:this.toTicks(e),callback:t},o=this._eventID++,this._scheduledEvents[o.toString()]={event:n,timeline:this._repeatedEvents},this._repeatedEvents.addEvent(n),o},t.Transport.prototype.scheduleOnce=function(t,e){var i={time:this.toTicks(e),callback:t},s=this._eventID++;return this._scheduledEvents[s.toString()]={event:i,timeline:this._onceEvents},this._onceEvents.addEvent(i),s},t.Transport.prototype.clear=function(t){if(this._scheduledEvents.hasOwnProperty(t)){var e=this._scheduledEvents[t.toString()];e.timeline.removeEvent(e.event),delete this._scheduledEvents[t.toString()]}return this},t.Transport.prototype.cancel=function(t){return t=this.defaultArg(t,0),t=this.toTicks(t),this._timeline.cancel(t),this._onceEvents.cancel(t),this._repeatedEvents.cancel(t),this},Object.defineProperty(t.Transport.prototype,"state",{get:function(){return this._clock.getStateAtTime(this.now())}}),t.Transport.prototype.start=function(e,i){return e=this.toSeconds(e),i=this.isUndef(i)?new t.Time(this._clock.ticks,"i"):new t.Time(i),this._clock.start(e,i.toTicks()),this.trigger("start",e,i.toSeconds()),this},t.Transport.prototype.stop=function(t){return t=this.toSeconds(t),this._clock.stop(t),this.trigger("stop",t),this},t.Transport.prototype.pause=function(t){return t=this.toSeconds(t),this._clock.pause(t),this.trigger("pause",t),this},Object.defineProperty(t.Transport.prototype,"timeSignature",{get:function(){return this._timeSignature},set:function(t){this.isArray(t)&&(t=t[0]/t[1]*4),this._timeSignature=t}}),Object.defineProperty(t.Transport.prototype,"loopStart",{get:function(){return t.TransportTime(this._loopStart,"i").toSeconds()},set:function(t){this._loopStart=this.toTicks(t)}}),Object.defineProperty(t.Transport.prototype,"loopEnd",{get:function(){return t.TransportTime(this._loopEnd,"i").toSeconds()},set:function(t){this._loopEnd=this.toTicks(t)}}),t.Transport.prototype.setLoopPoints=function(t,e){return this.loopStart=t,this.loopEnd=e,this},Object.defineProperty(t.Transport.prototype,"swing",{get:function(){return this._swingAmount},set:function(t){this._swingAmount=t}}),Object.defineProperty(t.Transport.prototype,"swingSubdivision",{get:function(){return t.Time(this._swingTicks,"i").toNotation()},set:function(t){this._swingTicks=this.toTicks(t)}}),Object.defineProperty(t.Transport.prototype,"position",{get:function(){return t.TransportTime(this.ticks,"i").toBarsBeatsSixteenths()},set:function(t){var e=this.toTicks(t);this.ticks=e}}),Object.defineProperty(t.Transport.prototype,"progress",{get:function(){return this.loop?(this.ticks-this._loopStart)/(this._loopEnd-this._loopStart):0}}),Object.defineProperty(t.Transport.prototype,"ticks",{get:function(){return this._clock.ticks},set:function(t){this._clock.ticks=t}}),Object.defineProperty(t.Transport.prototype,"PPQ",{get:function(){return this._ppq},set:function(t){var e=this.bpm.value;this._ppq=t,this.bpm.value=e}}),t.Transport.prototype._fromUnits=function(t){return 1/(60/t/this.PPQ)},t.Transport.prototype._toUnits=function(t){return t/this.PPQ*60},t.Transport.prototype.nextSubdivision=function(e){var i,s,n;return e=this.toSeconds(e),this.state!==t.State.Started?0:(i=this._clock._nextTick,s=t.Time(this.ticks,"i").eval(),n=e-s%e,0===n&&(n=e),i+n)},t.Transport.prototype.syncSignal=function(e,i){i||(i=0!==e._param.value?e._param.value/this.bpm._param.value:0);var s=new t.Gain(i);return this.bpm.chain(s,e._param),this._syncedSignals.push({ratio:s,signal:e,initial:e._param.value}),e._param.value=0,this},t.Transport.prototype.unsyncSignal=function(t){var e,i;for(e=this._syncedSignals.length-1;e>=0;e--)i=this._syncedSignals[e],i.signal===t&&(i.ratio.dispose(),i.signal._param.value=i.initial,this._syncedSignals.splice(e,1));return this},t.Transport.prototype.dispose=function(){return t.Emitter.prototype.dispose.call(this),this._clock.dispose(),this._clock=null,this._writable("bpm"),this.bpm=null,this._timeline.dispose(),this._timeline=null,this._onceEvents.dispose(),this._onceEvents=null,this._repeatedEvents.dispose(),this._repeatedEvents=null,this};var e=t.Transport;return t._initAudioContext(function(){if("function"==typeof t.Transport)t.Transport=new t.Transport;else{t.Transport.stop();var i=t.Transport.get();t.Transport.dispose(),e.call(t.Transport),t.Transport.set(i)}}),t.Transport}),e(function(t){return t.Volume=function(){var e=this.optionsObject(arguments,["volume"],t.Volume.defaults);this.output=this.input=new t.Gain(e.volume,t.Type.Decibels),this._unmutedVolume=0,this._muted=!1,this.volume=this.output.gain,this._readOnly("volume"),this.mute=e.mute},t.extend(t.Volume),t.Volume.defaults={volume:0,mute:!1},Object.defineProperty(t.Volume.prototype,"mute",{get:function(){return this._muted},set:function(t){!this._muted&&t?(this._unmutedVolume=this.volume.value,this.volume.value=-(1/0)):this._muted&&!t&&(this.volume.value=this._unmutedVolume),this._muted=t}}),t.Volume.prototype.dispose=function(){return this.input.dispose(),t.prototype.dispose.call(this),this._writable("volume"),this.volume.dispose(),this.volume=null,this},t.Volume}),e(function(t){t.Master=function(){t.call(this),this._volume=this.output=new t.Volume,this.volume=this._volume.volume,this._readOnly("volume"),this.input.chain(this.output,this.context.destination)},t.extend(t.Master),t.Master.defaults={volume:0,mute:!1},Object.defineProperty(t.Master.prototype,"mute",{get:function(){return this._volume.mute},set:function(t){this._volume.mute=t}}),t.Master.prototype.chain=function(){this.input.disconnect(),this.input.chain.apply(this.input,arguments),arguments[arguments.length-1].connect(this.output)},t.Master.prototype.dispose=function(){t.prototype.dispose.call(this),this._writable("volume"),this._volume.dispose(),this._volume=null,this.volume=null},t.prototype.toMaster=function(){return this.connect(t.Master),this},AudioNode.prototype.toMaster=function(){return this.connect(t.Master),this};var e=t.Master;return t._initAudioContext(function(){t.prototype.isUndef(t.Master)?(e.prototype.dispose.call(t.Master),e.call(t.Master)):t.Master=new e}),t.Master}),e(function(t){return t.Source=function(e){t.call(this),e=this.defaultArg(e,t.Source.defaults),this._volume=this.output=new t.Volume(e.volume),this.volume=this._volume.volume,this._readOnly("volume"),this._state=new t.TimelineState(t.State.Stopped),this._state.memory=10,this._syncStart=function(t,e){t=this.toSeconds(t),t+=this.toSeconds(this._startDelay),this.start(t,e)}.bind(this),this._syncStop=this.stop.bind(this),this._startDelay=0,this._volume.output.output.channelCount=2,this._volume.output.output.channelCountMode="explicit",this.mute=e.mute},t.extend(t.Source),t.Source.defaults={volume:0,mute:!1},Object.defineProperty(t.Source.prototype,"state",{get:function(){return this._state.getStateAtTime(this.now())}}),Object.defineProperty(t.Source.prototype,"mute",{get:function(){return this._volume.mute},set:function(t){this._volume.mute=t}}),t.Source.prototype.start=function(e){return e=this.toSeconds(e),(this._state.getStateAtTime(e)!==t.State.Started||this.retrigger)&&(this._state.setStateAtTime(t.State.Started,e),this._start&&this._start.apply(this,arguments)),this},t.Source.prototype.stop=function(e){return e=this.toSeconds(e),this._state.cancel(e),this._state.setStateAtTime(t.State.Stopped,e),this._stop&&this._stop.apply(this,arguments),this},t.Source.prototype.sync=function(e){return this._startDelay=this.defaultArg(e,0),t.Transport.on("start",this._syncStart),t.Transport.on("stop pause",this._syncStop),this},t.Source.prototype.unsync=function(){return this._startDelay=0,t.Transport.off("start",this._syncStart),t.Transport.off("stop pause",this._syncStop),this},t.Source.prototype.dispose=function(){this.stop(),t.prototype.dispose.call(this),this.unsync(),this._writable("volume"),this._volume.dispose(),this._volume=null,this.volume=null,this._state.dispose(),this._state=null,this._syncStart=null,this._syncStart=null},t.Source}),e(function(t){return t.Oscillator=function(){var e=this.optionsObject(arguments,["frequency","type"],t.Oscillator.defaults);t.Source.call(this,e),this._oscillator=null,this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this._wave=null,this._partials=this.defaultArg(e.partials,[1]),this._phase=e.phase,this._type=null,this.type=e.type,this.phase=this._phase,this._readOnly(["frequency","detune"])},t.extend(t.Oscillator,t.Source),t.Oscillator.defaults={type:"sine",frequency:440,detune:0,phase:0,partials:[]},t.Oscillator.Type={Sine:"sine",Triangle:"triangle",Sawtooth:"sawtooth",Square:"square",Custom:"custom"},t.Oscillator.prototype._start=function(t){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(t))},t.Oscillator.prototype._stop=function(t){return this._oscillator&&(this._oscillator.stop(this.toSeconds(t)),this._oscillator=null),this},t.Oscillator.prototype.syncFrequency=function(){return t.Transport.syncSignal(this.frequency),this},t.Oscillator.prototype.unsyncFrequency=function(){return t.Transport.unsyncSignal(this.frequency),this},Object.defineProperty(t.Oscillator.prototype,"type",{get:function(){return this._type},set:function(t){var e=this._getRealImaginary(t,this._phase),i=this.context.createPeriodicWave(e[0],e[1]);this._wave=i,null!==this._oscillator&&this._oscillator.setPeriodicWave(this._wave),this._type=t}}),t.Oscillator.prototype._getRealImaginary=function(e,i){var s,n,o,r,a=4096,h=a/2,l=new Float32Array(h),u=new Float32Array(h),c=1;for(e===t.Oscillator.Type.Custom?(c=this._partials.length+1,h=c):(s=/^(sine|triangle|square|sawtooth)(\d+)$/.exec(e),s&&(c=parseInt(s[2])+1,e=s[1],c=Math.max(c,2),h=c)),n=1;h>n;++n){switch(o=2/(n*Math.PI),e){case t.Oscillator.Type.Sine:r=c>=n?1:0;break;case t.Oscillator.Type.Square:r=1&n?2*o:0;break;case t.Oscillator.Type.Sawtooth:r=o*(1&n?1:-1);break;case t.Oscillator.Type.Triangle:r=1&n?2*o*o*(n-1>>1&1?-1:1):0;break;case t.Oscillator.Type.Custom:r=this._partials[n-1];break;default:throw new TypeError("Tone.Oscillator: invalid type: "+e)}0!==r?(l[n]=-r*Math.sin(i*n),u[n]=r*Math.cos(i*n)):(l[n]=0,u[n]=0)}return[l,u]},t.Oscillator.prototype._inverseFFT=function(t,e,i){var s,n=0,o=t.length;for(s=0;o>s;s++)n+=t[s]*Math.cos(s*i)+e[s]*Math.sin(s*i);return n},t.Oscillator.prototype._getInitialValue=function(){var t,e=this._getRealImaginary(this._type,0),i=e[0],s=e[1],n=0,o=2*Math.PI;for(t=0;8>t;t++)n=Math.max(this._inverseFFT(i,s,t/8*o),n);return-this._inverseFFT(i,s,this._phase)/n},Object.defineProperty(t.Oscillator.prototype,"partials",{get:function(){return this._type!==t.Oscillator.Type.Custom?[]:this._partials},set:function(e){this._partials=e,this.type=t.Oscillator.Type.Custom}}),Object.defineProperty(t.Oscillator.prototype,"phase",{get:function(){return this._phase*(180/Math.PI)},set:function(t){this._phase=t*Math.PI/180,this.type=this._type}}),t.Oscillator.prototype.dispose=function(){return t.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._partials=null,this},t.Oscillator}),e(function(t){return t.Zero=function(){this._gain=this.input=this.output=new t.Gain,t.Zero._zeros.connect(this._gain)},t.extend(t.Zero),t.Zero.prototype.dispose=function(){return t.prototype.dispose.call(this),this._gain.dispose(),this._gain=null,this},t.Zero._zeros=null,t._initAudioContext(function(e){var i,s=e.createBuffer(1,128,e.sampleRate),n=s.getChannelData(0);for(i=0;is&&s+o>i&&(r=t[n],this.value=this.isObject(r)?r.value:r),s+=o;else this.value=t;return this.value},t.CtrlMarkov.prototype._getProbDistribution=function(t){var e,i,s,n=[],o=0,r=!1;for(e=0;e=this.values.length&&(this.index=0)):e===t.CtrlPattern.Type.Down?(this.index--,this.index<0&&(this.index=this.values.length-1)):e===t.CtrlPattern.Type.UpDown||e===t.CtrlPattern.Type.DownUp?(this._direction===t.CtrlPattern.Type.Up?this.index++:this.index--,this.index<0?(this.index=1,this._direction=t.CtrlPattern.Type.Up):this.index>=this.values.length&&(this.index=this.values.length-2,this._direction=t.CtrlPattern.Type.Down)):e===t.CtrlPattern.Type.Random?this.index=Math.floor(Math.random()*this.values.length):e===t.CtrlPattern.Type.RandomWalk?Math.random()<.5?(this.index--,this.index=Math.max(this.index,0)):(this.index++,this.index=Math.min(this.index,this.values.length-1)):e===t.CtrlPattern.Type.RandomOnce?(this.index++,this.index>=this.values.length&&(this.index=0,this._shuffleValues())):e===t.CtrlPattern.Type.AlternateUp?(this._direction===t.CtrlPattern.Type.Up?(this.index+=2,this._direction=t.CtrlPattern.Type.Down):(this.index-=1,this._direction=t.CtrlPattern.Type.Up),this.index>=this.values.length&&(this.index=0,this._direction=t.CtrlPattern.Type.Up)):e===t.CtrlPattern.Type.AlternateDown&&(this._direction===t.CtrlPattern.Type.Up?(this.index+=1,this._direction=t.CtrlPattern.Type.Down):(this.index-=2,this._direction=t.CtrlPattern.Type.Up),this.index<0&&(this.index=this.values.length-1,this._direction=t.CtrlPattern.Type.Down)),this.value},t.CtrlPattern.prototype._shuffleValues=function(){var t,e,i=[];for(this._shuffled=[],t=0;t0;)e=i.splice(Math.floor(i.length*Math.random()),1),this._shuffled.push(e[0])},t.CtrlPattern.prototype.dispose=function(){this._shuffled=null,this.values=null},t.CtrlPattern}),e(function(t){return t.CtrlRandom=function(){var e=this.optionsObject(arguments,["min","max"],t.CtrlRandom.defaults);this.min=e.min,this.max=e.max,this.integer=e.integer},t.extend(t.CtrlRandom),t.CtrlRandom.defaults={min:0,max:1,integer:!1},Object.defineProperty(t.CtrlRandom.prototype,"value",{get:function(){var t=this.toSeconds(this.min),e=this.toSeconds(this.max),i=Math.random(),s=i*t+(1-i)*e;return this.integer&&(s=Math.floor(s)),s}}),t.CtrlRandom}),e(function(t){return t.Buffer=function(){var e=this.optionsObject(arguments,["url","onload"],t.Buffer.defaults);this._buffer=null,this._reversed=e.reverse,this.url=void 0,this.loaded=!1,this.onload=e.onload.bind(this,this),e.url instanceof AudioBuffer||e.url instanceof t.Buffer?(this.set(e.url),this.onload(this)):this.isString(e.url)&&(this.url=e.url,t.Buffer._addToQueue(e.url,this))},t.extend(t.Buffer),t.Buffer.defaults={url:void 0,onload:t.noOp,reverse:!1},t.Buffer.prototype.set=function(e){return this._buffer=e instanceof t.Buffer?e.get():e,this.loaded=!0,this},t.Buffer.prototype.get=function(){return this._buffer},t.Buffer.prototype.load=function(e,i){return this.url=e,this.onload=this.defaultArg(i,this.onload),t.Buffer._addToQueue(e,this),this},t.Buffer.prototype.dispose=function(){return t.prototype.dispose.call(this),t.Buffer._removeFromQueue(this),this._buffer=null,this.onload=t.Buffer.defaults.onload,this},Object.defineProperty(t.Buffer.prototype,"duration",{get:function(){return this._buffer?this._buffer.duration:0}}),t.Buffer.prototype._reverse=function(){if(this.loaded)for(var t=0;t0){if(t.Buffer._currentDownloads.length0){for(e=0;r>e;e++)i=t.Buffer._currentDownloads[e],o+=i.progress;a=o}s=r-a,n=t.Buffer._totalDownloads-t.Buffer._queue.length-s,t.Buffer.trigger("progress",n/t.Buffer._totalDownloads)},t.Buffer.baseUrl="",t.Buffer.load=function(e,i){var s=new XMLHttpRequest;return s.open("GET",t.Buffer.baseUrl+e,!0),s.responseType="arraybuffer",s.onload=function(){t.context.decodeAudioData(s.response,function(t){i(t)},function(){throw new Error("Tone.Buffer: could not decode audio data:"+e)})},s.send(),s},t.Buffer.supportsType=function(t){var e,i=t.split(".");return i=i[i.length-1],e=document.createElement("audio").canPlayType("audio/"+i),""!==e},t.Buffer}),e(function(t){return t.Buffers=function(t,e,i){this._buffers={},this.baseUrl=this.defaultArg(i,""),t=this._flattenUrls(t),this._loadingCount=0;for(var s in t)this._loadingCount++,this.add(s,t[s],this._bufferLoaded.bind(this,e))},t.extend(t.Buffers),t.Buffers.prototype.get=function(t){if(this._buffers.hasOwnProperty(t))return this._buffers[t];throw new Error("Tone.Buffers: no buffer named"+t)},t.Buffers.prototype._bufferLoaded=function(t){this._loadingCount--,0===this._loadingCount&&t&&t(this)},t.Buffers.prototype.add=function(e,i,s){return s=this.defaultArg(s,t.noOp),i instanceof t.Buffer?(this._buffers[e]=i,s(this)):i instanceof AudioBuffer?(this._buffers[e]=new t.Buffer(i),s(this)):this.isString(i)&&(this._buffers[e]=new t.Buffer(this.baseUrl+i,s)),this},t.Buffers.prototype._flattenUrls=function(t){var e,i,s,n={};for(e in t)if(t.hasOwnProperty(e))if(this.isObject(t[e])){i=this._flattenUrls(t[e]);for(s in i)i.hasOwnProperty(s)&&(n[e+"."+s]=i[s])}else n[e]=t[e];return n},t.Buffers.prototype.dispose=function(){for(var t in this._buffers)this._buffers[t].dispose();return this._buffers=null,this},t.Buffers}),e(function(t){var e={};return t.prototype.send=function(t,i){e.hasOwnProperty(t)||(e[t]=this.context.createGain());var s=this.context.createGain();return s.gain.value=this.dbToGain(this.defaultArg(i,1)),this.output.chain(s,e[t]),s},t.prototype.receive=function(t,i){return e.hasOwnProperty(t)||(e[t]=this.context.createGain()),this.isUndef(i)&&(i=this.input),e[t].connect(i),this},t}),e(function(t){return t.Delay=function(){var e=this.optionsObject(arguments,["delayTime","maxDelay"],t.Delay.defaults);this._delayNode=this.input=this.output=this.context.createDelay(this.toSeconds(e.maxDelay)),this.delayTime=new t.Param({param:this._delayNode.delayTime,units:t.Type.Time,value:e.delayTime}),this._readOnly("delayTime")},t.extend(t.Delay),t.Delay.defaults={maxDelay:1,delayTime:0},t.Delay.prototype.dispose=function(){return t.Param.prototype.dispose.call(this),this._delayNode.disconnect(),this._delayNode=null,this._writable("delayTime"),this.delayTime=null,this},t.Delay}),e(function(t){return t.Effect=function(){t.call(this);var e=this.optionsObject(arguments,["wet"],t.Effect.defaults);this._dryWet=new t.CrossFade(e.wet),this.wet=this._dryWet.fade,this.effectSend=this.context.createGain(),this.effectReturn=this.context.createGain(),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"])},t.extend(t.Effect),t.Effect.defaults={wet:1},t.Effect.prototype.connectEffect=function(t){return this.effectSend.chain(t,this.effectReturn),this},t.Effect.prototype.dispose=function(){return t.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,this},t.Effect}),e(function(t){return t.AutoFilter=function(){var e=this.optionsObject(arguments,["frequency","baseFrequency","octaves"],t.AutoFilter.defaults);t.Effect.call(this,e),this._lfo=new t.LFO({frequency:e.frequency,amplitude:e.depth}),this.depth=this._lfo.amplitude,this.frequency=this._lfo.frequency,this.filter=new t.Filter(e.filter),this._octaves=0,this.connectEffect(this.filter),this._lfo.connect(this.filter.frequency),this.type=e.type,this._readOnly(["frequency","depth"]),this.octaves=e.octaves,this.baseFrequency=e.baseFrequency},t.extend(t.AutoFilter,t.Effect),t.AutoFilter.defaults={frequency:1,type:"sine",depth:1,baseFrequency:200,octaves:2.6,filter:{type:"lowpass",rolloff:-12,Q:1}},t.AutoFilter.prototype.start=function(t){return this._lfo.start(t),this},t.AutoFilter.prototype.stop=function(t){return this._lfo.stop(t),this},t.AutoFilter.prototype.sync=function(t){return this._lfo.sync(t),this},t.AutoFilter.prototype.unsync=function(){return this._lfo.unsync(),this},Object.defineProperty(t.AutoFilter.prototype,"type",{get:function(){return this._lfo.type},set:function(t){this._lfo.type=t}}),Object.defineProperty(t.AutoFilter.prototype,"baseFrequency",{get:function(){return this._lfo.min},set:function(t){this._lfo.min=this.toFrequency(t)}}),Object.defineProperty(t.AutoFilter.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t,this._lfo.max=this.baseFrequency*Math.pow(2,t)}}),t.AutoFilter.prototype.dispose=function(){return t.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,this},t.AutoFilter}),e(function(t){return t.AutoPanner=function(){var e=this.optionsObject(arguments,["frequency"],t.AutoPanner.defaults);t.Effect.call(this,e),this._lfo=new t.LFO({frequency:e.frequency,amplitude:e.depth,min:0,max:1}),this.depth=this._lfo.amplitude,this._panner=new t.Panner,this.frequency=this._lfo.frequency,this.connectEffect(this._panner),this._lfo.connect(this._panner.pan),this.type=e.type,this._readOnly(["depth","frequency"])},t.extend(t.AutoPanner,t.Effect),t.AutoPanner.defaults={frequency:1,type:"sine",depth:1},t.AutoPanner.prototype.start=function(t){return this._lfo.start(t),this},t.AutoPanner.prototype.stop=function(t){return this._lfo.stop(t),this},t.AutoPanner.prototype.sync=function(t){return this._lfo.sync(t),this},t.AutoPanner.prototype.unsync=function(){return this._lfo.unsync(),this},Object.defineProperty(t.AutoPanner.prototype,"type",{get:function(){return this._lfo.type},set:function(t){this._lfo.type=t}}),t.AutoPanner.prototype.dispose=function(){return t.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,this},t.AutoPanner}),e(function(t){return t.AutoWah=function(){var e=this.optionsObject(arguments,["baseFrequency","octaves","sensitivity"],t.AutoWah.defaults);t.Effect.call(this,e),this.follower=new t.Follower(e.follower),this._sweepRange=new t.ScaleExp(0,1,.5),this._baseFrequency=e.baseFrequency,this._octaves=e.octaves,this._inputBoost=this.context.createGain(),this._bandpass=new t.Filter({rolloff:-48,frequency:0,Q:e.Q}),this._peaking=new t.Filter(0,"peaking"),this._peaking.gain.value=e.gain,this.gain=this._peaking.gain,this.Q=this._bandpass.Q,this.effectSend.chain(this._inputBoost,this.follower,this._sweepRange),this._sweepRange.connect(this._bandpass.frequency),this._sweepRange.connect(this._peaking.frequency),this.effectSend.chain(this._bandpass,this._peaking,this.effectReturn),this._setSweepRange(),this.sensitivity=e.sensitivity,this._readOnly(["gain","Q"])},t.extend(t.AutoWah,t.Effect),t.AutoWah.defaults={baseFrequency:100,octaves:6,sensitivity:0,Q:2,gain:2,follower:{attack:.3,release:.5}},Object.defineProperty(t.AutoWah.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t,this._setSweepRange()}}),Object.defineProperty(t.AutoWah.prototype,"baseFrequency",{get:function(){return this._baseFrequency},set:function(t){this._baseFrequency=t,this._setSweepRange()}}),Object.defineProperty(t.AutoWah.prototype,"sensitivity",{get:function(){return this.gainToDb(1/this._inputBoost.gain.value)},set:function(t){this._inputBoost.gain.value=1/this.dbToGain(t)}}),t.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)},t.AutoWah.prototype.dispose=function(){return t.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,this},t.AutoWah}),e(function(t){return t.BitCrusher=function(){var e,i=this.optionsObject(arguments,["bits"],t.BitCrusher.defaults);t.Effect.call(this,i),e=1/Math.pow(2,i.bits-1),this._subtract=new t.Subtract,this._modulo=new t.Modulo(e),this._bits=i.bits,this.effectSend.fan(this._subtract,this._modulo),this._modulo.connect(this._subtract,0,1),this._subtract.connect(this.effectReturn)},t.extend(t.BitCrusher,t.Effect),t.BitCrusher.defaults={bits:4},Object.defineProperty(t.BitCrusher.prototype,"bits",{get:function(){return this._bits},set:function(t){this._bits=t;var e=1/Math.pow(2,t-1);this._modulo.value=e}}),t.BitCrusher.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._subtract.dispose(),this._subtract=null,this._modulo.dispose(),this._modulo=null,this},t.BitCrusher}),e(function(t){return t.Chebyshev=function(){var e=this.optionsObject(arguments,["order"],t.Chebyshev.defaults);t.Effect.call(this,e),this._shaper=new t.WaveShaper(4096),this._order=e.order,this.connectEffect(this._shaper),this.order=e.order,this.oversample=e.oversample},t.extend(t.Chebyshev,t.Effect),t.Chebyshev.defaults={order:1,oversample:"none"},t.Chebyshev.prototype._getCoefficient=function(t,e,i){return i.hasOwnProperty(e)?i[e]:(i[e]=0===e?0:1===e?t:2*t*this._getCoefficient(t,e-1,i)-this._getCoefficient(t,e-2,i),i[e])},Object.defineProperty(t.Chebyshev.prototype,"order",{get:function(){return this._order},set:function(t){var e,i,s,n;for(this._order=t,e=new Array(4096),i=e.length,s=0;i>s;++s)n=2*s/i-1,e[s]=0===n?0:this._getCoefficient(n,t,{});this._shaper.curve=e}}),Object.defineProperty(t.Chebyshev.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){this._shaper.oversample=t}}),t.Chebyshev.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},t.Chebyshev}),e(function(t){return t.StereoEffect=function(){t.call(this);var e=this.optionsObject(arguments,["wet"],t.Effect.defaults);this._dryWet=new t.CrossFade(e.wet),this.wet=this._dryWet.fade,this._split=new t.Split,this.effectSendL=this._split.left,this.effectSendR=this._split.right,this._merge=new t.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"])},t.extend(t.StereoEffect,t.Effect),t.StereoEffect.prototype.dispose=function(){return t.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},t.StereoEffect}),e(function(t){return t.FeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"]);e=this.defaultArg(e,t.FeedbackEffect.defaults),t.Effect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),this._feedbackGain=this.context.createGain(),this.effectReturn.chain(this._feedbackGain,this.effectSend),this.feedback.connect(this._feedbackGain.gain),this._readOnly(["feedback"])},t.extend(t.FeedbackEffect,t.Effect),t.FeedbackEffect.defaults={feedback:.125},t.FeedbackEffect.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._writable(["feedback"]),this.feedback.dispose(),this.feedback=null,this._feedbackGain.disconnect(),this._feedbackGain=null,this},t.FeedbackEffect}),e(function(t){return t.StereoXFeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"],t.FeedbackEffect.defaults);t.StereoEffect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),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"])},t.extend(t.StereoXFeedbackEffect,t.FeedbackEffect),t.StereoXFeedbackEffect.prototype.dispose=function(){return t.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},t.StereoXFeedbackEffect}),e(function(t){return t.Chorus=function(){var e=this.optionsObject(arguments,["frequency","delayTime","depth"],t.Chorus.defaults);t.StereoXFeedbackEffect.call(this,e),this._depth=e.depth,this._delayTime=e.delayTime/1e3,this._lfoL=new t.LFO({frequency:e.frequency,min:0,max:1}),this._lfoR=new t.LFO({frequency:e.frequency,min:0,max:1,phase:180}),this._delayNodeL=this.context.createDelay(),this._delayNodeR=this.context.createDelay(),this.frequency=this._lfoL.frequency,this.effectSendL.chain(this._delayNodeL,this.effectReturnL),this.effectSendR.chain(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=e.frequency,this.type=e.type,this._readOnly(["frequency"]),this.spread=e.spread},t.extend(t.Chorus,t.StereoXFeedbackEffect),t.Chorus.defaults={frequency:1.5,delayTime:3.5,depth:.7,feedback:.1,type:"sine",spread:180},Object.defineProperty(t.Chorus.prototype,"depth",{get:function(){return this._depth},set:function(t){this._depth=t;var e=this._delayTime*t;this._lfoL.min=Math.max(this._delayTime-e,0),this._lfoL.max=this._delayTime+e,this._lfoR.min=Math.max(this._delayTime-e,0),this._lfoR.max=this._delayTime+e}}),Object.defineProperty(t.Chorus.prototype,"delayTime",{get:function(){return 1e3*this._delayTime},set:function(t){this._delayTime=t/1e3,this.depth=this._depth}}),Object.defineProperty(t.Chorus.prototype,"type",{get:function(){return this._lfoL.type},set:function(t){this._lfoL.type=t,this._lfoR.type=t}}),Object.defineProperty(t.Chorus.prototype,"spread",{get:function(){return this._lfoR.phase-this._lfoL.phase},set:function(t){this._lfoL.phase=90-t/2,this._lfoR.phase=t/2+90}}),t.Chorus.prototype.dispose=function(){return t.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},t.Chorus}),e(function(t){return t.Convolver=function(){var e=this.optionsObject(arguments,["url"],t.Convolver.defaults);t.Effect.call(this,e),this._convolver=this.context.createConvolver(),this._buffer=new t.Buffer(e.url,function(t){this.buffer=t,e.onload()}.bind(this)),this.connectEffect(this._convolver)},t.extend(t.Convolver,t.Effect),t.Convolver.defaults={url:"",onload:t.noOp},Object.defineProperty(t.Convolver.prototype,"buffer",{get:function(){return this._buffer.get()},set:function(t){this._buffer.set(t),this._convolver.buffer=this._buffer.get()}}),t.Convolver.prototype.load=function(t,e){return this._buffer.load(t,function(t){this.buffer=t,e&&e()}.bind(this)),this},t.Convolver.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._convolver.disconnect(),this._convolver=null,this._buffer.dispose(),this._buffer=null,this},t.Convolver}),e(function(t){return t.Distortion=function(){var e=this.optionsObject(arguments,["distortion"],t.Distortion.defaults);t.Effect.call(this,e),this._shaper=new t.WaveShaper(4096),this._distortion=e.distortion,this.connectEffect(this._shaper),this.distortion=e.distortion,this.oversample=e.oversample},t.extend(t.Distortion,t.Effect),t.Distortion.defaults={distortion:.4,oversample:"none"},Object.defineProperty(t.Distortion.prototype,"distortion",{get:function(){return this._distortion},set:function(t){var e,i;this._distortion=t,e=100*t,i=Math.PI/180,this._shaper.setMap(function(t){return Math.abs(t)<.001?0:(3+e)*t*20*i/(Math.PI+e*Math.abs(t))})}}),Object.defineProperty(t.Distortion.prototype,"oversample",{get:function(){return this._shaper.oversample},set:function(t){this._shaper.oversample=t}}),t.Distortion.prototype.dispose=function(){return t.Effect.prototype.dispose.call(this),this._shaper.dispose(),this._shaper=null,this},t.Distortion}),e(function(t){return t.FeedbackDelay=function(){var e=this.optionsObject(arguments,["delayTime","feedback"],t.FeedbackDelay.defaults);t.FeedbackEffect.call(this,e),this.delayTime=new t.Signal(e.delayTime,t.Type.Time),this._delayNode=this.context.createDelay(4),this.connectEffect(this._delayNode),this.delayTime.connect(this._delayNode.delayTime),this._readOnly(["delayTime"])},t.extend(t.FeedbackDelay,t.FeedbackEffect),t.FeedbackDelay.defaults={delayTime:.25},t.FeedbackDelay.prototype.dispose=function(){return t.FeedbackEffect.prototype.dispose.call(this),this.delayTime.dispose(),this._delayNode.disconnect(),this._delayNode=null,this._writable(["delayTime"]),this.delayTime=null,this},t.FeedbackDelay}),e(function(t){var e=[1557/44100,1617/44100,1491/44100,1422/44100,1277/44100,1356/44100,1188/44100,1116/44100],i=[225,556,441,341];return t.Freeverb=function(){var s,n,o,r,a,h,l=this.optionsObject(arguments,["roomSize","dampening"],t.Freeverb.defaults);for(t.StereoEffect.call(this,l),this.roomSize=new t.Signal(l.roomSize,t.Type.NormalRange),this.dampening=new t.Signal(l.dampening,t.Type.Frequency),this._combFilters=[],this._allpassFiltersL=[],this._allpassFiltersR=[],s=0;ss;s++)n=this.context.createBiquadFilter(),n.type="allpass",i.connect(n.Q),e.connect(n.frequency),o[s]=n;return this.connectSeries.apply(this,o),o},Object.defineProperty(t.Phaser.prototype,"octaves",{get:function(){return this._octaves},set:function(t){this._octaves=t;var e=this._baseFrequency*Math.pow(2,t);this._lfoL.max=e,this._lfoR.max=e}}),Object.defineProperty(t.Phaser.prototype,"baseFrequency",{get:function(){return this._baseFrequency},set:function(t){this._baseFrequency=t,this._lfoL.min=t,this._lfoR.min=t,this.octaves=this._octaves}}),t.Phaser.prototype.dispose=function(){var e,i;for(t.StereoEffect.prototype.dispose.call(this),this._writable(["frequency","Q"]),this.Q.dispose(),this.Q=null,this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,e=0;et?(this._lfoA.min=0,this._lfoA.max=this._windowSize,this._lfoB.min=0,this._lfoB.max=this._windowSize,e=this.intervalToFrequencyRatio(t-1)+1):(this._lfoA.min=this._windowSize,this._lfoA.max=0,this._lfoB.min=this._windowSize,this._lfoB.max=0,e=this.intervalToFrequencyRatio(t)-1),this._frequency.value=e*(1.2/this._windowSize)}}),Object.defineProperty(t.PitchShift.prototype,"windowSize",{get:function(){return this._windowSize},set:function(t){this._windowSize=this.toSeconds(t),this.pitch=this._pitch}}),t.PitchShift.prototype.dispose=function(){return t.FeedbackEffect.prototype.dispose.call(this),this._frequency.dispose(),this._frequency=null,this._delayA.disconnect(),this._delayA=null,this._delayB.disconnect(),this._delayB=null,this._lfoA.dispose(),this._lfoA=null,this._lfoB.dispose(),this._lfoB=null,this._crossFade.dispose(),this._crossFade=null,this._crossFadeLFO.dispose(),this._crossFadeLFO=null,this._writable("delayTime"),this._feedbackDelay.dispose(),this._feedbackDelay=null,this.delayTime=null,this},t.PitchShift}),e(function(t){return t.StereoFeedbackEffect=function(){var e=this.optionsObject(arguments,["feedback"],t.FeedbackEffect.defaults);t.StereoEffect.call(this,e),this.feedback=new t.Signal(e.feedback,t.Type.NormalRange),this._feedbackL=this.context.createGain(),this._feedbackR=this.context.createGain(),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"])},t.extend(t.StereoFeedbackEffect,t.FeedbackEffect),t.StereoFeedbackEffect.prototype.dispose=function(){return t.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,this},t.StereoFeedbackEffect}),e(function(t){return t.StereoWidener=function(){var e=this.optionsObject(arguments,["width"],t.StereoWidener.defaults);t.MidSideEffect.call(this,e),this.width=new t.Signal(e.width,t.Type.NormalRange),this._midMult=new t.Expr("$0 * ($1 * (1 - $2))"),this._sideMult=new t.Expr("$0 * ($1 * $2)"),this._two=new t.Signal(2),this._two.connect(this._midMult,0,1),this.width.connect(this._midMult,0,2),this._two.connect(this._sideMult,0,1),this.width.connect(this._sideMult,0,2),this.midSend.chain(this._midMult,this.midReturn),this.sideSend.chain(this._sideMult,this.sideReturn),this._readOnly(["width"])},t.extend(t.StereoWidener,t.MidSideEffect),t.StereoWidener.defaults={width:.5},t.StereoWidener.prototype.dispose=function(){return t.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,this},t.StereoWidener}),e(function(t){return t.Tremolo=function(){var e=this.optionsObject(arguments,["frequency","depth"],t.Tremolo.defaults);t.StereoEffect.call(this,e),this._lfoL=new t.LFO({phase:e.spread,min:1,max:0}),this._lfoR=new t.LFO({phase:e.spread,min:1,max:0}),this._amplitudeL=new t.Gain,this._amplitudeR=new t.Gain,this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.depth=new t.Signal(e.depth,t.Type.NormalRange),this._readOnly(["frequency","depth"]),this.effectSendL.chain(this._amplitudeL,this.effectReturnL),this.effectSendR.chain(this._amplitudeR,this.effectReturnR),this._lfoL.connect(this._amplitudeL.gain),this._lfoR.connect(this._amplitudeR.gain),this.frequency.fan(this._lfoL.frequency,this._lfoR.frequency),this.depth.fan(this._lfoR.amplitude,this._lfoL.amplitude),this.type=e.type,this.spread=e.spread},t.extend(t.Tremolo,t.StereoEffect),t.Tremolo.defaults={frequency:10,type:"sine",depth:.5,spread:180},t.Tremolo.prototype.start=function(t){return this._lfoL.start(t),this._lfoR.start(t),this},t.Tremolo.prototype.stop=function(t){return this._lfoL.stop(t),this._lfoR.stop(t),this},t.Tremolo.prototype.sync=function(t){return this._lfoL.sync(t),this._lfoR.sync(t),this},t.Tremolo.prototype.unsync=function(){return this._lfoL.unsync(),this._lfoR.unsync(),this},Object.defineProperty(t.Tremolo.prototype,"type",{get:function(){return this._lfoL.type},set:function(t){this._lfoL.type=t,this._lfoR.type=t}}),Object.defineProperty(t.Tremolo.prototype,"spread",{get:function(){return this._lfoR.phase-this._lfoL.phase},set:function(t){this._lfoL.phase=90-t/2,this._lfoR.phase=t/2+90}}),t.Tremolo.prototype.dispose=function(){return t.StereoEffect.prototype.dispose.call(this),this._writable(["frequency","depth"]),this._lfoL.dispose(),this._lfoL=null,this._lfoR.dispose(),this._lfoR=null,this._amplitudeL.dispose(),this._amplitudeL=null,this._amplitudeR.dispose(),this._amplitudeR=null,this.frequency=null,this.depth=null,this},t.Tremolo}),e(function(t){return t.Vibrato=function(){var e=this.optionsObject(arguments,["frequency","depth"],t.Vibrato.defaults);t.Effect.call(this,e),this._delayNode=new t.Delay(0,e.maxDelay),this._lfo=new t.LFO({type:e.type,min:0,max:e.maxDelay,frequency:e.frequency,phase:-90}).start().connect(this._delayNode.delayTime),this.frequency=this._lfo.frequency,this.depth=this._lfo.amplitude,this.depth.value=e.depth,this._readOnly(["frequency","depth"]),this.effectSend.chain(this._delayNode,this.effectReturn)},t.extend(t.Vibrato,t.Effect),t.Vibrato.defaults={maxDelay:.005,frequency:5,depth:.1,type:"sine"},Object.defineProperty(t.Vibrato.prototype,"type",{get:function(){return this._lfo.type},set:function(t){this._lfo.type=t}}),t.Vibrato.prototype.dispose=function(){t.Effect.prototype.dispose.call(this),this._delayNode.dispose(),this._delayNode=null,this._lfo.dispose(),this._lfo=null,this._writable(["frequency","depth"]),this.frequency=null,this.depth=null},t.Vibrato}),e(function(t){return t.Event=function(){var e=this.optionsObject(arguments,["callback","value"],t.Event.defaults);this._loop=e.loop,this.callback=e.callback,this.value=e.value,this._loopStart=this.toTicks(e.loopStart),this._loopEnd=this.toTicks(e.loopEnd),this._state=new t.TimelineState(t.State.Stopped),this._playbackRate=1,this._startOffset=0,this.probability=e.probability,this.humanize=e.humanize,this.mute=e.mute,this.playbackRate=e.playbackRate},t.extend(t.Event),t.Event.defaults={callback:t.noOp,loop:!1,loopEnd:"1m",loopStart:0,playbackRate:1,value:null,probability:1,mute:!1,humanize:!1},t.Event.prototype._rescheduleEvents=function(e){return e=this.defaultArg(e,-1),this._state.forEachFrom(e,function(e){var i,s,n,o;e.state===t.State.Started&&(this.isUndef(e.id)||t.Transport.clear(e.id),s=e.time+Math.round(this.startOffset/this._playbackRate),this._loop?(i=1/0,this.isNumber(this._loop)&&(i=this._loop*this._getLoopDuration()),n=this._state.getEventAfter(s),null!==n&&(i=Math.min(i,n.time-s)),i!==1/0&&(this._state.setStateAtTime(t.State.Stopped,s+i+1),i=t.Time(i,"i")),o=t.Time(this._getLoopDuration(),"i"),e.id=t.Transport.scheduleRepeat(this._tick.bind(this),o,t.TransportTime(s,"i"),i)):e.id=t.Transport.schedule(this._tick.bind(this),s+"i"))}.bind(this)),this},Object.defineProperty(t.Event.prototype,"state",{get:function(){return this._state.getStateAtTime(t.Transport.ticks)}}),Object.defineProperty(t.Event.prototype,"startOffset",{get:function(){return this._startOffset},set:function(t){this._startOffset=t}}),t.Event.prototype.start=function(e){return e=this.toTicks(e),this._state.getStateAtTime(e)===t.State.Stopped&&(this._state.addEvent({state:t.State.Started,time:e,id:void 0}),this._rescheduleEvents(e)),this},t.Event.prototype.stop=function(e){var i,s;return this.cancel(e),e=this.toTicks(e),this._state.getStateAtTime(e)===t.State.Started&&(this._state.setStateAtTime(t.State.Stopped,e),i=this._state.getEventBefore(e),s=e,null!==i&&(s=i.time),this._rescheduleEvents(s)),this},t.Event.prototype.cancel=function(e){return e=this.defaultArg(e,-(1/0)),e=this.toTicks(e),this._state.forEachFrom(e,function(e){t.Transport.clear(e.id)}),this._state.cancel(e),this},t.Event.prototype._tick=function(e){if(!this.mute&&this._state.getStateAtTime(t.Transport.ticks)===t.State.Started){if(this.probability<1&&Math.random()>this.probability)return;if(this.humanize){var i=.02;this.isBoolean(this.humanize)||(i=this.toSeconds(this.humanize)),e+=(2*Math.random()-1)*i}this.callback(e,this.value)}},t.Event.prototype._getLoopDuration=function(){return Math.round((this._loopEnd-this._loopStart)/this._playbackRate)},Object.defineProperty(t.Event.prototype,"loop",{get:function(){return this._loop},set:function(t){this._loop=t,this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"loopEnd",{get:function(){return t.TransportTime(this._loopEnd,"i").toNotation()},set:function(t){this._loopEnd=this.toTicks(t),this._loop&&this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"loopStart",{get:function(){return t.TransportTime(this._loopStart,"i").toNotation()},set:function(t){this._loopStart=this.toTicks(t),this._loop&&this._rescheduleEvents()}}),Object.defineProperty(t.Event.prototype,"progress",{get:function(){var e,i,s,n;return this._loop?(e=t.Transport.ticks,i=this._state.getEvent(e),null!==i&&i.state===t.State.Started?(s=this._getLoopDuration(),n=(e-i.time)%s,n/s):0):0}}),t.Event.prototype.dispose=function(){this.cancel(),this._state.dispose(),this._state=null,this.callback=null,this.value=null},t.Event}),e(function(t){return t.Loop=function(){var e=this.optionsObject(arguments,["callback","interval"],t.Loop.defaults);this._event=new t.Event({callback:this._tick.bind(this),loop:!0,loopEnd:e.interval,playbackRate:e.playbackRate,probability:e.probability}),this.callback=e.callback,this.iterations=e.iterations},t.extend(t.Loop),t.Loop.defaults={interval:"4n",callback:t.noOp,playbackRate:1,iterations:1/0,probability:!0,mute:!1},t.Loop.prototype.start=function(t){return this._event.start(t),this},t.Loop.prototype.stop=function(t){return this._event.stop(t),this},t.Loop.prototype.cancel=function(t){return this._event.cancel(t),this},t.Loop.prototype._tick=function(t){this.callback(t)},Object.defineProperty(t.Loop.prototype,"state",{get:function(){return this._event.state}}),Object.defineProperty(t.Loop.prototype,"progress",{get:function(){return this._event.progress}}),Object.defineProperty(t.Loop.prototype,"interval",{get:function(){return this._event.loopEnd},set:function(t){this._event.loopEnd=t}}),Object.defineProperty(t.Loop.prototype,"playbackRate",{get:function(){return this._event.playbackRate},set:function(t){this._event.playbackRate=t}}),Object.defineProperty(t.Loop.prototype,"humanize",{get:function(){return this._event.humanize},set:function(t){this._event.humanize=t}}),Object.defineProperty(t.Loop.prototype,"probability",{get:function(){return this._event.probability},set:function(t){this._event.probability=t}}),Object.defineProperty(t.Loop.prototype,"mute",{get:function(){return this._event.mute},set:function(t){this._event.mute=t}}),Object.defineProperty(t.Loop.prototype,"iterations",{get:function(){return this._event.loop===!0?1/0:this._event.loop},set:function(t){this._event.loop=t===1/0?!0:t}}),t.Loop.prototype.dispose=function(){this._event.dispose(),this._event=null,this.callback=null},t.Loop}),e(function(t){return t.Part=function(){var e,i,s=this.optionsObject(arguments,["callback","events"],t.Part.defaults);if(this._loop=s.loop,this._loopStart=this.toTicks(s.loopStart),this._loopEnd=this.toTicks(s.loopEnd),this._playbackRate=s.playbackRate,this._probability=s.probability,this._humanize=s.humanize,this._startOffset=0,this._state=new t.TimelineState(t.State.Stopped),this._events=[],this.callback=s.callback,this.mute=s.mute,e=this.defaultArg(s.events,[]),!this.isUndef(s.events))for(i=0;i=this._loopStart&&e.startOffset=s&&(e.loop=!1,e.start(t.TransportTime(i,"i"))):e.startOffset>=s&&e.start(t.TransportTime(i,"i"))},Object.defineProperty(t.Part.prototype,"startOffset",{get:function(){return this._startOffset},set:function(t){this._startOffset=t,this._forEach(function(t){t.startOffset+=this._startOffset})}}),t.Part.prototype.stop=function(e){var i=this.toTicks(e);return this._state.cancel(i),this._state.setStateAtTime(t.State.Stopped,i),this._forEach(function(t){t.stop(e)}),this},t.Part.prototype.at=function(e,i){var s,n,o;for(e=t.TransportTime(e),s=t.Time(1,"i").toSeconds(),n=0;n=0;s--)n=this._events[s],n instanceof t.Part?n.remove(e,i):n.startOffset===e&&(this.isUndef(i)||!this.isUndef(i)&&n.value===i)&&(this._events.splice(s,1),n.dispose());return this},t.Part.prototype.removeAll=function(){return this._forEach(function(t){t.dispose()}),this._events=[],this},t.Part.prototype.cancel=function(t){return this._forEach(function(e){e.cancel(t)}),this._state.cancel(t),this},t.Part.prototype._forEach=function(e,i){var s,n;for(i=this.defaultArg(i,this),s=this._events.length-1;s>=0;s--)n=this._events[s],n instanceof t.Part?n._forEach(e,i):e.call(i,n);return this},t.Part.prototype._setAll=function(t,e){this._forEach(function(i){i[t]=e})},t.Part.prototype._tick=function(t,e){this.mute||this.callback(t,e)},t.Part.prototype._testLoopBoundries=function(e){e.startOffset=this._loopEnd?e.cancel():e.state===t.State.Stopped&&this._restartEvent(e)},Object.defineProperty(t.Part.prototype,"probability",{get:function(){return this._probability},set:function(t){this._probability=t,this._setAll("probability",t)}}),Object.defineProperty(t.Part.prototype,"humanize",{get:function(){return this._humanize},set:function(t){this._humanize=t,this._setAll("humanize",t)}}),Object.defineProperty(t.Part.prototype,"loop",{get:function(){return this._loop},set:function(t){this._loop=t,this._forEach(function(e){e._loopStart=this._loopStart,e._loopEnd=this._loopEnd,e.loop=t,this._testLoopBoundries(e)})}}),Object.defineProperty(t.Part.prototype,"loopEnd",{get:function(){return t.TransportTime(this._loopEnd,"i").toNotation()},set:function(t){this._loopEnd=this.toTicks(t),this._loop&&this._forEach(function(t){t.loopEnd=this.loopEnd,this._testLoopBoundries(t)})}}),Object.defineProperty(t.Part.prototype,"loopStart",{get:function(){return t.TransportTime(this._loopStart,"i").toNotation()},set:function(t){this._loopStart=this.toTicks(t),this._loop&&this._forEach(function(t){t.loopStart=this.loopStart,this._testLoopBoundries(t)})}}),Object.defineProperty(t.Part.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this._setAll("playbackRate",t)}}),Object.defineProperty(t.Part.prototype,"length",{get:function(){return this._events.length}}),t.Part.prototype.dispose=function(){return this.removeAll(),this._state.dispose(),this._state=null,this.callback=null,this._events=null,this},t.Part}),e(function(t){return t.Pattern=function(){var e=this.optionsObject(arguments,["callback","values","pattern"],t.Pattern.defaults);t.Loop.call(this,e),this._pattern=new t.CtrlPattern({values:e.values,type:e.pattern,index:e.index})},t.extend(t.Pattern,t.Loop),t.Pattern.defaults={pattern:t.CtrlPattern.Type.Up,values:[]},t.Pattern.prototype._tick=function(t){this.callback(t,this._pattern.value),this._pattern.next()},Object.defineProperty(t.Pattern.prototype,"index",{get:function(){return this._pattern.index},set:function(t){this._pattern.index=t}}),Object.defineProperty(t.Pattern.prototype,"values",{get:function(){return this._pattern.values},set:function(t){this._pattern.values=t}}),Object.defineProperty(t.Pattern.prototype,"value",{get:function(){return this._pattern.value}}),Object.defineProperty(t.Pattern.prototype,"pattern",{get:function(){return this._pattern.type},set:function(t){this._pattern.type=t}}),t.Pattern.prototype.dispose=function(){t.Loop.prototype.dispose.call(this),this._pattern.dispose(),this._pattern=null},t.Pattern}),e(function(t){return t.Sequence=function(){var e,i=this.optionsObject(arguments,["callback","events","subdivision"],t.Sequence.defaults),s=i.events;if(delete i.events,t.Part.call(this,i),this._subdivision=this.toTicks(i.subdivision),this.isUndef(i.loopEnd)&&!this.isUndef(s)&&(this._loopEnd=s.length*this._subdivision),this._loop=!0,!this.isUndef(s))for(e=0;et?-1:1}),this._sawtooth.chain(this._thresh,this.output),this.width.chain(this._widthGate,this._thresh),this._readOnly(["width","frequency","detune"])},t.extend(t.PulseOscillator,t.Oscillator),t.PulseOscillator.defaults={frequency:440,detune:0,phase:0,width:.2},t.PulseOscillator.prototype._start=function(t){t=this.toSeconds(t),this._sawtooth.start(t),this._widthGate.gain.setValueAtTime(1,t)},t.PulseOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._sawtooth.stop(t),this._widthGate.gain.setValueAtTime(0,t)},Object.defineProperty(t.PulseOscillator.prototype,"phase",{get:function(){return this._sawtooth.phase},set:function(t){this._sawtooth.phase=t}}),Object.defineProperty(t.PulseOscillator.prototype,"type",{get:function(){return"pulse"}}),Object.defineProperty(t.PulseOscillator.prototype,"partials",{get:function(){return[]}}),t.PulseOscillator.prototype.dispose=function(){return t.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},t.PulseOscillator}),e(function(t){return t.PWMOscillator=function(){var e=this.optionsObject(arguments,["frequency","modulationFrequency"],t.PWMOscillator.defaults);t.Source.call(this,e),this._pulse=new t.PulseOscillator(e.modulationFrequency),this._pulse._sawtooth.type="sine",this._modulator=new t.Oscillator({frequency:e.frequency,detune:e.detune,phase:e.phase}),this._scale=new t.Multiply(2),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"])},t.extend(t.PWMOscillator,t.Oscillator),t.PWMOscillator.defaults={frequency:440,detune:0,phase:0,modulationFrequency:.4},t.PWMOscillator.prototype._start=function(t){t=this.toSeconds(t),this._modulator.start(t),this._pulse.start(t)},t.PWMOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._modulator.stop(t),this._pulse.stop(t)},Object.defineProperty(t.PWMOscillator.prototype,"type",{get:function(){return"pwm"}}),Object.defineProperty(t.PWMOscillator.prototype,"partials",{get:function(){return[]}}),Object.defineProperty(t.PWMOscillator.prototype,"phase",{get:function(){return this._modulator.phase},set:function(t){this._modulator.phase=t}}),t.PWMOscillator.prototype.dispose=function(){return t.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},t.PWMOscillator}),e(function(t){return t.FMOscillator=function(){var e=this.optionsObject(arguments,["frequency","type","modulationType"],t.FMOscillator.defaults);t.Source.call(this,e),this._carrier=new t.Oscillator(e.frequency,e.type),this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=this._carrier.detune,this.detune.value=e.detune,this.modulationIndex=new t.Multiply(e.modulationIndex),this.modulationIndex.units=t.Type.Positive,this._modulator=new t.Oscillator(e.frequency,e.modulationType),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this._modulationNode=new t.Gain(0),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.connect(this._carrier.frequency),this._carrier.connect(this.output),this.detune.connect(this._modulator.detune),this.phase=e.phase,this._readOnly(["modulationIndex","frequency","detune","harmonicity"])},t.extend(t.FMOscillator,t.Oscillator),t.FMOscillator.defaults={frequency:440,detune:0,phase:0,modulationIndex:2,modulationType:"square",harmonicity:1},t.FMOscillator.prototype._start=function(t){t=this.toSeconds(t),this._modulator.start(t),this._carrier.start(t)},t.FMOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._modulator.stop(t),this._carrier.stop(t)},Object.defineProperty(t.FMOscillator.prototype,"type",{get:function(){return this._carrier.type},set:function(t){this._carrier.type=t}}),Object.defineProperty(t.FMOscillator.prototype,"modulationType",{get:function(){return this._modulator.type},set:function(t){this._modulator.type=t}}),Object.defineProperty(t.FMOscillator.prototype,"phase",{get:function(){return this._carrier.phase},set:function(t){this._carrier.phase=t,this._modulator.phase=t}}),Object.defineProperty(t.FMOscillator.prototype,"partials",{get:function(){return this._carrier.partials; + +},set:function(t){this._carrier.partials=t}}),t.FMOscillator.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),this._writable(["modulationIndex","frequency","detune","harmonicity"]),this.frequency.dispose(),this.frequency=null,this.detune=null,this.harmonicity.dispose(),this.harmonicity=null,this._carrier.dispose(),this._carrier=null,this._modulator.dispose(),this._modulator=null,this._modulationNode.dispose(),this._modulationNode=null,this.modulationIndex.dispose(),this.modulationIndex=null,this},t.FMOscillator}),e(function(t){return t.AMOscillator=function(){var e=this.optionsObject(arguments,["frequency","type","modulationType"],t.AMOscillator.defaults);t.Source.call(this,e),this._carrier=new t.Oscillator(e.frequency,e.type),this.frequency=this._carrier.frequency,this.detune=this._carrier.detune,this.detune.value=e.detune,this._modulator=new t.Oscillator(e.frequency,e.modulationType),this._modulationScale=new t.AudioToGain,this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this._modulationNode=new t.Gain(0),this.frequency.chain(this.harmonicity,this._modulator.frequency),this.detune.connect(this._modulator.detune),this._modulator.chain(this._modulationScale,this._modulationNode.gain),this._carrier.chain(this._modulationNode,this.output),this.phase=e.phase,this._readOnly(["frequency","detune","harmonicity"])},t.extend(t.AMOscillator,t.Oscillator),t.AMOscillator.defaults={frequency:440,detune:0,phase:0,modulationType:"square",harmonicity:1},t.AMOscillator.prototype._start=function(t){t=this.toSeconds(t),this._modulator.start(t),this._carrier.start(t)},t.AMOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._modulator.stop(t),this._carrier.stop(t)},Object.defineProperty(t.AMOscillator.prototype,"type",{get:function(){return this._carrier.type},set:function(t){this._carrier.type=t}}),Object.defineProperty(t.AMOscillator.prototype,"modulationType",{get:function(){return this._modulator.type},set:function(t){this._modulator.type=t}}),Object.defineProperty(t.AMOscillator.prototype,"phase",{get:function(){return this._carrier.phase},set:function(t){this._carrier.phase=t,this._modulator.phase=t}}),Object.defineProperty(t.AMOscillator.prototype,"partials",{get:function(){return this._carrier.partials},set:function(t){this._carrier.partials=t}}),t.AMOscillator.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),this._writable(["frequency","detune","harmonicity"]),this.frequency=null,this.detune=null,this.harmonicity.dispose(),this.harmonicity=null,this._carrier.dispose(),this._carrier=null,this._modulator.dispose(),this._modulator=null,this._modulationNode.dispose(),this._modulationNode=null,this._modulationScale.dispose(),this._modulationScale=null,this},t.AMOscillator}),e(function(t){return t.FatOscillator=function(){var e=this.optionsObject(arguments,["frequency","type","spread"],t.FatOscillator.defaults);t.Source.call(this,e),this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this._oscillators=[],this._spread=e.spread,this._type=e.type,this._phase=e.phase,this._partials=this.defaultArg(e.partials,[]),this.count=e.count,this._readOnly(["frequency","detune"])},t.extend(t.FatOscillator,t.Oscillator),t.FatOscillator.defaults={frequency:440,detune:0,phase:0,spread:20,count:3,type:"sawtooth"},t.FatOscillator.prototype._start=function(t){t=this.toSeconds(t),this._forEach(function(e){e.start(t)})},t.FatOscillator.prototype._stop=function(t){t=this.toSeconds(t),this._forEach(function(e){e.stop(t)})},t.FatOscillator.prototype._forEach=function(t){for(var e=0;e1&&(e=-t/2,i=t/(this._oscillators.length-1),this._forEach(function(t,s){t.detune.value=e+i*s}))}}),Object.defineProperty(t.FatOscillator.prototype,"count",{get:function(){return this._oscillators.length},set:function(e){var i,s;if(e=Math.max(e,1),this._oscillators.length!==e){for(this._forEach(function(t){t.dispose()}),this._oscillators=[],i=0;e>i;i++)s=new t.Oscillator,this.type===t.Oscillator.Type.Custom?s.partials=this._partials:s.type=this._type,s.phase=this._phase,s.volume.value=-6-e,this.frequency.connect(s.frequency),this.detune.connect(s.detune),s.connect(this.output),this._oscillators[i]=s;this.spread=this._spread,this.state===t.State.Started&&this._forEach(function(t){t.start()})}}}),Object.defineProperty(t.FatOscillator.prototype,"phase",{get:function(){return this._phase},set:function(t){this._phase=t,this._forEach(function(e){e.phase=t})}}),Object.defineProperty(t.FatOscillator.prototype,"partials",{get:function(){return this._partials},set:function(e){this._partials=e,this._type=t.Oscillator.Type.Custom,this._forEach(function(t){t.partials=e})}}),t.FatOscillator.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),this._writable(["frequency","detune"]),this.frequency.dispose(),this.frequency=null,this.detune.dispose(),this.detune=null,this._forEach(function(t){t.dispose()}),this._oscillators=null,this._partials=null,this},t.FatOscillator}),e(function(t){t.OmniOscillator=function(){var e=this.optionsObject(arguments,["frequency","type"],t.OmniOscillator.defaults);t.Source.call(this,e),this.frequency=new t.Signal(e.frequency,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this._sourceType=void 0,this._oscillator=null,this.type=e.type,this._readOnly(["frequency","detune"]),this.set(e)},t.extend(t.OmniOscillator,t.Oscillator),t.OmniOscillator.defaults={frequency:440,detune:0,type:"sine",phase:0};var e={Pulse:"PulseOscillator",PWM:"PWMOscillator",Osc:"Oscillator",FM:"FMOscillator",AM:"AMOscillator",Fat:"FatOscillator"};return t.OmniOscillator.prototype._start=function(t){this._oscillator.start(t)},t.OmniOscillator.prototype._stop=function(t){this._oscillator.stop(t)},Object.defineProperty(t.OmniOscillator.prototype,"type",{get:function(){var t="";return this._sourceType===e.FM?t="fm":this._sourceType===e.AM?t="am":this._sourceType===e.Fat&&(t="fat"),t+this._oscillator.type},set:function(t){"fm"===t.substr(0,2)?(this._createNewOscillator(e.FM),this._oscillator.type=t.substr(2)):"am"===t.substr(0,2)?(this._createNewOscillator(e.AM),this._oscillator.type=t.substr(2)):"fat"===t.substr(0,3)?(this._createNewOscillator(e.Fat),this._oscillator.type=t.substr(3)):"pwm"===t?this._createNewOscillator(e.PWM):"pulse"===t?this._createNewOscillator(e.Pulse):(this._createNewOscillator(e.Osc),this._oscillator.type=t)}}),Object.defineProperty(t.OmniOscillator.prototype,"partials",{get:function(){return this._oscillator.partials},set:function(t){this._oscillator.partials=t}}),t.OmniOscillator.prototype.set=function(e,i){return"type"===e?this.type=i:this.isObject(e)&&e.hasOwnProperty("type")&&(this.type=e.type),t.prototype.set.apply(this,arguments),this},t.OmniOscillator.prototype._createNewOscillator=function(e){var i,s,n;e!==this._sourceType&&(this._sourceType=e,i=t[e],s=this.now()+this.blockTime,null!==this._oscillator&&(n=this._oscillator,n.stop(s),setTimeout(function(){n.dispose(),n=null},1e3*this.blockTime)),this._oscillator=new i,this.frequency.connect(this._oscillator.frequency),this.detune.connect(this._oscillator.detune),this._oscillator.connect(this.output),this.state===t.State.Started&&this._oscillator.start(s))},Object.defineProperty(t.OmniOscillator.prototype,"phase",{get:function(){return this._oscillator.phase},set:function(t){this._oscillator.phase=t}}),Object.defineProperty(t.OmniOscillator.prototype,"width",{get:function(){return this._sourceType===e.Pulse?this._oscillator.width:void 0}}),Object.defineProperty(t.OmniOscillator.prototype,"count",{get:function(){return this._sourceType===e.Fat?this._oscillator.count:void 0},set:function(t){this._sourceType===e.Fat&&(this._oscillator.count=t)}}),Object.defineProperty(t.OmniOscillator.prototype,"spread",{get:function(){return this._sourceType===e.Fat?this._oscillator.spread:void 0},set:function(t){this._sourceType===e.Fat&&(this._oscillator.spread=t)}}),Object.defineProperty(t.OmniOscillator.prototype,"modulationType",{get:function(){return this._sourceType===e.FM||this._sourceType===e.AM?this._oscillator.modulationType:void 0},set:function(t){(this._sourceType===e.FM||this._sourceType===e.AM)&&(this._oscillator.modulationType=t)}}),Object.defineProperty(t.OmniOscillator.prototype,"modulationIndex",{get:function(){return this._sourceType===e.FM?this._oscillator.modulationIndex:void 0}}),Object.defineProperty(t.OmniOscillator.prototype,"harmonicity",{get:function(){return this._sourceType===e.FM||this._sourceType===e.AM?this._oscillator.harmonicity:void 0}}),Object.defineProperty(t.OmniOscillator.prototype,"modulationFrequency",{get:function(){return this._sourceType===e.PWM?this._oscillator.modulationFrequency:void 0}}),t.OmniOscillator.prototype.dispose=function(){return t.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},t.OmniOscillator}),e(function(t){return t.Instrument=function(e){e=this.defaultArg(e,t.Instrument.defaults),this._volume=this.output=new t.Volume(e.volume),this.volume=this._volume.volume,this._readOnly("volume")},t.extend(t.Instrument),t.Instrument.defaults={volume:0},t.Instrument.prototype.triggerAttack=t.noOp,t.Instrument.prototype.triggerRelease=t.noOp,t.Instrument.prototype.triggerAttackRelease=function(t,e,i,s){return i=this.toSeconds(i),e=this.toSeconds(e),this.triggerAttack(t,i,s),this.triggerRelease(i+e),this},t.Instrument.prototype.dispose=function(){return t.prototype.dispose.call(this),this._volume.dispose(),this._volume=null,this._writable(["volume"]),this.volume=null,this},t.Instrument}),e(function(t){return t.Monophonic=function(e){e=this.defaultArg(e,t.Monophonic.defaults),t.Instrument.call(this,e),this.portamento=e.portamento},t.extend(t.Monophonic,t.Instrument),t.Monophonic.defaults={portamento:0},t.Monophonic.prototype.triggerAttack=function(t,e,i){return e=this.toSeconds(e),this._triggerEnvelopeAttack(e,i),this.setNote(t,e),this},t.Monophonic.prototype.triggerRelease=function(t){return this._triggerEnvelopeRelease(t),this},t.Monophonic.prototype._triggerEnvelopeAttack=function(){},t.Monophonic.prototype._triggerEnvelopeRelease=function(){},t.Monophonic.prototype.setNote=function(t,e){var i,s;return e=this.toSeconds(e),this.portamento>0?(i=this.frequency.value,this.frequency.setValueAtTime(i,e),s=this.toSeconds(this.portamento),this.frequency.exponentialRampToValueAtTime(t,e+s)):this.frequency.setValueAtTime(t,e),this},t.Monophonic}),e(function(t){return t.Synth=function(e){e=this.defaultArg(e,t.Synth.defaults),t.Monophonic.call(this,e),this.oscillator=new t.OmniOscillator(e.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.envelope=new t.AmplitudeEnvelope(e.envelope),this.oscillator.chain(this.envelope,this.output),this.oscillator.start(),this._readOnly(["oscillator","frequency","detune","envelope"])},t.extend(t.Synth,t.Monophonic),t.Synth.defaults={oscillator:{type:"triangle"},envelope:{attack:.005,decay:.1,sustain:.3,release:1}},t.Synth.prototype._triggerEnvelopeAttack=function(t,e){return this.envelope.triggerAttack(t,e),this},t.Synth.prototype._triggerEnvelopeRelease=function(t){return this.envelope.triggerRelease(t),this},t.Synth.prototype.dispose=function(){return t.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},t.Synth}),e(function(t){return t.AMSynth=function(e){e=this.defaultArg(e,t.AMSynth.defaults),t.Monophonic.call(this,e),this._carrier=new t.Synth,this._carrier.volume.value=-10,this.oscillator=this._carrier.oscillator,this.envelope=this._carrier.envelope.set(e.envelope),this._modulator=new t.Synth,this._modulator.volume.value=-10,this.modulation=this._modulator.oscillator.set(e.modulation),this.modulationEnvelope=this._modulator.envelope.set(e.modulationEnvelope),this.frequency=new t.Signal(440,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this._modulationScale=new t.AudioToGain,this._modulationNode=this.context.createGain(),this.frequency.connect(this._carrier.frequency),this.frequency.chain(this.harmonicity,this._modulator.frequency),this.detune.fan(this._carrier.detune,this._modulator.detune),this._modulator.chain(this._modulationScale,this._modulationNode.gain),this._carrier.chain(this._modulationNode,this.output),this._readOnly(["frequency","harmonicity","oscillator","envelope","modulation","modulationEnvelope","detune"])},t.extend(t.AMSynth,t.Monophonic),t.AMSynth.defaults={harmonicity:3,detune:0,oscillator:{type:"sine"},envelope:{attack:.01,decay:.01,sustain:1,release:.5},moduation:{type:"square"},modulationEnvelope:{attack:.5,decay:0,sustain:1,release:.5}},t.AMSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.envelope.triggerAttack(t,e),this.modulationEnvelope.triggerAttack(t,e),this},t.AMSynth.prototype._triggerEnvelopeRelease=function(t){return this.envelope.triggerRelease(t),this.modulationEnvelope.triggerRelease(t),this},t.AMSynth.prototype.dispose=function(){return t.Monophonic.prototype.dispose.call(this),this._writable(["frequency","harmonicity","oscillator","envelope","modulation","modulationEnvelope","detune"]),this._carrier.dispose(),this._carrier=null,this._modulator.dispose(),this._modulator=null,this.frequency.dispose(),this.frequency=null,this.detune.dispose(),this.detune=null,this.harmonicity.dispose(),this.harmonicity=null,this._modulationScale.dispose(),this._modulationScale=null,this._modulationNode.disconnect(),this._modulationNode=null,this.oscillator=null,this.envelope=null,this.modulationEnvelope=null,this.modulation=null,this},t.AMSynth}),e(function(t){return t.MonoSynth=function(e){e=this.defaultArg(e,t.MonoSynth.defaults),t.Monophonic.call(this,e),this.oscillator=new t.OmniOscillator(e.oscillator),this.frequency=this.oscillator.frequency,this.detune=this.oscillator.detune,this.filter=new t.Filter(e.filter),this.filterEnvelope=new t.FrequencyEnvelope(e.filterEnvelope),this.envelope=new t.AmplitudeEnvelope(e.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"])},t.extend(t.MonoSynth,t.Monophonic),t.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,baseFrequency:200,octaves:7,exponent:2}},t.MonoSynth.prototype._triggerEnvelopeAttack=function(t,e){return this.envelope.triggerAttack(t,e),this.filterEnvelope.triggerAttack(t),this},t.MonoSynth.prototype._triggerEnvelopeRelease=function(t){return this.envelope.triggerRelease(t),this.filterEnvelope.triggerRelease(t),this},t.MonoSynth.prototype.dispose=function(){return t.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},t.MonoSynth}),e(function(t){return t.DuoSynth=function(e){e=this.defaultArg(e,t.DuoSynth.defaults),t.Monophonic.call(this,e),this.voice0=new t.MonoSynth(e.voice0),this.voice0.volume.value=-10,this.voice1=new t.MonoSynth(e.voice1),this.voice1.volume.value=-10,this._vibrato=new t.LFO(e.vibratoRate,-50,50),this._vibrato.start(),this.vibratoRate=this._vibrato.frequency,this._vibratoGain=this.context.createGain(),this.vibratoAmount=new t.Param({param:this._vibratoGain.gain,units:t.Type.Positive,value:e.vibratoAmount}),this.frequency=new t.Signal(440,t.Type.Frequency),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,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"])},t.extend(t.DuoSynth,t.Monophonic),t.DuoSynth.defaults={vibratoAmount:.5,vibratoRate:5,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}}},t.DuoSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.voice0.envelope.triggerAttack(t,e),this.voice1.envelope.triggerAttack(t,e),this.voice0.filterEnvelope.triggerAttack(t),this.voice1.filterEnvelope.triggerAttack(t),this},t.DuoSynth.prototype._triggerEnvelopeRelease=function(t){return this.voice0.triggerRelease(t),this.voice1.triggerRelease(t),this},t.DuoSynth.prototype.dispose=function(){return t.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},t.DuoSynth}),e(function(t){return t.FMSynth=function(e){e=this.defaultArg(e,t.FMSynth.defaults),t.Monophonic.call(this,e),this._carrier=new t.Synth(e.carrier),this._carrier.volume.value=-10,this.oscillator=this._carrier.oscillator,this.envelope=this._carrier.envelope.set(e.envelope),this._modulator=new t.Synth(e.modulator),this._modulator.volume.value=-10,this.modulation=this._modulator.oscillator.set(e.modulation),this.modulationEnvelope=this._modulator.envelope.set(e.modulationEnvelope),this.frequency=new t.Signal(440,t.Type.Frequency),this.detune=new t.Signal(e.detune,t.Type.Cents),this.harmonicity=new t.Multiply(e.harmonicity),this.harmonicity.units=t.Type.Positive,this.modulationIndex=new t.Multiply(e.modulationIndex),this.modulationIndex.units=t.Type.Positive,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.detune.fan(this._carrier.detune,this._modulator.detune),this._modulator.connect(this._modulationNode.gain),this._modulationNode.gain.value=0,this._modulationNode.connect(this._carrier.frequency),this._carrier.connect(this.output),this._readOnly(["frequency","harmonicity","modulationIndex","oscillator","envelope","modulation","modulationEnvelope","detune"])},t.extend(t.FMSynth,t.Monophonic),t.FMSynth.defaults={harmonicity:3,modulationIndex:10,detune:0,oscillator:{type:"sine"},envelope:{attack:.01,decay:.01,sustain:1,release:.5},moduation:{type:"square"},modulationEnvelope:{attack:.5,decay:0,sustain:1,release:.5}},t.FMSynth.prototype._triggerEnvelopeAttack=function(t,e){return t=this.toSeconds(t),this.envelope.triggerAttack(t,e),this.modulationEnvelope.triggerAttack(t),this},t.FMSynth.prototype._triggerEnvelopeRelease=function(t){return t=this.toSeconds(t),this.envelope.triggerRelease(t),this.modulationEnvelope.triggerRelease(t),this},t.FMSynth.prototype.dispose=function(){return t.Monophonic.prototype.dispose.call(this),this._writable(["frequency","harmonicity","modulationIndex","oscillator","envelope","modulation","modulationEnvelope","detune"]),this._carrier.dispose(),this._carrier=null,this._modulator.dispose(),this._modulator=null,this.frequency.dispose(),this.frequency=null,this.detune.dispose(),this.detune=null,this.modulationIndex.dispose(),this.modulationIndex=null,this.harmonicity.dispose(),this.harmonicity=null,this._modulationNode.disconnect(),this._modulationNode=null,this.oscillator=null,this.envelope=null,this.modulationEnvelope=null,this.modulation=null,this},t.FMSynth}),e(function(t){return t.MembraneSynth=function(e){e=this.defaultArg(e,t.MembraneSynth.defaults),t.Instrument.call(this,e),this.oscillator=new t.Oscillator(e.oscillator).start(),this.envelope=new t.AmplitudeEnvelope(e.envelope),this.octaves=e.octaves,this.pitchDecay=e.pitchDecay,this.oscillator.chain(this.envelope,this.output),this._readOnly(["oscillator","envelope"])},t.extend(t.MembraneSynth,t.Instrument),t.MembraneSynth.defaults={pitchDecay:.05,octaves:10,oscillator:{type:"sine"},envelope:{attack:.001,decay:.4,sustain:.01,release:1.4,attackCurve:"exponential"}},t.MembraneSynth.prototype.triggerAttack=function(t,e,i){e=this.toSeconds(e),t=this.toFrequency(t);var s=t*this.octaves;return this.oscillator.frequency.setValueAtTime(s,e),this.oscillator.frequency.exponentialRampToValueAtTime(t,e+this.toSeconds(this.pitchDecay)),this.envelope.triggerAttack(e,i),this},t.MembraneSynth.prototype.triggerRelease=function(t){return this.envelope.triggerRelease(t),this},t.MembraneSynth.prototype.dispose=function(){return t.Instrument.prototype.dispose.call(this),this._writable(["oscillator","envelope"]),this.oscillator.dispose(),this.oscillator=null,this.envelope.dispose(),this.envelope=null,this},t.MembraneSynth}),e(function(t){var e=[1,1.483,1.932,2.546,2.63,3.897];return t.MetalSynth=function(i){var s,n,o;for(i=this.defaultArg(i,t.MetalSynth.defaults),t.Instrument.call(this,i),this.frequency=new t.Signal(i.frequency,t.Type.Frequency),this._oscillators=[],this._freqMultipliers=[],this._amplitue=new t.Gain(0).connect(this.output),this._highpass=new t.Filter({type:"highpass",Q:0}).connect(this._amplitue),this._octaves=i.octaves,this._filterFreqScaler=new t.Scale(i.resonance,7e3),this.envelope=new t.Envelope({attack:i.envelope.attack,attackCurve:"exponential",decay:i.envelope.decay,sustain:0,release:i.envelope.release}).chain(this._filterFreqScaler,this._highpass.frequency),this.envelope.connect(this._amplitue.gain),s=0;sp;p++)f=2*Math.random()-1,s=.99886*s+.0555179*f,r=.99332*r+.0750759*f,a=.969*a+.153852*f,h=.8665*h+.3104856*f,l=.55*l+.5329522*f,u=-.7616*u-.016898*f,i[p]=s+r+a+h+l+u+c+.5362*f,i[p]*=.11,c=.115926*f;return d}(),i=function(){var e,i,s,r,a,h=t.createBuffer(2,o,n);for(e=0;er;r++)a=2*Math.random()-1,i[r]=(s+.02*a)/1.02,s=i[r],i[r]*=3.5;return h}(),s=function(){var e,i,s,r=t.createBuffer(2,o,n);for(e=0;es;s++)i[s]=2*Math.random()-1;return r}()}),t.Noise}),e(function(t){return t.NoiseSynth=function(e){e=this.defaultArg(e,t.NoiseSynth.defaults),t.Instrument.call(this,e),this.noise=new t.Noise,this.envelope=new t.AmplitudeEnvelope(e.envelope),this.noise.chain(this.envelope,this.output),this.noise.start(),this._readOnly(["noise","envelope"])},t.extend(t.NoiseSynth,t.Instrument),t.NoiseSynth.defaults={noise:{type:"white"},envelope:{attack:.005,decay:.1,sustain:0}},t.NoiseSynth.prototype.triggerAttack=function(t,e){return this.envelope.triggerAttack(t,e),this},t.NoiseSynth.prototype.triggerRelease=function(t){return this.envelope.triggerRelease(t),this},t.NoiseSynth.prototype.triggerAttackRelease=function(t,e,i){return e=this.toSeconds(e),t=this.toSeconds(t),this.triggerAttack(e,i),this.triggerRelease(e+t),this},t.NoiseSynth.prototype.dispose=function(){return t.Instrument.prototype.dispose.call(this),this._writable(["noise","envelope"]),this.noise.dispose(),this.noise=null,this.envelope.dispose(),this.envelope=null,this},t.NoiseSynth}),e(function(t){return t.PluckSynth=function(e){e=this.defaultArg(e,t.PluckSynth.defaults),t.Instrument.call(this,e),this._noise=new t.Noise("pink"),this.attackNoise=1,this._lfcf=new t.LowpassCombFilter({resonance:e.resonance,dampening:e.dampening}),this.resonance=this._lfcf.resonance,this.dampening=this._lfcf.dampening,this._noise.connect(this._lfcf),this._lfcf.connect(this.output),this._readOnly(["resonance","dampening"])},t.extend(t.PluckSynth,t.Instrument),t.PluckSynth.defaults={attackNoise:1,dampening:4e3,resonance:.9},t.PluckSynth.prototype.triggerAttack=function(t,e){t=this.toFrequency(t),e=this.toSeconds(e);var i=1/t;return this._lfcf.delayTime.setValueAtTime(i,e),this._noise.start(e),this._noise.stop(e+i*this.attackNoise),this},t.PluckSynth.prototype.dispose=function(){return t.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},t.PluckSynth}),e(function(t){return t.PolySynth=function(){var e,i,s;for(t.Instrument.call(this),e=this.optionsObject(arguments,["polyphony","voice"],t.PolySynth.defaults),e=this.defaultArg(e,t.Instrument.defaults),e.polyphony=Math.min(t.PolySynth.MAX_POLYPHONY,e.polyphony),this.voices=new Array(e.polyphony),this._triggers=new Array(e.polyphony),this.detune=new t.Signal(e.detune,t.Type.Cents),this._readOnly("detune"),i=0;ie&&(o.voice.triggerRelease(e),o.release=e);return this},t.PolySynth.prototype.set=function(t,e,i){for(var s=0;st&&(i.release=t,i.voice.triggerRelease(t));return this},t.PolySynth.prototype.dispose=function(){t.Instrument.prototype.dispose.call(this);for(var e=0;ethis._startTime?t.State.Started:t.State.Stopped}}),t.BufferSource.prototype.start=function(t,e,i,s,n){if(-1!==this._startTime)throw new Error("Tone.BufferSource: can only be started once.");if(!this.buffer)throw new Error("Tone.BufferSource: no buffer set.");return t=this.toSeconds(t),e=this.loop?this.defaultArg(e,this.loopStart):this.defaultArg(e,0),e=this.toSeconds(e),t=this.toSeconds(t),this._source.start(t,e),s=this.defaultArg(s,1),this._gain=s,n=this.toSeconds(this.isUndef(n)?this.fadeIn:n),n>0?(this._gainNode.gain.setValueAtTime(0,t),this._gainNode.gain.linearRampToValueAtTime(this._gain,t+n)):this._gainNode.gain.setValueAtTime(s,t),this._startTime=t+n,this.isUndef(i)||(i=this.defaultArg(i,this.buffer.duration-e),i=this.toSeconds(i),this.stop(t+i+n,n)),this},t.BufferSource.prototype.stop=function(t,e){if(!this.buffer)throw new Error("Tone.BufferSource: no buffer set.");return t=this.toSeconds(t),e=this.toSeconds(this.isUndef(e)?this.fadeOut:e),this._gainNode.gain.cancelScheduledValues(this._startTime+this.sampleTime),e>0?(this._gainNode.gain.setValueAtTime(this._gain,t),this._gainNode.gain.linearRampToValueAtTime(0,t+e),t+=e):this._gainNode.gain.setValueAtTime(0,t),this._source.stop(t),this},t.BufferSource.prototype._onended=function(){this.onended(this),this.dispose()},Object.defineProperty(t.BufferSource.prototype,"loopStart",{get:function(){return this._source.loopStart},set:function(t){this._source.loopStart=this.toSeconds(t)}}),Object.defineProperty(t.BufferSource.prototype,"loopEnd",{get:function(){return this._source.loopEnd},set:function(t){this._source.loopEnd=this.toSeconds(t)}}),Object.defineProperty(t.BufferSource.prototype,"buffer",{get:function(){return this._source.buffer},set:function(e){this._source.buffer=e instanceof t.Buffer?e.get():e}}),Object.defineProperty(t.BufferSource.prototype,"loop",{get:function(){return this._source.loop},set:function(t){this._source.loop=t}}),t.BufferSource.prototype.dispose=function(){return this.onended=null,this._source&&(this._source.onended=null,this._source.disconnect(),this._source=null),this._gainNode&&(this._gainNode.disconnect(),this._gainNode=null),this._startTime=-1,this.playbackRate=null,this.output=null,this},t.BufferSource}),e(function(t){return navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia,t.ExternalInput=function(){var e=this.optionsObject(arguments,["inputNum"],t.ExternalInput.defaults);t.Source.call(this,e),this._mediaStream=null,this._stream=null,this._constraints={audio:!0},this._inputNum=e.inputNum,this._gate=new t.Gain(0).connect(this.output)},t.extend(t.ExternalInput,t.Source),t.ExternalInput.defaults={inputNum:0},t.ExternalInput.prototype._getUserMedia=function(e,i){t.ExternalInput.supported||i("browser does not support 'getUserMedia'"),t.ExternalInput.sources[this._inputNum]&&(this._constraints={audio:{optional:[{sourceId:t.ExternalInput.sources[this._inputNum].id}]}}),navigator.getUserMedia(this._constraints,function(t){this._onStream(t),e()}.bind(this),function(t){i(t)})},t.ExternalInput.prototype._onStream=function(t){if(!this.isFunction(this.context.createMediaStreamSource))throw new Error("Tone.ExternalInput: browser does not support the 'MediaStreamSourceNode'");this._stream||(this._stream=t,this._mediaStream=this.context.createMediaStreamSource(t),this._mediaStream.connect(this._gate))},t.ExternalInput.prototype.open=function(e,i){return e=this.defaultArg(e,t.noOp),i=this.defaultArg(i,t.noOp),t.ExternalInput.getSources(function(){this._getUserMedia(e,i)}.bind(this)),this},t.ExternalInput.prototype.close=function(){if(this._stream){var t=this._stream.getTracks()[this._inputNum];this.isUndef(t)||t.stop(),this._stream=null}return this},t.ExternalInput.prototype._start=function(t){return t=this.toSeconds(t),this._gate.gain.setValueAtTime(1,t),this},t.ExternalInput.prototype._stop=function(t){return t=this.toSeconds(t),this._gate.gain.setValueAtTime(0,t),this},t.ExternalInput.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),this.close(),this._mediaStream&&(this._mediaStream.disconnect(),this._mediaStream=null),this._constraints=null,this._gate.dispose(),this._gate=null,this},t.ExternalInput.sources=[],t.ExternalInput._canGetSources=!t.prototype.isUndef(window.MediaStreamTrack)&&t.prototype.isFunction(MediaStreamTrack.getSources),Object.defineProperty(t.ExternalInput,"supported",{get:function(){return t.prototype.isFunction(navigator.getUserMedia)}}),t.ExternalInput.getSources=function(e){return 0===t.ExternalInput.sources.length&&t.ExternalInput._canGetSources?MediaStreamTrack.getSources(function(i){for(var s=0;s0?t.State.Started:t.State.Stopped}}),Object.defineProperty(t.MultiPlayer.prototype,"mute",{get:function(){return this._volume.mute},set:function(t){this._volume.mute=t}}),t.MultiPlayer.prototype.dispose=function(){t.prototype.dispose.call(this),this._volume.dispose(),this._volume=null,this._writable("volume"),this.volume=null,this.buffers.dispose(),this.buffers=null;for(var e=0;e0&&(a=this._loopEnd),e=(2*Math.random()-1)*this.drift,i=this._offset-this._overlap+e,s=this.detune/100,n=this._player.fadeIn,this.loop&&this._offset>a?(o=this._offset-a,this._player.start(this.buffer,t,i,o+this._overlap,s),i=this._offset%a,this._offset=this._loopStart,this._player.fadeIn=0,this._player.start(this.buffer,t+o,this._offset,i+this._overlap,s)):this._offset>a?this.stop(t):(0>i&&(this._player.fadeIn=Math.max(this._player.fadeIn+i,0),i=0),this._player.start(this.buffer,t,i,this.grainSize+this._overlap,s)),this._player.fadeIn=n,r=this._clock._nextTick-t,this._offset+=r*this._playbackRate},t.GrainPlayer.prototype.scrub=function(t,e){return this._offset=this.toSeconds(t),this._tick(this.toSeconds(e)),this},Object.defineProperty(t.GrainPlayer.prototype,"playbackRate",{get:function(){return this._playbackRate},set:function(t){this._playbackRate=t,this.grainSize=this._grainSize}}),Object.defineProperty(t.GrainPlayer.prototype,"loopStart",{get:function(){return this._loopStart},set:function(t){this._loopStart=this.toSeconds(t)}}),Object.defineProperty(t.GrainPlayer.prototype,"loopEnd",{get:function(){return this._loopEnd},set:function(t){this._loopEnd=this.toSeconds(t)}}),Object.defineProperty(t.GrainPlayer.prototype,"reverse",{get:function(){return this.buffer.reverse},set:function(t){this.buffer.reverse=t}}),Object.defineProperty(t.GrainPlayer.prototype,"grainSize",{get:function(){return this._grainSize},set:function(t){this._grainSize=this.toSeconds(t),this._clock.frequency.value=this._playbackRate/this._grainSize}}),Object.defineProperty(t.GrainPlayer.prototype,"overlap",{get:function(){return this._overlap},set:function(t){t=this.toSeconds(t),this._overlap=t,this._overlap<0?(this._player.fadeIn=.01,this._player.fadeOut=.01):(this._player.fadeIn=t,this._player.fadeOut=t)}}),t.GrainPlayer.prototype.dispose=function(){return t.Source.prototype.dispose.call(this),this.buffer.dispose(),this.buffer=null,this._player.dispose(),this._player=null,this._clock.dispose(),this._clock=null,this},t.GrainPlayer}),e(function(t){return t.Microphone=function(){t.ExternalInput.call(this,0)},t.extend(t.Microphone,t.ExternalInput),Object.defineProperty(t.Microphone,"supported",{get:function(){return t.ExternalInput.supported}}),t.Microphone}),i}); \ No newline at end of file diff --git a/examples/analysis.html b/examples/analysis.html index d85a3dcaa..0e8e3ff44 100644 --- a/examples/analysis.html +++ b/examples/analysis.html @@ -10,7 +10,8 @@ - + + @@ -30,7 +31,7 @@
Analyser
- Tone.Analyser + Tone.Analyser analyses the incoming audio to produce a TypedArray of either the FFT data or the waveform. The default returnType is "byte" which returns values @@ -41,10 +42,10 @@ + + + + + + + + + + + + + +
+
Bembe
+
+ Tone.MetalSynth creates metallic, inharmonic sounds using 6 Tone.FMOscillators with a tuning based on the TR-808 Cymbal. Tone.MembraneSynth makes kick and tom-like sounds using a frequency envelope which is triggered on notes attack. +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/buses.html b/examples/buses.html index b232b41db..276b24384 100644 --- a/examples/buses.html +++ b/examples/buses.html @@ -10,8 +10,9 @@ + - + @@ -29,8 +30,8 @@ audio to a named bus from an instrument and then receive that channel on your effect.

- Docs on send and - receive. + Docs on send and + receive.
@@ -38,7 +39,7 @@ + - + @@ -45,7 +46,7 @@
Envelope
Envelopes ramp amplitude, frequency or any other parameter over time. - Tone.Envelope and the classes that extend it + Tone.Envelope and the classes that extend it implement an ADSR envelope type which splits its ramp into four distinct phases: Attack, Decay, Sustain, Release. diff --git a/examples/events.html b/examples/events.html index 19d0cf96d..31d2552a2 100644 --- a/examples/events.html +++ b/examples/events.html @@ -10,8 +10,9 @@ + - + @@ -24,13 +25,13 @@
Events
- Tone's Event classes (Tone.Event, - Tone.Loop, - Tone.Part and - Tone.Sequence) + Tone's Event classes (Tone.Event, + Tone.Loop, + Tone.Part and + Tone.Sequence) simplify scheduling events along the Transport. Each class abstracts away calls to - Transport.schedule or - scheduleRepeat + Transport.schedule or + scheduleRepeat and lets you create precise, rhythmic events which are startable, stoppable and loopable.
@@ -40,7 +41,7 @@ /* KICK */ - var kick = new Tone.DrumSynth({ + var kick = new Tone.MembraneSynth({ "envelope" : { "sustain" : 0, "attack" : 0.02, @@ -79,7 +80,7 @@ /** * PIANO */ - var piano = new Tone.PolySynth(4, Tone.SimpleSynth, { + var piano = new Tone.PolySynth(4, Tone.Synth, { "volume" : -8, "oscillator" : { "partials" : [1, 2, 1], diff --git a/examples/fmSynth.html b/examples/fmSynth.html index 993e4a2f4..f70edafda 100644 --- a/examples/fmSynth.html +++ b/examples/fmSynth.html @@ -2,7 +2,7 @@ - SimpleFM + FMSynth @@ -10,8 +10,9 @@ + - + @@ -26,30 +27,26 @@
FMSynth
- Tone.SimpleFM + Tone.FMSynth is composed of two - Tone.SimpleSynths - where one Tone.SimpleSynth modulates the frequency of a second Tone.SimpleSynth. + Tone.Synths + where one Tone.Synth modulates the frequency of a second Tone.Synth.
diff --git a/examples/funkyShape.html b/examples/funkyShape.html index 067c16c34..44516dd92 100644 --- a/examples/funkyShape.html +++ b/examples/funkyShape.html @@ -15,8 +15,9 @@ + - + diff --git a/examples/grainPlayer.html b/examples/grainPlayer.html new file mode 100644 index 000000000..afedf61cd --- /dev/null +++ b/examples/grainPlayer.html @@ -0,0 +1,103 @@ + + + + + GRAINPLAYER + + + + + + + + + + + + + + + + + +
+
Granular Synthesis
+
+ Tone.GrainPlayer uses + granular synthesis + to enable you to adjust pitch and playback rate independently. The grainSize is the + amount of time each small chunk of audio is played for and the overlap is the + amount of crossfading transition time between successive grains. +
+
+
+ + + + + \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 17a1c838a..3643f6fac 100644 --- a/examples/index.html +++ b/examples/index.html @@ -8,7 +8,7 @@ - + @@ -48,8 +48,8 @@ $(function(){ var topbar = $("
").attr("id", "TopBar"); $("body").prepend(topbar); - var logo = new Logo({ - "container" : topbar, + Logo({ + "container" : topbar.get(0), "height" : topbar.height() - 6, "width" : 140 }); diff --git a/examples/jump.html b/examples/jump.html new file mode 100644 index 000000000..1dacfeda8 --- /dev/null +++ b/examples/jump.html @@ -0,0 +1,285 @@ + + + + + FatOscillator + + + + + + + + + + + + + + + + + + +
+
Supersaw
+
+ Tone.FatOscillator creates multiple oscillators + and detunes them slightly from each other to thicken the sound. The count parameter sets + the number of oscillators and spread sets the total spread (in cents) between the oscillators. +

+ FatOscillator is also available in Tone.OmniOscillator + by prefixing another type with "fat", then use the count and spread to control the number and detune of the oscillators. To create a "supersaw": omniOscillator.type = "fatsawtooth". +

+ Jump by Van Halen MIDI converted using MidiConvert +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/lfoEffects.html b/examples/lfoEffects.html index 179a1e149..a5421e176 100644 --- a/examples/lfoEffects.html +++ b/examples/lfoEffects.html @@ -10,8 +10,9 @@ + - + @@ -23,11 +24,11 @@
LFO Effects
- These effects use an LFO (Low Frequency Oscillator) to modulate the effect. Click and drag the dot to change the frequency and depth of the effect. + These effects use an LFO (Low Frequency Oscillator) to modulate the effect. Click and drag the dot to change the frequency and depth of the effect.

- Docs on Tone.AutoPanner, - Tone.AutoFilter, and - Tone.Tremolo + Docs on Tone.AutoPanner, + Tone.AutoFilter, and + Tone.Tremolo
diff --git a/examples/meter.html b/examples/meter.html new file mode 100644 index 000000000..b48a9c00e --- /dev/null +++ b/examples/meter.html @@ -0,0 +1,115 @@ + + + + + Meter + + + + + + + + + + + + + + + + + +
+
Meter
+
+ Tone.Meter + gives you the level of the incoming signal (between 0-1). Values above 1 + are clipping. +
+ +
+ + + + + + \ No newline at end of file diff --git a/examples/mic.html b/examples/mic.html index a4af53527..bc829e053 100644 --- a/examples/mic.html +++ b/examples/mic.html @@ -10,8 +10,9 @@ + - + @@ -34,8 +35,7 @@
Microphone
- If supported, Tone.Microphone uses getUserMedia to open - the user's microphone where it can then be processed with Tone.js. + If supported, Tone.Microphone uses getUserMedia to open the user's microphone where it can then be processed with Tone.js. Note that WebRTC has been deprecated for non-https domains.
+ - + @@ -28,7 +29,7 @@
MonoSynth
- Tone.MonoSynth + Tone.MonoSynth is composed of one oscillator, one filter, and two envelopes. Both envelopes are triggered simultaneously when a note is triggered. diff --git a/examples/noises.html b/examples/noises.html index dcae9412e..931ae8380 100644 --- a/examples/noises.html +++ b/examples/noises.html @@ -10,8 +10,9 @@ + - + @@ -29,7 +30,7 @@
Noise
- Tone.Noise + Tone.Noise has 3 different types of noise. Careful, it's loud!
diff --git a/examples/oscillator.html b/examples/oscillator.html index 5303516fe..ab0a89b01 100644 --- a/examples/oscillator.html +++ b/examples/oscillator.html @@ -10,8 +10,9 @@ + - + @@ -24,9 +25,9 @@
Oscillator
- Click and drag the dot to hear the oscillator. The x-axis controls the freqency of the oscillator and the y-axis controls the volume. + Click and drag the dot to hear the oscillator. The x-axis controls the frequency of the oscillator and the y-axis controls the volume.

- Tone.Oscillator docs. + Tone.Oscillator docs.
diff --git a/examples/pianoPhase.html b/examples/pianoPhase.html index b1d719d11..de994341f 100644 --- a/examples/pianoPhase.html +++ b/examples/pianoPhase.html @@ -2,7 +2,7 @@ - SimpleSynth + Phasing @@ -10,7 +10,8 @@ - + + @@ -83,17 +84,17 @@ }; //left and right synthesizers - var synthL = new Tone.SimpleSynth(synthSettings).connect(merge.left); - var synthR = new Tone.SimpleSynth(synthSettings).connect(merge.right); + var synthL = new Tone.Synth(synthSettings).connect(merge.left); + var synthR = new Tone.Synth(synthSettings).connect(merge.right); //the two Tone.Sequences var partL = new Tone.Sequence(function(time, note){ synthL.triggerAttackRelease(note, "8n", time); - }, ["E4", "F#4", "B4", "C#5", "D5", "F#4", "E4", "C#5", "B4", "F#4", "C#5", "B4"], "8n").start(); + }, ["E4", "F#4", "B4", "C#5", "D5", "F#4", "E4", "C#5", "B4", "F#4", "D5", "C#5"], "8n").start(); var partR = new Tone.Sequence(function(time, note){ synthR.triggerAttackRelease(note, "8n", time); - }, ["E4", "F#4", "B4", "C#5", "D5", "F#4", "E4", "C#5", "B4", "F#4", "C#5", "B4"], "8n").start("2m"); + }, ["E4", "F#4", "B4", "C#5", "D5", "F#4", "E4", "C#5", "B4", "F#4", "D5", "C#5"], "8n").start("2m"); //set the playback rate of the right part to be slightly slower partR.playbackRate = 0.985; diff --git a/examples/pingPongDelay.html b/examples/pingPongDelay.html index b151423fd..76da428a8 100644 --- a/examples/pingPongDelay.html +++ b/examples/pingPongDelay.html @@ -10,8 +10,9 @@ + - + @@ -32,7 +33,7 @@
A Ping Pong Delay is a stereo feedback delay where the delay bounces back and forth between the left and right channels. Hit the button to trigger a snare sample into the effect.

- Tone.PingPongDelay docs. + Tone.PingPongDelay docs.
+ - + @@ -25,7 +26,7 @@
Grains
Click on the button to play short looped section of the audio file - using Tone.Player. + using Tone.Player.
+ - + @@ -28,7 +29,7 @@
PolySynth
- Tone.PolySynth + Tone.PolySynth handles voice creation and allocation for any instruments passed in as the second parameter. PolySynth is not a synthesizer by itself, it merely manages voices of @@ -40,7 +41,7 @@ - + + @@ -36,7 +37,7 @@
- + + @@ -31,7 +32,7 @@
rampTo
- In Tone.js, many of a class' members are Tone.Signals. + In Tone.js, many of a class' members are Tone.Signals. Working with signals is different than working with numbers or strings: Signals are values which are updated at audio rate, which allows for sample-accurate scheduling and ramping. .rampTo(value, rampTime) diff --git a/examples/require.html b/examples/require.html index f3fe86a83..f03eb6682 100644 --- a/examples/require.html +++ b/examples/require.html @@ -4,6 +4,7 @@ MODULE LOADERS + @@ -38,9 +39,9 @@ } }); - require(["Tone/core/Master", "Tone/instrument/SimpleSynth"], - function(Master, SimpleSynth){ - var synth = new SimpleSynth().toMaster(); + require(["Tone/core/Master", "Tone/instrument/Synth"], + function(Master, Synth){ + var synth = new Synth().toMaster(); synth.triggerAttackRelease("C4", "8n"); }); diff --git a/examples/scripts/ExampleList.js b/examples/scripts/ExampleList.js index 5707a95cb..e891dbefd 100644 --- a/examples/scripts/ExampleList.js +++ b/examples/scripts/ExampleList.js @@ -7,10 +7,13 @@ var ExampleList = { "Microphone" : "mic" }, "Instruments" : { - "SimpleSynth" : "simpleSynth", + "Synth" : "simpleSynth", "MonoSynth" : "monoSynth", "FMSynth" : "fmSynth", "PolySynth" : "polySynth", + "FatOscillator" : "jump", + "MetalSynth" : "bembe", + "Granular Synthesis" : "grainPlayer", }, "Effects" : { "LFO Effects" : "lfoEffects", @@ -21,7 +24,6 @@ var ExampleList = { "Step Sequencer" : "stepSequencer", "Events" : "events", "Play Along" : "shiny", - "Visualizing Envelopes": "funkyShape", "Quantization" : "quantization", "Playback Rate" : "pianoPhase", }, @@ -29,8 +31,12 @@ var ExampleList = { "Control Voltage" : "signal", "Ramping Values" : "rampTo", }, + "Visualization" : { + "Envelopes": "funkyShape", + "Analysis" : "analysis", + "Meter" : "meter" + }, "Misc" : { "Module Loaders" : "require", - "Analysis" : "analysis" }, }; \ No newline at end of file diff --git a/examples/scripts/Interface.js b/examples/scripts/Interface.js index f40d51602..d43a6cf8e 100644 --- a/examples/scripts/Interface.js +++ b/examples/scripts/Interface.js @@ -1,4 +1,4 @@ -/* globals Tone */ +/* globals Tone, StartAudioContext */ var Interface = { @@ -16,8 +16,8 @@ $(function(){ $("body").prepend(topbar); if (typeof Tone !== "undefined"){ - var logo = new Logo({ - "container" : topbar, + Logo({ + "container" : topbar.get(0), "height" : topbar.height() - 6, "width" : 140 }); @@ -33,14 +33,12 @@ $(function(){ Interface.isMobile = true; $("body").addClass("Mobile"); var element = $("
", {"id" : "MobileStart"}).appendTo("body"); - $("
").attr("id", "Button") - .text("Enter") - .on("touchend", function(e){ - e.preventDefault(); - Tone.startMobile(); - element.remove(); - }) - .appendTo(element); + var button = $("
").attr("id", "Button").text("Enter").appendTo(element); + StartAudioContext.setContext(Tone.context); + StartAudioContext.on(button); + StartAudioContext.onStarted(function(){ + element.remove(); + }); } }); diff --git a/examples/scripts/Logo.js b/examples/scripts/Logo.js deleted file mode 100644 index 682419dc8..000000000 --- a/examples/scripts/Logo.js +++ /dev/null @@ -1 +0,0 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("jQuery")):"function"==typeof define&&define.amd?define(["jQuery"],e):"object"==typeof exports?exports.Logo=e(require("jQuery")):t.Logo=e(t.jQuery)}(this,function(t){return function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return t[o].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){var o,r;o=[n(6),n(5),n(1)],r=function(t,e,n){var o=256,r=n(o).random,i="function"==typeof window.Tone,s=function(e){for(var n in s.defaults)"undefined"==typeof e[n]&&(e[n]=s.defaults[n]);this.element=t("
",{id:"TonejsLogo"}).appendTo(e.container).on("click",function(t){t.preventDefault(),window.location.href="http://tonejs.org"}),this.textContainer=t("
",{id:"TextContainer"}).appendTo(this.element),this.canvas=t("",{id:"Canvas"}).appendTo(this.textContainer),this.context=this.canvas.get(0).getContext("2d"),this.title=t("
",{id:"Title"}).appendTo(this.textContainer).html("Tone.js"),i&&(this.analyser=new Tone.Analyser({size:o,type:"waveform",returnType:"byte"}),this._signal=new Tone.Signal(0).connect(this.analyser),Tone.Master.connect(this.analyser)),this._silentThresh=.01,this._rms=0,this.resize(e.width,e.height),i?this._draw():this._drawBuffer(r,!0)};return s.defaults={container:"body",width:300,height:80},s.prototype.resize=function(t,e){return this.element.width(t),this.element.height(e),this.context.canvas.width=2*this.canvas.height(),this.context.canvas.height=2*this.canvas.height(),this.title.css({"line-height":(.85*e).toString()+"px","font-size":.88*e}),this.canvas.css({"border-radius":e/50,width:this.canvas.height(),height:this.canvas.height()}),this},s.prototype._draw=function(){requestAnimationFrame(this._draw.bind(this));var t=this.analyser.analyse();this._isSilent(t)?this._drawBuffer(r,!0):this._drawBuffer(t,!1)},s.prototype._drawBuffer=function(t,e){var n=this.context,o=this.context.canvas.width,r=this.context.canvas.height;e?margin=this._scale(this._rms,0,this._silentThresh,.2*r,.5*r):margin=.2*r,n.clearRect(0,0,o,r),n.beginPath();for(var i,s=0,a=t.length;a>s;s++){var h=this._scale(s,0,a-1,0,o),l=this._scale(t[s],0,255,r-margin,margin);0===s?(i=l,n.moveTo(h,l)):n.lineTo(h,l)}n.lineTo(o,r),n.lineTo(0,r),n.lineTo(0,i),n.lineCap="round",n.fillStyle="#22DBC0",n.fill()},s.prototype._isSilent=function(t){for(var e=0,n=0;ne;e++)n[e]=128*(Math.sin(2*Math.PI*e/255)+1);for(e=0;t>e;e++)r[e]=(e+t/2)%t/t*255;for(e=0;t>e;e++)t/4>e?i[e]=e/(t/4)*127+128:.75*t>e?i[e]=255*(1-(e-t/4)/(t/2)):i[e]=(e-.75*t)/(t/4)*127;for(e=0;t>e;e++){var a=t/16;a>e?o[e]=0:t/2>e?o[e]=255:t-a>e?o[e]=0:o[e]=255}var h=s[Math.floor(Math.random()*s.length)];return{sawtooth:r,sine:n,triangle:i,square:o,random:h}}}.call(e,n,e,t),!(void 0!==o&&(t.exports=o))},function(t,e,n){e=t.exports=n(3)(),e.push([t.id,"@import url(https://fonts.googleapis.com/css?family=Roboto+Mono);",""]),e.push([t.id,"#TonejsLogo{background-color:#000;cursor:pointer}#TonejsLogo,#TonejsLogo #Border,#TonejsLogo #Canvas,#TonejsLogo #Title{position:absolute}#TonejsLogo #TextContainer{position:absolute;width:auto;-webkit-transform:translate(-50%, 0px);-ms-transform:translate(-50%, 0px);transform:translate(-50%, 0px);left:50%;height:100%}#TonejsLogo #TextContainer #Title{position:relative;display:inline-block;font-family:Roboto Mono,monospace;color:#fff;text-align:center;height:100%;top:0;width:100%;font-weight:400}#TonejsLogo #TextContainer #Title .Closer{margin:-3%}#TonejsLogo #TextContainer #Canvas{position:absolute;height:100%;top:0;border-radius:2%;z-index:0;right:0;width:10px;background-color:#f734d7}",""])},function(t,e){t.exports=function(){var t=[];return t.toString=function(){for(var t=[],e=0;e + - + @@ -47,23 +48,14 @@ }); //hats - var hats = new Tone.Sampler("./audio/505/hh.mp3", { + var hats = new Tone.Sampler({ + "url" : "./audio/505/hh.mp3", "volume" : -10, "envelope" : { "attack" : 0.001, "decay" : 0.02, "sustain" : 0.01, "release" : 0.01 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.02, - "sustain" : 1, - "baseFrequency" : 6000, - "octaves" : -3.3 - }, - "filter" : { - "type" : "highpass" } }).chain(distortion, drumCompress); @@ -76,26 +68,20 @@ }).start("1m"); //SNARE PART - var snare = new Tone.Sampler("./audio/505/snare.mp3", { + var snare = new Tone.Sampler({ + "url" : "./audio/505/snare.mp3", "envelope" : { "attack" : 0.01, "decay" : 0.05, "sustain" : 0 }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.01, - "sustain" : 0, - "baseFrequency" : 3000, - "octaves" : 2 - }, }).chain(distortion, drumCompress); var snarePart = new Tone.Sequence(function(time, velocity){ snare.triggerAttackRelease(0, "8n", time, velocity); }, [null, 1, null, [1, 0.3]]).start(0); - var kick = new Tone.DrumSynth({ + var kick = new Tone.MembraneSynth({ "pitchDecay" : 0.01, "octaves" : 6, "oscillator" : { @@ -115,7 +101,7 @@ }, [1, [1, [null, 0.3]], 1, [1, [null, 0.5]], 1, 1, 1, [1, [null, 0.8]]], "2n").start(0); // BASS - var bass = new Tone.SimpleFM({ + var bass = new Tone.FMSynth({ "harmonicity" : 1, "modulationIndex" : 3.5, "carrier" : { diff --git a/examples/signal.html b/examples/signal.html index f4340b387..9e4548eda 100644 --- a/examples/signal.html +++ b/examples/signal.html @@ -10,7 +10,8 @@ - + + @@ -33,8 +34,8 @@ This example applies a series of mappings to a signal value and applies the results of those mappings to the frequency attribute of 2 - Tone.Oscillators - and a Tone.LFO. + Tone.Oscillators + and a Tone.LFO.
+ - + @@ -37,12 +38,12 @@ }
-
SimpleSynth
+
Synth
- Tone.SimpleSynth is composed simply of a - Tone.OmniOscillator + Tone.Synth is composed simply of a + Tone.OmniOscillator routed through a - Tone.AmplitudeEnvelope. + Tone.AmplitudeEnvelope.
@@ -50,7 +51,7 @@ - + + @@ -30,36 +31,34 @@
Tone.Transport
- Tone.Transport - is the application-wide timekeeper. It uses an OscillatorNode - as it's clock source which enables sample-accurate scheduling as well as - tempo-curves and automation. This example uses Tone.Transport.setInterval - to invoke a callback every 16th note. + Tone.Transport + is the application-wide timekeeper. It's clock source enables sample-accurate scheduling as well as tempo-curves and automation. This example uses Tone.Sequence to invoke a callback every 16th note.