diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index b185ad72a..828b7af5a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,8 +35,15 @@ examples/crashes.html examples/style/examples.css.map -examples/style/examples.scss - examples/deps/Tone.dat.gui.js examples/deps/dat.gui.js + +test/mainTest.js + +test/Main.js + +build/p5.Tone.min.js +build/p5.Tone.js + +.DS_Store \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index dbf0f115f..7561cf848 100644 --- a/.jshintrc +++ b/.jshintrc @@ -19,7 +19,8 @@ "AudioNode" : false, "AudioParam" : false, "WaveShaperNode" : false, - "DynamicsCompressorNode" : false + "DynamicsCompressorNode" : false, + "MediaStreamTrack" : false }, "jquery" : true, "globalstrict": false, diff --git a/CHANGELOG.md b/CHANGELOG.md index e58c74023..18aa1be61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +### r6 + +* Added PitchShift and Vibrato Effect. +* Added Timeline/TimelineState/TimelineSignal which keeps track of all scheduled state changes. +* Clock uses requestAnimationFrame instead of ScriptProcessorNode +* Removed `onended` event from Tone.Source +* Refactored tests into individual files. +* Renamed some Signal methods: `exponentialRampToValueNow`->`exponentialRampToValue`, `setCurrentValueNow`->`setRampPoint` +* LFO no longer starts at bottom of cycle. Starts at whatever phase it's set at. +* Transport is an event emitter. triggers events on "start", "stop", "pause", and "loop". +* Oscillator accepts a "partials" array. +* Microphone inherits from ExternalInput which is generalized for different inputs. +* New scheduling methods on Transport - `schedule`, `scheduleOnce`, and `scheduleRepeat`. +* Tone.Gain and Tone.Delay classes wrap the native Web Audio nodes. +* Moved [MidiToScore](https://github.com/Tonejs/MidiConvert) and [TypeScript](https://github.com/Tonejs/TypeScript) definitions to separate repos. +* Tone.Param wraps the native AudioParam and allows for unit conversion. +* Quantization with Transport.quantize and using "@" in any Time. [Read more](https://github.com/Tonejs/Tone.js/wiki/Time). +* Control-rate generators for value interpolation, patterns, random numbers, and markov chains. +* Scheduable musical events: Tone.Event, Tone.Loop, Tone.Part, Tone.Pattern, Tone.Sequence. +* Player's playbackRate is now a signal and Noise includes a playbackRate signal. +* All filterEnvelopes use new Tone.FrequencyEnvelope with frequency units and `baseFrequency` and `octaves` instead of `min` and `max`. +* Phaser uses "octaves" instead of "depth" to be more consistent across the whole Tone.js API. +* Presets now have [their own repo](https://github.com/Tonejs/Presets) + +DEPRECATED: +* `setTimeout`, `setInterval`, `setTimeline` in favor of new `schedule`, `scheduleOnce`, and `scheduleRepeat`. +* Tone.Signal no longer takes an AudioParam in the first argument. Use Tone.Param instead. +* Tone.Buffer.onload/onprogress/onerror is deprecated. Use `Tone.Buffer.on("load", callback)` instead. + ### r5 * reverse buffer for Player and Sampler. @@ -16,9 +45,9 @@ * expose Q in Phaser. * unit conversions using Tone.Type for signals and LFO. * [new docs](http://tonejs.org/docs) -* [updated examples](http://tonejs.org/docs) +* [updated examples](http://tonejs.org/examples) -### r4 - Cool is cool +### r4 * `toFrequency` accepts notes by name (i.e. `"C4"`) * Envelope no longer accepts exponential scaling, only Tone.ScaledEnvelope @@ -49,7 +78,7 @@ Or if setBpm was being invoked with a rampTime: find `Tone.Transport.setBpm\((\d+)\, (\d+)\)` and replace it with `Tone.Transport.bpm.rampTo($1, $2)`. -### r3 - Expressive Signal +### r3 Core Change: @@ -84,7 +113,7 @@ Synths: * NoiseSynth -### r2 - Getting Physical +### r2 * PluckSynth - Karplus-Strong Plucked String modeling synth * Freeverb diff --git a/README.md b/README.md index 4700c530e..7c752a304 100644 --- a/README.md +++ b/README.md @@ -10,36 +10,32 @@ Tone.js is a Web Audio framework for creating interactive music in the browser. # Demos * [Jazz.Computer - Yotam Mann](http://jazz.computer/) -* [motionEmotion - emotion & gesture-based arpeggiator and synthesizer](http://motionemotion.herokuapp.com/) -* [p5.sound - built with Tone.js](https://github.com/processing/p5.js-sound) -* [Hypercube by @eddietree](http://eddietree.github.io/hypercube/) -* [randomcommander.io by Jake Albaugh](http://randomcommander.io/) -* [Tone.js + NexusUI by taylorbf](http://taylorbf.github.io/Tone-Rack/) +* [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/) +* [randomcommander.io - Jake Albaugh](http://randomcommander.io/) +* [Tone.js + NexusUI - Ben Taylor](http://taylorbf.github.io/Tone-Rack/) * [Solarbeat - Luke Twyman](http://www.whitevinyldesign.com/solarbeat/) -* [João Costa - Wind](http://wind.joaocosta.co) -* [Abe Rubenstein - Block Chords](http://dev.abe.sh/block-chords/) +* [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/) Using Tone.js? I'd love to hear it: yotammann@gmail.com # Installation -Tone can be installed in a few of ways: - -* Download Tone.js from Github - [full](https://raw.githubusercontent.com/TONEnoTONE/Tone.js/master/build/Tone.js) | [min](https://raw.githubusercontent.com/TONEnoTONE/Tone.js/master/build/Tone.min.js) +* CDN - [full](http://cdn.tonejs.org/latest/Tone.js) | [min](http://cdn.tonejs.org/latest/Tone.min.js) * [bower](http://bower.io/) - `bower install tone` * [npm](https://www.npmjs.org/) - `npm install tone` -The fastest way to include Tone.js on your page is to use the CDN (not for production use, please): - -```html - -``` - -It's always much safer to use a specific version rather than just "latest". - -[Full Installation Instruction](https://github.com/TONEnoTONE/Tone.js/wiki/Installation) +[Full Installation Instruction](https://github.com/Tonejs/Tone.js/wiki/Installation) -# Hello World +# Hello Tone ```javascript //create one of Tone's built-in synthesizers and connect it to the master output @@ -49,52 +45,140 @@ var synth = new Tone.SimpleSynth().toMaster(); synth.triggerAttackRelease("C4", "8n"); ``` -# Tone.Transport +[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. -A unique feature of the library is the oscillator-based Transport which allows for application-wide synchronization of sources and signals. The Transport allows you to register callbacks at precise moments along the timeline which are invoked right before the event with the exact time of the event. Additionally, because the Transport is implemented with an oscillator, it is capable of elaborate tempo curves and automation. +### Time -[Read more](https://github.com/TONEnoTONE/Tone.js/wiki/Transport). +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: -### Time +```javascript +synth.triggerAttackRelease("C4", "8n", "+1m"); +``` -In the Tone library, time can be described in a number of ways. Any method which takes a time as a parameter will accept the number in seconds as well as a tempo-relative form. +[Read about Time encodings.](https://github.com/Tonejs/Tone.js/wiki/Time) -For example to `"4n"` is a quarter-note and `"4:2:0"` is the third beat of the fifth measure (remember we're counting from 0). +### Transport -[Read more Time](https://github.com/TONEnoTONE/Tone.js/wiki/Time). +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. -# Sources +```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) + +### 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. -Aside from the 4 basic oscillator types (sine, square, triangle, sawtooth), Tone.js provides a few other sources such as a buffer player (Tone.Player), a noise generator, and two additional oscillator types (pwm, pulse). +```javascript +//play a note every quarter-note +var loop = new Tone.Loop(function(time){ + synth.triggerAttackRelease("C2", "8n", time); +}, "4n"); + +//loop between the first and fourth measures of the Transport's timeline +loop.start("1m").stop("4m"); +``` -[Read more](https://github.com/TONEnoTONE/Tone.js/wiki/Sources). +Start the Transport to hear the looped notes: + +```javascript +Transport.start(); +``` + +[Read about Tone.js' Event classes.](https://github.com/Tonejs/Tone.js/wiki/Events) # Instruments -Tone has a few prebuilt synthesizers. [Read more about their common interface](https://github.com/TONEnoTONE/Tone.js/wiki/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. + +```javascript +//pass in some initial values for the filter and filter envelope +var monoSynth = new Tone.MonoSynth({ + "filter" : { + "type" : "lowpass", + "Q" : 7 + }, + "filterEnvelope" : { + "attack" : 0.02, + "decay" : 0.1, + "sustain" : 0.2, + "release" : 0.9, + } +}).toMaster(); + +//start the note "D3" one second from now +monoSynth.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). + +```javascript +//a 4 voice MonoSynth +var polySynth = new Tone.PolySynth(4, Tone.MonoSynth).toMaster(); +//play a chord +polySimpleSynth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "2n"); +``` + +[Read more about Instruments.](https://github.com/Tonejs/Tone.js/wiki/Instruments) # Effects -Tone.js also has a few stereo and mono effects some of which also have their own presets. [Read more about using effects](https://github.com/TONEnoTONE/Tone.js/wiki/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. + +```javascript +//create a distortion effect +var distortion = new Tone.Distortion(0.4).toMaster(); +//connect a synth to the distortion +synth.connect(distortion); +``` + +[Read more about Effects](https://github.com/Tonejs/Tone.js/wiki/Effects) + +# 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)). + +```javascript +//a pwm oscillator which is connected to the speaker and started right away +var pwm = new Tone.PWMOscillator("Bb3").toMaster().start(); +``` + +[Read more](https://github.com/Tonejs/Tone.js/wiki/Sources) # Signals -Like the underlying Web Audio API, Tone.js is built with audio-rate signal control over nearly everything. This is a powerful feature which allows for sample-accurate synchronization of multiple parameters with a single signal. Signals are built entirely without the ScriptProcessorNode so they do not introduce much latency and processing overhead. Instead, all signal math and logic let GainNodes and WaveShaperNodes do all of the work so that all processing is done in the underlying Assembly/C/C++ provided by the API. Signals are used extensively internally and are also useful for general DSP and control signal logic and transformations. +Like the underlying Web Audio API, Tone.js is built with audio-rate signal control over nearly everything. This is a powerful feature which allows for sample-accurate synchronization of multiple parameters with a single signal. Signals are built entirely without the ScriptProcessorNode so they do not introduce minimal processing overhead and no latency. Instead, this signal math and logic lets the native Web Audio GainNodes and WaveShaperNodes do all of the work meaning all processing is done in the underlying Assembly/C/C++ provided by the API. Signals are used extensively internally and are also useful for general DSP and control signal logic and transformations. -Read more about [signals](https://github.com/TONEnoTONE/Tone.js/wiki/Signals). +[Read more](https://github.com/Tonejs/Tone.js/wiki/Signals) # AudioContext -Tone.js creates an AudioContext when it loads and shims it for maximum browser compatibility. The AudioContext can be found at `Tone.context` or from within any Object extending Tone as `this.context`. +Tone.js creates an AudioContext when it loads and shims it for maximum browser compatibility. The AudioContext can be found at `Tone.context`. Or set your own AudioContext using `Tone.setContext(audioContext)`. + +# MIDI -Tone also let's you set your own AudioContext using `Tone.setContext`. +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/). # Performance -Tone.js uses very few ScriptProcessorNodes. Nearly all of the Tone Modules find a native Web Audio component workaround, making extensive use of the GainNode and WaveShaperNode especially, which enables Tone.js to work well on both desktop and mobile browsers. While the ScriptProcessorNode is extremely powerful, it introduces a lot of latency and the potential for glitches more than any other node. +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. + +# 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. # References and Inspiration * [Tuna.js](https://github.com/Dinahmoe/tuna) * [Many of Chris Wilson's Repositories](https://github.com/cwilso) * [The Spec](http://webaudio.github.io/web-audio-api/) +* [Sound on Sound - Synth Secrets](http://www.soundonsound.com/sos/may99/articles/synthsec.htm) +* [Miller Puckette - Theory and Techniques of Electronic Music](http://msp.ucsd.edu/techniques.htm) diff --git a/Tone/component/AmplitudeEnvelope.js b/Tone/component/AmplitudeEnvelope.js index 64d70c47e..d1608fac4 100644 --- a/Tone/component/AmplitudeEnvelope.js +++ b/Tone/component/AmplitudeEnvelope.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/component/Envelope"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/Envelope", "Tone/core/Gain"], function(Tone){ "use strict"; @@ -38,12 +38,23 @@ define(["Tone/core/Tone", "Tone/component/Envelope"], function(Tone){ * @type {GainNode} * @private */ - this.input = this.output = this.context.createGain(); + this.input = this.output = new Tone.Gain(); this._sig.connect(this.output.gain); }; Tone.extend(Tone.AmplitudeEnvelope, Tone.Envelope); + /** + * Clean up + * @return {Tone.AmplitudeEnvelope} this + */ + Tone.AmplitudeEnvelope.prototype.dispose = function(){ + this.input.dispose(); + this.input = null; + Tone.Envelope.prototype.dispose.call(this); + return this; + }; + return Tone.AmplitudeEnvelope; }); \ No newline at end of file diff --git a/Tone/component/Analyser.js b/Tone/component/Analyser.js new file mode 100644 index 000000000..38eb08e32 --- /dev/null +++ b/Tone/component/Analyser.js @@ -0,0 +1,225 @@ +define(["Tone/core/Tone"], function (Tone) { + + "use strict"; + + /** + * @class Wrapper around the native Web Audio's + * [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode). + * Extracts FFT or Waveform data from the incoming signal. + * @extends {Tone} + * @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); + + /** + * The analyser node. + * @private + * @type {AnalyserNode} + */ + this._analyser = this.input = this.context.createAnalyser(); + + /** + * The analysis type + * @type {String} + * @private + */ + this._type = options.type; + + /** + * The return type of the analysis + * @type {String} + * @private + */ + this._returnType = options.returnType; + + /** + * The buffer that the FFT data is written to + * @type {TypedArray} + * @private + */ + this._buffer = null; + + //set the values initially + this.size = options.size; + this.type = options.type; + this.returnType = options.returnType; + this.minDecibels = options.minDecibels; + this.maxDecibels = options.maxDecibels; + }; + + Tone.extend(Tone.Analyser); + + /** + * The default values. + * @type {Object} + * @const + */ + Tone.Analyser.defaults = { + "size" : 2048, + "returnType" : "byte", + "type" : "fft", + "smoothing" : 0.8, + "maxDecibels" : -30, + "minDecibels" : -100 + }; + + /** + * Possible return types of Tone.Analyser.value + * @enum {String} + */ + Tone.Analyser.Type = { + Waveform : "waveform", + FFT : "fft" + }; + + /** + * Possible return types of Tone.Analyser.value + * @enum {String} + */ + Tone.Analyser.ReturnType = { + Byte : "byte", + Float : "float" + }; + + /** + * Run the analysis given the current settings and return the + * result as a TypedArray. + * @returns {TypedArray} + */ + Tone.Analyser.prototype.analyse = function(){ + if (this._type === Tone.Analyser.Type.FFT){ + if (this._returnType === Tone.Analyser.ReturnType.Byte){ + this._analyser.getByteFrequencyData(this._buffer); + } else { + this._analyser.getFloatFrequencyData(this._buffer); + } + } else if (this._type === Tone.Analyser.Type.Waveform){ + if (this._returnType === Tone.Analyser.ReturnType.Byte){ + this._analyser.getByteTimeDomainData(this._buffer); + } else { + this._analyser.getFloatTimeDomainData(this._buffer); + } + } + return this._buffer; + }; + + /** + * The size of analysis. This must be a power of two in the range 32 to 32768. + * @memberOf Tone.Analyser# + * @type {Number} + * @name size + */ + Object.defineProperty(Tone.Analyser.prototype, "size", { + get : function(){ + return this._analyser.frequencyBinCount; + }, + set : function(size){ + this._analyser.fftSize = size * 2; + this.type = this._type; + } + }); + + /** + * The return type of Tone.Analyser.value, 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. + * @memberOf Tone.Analyser# + * @type {String} + * @name type + */ + Object.defineProperty(Tone.Analyser.prototype, "returnType", { + get : function(){ + return this._returnType; + }, + set : function(type){ + if (type === Tone.Analyser.ReturnType.Byte){ + this._buffer = new Uint8Array(this._analyser.frequencyBinCount); + } else if (type === Tone.Analyser.ReturnType.Float){ + this._buffer = new Float32Array(this._analyser.frequencyBinCount); + } else { + throw new Error("Invalid Return Type: "+type); + } + this._returnType = type; + } + }); + + /** + * The analysis function returned by Tone.Analyser.value, either "fft" or "waveform". + * @memberOf Tone.Analyser# + * @type {String} + * @name type + */ + Object.defineProperty(Tone.Analyser.prototype, "type", { + get : function(){ + return this._type; + }, + set : function(type){ + if (type !== Tone.Analyser.Type.Waveform && type !== Tone.Analyser.Type.FFT){ + throw new Error("Invalid Type: "+type); + } + this._type = type; + } + }); + + /** + * 0 represents no time averaging with the last analysis frame. + * @memberOf Tone.Analyser# + * @type {NormalRange} + * @name smoothing + */ + Object.defineProperty(Tone.Analyser.prototype, "smoothing", { + get : function(){ + return this._analyser.smoothingTimeConstant; + }, + set : function(val){ + this._analyser.smoothingTimeConstant = val; + } + }); + + /** + * The smallest decibel value which is analysed by the FFT. + * @memberOf Tone.Analyser# + * @type {Decibels} + * @name minDecibels + */ + Object.defineProperty(Tone.Analyser.prototype, "minDecibels", { + get : function(){ + return this._analyser.minDecibels; + }, + set : function(val){ + this._analyser.minDecibels = val; + } + }); + + /** + * The largest decibel value which is analysed by the FFT. + * @memberOf Tone.Analyser# + * @type {Decibels} + * @name maxDecibels + */ + Object.defineProperty(Tone.Analyser.prototype, "maxDecibels", { + get : function(){ + return this._analyser.maxDecibels; + }, + set : function(val){ + this._analyser.maxDecibels = val; + } + }); + + /** + * Clean up. + * @return {Tone.Analyser} this + */ + Tone.Analyser.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._analyser.disconnect(); + this._analyser = null; + this._buffer = null; + }; + + return Tone.Analyser; +}); \ No newline at end of file diff --git a/Tone/component/Compressor.js b/Tone/component/Compressor.js index 84c3beefc..2a8b43ec3 100644 --- a/Tone/component/Compressor.js +++ b/Tone/component/Compressor.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/core/Param"], function(Tone){ "use strict"; @@ -39,14 +39,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * @type {Time} * @signal */ - this.attack = new Tone.Signal(this._compressor.attack, Tone.Type.Time); + this.attack = new Tone.Param(this._compressor.attack, Tone.Type.Time); /** * The release parameter * @type {Time} * @signal */ - this.release = new Tone.Signal(this._compressor.release, Tone.Type.Time); + this.release = new Tone.Param(this._compressor.release, Tone.Type.Time); /** * The knee parameter diff --git a/Tone/component/EQ3.js b/Tone/component/EQ3.js index 351a8ba52..da63f7791 100644 --- a/Tone/component/EQ3.js +++ b/Tone/component/EQ3.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/core/Gain"], function(Tone){ "use strict"; @@ -37,46 +37,46 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] }); /** - * the low gain - * @type {GainNode} + * The gain for the lower signals + * @type {Tone.Gain} * @private */ - this._lowGain = this.context.createGain(); + this._lowGain = new Tone.Gain(options.low, Tone.Type.Decibels); /** - * the mid gain - * @type {GainNode} + * The gain for the mid signals + * @type {Tone.Gain} * @private */ - this._midGain = this.context.createGain(); + this._midGain = new Tone.Gain(options.mid, Tone.Type.Decibels); /** - * the high gain - * @type {GainNode} - * @private + * The gain in decibels of the high part + * @type {Tone.Gain} + * @private */ - this._highGain = this.context.createGain(); + this._highGain = new Tone.Gain(options.high, Tone.Type.Decibels); /** * The gain in decibels of the low part * @type {Decibels} * @signal */ - this.low = new Tone.Signal(this._lowGain.gain, Tone.Type.Decibels); + this.low = this._lowGain.gain; /** * The gain in decibels of the mid part * @type {Decibels} * @signal */ - this.mid = new Tone.Signal(this._midGain.gain, Tone.Type.Decibels); + this.mid = this._midGain.gain; /** * The gain in decibels of the high part * @type {Decibels} * @signal */ - this.high = new Tone.Signal(this._highGain.gain, Tone.Type.Decibels); + this.high = this._highGain.gain; /** * The Q value for all of the filters. @@ -103,10 +103,6 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] this._multibandSplit.low.chain(this._lowGain, this.output); this._multibandSplit.mid.chain(this._midGain, this.output); this._multibandSplit.high.chain(this._highGain, this.output); - //set the gains - this.low.value = options.low; - this.mid.value = options.mid; - this.high.value = options.high; this._readOnly(["low", "mid", "high", "lowFrequency", "highFrequency"]); }; @@ -134,17 +130,14 @@ define(["Tone/core/Tone", "Tone/component/MultibandSplit", "Tone/signal/Signal"] this._multibandSplit = null; this.lowFrequency = null; this.highFrequency = null; - this._lowGain.disconnect(); + this._lowGain.dispose(); this._lowGain = null; - this._midGain.disconnect(); + this._midGain.dispose(); this._midGain = null; - this._highGain.disconnect(); + this._highGain.dispose(); this._highGain = null; - this.low.dispose(); this.low = null; - this.mid.dispose(); this.mid = null; - this.high.dispose(); this.high = null; this.Q = null; return this; diff --git a/Tone/component/Envelope.js b/Tone/component/Envelope.js index e6d039adb..21726456f 100644 --- a/Tone/component/Envelope.js +++ b/Tone/component/Envelope.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/TimelineSignal", + "Tone/signal/Pow", "Tone/core/Type"], function(Tone){ "use strict"; @@ -63,71 +64,38 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton */ this.release = options.release; - /** - * the next time the envelope is attacked - * @type {number} - * @private - */ - this._nextAttack = Infinity; - - /** - * the next time the envelope is decayed - * @type {number} - * @private - */ - this._nextDecay = Infinity; - - /** - * the next time the envelope is sustain - * @type {number} - * @private - */ - this._nextSustain = Infinity; - - /** - * the next time the envelope is released - * @type {number} - * @private - */ - this._nextRelease = Infinity; - /** * the next time the envelope is at standby * @type {number} * @private */ - this._nextStandby = Infinity; + this._attackCurve = Tone.Envelope.Type.Linear; /** * the next time the envelope is at standby * @type {number} * @private */ - this._attackCurve = Tone.Envelope.Type.Linear; - - /** - * the last recorded velocity value - * @type {number} - * @private - */ - this._peakValue = 1; + this._releaseCurve = Tone.Envelope.Type.Exponential; /** * the minimum output value * @type {number} * @private */ - this._minOutput = 0.0001; + this._minOutput = 0.00001; /** * the signal - * @type {Tone.Signal} + * @type {Tone.TimelineSignal} * @private */ - this._sig = this.output = new Tone.Signal(0); + this._sig = this.output = new Tone.TimelineSignal(); + this._sig.setValueAtTime(this._minOutput, 0); //set the attackCurve initially this.attackCurve = options.attackCurve; + this.releaseCurve = options.releaseCurve; }; Tone.extend(Tone.Envelope); @@ -142,7 +110,8 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton "decay" : 0.1, "sustain" : 0.5, "release" : 1, - "attackCurve" : "linear" + "attackCurve" : "linear", + "releaseCurve" : "exponential", }; /** @@ -189,79 +158,26 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton }); /** - * Get the phase of the envelope at the specified time. - * @param {number} time - * @return {Tone.Envelope.Phase} - * @private + * The slope of the Release. Either "linear" or "exponential". + * @memberOf Tone.Envelope# + * @type {string} + * @name releaseCurve + * @example + * env.releaseCurve = "linear"; */ - Tone.Envelope.prototype._phaseAtTime = function(time){ - if (this._nextRelease > time){ - if (this._nextAttack <= time && this._nextDecay > time){ - return Tone.Envelope.Phase.Attack; - } else if (this._nextDecay <= time && this._nextSustain > time){ - return Tone.Envelope.Phase.Decay; - } else if (this._nextSustain <= time && this._nextRelease > time){ - return Tone.Envelope.Phase.Sustain; + Object.defineProperty(Tone.Envelope.prototype, "releaseCurve", { + get : function(){ + return this._releaseCurve; + }, + set : function(type){ + if (type === Tone.Envelope.Type.Linear || + type === Tone.Envelope.Type.Exponential){ + this._releaseCurve = type; } else { - return Tone.Envelope.Phase.Standby; + throw Error("releaseCurve must be either \"linear\" or \"exponential\". Invalid type: ", type); } - } else if (this._nextRelease < time && this._nextStandby > time){ - return Tone.Envelope.Phase.Release; - } else { - return Tone.Envelope.Phase.Standby; - } - }; - - /** - * https://github.com/jsantell/web-audio-automation-timeline - * MIT License, copyright (c) 2014 Jordan Santell - * @private - */ - Tone.Envelope.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { - return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); - }; - /** - * @private - */ - Tone.Envelope.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { - return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); - }; - /** - * @private - */ - Tone.Envelope.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { - return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); - }; - - /** - * Get the envelopes value at the given time - * @param {number} time - * @param {number} velocity - * @return {number} - * @private - */ - Tone.Envelope.prototype._valueAtTime = function(time){ - var attack = this.toSeconds(this.attack); - var decay = this.toSeconds(this.decay); - var release = this.toSeconds(this.release); - switch(this._phaseAtTime(time)){ - case Tone.Envelope.Phase.Attack: - if (this._attackCurve === Tone.Envelope.Type.Linear){ - return this._linearInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time); - } else { - return this._exponentialInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time); - } - break; - case Tone.Envelope.Phase.Decay: - return this._exponentialApproach(this._nextDecay, this._peakValue, this.sustain * this._peakValue, decay * this._timeMult, time); - case Tone.Envelope.Phase.Release: - return this._exponentialApproach(this._nextRelease, this._peakValue, this._minOutput, release * this._timeMult, time); - case Tone.Envelope.Phase.Sustain: - return this.sustain * this._peakValue; - case Tone.Envelope.Phase.Standby: - return this._minOutput; } - }; + }); /** * Trigger the attack/decay portion of the ADSR envelope. @@ -275,34 +191,20 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton */ Tone.Envelope.prototype.triggerAttack = function(time, velocity){ //to seconds - time = this.toSeconds(time); - var attack = this.toSeconds(this.attack); + var now = this.now() + this.blockTime; + time = this.toSeconds(time, now); + var attack = this.toSeconds(this.attack) + time; var decay = this.toSeconds(this.decay); - - //get the phase and position - var valueAtTime = this._valueAtTime(time); - var attackPast = valueAtTime * attack; - - //compute the timing - this._nextAttack = time - attackPast; - this._nextDecay = this._nextAttack + attack; - this._nextSustain = this._nextDecay + decay; - this._nextRelease = Infinity; - - //get the values - this._peakValue = this.defaultArg(velocity, 1); - var scaledMax = this._peakValue; - var sustainVal = this.sustain * scaledMax; - - //set the curve - this._sig.cancelScheduledValues(time); - this._sig.setValueAtTime(valueAtTime, time); + velocity = this.defaultArg(velocity, 1); + //attack if (this._attackCurve === Tone.Envelope.Type.Linear){ - this._sig.linearRampToValueAtTime(scaledMax, this._nextDecay); + this._sig.linearRampToValueBetween(velocity, time, attack); } else { - this._sig.exponentialRampToValueAtTime(scaledMax, this._nextDecay); + this._sig.exponentialRampToValueBetween(velocity, time, attack); } - this._sig.setTargetAtTime(sustainVal, this._nextDecay, decay * this._timeMult); + //decay + this._sig.setValueAtTime(velocity, attack); + this._sig.exponentialRampToValueAtTime(this.sustain * velocity, attack + decay); return this; }; @@ -315,32 +217,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Pow"], function(Ton * env.triggerRelease(); */ Tone.Envelope.prototype.triggerRelease = function(time){ - time = this.toSeconds(time); - var phase = this._phaseAtTime(time); + var now = this.now() + this.blockTime; + time = this.toSeconds(time, now); var release = this.toSeconds(this.release); - - //computer the value at the start of the next release - var valueAtTime = this._valueAtTime(time); - this._peakValue = valueAtTime; - - this._nextRelease = time; - this._nextStandby = this._nextRelease + release; - - //set the values - this._sig.cancelScheduledValues(this._nextRelease); - - //if the phase is in the attack still, must reschedule the rest of the attack - if (phase === Tone.Envelope.Phase.Attack){ - this._sig.setCurrentValueNow(); - if (this.attackCurve === Tone.Envelope.Type.Linear){ - this._sig.linearRampToValueAtTime(this._peakValue, this._nextRelease); - } else { - this._sig.exponentialRampToValueAtTime(this._peakValue, this._nextRelease); - } + if (this._releaseCurve === Tone.Envelope.Type.Linear){ + this._sig.linearRampToValueBetween(this._minOutput, time, time + release); } else { - this._sig.setValueAtTime(this._peakValue, this._nextRelease); + this._sig.exponentialRampToValueBetween(this._minOutput, time, release + time); } - this._sig.setTargetAtTime(this._minOutput, this._nextRelease, release * this._timeMult); return this; }; diff --git a/Tone/component/FeedbackCombFilter.js b/Tone/component/FeedbackCombFilter.js index 7bf960889..980cf1fa7 100644 --- a/Tone/component/FeedbackCombFilter.js +++ b/Tone/component/FeedbackCombFilter.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal", "Tone/core/Param"], function(Tone){ "use strict"; @@ -16,13 +16,6 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio Tone.call(this); var options = this.optionsObject(arguments, ["delayTime", "resonance"], Tone.FeedbackCombFilter.defaults); - /** - * The amount of feedback of the delayed signal. - * @type {NormalRange} - * @signal - */ - this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange); - /** * the delay node * @type {DelayNode} @@ -35,7 +28,11 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio * @type {Time} * @signal */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + this.delayTime = new Tone.Param({ + "param" : this._delay.delayTime, + "value" : options.delayTime, + "units" : Tone.Type.Time + }); /** * the feedback node @@ -44,9 +41,18 @@ define(["Tone/core/Tone", "Tone/signal/ScaleExp", "Tone/signal/Signal"], functio */ this._feedback = this.context.createGain(); + /** + * The amount of feedback of the delayed signal. + * @type {NormalRange} + * @signal + */ + this.resonance = new Tone.Param({ + "param" : this._feedback.gain, + "value" : options.resonance, + "units" : Tone.Type.NormalRange + }); + this._delay.chain(this._feedback, this._delay); - this.resonance.connect(this._feedback.gain); - this.delayTime.connect(this._delay.delayTime); this._readOnly(["resonance", "delayTime"]); }; diff --git a/Tone/component/Filter.js b/Tone/component/Filter.js index 2494c53a8..5783f8fa2 100644 --- a/Tone/component/Filter.js +++ b/Tone/component/Filter.js @@ -45,12 +45,11 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ /** * The gain of the filter, only used in certain filter types - * @type {Gain} + * @type {Number} * @signal */ this.gain = new Tone.Signal({ "value" : options.gain, - "units" : Tone.Type.Decibels, "convert" : false }); @@ -110,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 TypeError("Tone.Filter does not have filter type "+type); + throw new Error("Tone.Filter does not have filter type "+type); } this._type = type; for (var i = 0; i < this._filters.length; i++){ @@ -122,7 +121,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ /** * The rolloff of the filter which is the drop in db * per octave. Implemented internally by cascading filters. - * Only accepts the values -12, -24, and -48. + * Only accepts the values -12, -24, -48 and -96. * @memberOf Tone.Filter# * @type {number} * @name rolloff @@ -132,13 +131,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ return this._rolloff; }, set : function(rolloff){ - var possibilities = [-12, -24, -48]; + 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("Filter rolloff can only be -12, -24, or -48"); + throw new Error("Filter rolloff can only be -12, -24, -48 or -96"); } - cascadingCount++; + cascadingCount += 1; this._rolloff = rolloff; //first disconnect the filters and throw them away this.input.disconnect(); diff --git a/Tone/component/Follower.js b/Tone/component/Follower.js index 91b88da9e..1c02c45d7 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/signal/Multiply", "Tone/signal/Signal", "Tone/signal/WaveShaper", "Tone/core/Type"], function(Tone){ "use strict"; @@ -57,7 +57,7 @@ function(Tone){ * @private */ this._delay = this.context.createDelay(); - this._delay.delayTime.value = this.bufferTime; + this._delay.delayTime.value = this.blockTime; /** * this keeps it far from 0, even for very small differences @@ -107,7 +107,7 @@ function(Tone){ * @private */ Tone.Follower.prototype._setAttackRelease = function(attack, release){ - var minTime = this.bufferTime; + var minTime = this.blockTime; attack = this.secondsToFrequency(this.toSeconds(attack)); release = this.secondsToFrequency(this.toSeconds(release)); attack = Math.max(attack, minTime); diff --git a/Tone/component/FrequencyEnvelope.js b/Tone/component/FrequencyEnvelope.js new file mode 100644 index 000000000..40ecf444a --- /dev/null +++ b/Tone/component/FrequencyEnvelope.js @@ -0,0 +1,112 @@ +define(["Tone/core/Tone", "Tone/component/ScaledEnvelope", "Tone/component/Envelope"], + function(Tone){ + + "use strict"; + + /** + * @class Tone.FrequencyEnvelope is a Tone.ScaledEnvelope, but instead of `min` and `max` + * it's got a `baseFrequency` and `octaves` parameter. + * + * @extends {Tone.Envelope} + * @constructor + * @param {Time|Object} [attack] the attack time in seconds + * @param {Time} [decay] the decay time in seconds + * @param {number} [sustain] a percentage (0-1) of the full amplitude + * @param {Time} [release] the release time in seconds + * @example + * var env = new Tone.FrequencyEnvelope({ + * "attack" : 0.2, + * "baseFrequency" : "C2", + * "octaves" : 4 + * }); + * scaledEnv.connect(oscillator.frequency); + */ + Tone.FrequencyEnvelope = function(){ + + var options = this.optionsObject(arguments, ["attack", "decay", "sustain", "release"], Tone.Envelope.defaults); + Tone.ScaledEnvelope.call(this, options); + options = this.defaultArg(options, Tone.FrequencyEnvelope.defaults); + + /** + * Stores the octave value + * @type {Positive} + * @private + */ + this._octaves = options.octaves; + + //setup + this.baseFrequency = options.baseFrequency; + this.octaves = options.octaves; + }; + + Tone.extend(Tone.FrequencyEnvelope, Tone.Envelope); + + /** + * the default parameters + * @static + */ + Tone.FrequencyEnvelope.defaults = { + "baseFrequency" : 200, + "octaves" : 4, + "exponent" : 2 + }; + + /** + * The envelope's mininum output value. This is the value which it + * starts at. + * @memberOf Tone.FrequencyEnvelope# + * @type {Frequency} + * @name baseFrequency + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, "baseFrequency", { + get : function(){ + return this._scale.min; + }, + set : function(min){ + this._scale.min = this.toFrequency(min); + } + }); + + /** + * The number of octaves above the baseFrequency that the + * envelope will scale to. + * @memberOf Tone.FrequencyEnvelope# + * @type {Positive} + * @name octaves + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, "octaves", { + get : function(){ + return this._octaves; + }, + set : function(octaves){ + this._octaves = octaves; + this._scale.max = this.baseFrequency * Math.pow(2, octaves); + } + }); + + /** + * The envelope's exponent value. + * @memberOf Tone.FrequencyEnvelope# + * @type {number} + * @name exponent + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, "exponent", { + get : function(){ + return this._exp.value; + }, + set : function(exp){ + this._exp.value = exp; + } + }); + + /** + * clean up + * @returns {Tone.FrequencyEnvelope} this + */ + Tone.FrequencyEnvelope.prototype.dispose = function(){ + Tone.ScaledEnvelope.prototype.dispose.call(this); + return this; + }; + + return Tone.FrequencyEnvelope; +}); \ No newline at end of file diff --git a/Tone/component/LFO.js b/Tone/component/LFO.js index 90676d1d3..c3f90f82b 100644 --- a/Tone/component/LFO.js +++ b/Tone/component/LFO.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/source/Oscillator", "Tone/signal/Scale", "Tone/signal/Signal", "Tone/signal/AudioToGain"], +define(["Tone/core/Tone", "Tone/source/Oscillator", "Tone/signal/Scale", + "Tone/signal/Signal", "Tone/signal/AudioToGain", "Tone/core/Type"], function(Tone){ "use strict"; @@ -13,8 +14,7 @@ function(Tone){ * @extends {Tone.Oscillator} * @param {Frequency|Object} [frequency] The frequency of the oscillation. Typically, LFOs will be * in the frequency range of 0.1 to 10 hertz. - * @param {number=} min The minimum output value of the LFO. The LFO starts - * at it's minimum value. + * @param {number=} min The minimum output value of the LFO. * @param {number=} max The maximum value of the LFO. * @example * var lfo = new Tone.LFO("4n", 400, 4000); @@ -29,10 +29,9 @@ function(Tone){ * @type {Tone.Oscillator} * @private */ - this.oscillator = new Tone.Oscillator({ + this._oscillator = new Tone.Oscillator({ "frequency" : options.frequency, "type" : options.type, - "phase" : options.phase + 90 }); /** @@ -40,7 +39,7 @@ function(Tone){ * @type {Frequency} * @signal */ - this.frequency = this.oscillator.frequency; + this.frequency = this._oscillator.frequency; /** * The amplitude of the LFO, which controls the output range between @@ -50,10 +49,24 @@ function(Tone){ * @type {Number} * @signal */ - this.amplitude = this.oscillator.volume; + this.amplitude = this._oscillator.volume; this.amplitude.units = Tone.Type.NormalRange; this.amplitude.value = options.amplitude; + /** + * The signal which is output when the LFO is stopped + * @type {Tone.Signal} + * @private + */ + this._stoppedSignal = new Tone.Signal(0, Tone.Type.AudioRange); + + /** + * The value that the LFO outputs when it's stopped + * @type {AudioRange} + * @private + */ + this._stoppedValue = 0; + /** * @type {Tone.AudioToGain} * @private @@ -68,14 +81,17 @@ function(Tone){ /** * the units of the LFO (used for converting) - * @type {string} + * @type {Tone.Type} * @private */ this._units = Tone.Type.Default; + this.units = options.units; //connect it up - this.oscillator.chain(this._a2g, this._scaler); - this._readOnly(["amplitude", "frequency", "oscillator"]); + this._oscillator.chain(this._a2g, this._scaler); + this._stoppedSignal.connect(this._a2g); + this._readOnly(["amplitude", "frequency"]); + this.phase = options.phase; }; Tone.extend(Tone.LFO, Tone.Oscillator); @@ -93,7 +109,8 @@ function(Tone){ "max" : 1, "phase" : 0, "frequency" : "4n", - "amplitude" : 1 + "amplitude" : 1, + "units" : Tone.Type.Default }; /** @@ -102,7 +119,9 @@ function(Tone){ * @returns {Tone.LFO} this */ Tone.LFO.prototype.start = function(time){ - this.oscillator.start(time); + time = this.toSeconds(time); + this._stoppedSignal.setValueAtTime(0, time); + this._oscillator.start(time); return this; }; @@ -112,7 +131,9 @@ function(Tone){ * @returns {Tone.LFO} this */ Tone.LFO.prototype.stop = function(time){ - this.oscillator.stop(time); + time = this.toSeconds(time); + this._stoppedSignal.setValueAtTime(this._stoppedValue, time); + this._oscillator.stop(time); return this; }; @@ -130,8 +151,8 @@ function(Tone){ * //even as the tempo changes */ Tone.LFO.prototype.sync = function(delay){ - this.oscillator.sync(delay); - this.oscillator.syncFrequency(); + this._oscillator.sync(delay); + this._oscillator.syncFrequency(); return this; }; @@ -140,8 +161,8 @@ function(Tone){ * @returns {Tone.LFO} this */ Tone.LFO.prototype.unsync = function(){ - this.oscillator.unsync(); - this.oscillator.unsyncFrequency(); + this._oscillator.unsync(); + this._oscillator.unsyncFrequency(); return this; }; @@ -185,10 +206,12 @@ function(Tone){ */ Object.defineProperty(Tone.LFO.prototype, "type", { get : function(){ - return this.oscillator.type; + return this._oscillator.type; }, set : function(type){ - this.oscillator.type = type; + this._oscillator.type = type; + this._stoppedValue = this._oscillator._getInitialValue(); + this._stoppedSignal.value = this._stoppedValue; } }); @@ -200,10 +223,12 @@ function(Tone){ */ Object.defineProperty(Tone.LFO.prototype, "phase", { get : function(){ - return this.oscillator.phase - 90; + return this._oscillator.phase; }, set : function(phase){ - this.oscillator.phase = phase + 90; + this._oscillator.phase = phase; + this._stoppedValue = this._oscillator._getInitialValue(); + this._stoppedSignal.value = this._stoppedValue; } }); @@ -228,7 +253,21 @@ function(Tone){ }); /** - * Connect the output of a ToneNode to an AudioParam, AudioNode, or Tone Node. + * Returns the playback state of the source, either "started" or "stopped". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.LFO# + * @name state + */ + Object.defineProperty(Tone.LFO.prototype, "state", { + get : function(){ + return this._oscillator.state; + } + }); + + /** + * Connect the output of the LFO to an AudioParam, AudioNode, or Tone Node. + * Tone.LFO will automatically convert to the destination units of the * will get the units from the connected node. * @param {Tone | AudioParam | AudioNode} node * @param {number} [outputNum=0] optionally which output to connect from @@ -237,7 +276,7 @@ function(Tone){ * @private */ Tone.LFO.prototype.connect = function(node){ - if (node.constructor === Tone.Signal){ + if (node.constructor === Tone.Signal || node.constructor === Tone.Param || node.constructor === Tone.TimelineSignal){ this.convert = node.convert; this.units = node.units; } @@ -246,20 +285,20 @@ function(Tone){ }; /** - * private method borroed from Signal converts + * private method borrowed from Param converts * units from their destination value * @function * @private */ - Tone.LFO.prototype._fromUnits = Tone.Signal.prototype._fromUnits; + Tone.LFO.prototype._fromUnits = Tone.Param.prototype._fromUnits; /** - * private method borroed from Signal converts + * private method borrowed from Param converts * units to their destination value * @function * @private */ - Tone.LFO.prototype._toUnits = Tone.Signal.prototype._toUnits; + Tone.LFO.prototype._toUnits = Tone.Param.prototype._toUnits; /** * disconnect and dispose @@ -267,9 +306,11 @@ function(Tone){ */ Tone.LFO.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._writable(["amplitude", "frequency", "oscillator"]); - this.oscillator.dispose(); - this.oscillator = null; + 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(); diff --git a/Tone/component/Limiter.js b/Tone/component/Limiter.js index 2de5e5c11..a61a5035c 100644 --- a/Tone/component/Limiter.js +++ b/Tone/component/Limiter.js @@ -16,7 +16,9 @@ define(["Tone/core/Tone", "Tone/component/Compressor"], function(Tone){ * @example * var limiter = new Tone.Limiter(-6); */ - Tone.Limiter = function(threshold){ + Tone.Limiter = function(){ + + var options = this.optionsObject(arguments, ["threshold"], Tone.Limiter.defaults); /** * the compressor @@ -26,7 +28,7 @@ define(["Tone/core/Tone", "Tone/component/Compressor"], function(Tone){ this._compressor = this.input = this.output = new Tone.Compressor({ "attack" : 0.001, "decay" : 0.001, - "threshold" : threshold + "threshold" : options.threshold }); /** @@ -41,6 +43,16 @@ define(["Tone/core/Tone", "Tone/component/Compressor"], function(Tone){ Tone.extend(Tone.Limiter); + /** + * The default value + * @type {Object} + * @const + * @static + */ + Tone.Limiter.defaults = { + "threshold" : -12 + }; + /** * Clean up. * @returns {Tone.Limiter} this diff --git a/Tone/component/LowpassCombFilter.js b/Tone/component/LowpassCombFilter.js index c416600a0..c9fcde7a1 100644 --- a/Tone/component/LowpassCombFilter.js +++ b/Tone/component/LowpassCombFilter.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/component/Filter"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/component/Filter", "Tone/core/Param"], function(Tone){ "use strict"; @@ -47,8 +47,11 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/component/Filter"], functi * @type {Frequency} * @signal */ - this.dampening = new Tone.Signal(this._lowpass.frequency, Tone.Type.Frequency); - this.dampening.value = options.dampening; + this.dampening = new Tone.Param({ + "param" : this._lowpass.frequency, + "units" : Tone.Type.Frequency, + "value" : options.dampening + }); /** * the feedback gain @@ -62,13 +65,15 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/component/Filter"], functi * @type {NormalRange} * @signal */ - this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange); + this.resonance = new Tone.Param({ + "param" : this._feedback.gain, + "units" : Tone.Type.NormalRange, + "value" : options.resonance + }); //connections this._delay.chain(this._lowpass, this._feedback, this._delay); this.delayTime.connect(this._delay.delayTime); - this.resonance.connect(this._feedback.gain); - this.dampening.connect(this._lowpass.frequency); this._readOnly(["dampening", "resonance", "delayTime"]); }; diff --git a/Tone/component/Merge.js b/Tone/component/Merge.js index 2a46b8e9e..9fbb4bf3b 100644 --- a/Tone/component/Merge.js +++ b/Tone/component/Merge.js @@ -46,6 +46,11 @@ define(["Tone/core/Tone"], function(Tone){ //connections 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"; }; Tone.extend(Tone.Merge); diff --git a/Tone/component/Meter.js b/Tone/component/Meter.js index 1fabbb88e..85a4e1bed 100644 --- a/Tone/component/Meter.js +++ b/Tone/component/Meter.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ +define(["Tone/core/Tone"], function(Tone){ "use strict"; @@ -25,7 +25,9 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ * //to access meter level * meter.getLevel(); */ - Tone.Meter = function(channels, smoothing, clipMemory){ + Tone.Meter = function(){ + + var options = this.optionsObject(arguments, ["channels", "smoothing"], Tone.Meter.defaults); //extends Unit Tone.call(this); @@ -34,21 +36,26 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ * @type {number} * @private */ - this._channels = this.defaultArg(channels, 1); + 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 smoothing value - * @type {number} - * @private + * The amount of time a clip is remember for. + * @type {Time} */ - this._smoothing = this.defaultArg(smoothing, 0.8); + this.clipMemory = options.clipMemory; /** - * the amount of time a clip is remember for. - * @type {number} - * @private + * The value above which the signal is considered clipped. + * @type {Number} */ - this._clipMemory = this.defaultArg(clipMemory, 0.5) * 1000; + this.clipLevel = options.clipLevel; /** * the rms for each of the channels @@ -73,15 +80,20 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ /** * last time the values clipped * @private - * @type {number} + * @type {Array} */ - this._lastClip = 0; + this._lastClip = new Array(this._channels); + + //zero out the clip array + for (var j = 0; j < this._lastClip.length; j++){ + this._lastClip[j] = 0; + } /** * @private * @type {ScriptProcessorNode} */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, this._channels, 1); + 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(); @@ -93,6 +105,20 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ Tone.extend(Tone.Meter); + /** + * The defaults + * @type {Object} + * @static + * @const + */ + Tone.Meter.defaults = { + "smoothing" : 0.8, + "bufferSize" : 1024, + "clipMemory" : 0.5, + "clipLevel" : 0.9, + "channels" : 1 + }; + /** * called on each processing frame * @private @@ -100,24 +126,22 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ */ Tone.Meter.prototype._onprocess = function(event){ var bufferSize = this._jsNode.bufferSize; - var smoothing = this._smoothing; + var smoothing = this.smoothing; for (var channel = 0; channel < this._channels; channel++){ var input = event.inputBuffer.getChannelData(channel); var sum = 0; var total = 0; var x; - var clipped = false; for (var i = 0; i < bufferSize; i++){ x = input[i]; - if (!clipped && x > 0.95){ - clipped = true; - this._lastClip = Date.now(); - } total += x; sum += x * x; } var average = total / bufferSize; var rms = Math.sqrt(sum / bufferSize); + if (rms > 0.9){ + this._lastClip[channel] = Date.now(); + } this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing); this._values[channel] = average; } @@ -161,8 +185,9 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ * @returns {boolean} if the audio has clipped. The value resets * based on the clipMemory defined. */ - Tone.Meter.prototype.isClipped = function(){ - return Date.now() - this._lastClip < this._clipMemory; + Tone.Meter.prototype.isClipped = function(channel){ + channel = this.defaultArg(channel, 0); + return Date.now() - this._lastClip[channel] < this._clipMemory * 1000; }; /** @@ -173,8 +198,10 @@ define(["Tone/core/Tone", "Tone/core/Master"], function(Tone){ Tone.prototype.dispose.call(this); this._jsNode.disconnect(); this._jsNode.onaudioprocess = null; + this._jsNode = null; this._volume = null; this._values = null; + this._lastClip = null; return this; }; diff --git a/Tone/component/PanVol.js b/Tone/component/PanVol.js index c6750a10d..c5350d3a0 100644 --- a/Tone/component/PanVol.js +++ b/Tone/component/PanVol.js @@ -13,14 +13,16 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/component/Volume"], fun * //pan the incoming signal left and drop the volume * var panVol = new Tone.PanVol(0.25, -12); */ - Tone.PanVol = function(pan, volume){ + Tone.PanVol = function(){ + + var options = this.optionsObject(arguments, ["pan", "volume"], Tone.PanVol.defaults); /** * The panning node * @type {Tone.Panner} * @private */ - this._panner = this.input = new Tone.Panner(pan); + this._panner = this.input = new Tone.Panner(options.pan); /** * The L/R panning control. @@ -30,12 +32,10 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/component/Volume"], fun this.pan = this._panner.pan; /** - * The volume object. - * @type {Tone.Volume} - * @signal - * @private + * The volume node + * @type {Tone.Volume} */ - this._volume = this.output = new Tone.Volume(volume); + this._volume = this.output = new Tone.Volume(options.volume); /** * The volume control in decibels. @@ -52,6 +52,17 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/component/Volume"], fun Tone.extend(Tone.PanVol); + /** + * The defaults + * @type {Object} + * @const + * @static + */ + Tone.PanVol.defaults = { + "pan" : 0.5, + "volume" : 0 + }; + /** * clean up * @returns {Tone.PanVol} this @@ -61,9 +72,9 @@ define(["Tone/core/Tone", "Tone/component/Panner", "Tone/component/Volume"], fun this._writable(["pan", "volume"]); this._panner.dispose(); this._panner = null; + this.pan = null; this._volume.dispose(); this._volume = null; - this.pan = null; this.volume = null; return this; }; diff --git a/Tone/component/Volume.js b/Tone/component/Volume.js index d705787f4..886fe583f 100644 --- a/Tone/component/Volume.js +++ b/Tone/component/Volume.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Master"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Gain"], function(Tone){ "use strict"; @@ -12,33 +12,45 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Master"], function(To * var vol = new Tone.Volume(-12); * instrument.chain(vol, Tone.Master); */ - Tone.Volume = function(volume){ + Tone.Volume = function(){ + + var options = this.optionsObject(arguments, ["volume"], Tone.Volume.defaults); /** * the output node * @type {GainNode} * @private */ - this.output = this.input = this.context.createGain(); + this.output = this.input = new Tone.Gain(options.volume, Tone.Type.Decibels); /** * The volume control in decibels. * @type {Decibels} * @signal */ - this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels); - this.volume.value = this.defaultArg(volume, 0); + this.volume = this.output.gain; this._readOnly("volume"); }; Tone.extend(Tone.Volume); + /** + * Defaults + * @type {Object} + * @const + * @static + */ + Tone.Volume.defaults = { + "volume" : 0 + }; + /** * clean up * @returns {Tone.Volume} this */ Tone.Volume.prototype.dispose = function(){ + this.input.dispose(); Tone.prototype.dispose.call(this); this._writable("volume"); this.volume.dispose(); diff --git a/Tone/control/CtrlInterpolate.js b/Tone/control/CtrlInterpolate.js new file mode 100644 index 000000000..ef2886ff1 --- /dev/null +++ b/Tone/control/CtrlInterpolate.js @@ -0,0 +1,133 @@ +define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class Tone.CtrlInterpolate will interpolate between given values based + * on the "index" property. Passing in an array or object literal + * will interpolate each of the parameters. Note (i.e. "C3") + * and Time (i.e. "4n + 2") can be interpolated. All other values are + * assumed to be numbers. + * @example + * var interp = new Tone.CtrlInterpolate([0, 2, 9, 4]); + * interp.index = 0.75; + * interp.value; //returns 1.5 + * + * @example + * var interp = new Tone.CtrlInterpolate([ + * ["C3", "G4", "E5"], + * ["D4", "F#4", "E5"], + * ]); + * @param {Array} values The array of values to interpolate over + * @param {Positive} index The initial interpolation index. + * @extends {Tone} + */ + Tone.CtrlInterpolate = function(){ + + var options = this.optionsObject(arguments, ["values", "index"], Tone.CtrlInterpolate.defaults); + + /** + * The values to interpolate between + * @type {Array} + */ + this.values = options.values; + + /** + * The interpolated index between values. For example: a value of 1.5 + * would interpolate equally between the value at index 1 + * and the value at index 2. + * @example + * interp.index = 0; + * interp.value; //returns the value at 0 + * interp.index = 0.5; + * interp.value; //returns the value between indices 0 and 1. + * @type {Positive} + */ + this.index = options.index; + }; + + Tone.extend(Tone.CtrlInterpolate); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.CtrlInterpolate.defaults = { + "index" : 0, + "values" : [] + }; + + /** + * The current interpolated value based on the index + * @readOnly + * @memberOf Tone.CtrlInterpolate# + * @type {*} + * @name value + */ + Object.defineProperty(Tone.CtrlInterpolate.prototype, "value", { + get : function(){ + var index = this.index; + index = Math.min(index, this.values.length - 1); + var lowerPosition = Math.floor(index); + var lower = this.values[lowerPosition]; + var upper = this.values[Math.ceil(index)]; + return this._interpolate(index - lowerPosition, lower, upper); + } + }); + + /** + * Internal interpolation routine + * @param {NormalRange} index The index between the lower and upper + * @param {*} lower + * @param {*} upper + * @return {*} The interpolated value + * @private + */ + Tone.CtrlInterpolate.prototype._interpolate = function(index, lower, upper){ + if (this.isArray(lower)){ + var retArray = []; + for (var i = 0; i < lower.length; i++){ + retArray[i] = this._interpolate(index, lower[i], upper[i]); + } + return retArray; + } else if (this.isObject(lower)){ + var retObj = {}; + for (var attr in lower){ + retObj[attr] = this._interpolate(index, lower[attr], upper[attr]); + } + return retObj; + } else { + lower = this._toNumber(lower); + upper = this._toNumber(upper); + return (1 - index) * lower + index * upper; + } + }; + + /** + * Convert from the given type into a number + * @param {Number|String} value + * @return {Number} + * @private + */ + 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); + } + }; + + /** + * Clean up + * @return {Tone.CtrlInterpolate} this + */ + Tone.CtrlInterpolate.prototype.dispose = function(){ + this.values = null; + }; + + return Tone.CtrlInterpolate; +}); \ No newline at end of file diff --git a/Tone/control/CtrlMarkov.js b/Tone/control/CtrlMarkov.js new file mode 100644 index 000000000..e6022c925 --- /dev/null +++ b/Tone/control/CtrlMarkov.js @@ -0,0 +1,122 @@ +define(["Tone/core/Tone"], function (Tone) { + + "use strict"; + + /** + * @class Tone.CtrlMarkov represents a Markov Chain where each call + * to Tone.CtrlMarkov.next will move to the next state. If the next + * state choice is an array, the next state is chosen randomly with + * even probability for all of the choices. For a weighted probability + * of the next choices, pass in an object with "state" and "probability" attributes. + * The probabilities will be normalized and then chosen. If no next options + * are given for the current state, the state will stay there. + * @extends {Tone} + * @example + * var chain = new Tone.CtrlMarkov({ + * "beginning" : ["end", "middle"], + * "middle" : "end" + * }); + * chain.value = "beginning"; + * chain.next(); //returns "end" or "middle" with 50% probability + * + * @example + * var chain = new Tone.CtrlMarkov({ + * "beginning" : [{"value" : "end", "probability" : 0.8}, + * {"value" : "middle", "probability" : 0.2}], + * "middle" : "end" + * }); + * chain.value = "beginning"; + * chain.next(); //returns "end" with 80% probability or "middle" with 20%. + * @param {Object} values An object with the state names as the keys + * and the next state(s) as the values. + */ + Tone.CtrlMarkov = function(values, initial){ + + /** + * The Markov values with states as the keys + * and next state(s) as the values. + * @type {Object} + */ + this.values = this.defaultArg(values, {}); + + /** + * The current state of the Markov values. The next + * state will be evaluated and returned when Tone.CtrlMarkov.next + * is invoked. + * @type {String} + */ + this.value = this.defaultArg(initial, Object.keys(this.values)[0]); + }; + + Tone.extend(Tone.CtrlMarkov); + + /** + * Returns the next state of the Markov values. + * @return {String} + */ + Tone.CtrlMarkov.prototype.next = function(){ + if (this.values.hasOwnProperty(this.value)){ + var next = this.values[this.value]; + if (this.isArray(next)){ + var distribution = this._getProbDistribution(next); + var rand = Math.random(); + var total = 0; + for (var i = 0; i < distribution.length; i++){ + var dist = distribution[i]; + if (rand > total && rand < total + dist){ + var chosen = next[i]; + if (this.isObject(chosen)){ + this.value = chosen.value; + } else { + this.value = chosen; + } + } + total += dist; + } + } else { + this.value = next; + } + } + return this.value; + }; + + /** + * Choose randomly from an array weighted options in the form + * {"state" : string, "probability" : number} or an array of values + * @param {Array} options + * @return {Array} The randomly selected choice + * @private + */ + Tone.CtrlMarkov.prototype._getProbDistribution = function(options){ + var distribution = []; + var total = 0; + var needsNormalizing = false; + for (var i = 0; i < options.length; i++){ + var option = options[i]; + if (this.isObject(option)){ + needsNormalizing = true; + distribution[i] = option.probability; + } else { + distribution[i] = 1 / options.length; + } + total += distribution[i]; + } + if (needsNormalizing){ + //normalize the values + for (var j = 0; j < distribution.length; j++){ + distribution[j] = distribution[j] / total; + } + } + return distribution; + }; + + /** + * Clean up + * @return {Tone.CtrlMarkov} this + */ + Tone.CtrlMarkov.prototype.dispose = function(){ + this.values = null; + }; + + return Tone.CtrlMarkov; +}); \ No newline at end of file diff --git a/Tone/control/CtrlPattern.js b/Tone/control/CtrlPattern.js new file mode 100644 index 000000000..7fba53021 --- /dev/null +++ b/Tone/control/CtrlPattern.js @@ -0,0 +1,270 @@ +define(["Tone/core/Tone"], function (Tone) { + + "use strict"; + + /** + * @class Generate patterns from an array of values. + * Has a number of arpeggiation and randomized + * selection patterns. + * + * @param {Array} values An array of options to choose from. + * @param {Tone.CtrlPattern.Type=} type The name of the pattern. + * @extends {Tone} + */ + Tone.CtrlPattern = function(){ + + var options = this.optionsObject(arguments, ["values", "type"], Tone.CtrlPattern.defaults); + + /** + * The array of values to arpeggiate over + * @type {Array} + */ + this.values = options.values; + + /** + * The current position in the values array + * @type {Number} + */ + this.index = 0; + + /** + * The type placeholder + * @type {Tone.CtrlPattern.Type} + * @private + */ + this._type = null; + + /** + * Shuffled values for the RandomOnce type + * @type {Array} + * @private + */ + this._shuffled = null; + + /** + * The direction of the movement + * @type {String} + * @private + */ + this._direction = null; + + this.type = options.type; + }; + + Tone.extend(Tone.CtrlPattern); + + /** + * The Control Patterns + * @type {Object} + * @static + */ + Tone.CtrlPattern.Type = { + Up : "up", + Down : "down", + UpDown : "upDown", + DownUp : "downUp", + AlternateUp : "alternateUp", + AlternateDown : "alternateDown", + Random : "random", + RandomWalk : "randomWalk", + RandomOnce : "randomOnce", + }; + + /** + * The default values. + * @type {Object} + */ + Tone.CtrlPattern.defaults = { + "type" : Tone.CtrlPattern.Type.Up, + "values" : [] + }; + + /** + * The value at the current index of the pattern. + * @readOnly + * @memberOf Tone.CtrlPattern# + * @type {*} + * @name value + */ + Object.defineProperty(Tone.CtrlPattern.prototype, "value", { + get : function(){ + //some safeguards + if (this.values.length === 0){ + return; + } else if (this.values.length === 1){ + return this.values[0]; + } + this.index = Math.min(this.index, this.values.length - 1); + var val = this.values[this.index]; + if (this.type === Tone.CtrlPattern.Type.RandomOnce){ + if (this.values.length !== this._shuffled.length){ + this._shuffleValues(); + } + val = this.values[this._shuffled[this.index]]; + } + return val; + } + }); + + + /** + * The pattern used to select the next + * item from the values array + * @memberOf Tone.CtrlPattern# + * @type {Tone.CtrlPattern.Type} + * @name type + */ + Object.defineProperty(Tone.CtrlPattern.prototype, "type", { + get : function(){ + return this._type; + }, + set : function(type){ + this._type = type; + this._shuffled = null; + + //the first index + if (this._type === Tone.CtrlPattern.Type.Up || + this._type === Tone.CtrlPattern.Type.UpDown || + this._type === Tone.CtrlPattern.Type.RandomOnce || + this._type === Tone.CtrlPattern.Type.AlternateUp){ + this.index = 0; + } else if (this._type === Tone.CtrlPattern.Type.Down || + this._type === Tone.CtrlPattern.Type.DownUp || + this._type === Tone.CtrlPattern.Type.AlternateDown){ + this.index = this.values.length - 1; + } + + //the direction + if (this._type === Tone.CtrlPattern.Type.UpDown || + this._type === Tone.CtrlPattern.Type.AlternateUp){ + this._direction = Tone.CtrlPattern.Type.Up; + } else if (this._type === Tone.CtrlPattern.Type.DownUp || + this._type === Tone.CtrlPattern.Type.AlternateDown){ + this._direction = Tone.CtrlPattern.Type.Down; + } + + //randoms + if (this._type === Tone.CtrlPattern.Type.RandomOnce){ + this._shuffleValues(); + } else if (this._type === Tone.CtrlPattern.Random){ + this.index = Math.floor(Math.random() * this.values.length); + } + } + }); + + /** + * Return the next value given the current position + * and pattern. + * @return {*} The next value + */ + Tone.CtrlPattern.prototype.next = function(){ + + var type = this.type; + + //choose the next index + if (type === Tone.CtrlPattern.Type.Up){ + this.index++; + if (this.index >= this.values.length){ + this.index = 0; + } + } else if (type === Tone.CtrlPattern.Type.Down){ + this.index--; + if (this.index < 0){ + this.index = this.values.length - 1; + } + } else if (type === Tone.CtrlPattern.Type.UpDown || + type === Tone.CtrlPattern.Type.DownUp){ + if (this._direction === Tone.CtrlPattern.Type.Up){ + this.index++; + } else { + this.index--; + } + if (this.index < 0){ + this.index = 1; + this._direction = Tone.CtrlPattern.Type.Up; + } else if (this.index >= this.values.length){ + this.index = this.values.length - 2; + this._direction = Tone.CtrlPattern.Type.Down; + } + } else if (type === Tone.CtrlPattern.Type.Random){ + this.index = Math.floor(Math.random() * this.values.length); + } else if (type === Tone.CtrlPattern.Type.RandomWalk){ + if (Math.random() < 0.5){ + this.index--; + this.index = Math.max(this.index, 0); + } else { + this.index++; + this.index = Math.min(this.index, this.values.length - 1); + } + } else if (type === Tone.CtrlPattern.Type.RandomOnce){ + this.index++; + if (this.index >= this.values.length){ + this.index = 0; + //reshuffle the values for next time + this._shuffleValues(); + } + } else if (type === Tone.CtrlPattern.Type.AlternateUp){ + if (this._direction === Tone.CtrlPattern.Type.Up){ + this.index += 2; + this._direction = Tone.CtrlPattern.Type.Down; + } else { + this.index -= 1; + this._direction = Tone.CtrlPattern.Type.Up; + } + if (this.index >= this.values.length){ + this.index = 0; + this._direction = Tone.CtrlPattern.Type.Up; + } + } else if (type === Tone.CtrlPattern.Type.AlternateDown){ + if (this._direction === Tone.CtrlPattern.Type.Up){ + this.index += 1; + this._direction = Tone.CtrlPattern.Type.Down; + } else { + this.index -= 2; + this._direction = Tone.CtrlPattern.Type.Up; + } + if (this.index < 0){ + this.index = this.values.length - 1; + this._direction = Tone.CtrlPattern.Type.Down; + } + } + return this.value; + }; + + /** + * Shuffles the values and places the results into the _shuffled + * @private + */ + Tone.CtrlPattern.prototype._shuffleValues = function(){ + var copy = []; + this._shuffled = []; + for (var i = 0; i < this.values.length; i++){ + copy[i] = i; + } + while(copy.length > 0){ + var randVal = copy.splice(Math.floor(copy.length * Math.random()), 1); + this._shuffled.push(randVal[0]); + } + }; + + /** + * Clean up + * @returns {Tone.CtrlPattern} this + */ + Tone.CtrlPattern.prototype.dispose = function(){ + this._shuffled = null; + this.values = null; + }; + + return Tone.CtrlPattern; +}); \ No newline at end of file diff --git a/Tone/control/CtrlRandom.js b/Tone/control/CtrlRandom.js new file mode 100644 index 000000000..04a45091c --- /dev/null +++ b/Tone/control/CtrlRandom.js @@ -0,0 +1,76 @@ +define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class Choose a random value. + * @extends {Tone} + * @example + * var randomWalk = new Tone.CtrlRandom({ + * "min" : 0, + * "max" : 10, + * "integer" : true + * }); + * randomWalk.eval(); + * + * @param {Number|Time=} min The minimum return value. + * @param {Number|Time=} max The maximum return value. + */ + Tone.CtrlRandom = function(){ + + var options = this.optionsObject(arguments, ["min", "max"], Tone.CtrlRandom.defaults); + + /** + * The minimum return value + * @type {Number|Time} + */ + this.min = options.min; + + /** + * The maximum return value + * @type {Number|Time} + */ + this.max = options.max; + + /** + * If the return value should be an integer + * @type {Boolean} + */ + this.integer = options.integer; + }; + + Tone.extend(Tone.CtrlRandom); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.CtrlRandom.defaults = { + "min" : 0, + "max" : 1, + "integer" : false + }; + + /** + * Return a random value between min and max. + * @readOnly + * @memberOf Tone.CtrlRandom# + * @type {*} + * @name value + */ + Object.defineProperty(Tone.CtrlRandom.prototype, "value", { + get : function(){ + var min = this.toSeconds(this.min); + var max = this.toSeconds(this.max); + var rand = Math.random(); + var val = rand * min + (1 - rand) * max; + if (this.integer){ + val = Math.floor(val); + } + return val; + } + }); + + return Tone.CtrlRandom; +}); \ No newline at end of file diff --git a/Tone/core/Buffer.js b/Tone/core/Buffer.js index fd3e7ea61..d27b05cfe 100644 --- a/Tone/core/Buffer.js +++ b/Tone/core/Buffer.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone"], function(Tone){ +define(["Tone/core/Tone", "Tone/core/Emitter"], function(Tone){ "use strict"; @@ -63,10 +63,10 @@ define(["Tone/core/Tone"], function(Tone){ */ this.onload = options.onload.bind(this, this); - if (options.url instanceof AudioBuffer){ - this._buffer.set(options.url); + if (options.url instanceof AudioBuffer || options.url instanceof Tone.Buffer){ + this.set(options.url); this.onload(this); - } else if (typeof options.url === "string"){ + } else if (this.isString(options.url)){ this.url = options.url; Tone.Buffer._addToQueue(options.url, this); } @@ -186,6 +186,9 @@ define(["Tone/core/Tone"], function(Tone){ /////////////////////////////////////////////////////////////////////////// // STATIC METHODS /////////////////////////////////////////////////////////////////////////// + + //statically inherits Emitter methods + Tone.Emitter.mixin(Tone.Buffer); /** * the static queue for all of the xhr requests @@ -283,10 +286,12 @@ define(["Tone/core/Tone"], function(Tone){ next.progress = event.loaded / event.total; Tone.Buffer._onprogress(); }; - next.xhr.onerror = Tone.Buffer.onerror; + next.xhr.onerror = function(e){ + Tone.Buffer.trigger("error", e); + }; } } else if (Tone.Buffer._currentDownloads.length === 0){ - Tone.Buffer.onload(); + Tone.Buffer.trigger("load"); //reset the downloads Tone.Buffer._totalDownloads = 0; } @@ -309,7 +314,7 @@ define(["Tone/core/Tone"], function(Tone){ } var currentDownloadProgress = currentDLLen - inprogress; var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress; - Tone.Buffer.onprogress(completed / Tone.Buffer._totalDownloads); + Tone.Buffer.trigger("progress", completed / Tone.Buffer._totalDownloads); }; /** @@ -341,42 +346,28 @@ define(["Tone/core/Tone"], function(Tone){ }; /** - * Callback when all of the buffers in the queue have loaded - * @static - * @function - * @example - * //invoked when all of the queued samples are done loading - * Tone.Buffer.onload = function(){ - * console.log("everything is loaded"); - * }; + * @deprecated us on([event]) instead */ - Tone.Buffer.onload = Tone.noOp; + 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); + } + }); - /** - * Callback function is invoked with the progress of all of the loads in the queue. - * The value passed to the callback is between 0-1. - * @static - * @param {Number} percent The progress between 0 and 1. - * @function - * @example - * Tone.Buffer.onprogress = function(percent){ - * console.log("progress:" + (percent * 100).toFixed(1) + "%"); - * }; - */ - Tone.Buffer.onprogress = Tone.noOp; + 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); + } + }); - /** - * Callback if one of the buffers in the queue encounters an error. The error - * is passed in as the argument. - * @static - * @param {Error} err - * @function - * @example - * Tone.Buffer.onerror = function(e){ - * console.log("there was an error while loading the buffers: "+e); - * } - */ - Tone.Buffer.onerror = Tone.noOp; + 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); + } + }); return Tone.Buffer; }); \ No newline at end of file diff --git a/Tone/core/Clock.js b/Tone/core/Clock.js index 8c8acea4d..6bcbce4a5 100644 --- a/Tone/core/Clock.js +++ b/Tone/core/Clock.js @@ -1,171 +1,302 @@ -define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "Tone/core/TimelineState"], function (Tone) { "use strict"; - + /** * @class A sample accurate clock which provides a callback at the given rate. * While the callback is not sample-accurate (it is still susceptible to * loose JS timing), the time passed in as the argument to the callback * is precise. For most applications, it is better to use Tone.Transport - * instead of the clock. + * instead of the Clock by itself since you can synchronize multiple callbacks. * * @constructor * @extends {Tone} - * @param {Frequency} frequency The rate of the callback * @param {function} callback The callback to be invoked with the time of the audio event + * @param {Frequency} frequency The rate of the callback * @example * //the callback will be invoked approximately once a second * //and will print the time exactly once a second apart. - * var clock = new Tone.Clock(1, function(time){ + * var clock = new Tone.Clock(function(time){ * console.log(time); - * }); + * }, 1); */ - Tone.Clock = function(frequency, callback){ + Tone.Clock = function(){ + + var options = this.optionsObject(arguments, ["callback", "frequency"], Tone.Clock.defaults); + + /** + * The callback function to invoke at the scheduled tick. + * @type {Function} + */ + this.callback = options.callback; + + /** + * The time which the clock will schedule events in advance + * of the current time. Scheduling notes in advance improves + * performance and decreases the chance for clicks caused + * by scheduling events in the past. If set to "auto", + * this value will be automatically computed based on the + * rate of requestAnimationFrame (0.016 seconds). Larger values + * will yeild better performance, but at the cost of latency. + * Values less than 0.016 are not recommended. + * @type {Number|String} + */ + this._lookAhead = "auto"; /** - * the oscillator - * @type {OscillatorNode} + * The lookahead value which was automatically + * computed using a time-based averaging. + * @type {Number} * @private */ - this._oscillator = null; + this._computedLookAhead = 1/60; /** - * the script processor which listens to the oscillator - * @type {ScriptProcessorNode} + * The value afterwhich events are thrown out + * @type {Number} * @private */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); - this._jsNode.onaudioprocess = this._processBuffer.bind(this); + this._threshold = 0.5; /** - * The frequency in which the callback will be invoked. - * @type {Frequency} - * @signal + * The next time the callback is scheduled. + * @type {Number} + * @private */ - this.frequency = new Tone.Signal(frequency, Tone.Type.Frequency); + this._nextTick = -1; /** - * whether the tick is on the up or down - * @type {boolean} + * The last time the callback was invoked + * @type {Number} * @private */ - this._upTick = false; + this._lastUpdate = 0; /** - * The callback which is invoked on every tick - * with the time of that tick as the argument - * @type {function(number)} + * The id of the requestAnimationFrame + * @type {Number} + * @private */ - this.tick = callback; + this._loopID = -1; /** - * Callback is invoked when the clock is stopped. - * @type {function} - * @example - * clock.onended = function(){ - * console.log("the clock is stopped"); - * } + * The rate the callback function should be invoked. + * @type {BPM} + * @signal */ - this.onended = Tone.noOp; + this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency); - //setup - this._jsNode.noGC(); + /** + * The number of times the callback was invoked. Starts counting at 0 + * and increments after the callback was invoked. + * @type {Ticks} + * @readOnly + */ + this.ticks = 0; + + /** + * The state timeline + * @type {Tone.TimelineState} + * @private + */ + 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} + * @private + */ + this._boundLoop = this._loop.bind(this); + + this._readOnly("frequency"); + //start the loop + this._loop(); }; Tone.extend(Tone.Clock); /** - * Start the clock. - * @param {Time} [time=now] the time when the clock should start - * @returns {Tone.Clock} this - * @example - * clock.start(); + * The defaults + * @const + * @type {Object} + */ + Tone.Clock.defaults = { + "callback" : Tone.noOp, + "frequency" : 1, + "lookAhead" : "auto", + }; + + /** + * Returns the playback state of the source, either "started", "stopped" or "paused". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.Clock# + * @name state */ - Tone.Clock.prototype.start = function(time){ - if (!this._oscillator){ - this._oscillator = this.context.createOscillator(); - this._oscillator.type = "square"; - this._oscillator.connect(this._jsNode); - //connect it up - this.frequency.connect(this._oscillator.frequency); - this._upTick = false; - var startTime = this.toSeconds(time); - this._oscillator.start(startTime); + Object.defineProperty(Tone.Clock.prototype, "state", { + get : function(){ + return this._state.getStateAtTime(this.now()); } - return this; + }); + + /** + * The time which the clock will schedule events in advance + * of the current time. Scheduling notes in advance improves + * performance and decreases the chance for clicks caused + * by scheduling events in the past. If set to "auto", + * this value will be automatically computed based on the + * rate of requestAnimationFrame (0.016 seconds). Larger values + * will yeild better performance, but at the cost of latency. + * Values less than 0.016 are not recommended. + * @type {Number|String} + * @memberOf Tone.Clock# + * @name lookAhead + */ + Object.defineProperty(Tone.Clock.prototype, "lookAhead", { + get : function(){ + return this._lookAhead; + }, + set : function(val){ + if (val === "auto"){ + this._lookAhead = "auto"; + } else { + this._lookAhead = this.toSeconds(val); + } + } + }); + + + /** + * Start the clock at the given time. Optionally pass in an offset + * of where to start the tick counter from. + * @param {Time} time The time the clock should start + * @param {Ticks=} offset Where the tick counter starts counting from. + * @return {Tone.Clock} this + */ + Tone.Clock.prototype.start = function(time, offset){ + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Started){ + this._state.addEvent({ + "state" : Tone.State.Started, + "time" : time, + "offset" : offset + }); + } + return this; }; /** - * Stop the clock. + * Stop the clock. Stopping the clock resets the tick counter to 0. * @param {Time} [time=now] The time when the clock should stop. * @returns {Tone.Clock} this * @example * clock.stop(); */ Tone.Clock.prototype.stop = function(time){ - if (this._oscillator){ - var now = this.now(); - var stopTime = this.toSeconds(time, now); - this._oscillator.stop(stopTime); - this._oscillator = null; - if (time){ - //set a timeout for when it stops - setTimeout(this.onended, (stopTime - now) * 1000); - } else { - this.onended(); - } + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Stopped){ + this._state.setStateAtTime(Tone.State.Stopped, time); } - return this; + return this; }; + /** + * Pause the clock. Pausing does not reset the tick counter. + * @param {Time} [time=now] The time when the clock should stop. + * @returns {Tone.Clock} this + */ + Tone.Clock.prototype.pause = function(time){ + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) === Tone.State.Started){ + this._state.setStateAtTime(Tone.State.Paused, time); + } + return this; + }; + + /** + * The scheduling loop. + * @param {Number} time The current page time starting from 0 + * when the page was loaded. * @private - * @param {AudioProcessingEvent} event */ - Tone.Clock.prototype._processBuffer = function(event){ - var now = this.defaultArg(event.playbackTime, this.now()); - var bufferSize = this._jsNode.bufferSize; - var incomingBuffer = event.inputBuffer.getChannelData(0); - var upTick = this._upTick; - var self = this; - for (var i = 0; i < bufferSize; i++){ - var sample = incomingBuffer[i]; - if (sample > 0 && !upTick){ - upTick = true; - //get the callback out of audio thread - setTimeout(function(){ - //to account for the double buffering - var tickTime = now + self.samplesToSeconds(i + bufferSize * 2); - return function(){ - if (self.tick){ - self.tick(tickTime); - } - }; - }(), 0); // jshint ignore:line - } else if (sample < 0 && upTick){ - upTick = false; + Tone.Clock.prototype._loop = function(time){ + this._loopID = requestAnimationFrame(this._boundLoop); + //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; + } + } + } else { + this._computedLookAhead = this._lookAhead; + } + //get the frequency value to compute the value of the next loop + var now = this.now(); + //if it's started + var lookAhead = this._computedLookAhead * 2; + var event = this._state.getEvent(now + lookAhead); + var state = Tone.State.Stopped; + if (event){ + state = event.state; + //if it was stopped and now started + if (this._nextTick === -1 && state === Tone.State.Started){ + this._nextTick = event.time; + if (!this.isUndef(event.offset)){ + this.ticks = event.offset; + } } } - this._upTick = upTick; + 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); + this.ticks++; + } + } else if (state === Tone.State.Stopped){ + this._nextTick = -1; + this.ticks = 0; + } }; /** - * Clean up. + * Returns the scheduled state at the given time. + * @param {Time} time The time to query. + * @return {String} The name of the state input in setStateAtTime. + * @example + * clock.start("+0.1"); + * clock.getStateAtTime("+0.1"); //returns "started" + */ + Tone.Clock.prototype.getStateAtTime = function(time){ + return this._state.getStateAtTime(time); + }; + + /** + * Clean up * @returns {Tone.Clock} this */ Tone.Clock.prototype.dispose = function(){ - this._jsNode.disconnect(); + cancelAnimationFrame(this._loopID); + Tone.TimelineState.prototype.dispose.call(this); + this._writable("frequency"); this.frequency.dispose(); this.frequency = null; - if (this._oscillator){ - this._oscillator.disconnect(); - this._oscillator = null; - } - this._jsNode.onaudioprocess = Tone.noOp; - this._jsNode = null; - this.tick = null; - this.onended = Tone.noOp; - return this; + this._boundLoop = Tone.noOp; + this._nextTick = Infinity; + this.callback = null; + this._state.dispose(); + this._state = null; }; return Tone.Clock; diff --git a/Tone/core/Delay.js b/Tone/core/Delay.js new file mode 100644 index 000000000..7f408272f --- /dev/null +++ b/Tone/core/Delay.js @@ -0,0 +1,63 @@ +define(["Tone/core/Tone", "Tone/core/Param"], function (Tone) { + + "use strict"; + + /** + * @class Wrapper around Web Audio's native [DelayNode](http://webaudio.github.io/web-audio-api/#the-delaynode-interface). + * @extends {Tone} + * @param {Time=} delayTime The delay applied to the incoming signal. + * @param {Time=} maxDelay The maximum delay time. + */ + Tone.Delay = function(){ + + var options = this.optionsObject(arguments, ["delayTime", "maxDelay"], Tone.Delay.defaults); + + /** + * The native delay node + * @type {DelayNode} + * @private + */ + this._delayNode = this.input = this.output = this.context.createDelay(this.toSeconds(options.maxDelay)); + + /** + * The amount of time the incoming signal is + * delayed. + * @type {Tone.Param} + * @signal + */ + this.delayTime = new Tone.Param({ + "param" : this._delayNode.delayTime, + "units" : Tone.Type.Time, + "value" : options.delayTime + }); + + this._readOnly("delayTime"); + }; + + Tone.extend(Tone.Delay); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.Delay.defaults = { + "maxDelay" : 1, + "delayTime" : 0 + }; + + /** + * 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; +}); \ No newline at end of file diff --git a/Tone/core/Emitter.js b/Tone/core/Emitter.js new file mode 100644 index 000000000..c4574de24 --- /dev/null +++ b/Tone/core/Emitter.js @@ -0,0 +1,118 @@ +define(["Tone/core/Tone"], function (Tone) { + + "use strict"; + + /** + * @class Tone.Emitter gives classes which extend it + * the ability to listen for and trigger events. + * Inspiration and reference from Jerome Etienne's [MicroEvent](https://github.com/jeromeetienne/microevent.js). + * MIT (c) 2011 Jerome Etienne. + * + * @extends {Tone} + */ + Tone.Emitter = function(){ + /** + * Contains all of the events. + * @private + * @type {Object} + */ + this._events = {}; + }; + + Tone.extend(Tone.Emitter); + + /** + * Bind a callback to a specific event. + * @param {String} event The name of the event to listen for. + * @param {Function} callback The callback to invoke when the + * event is triggered + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.on = function(event, callback){ + //split the event + var events = event.split(/\W+/); + for (var i = 0; i < events.length; i++){ + var eventName = events[i]; + if (!this._events.hasOwnProperty(eventName)){ + this._events[eventName] = []; + } + this._events[eventName].push(callback); + } + return this; + }; + + /** + * Remove the event listener. + * @param {String} event The event to stop listening to. + * @param {Function=} callback The callback which was bound to + * the event with Tone.Emitter.on. + * If no callback is given, all callbacks + * events are removed. + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.off = function(event, callback){ + var events = event.split(/\W+/); + for (var ev = 0; ev < events.length; ev++){ + event = events[ev]; + if (this._events.hasOwnProperty(event)){ + if (Tone.prototype.isUndef(callback)){ + this._events[event] = []; + } else { + var eventList = this._events[event]; + for (var i = 0; i < eventList.length; i++){ + if (eventList[i] === callback){ + eventList.splice(i, 1); + } + } + } + } + } + return this; + }; + + /** + * Invoke all of the callbacks bound to the event + * with any arguments passed in. + * @param {String} event The name of the event. + * @param {*...} args The arguments to pass to the functions listening. + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.trigger = function(event){ + if (this._events){ + var args = Array.prototype.slice.call(arguments, 1); + if (this._events.hasOwnProperty(event)){ + var eventList = this._events[event]; + for (var i = 0, len = eventList.length; i < len; i++){ + eventList[i].apply(this, args); + } + } + } + return this; + }; + + /** + * Add Emitter functions (on/off/trigger) to the object + * @param {Object|Function} object The object or class to extend. + */ + Tone.Emitter.mixin = function(object){ + var functions = ["on", "off", "trigger"]; + object._events = {}; + for (var i = 0; i < functions.length; i++){ + var func = functions[i]; + var emitterFunc = Tone.Emitter.prototype[func]; + object[func] = emitterFunc; + } + }; + + /** + * Clean up + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._events = null; + return this; + }; + + return Tone.Emitter; +}); \ No newline at end of file diff --git a/Tone/core/Gain.js b/Tone/core/Gain.js new file mode 100644 index 000000000..f5db11a79 --- /dev/null +++ b/Tone/core/Gain.js @@ -0,0 +1,64 @@ +define(["Tone/core/Tone", "Tone/core/Param", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class A thin wrapper around the Native Web Audio GainNode. + * The GainNode is a basic building block of the Web Audio + * API and is useful for routing audio and adjusting gains. + * @extends {Tone} + * @param {Number=} gain The initial gain of the GainNode + * @param {Tone.Type=} units The units of the gain parameter. + */ + Tone.Gain = function(){ + + var options = this.optionsObject(arguments, ["gain", "units"], Tone.Gain.defaults); + + /** + * The GainNode + * @type {GainNode} + * @private + */ + this.input = this.output = this._gainNode = this.context.createGain(); + + /** + * The gain parameter of the gain node. + * @type {AudioParam} + * @signal + */ + this.gain = new Tone.Param({ + "param" : this._gainNode.gain, + "units" : options.units, + "value" : options.gain, + "convert" : options.convert + }); + this._readOnly("gain"); + }; + + Tone.extend(Tone.Gain); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.Gain.defaults = { + "gain" : 1, + "convert" : true, + }; + + /** + * Clean up. + * @return {Tone.Gain} this + */ + Tone.Gain.prototype.dispose = function(){ + Tone.Param.prototype.dispose.call(this); + this._gainNode.disconnect(); + this._gainNode = null; + this._writable("gain"); + this.gain.dispose(); + this.gain = null; + }; + + return Tone.Gain; +}); \ No newline at end of file diff --git a/Tone/core/IntervalTimeline.js b/Tone/core/IntervalTimeline.js new file mode 100644 index 000000000..77fe80bb5 --- /dev/null +++ b/Tone/core/IntervalTimeline.js @@ -0,0 +1,605 @@ +define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class Similar to Tone.Timeline, but all events represent + * intervals with both "time" and "duration" times. The + * events are placed in a tree structure optimized + * for querying an intersection point with the timeline + * events. Internally uses an [Interval Tree](https://en.wikipedia.org/wiki/Interval_tree) + * to represent the data. + * @extends {Tone} + */ + Tone.IntervalTimeline = function(){ + + /** + * The root node of the inteval tree + * @type {IntervalNode} + * @private + */ + this._root = null; + + /** + * Keep track of the length of the timeline. + * @type {Number} + * @private + */ + this._length = 0; + }; + + Tone.extend(Tone.IntervalTimeline); + + /** + * The event to add to the timeline. All events must + * have a time and duration value + * @param {Object} event The event to add to the timeline + * @return {Tone.IntervalTimeline} this + */ + 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"); + } + var node = new IntervalNode(event.time, event.time + event.duration, event); + if (this._root === null){ + this._root = node; + } else { + this._root.insert(node); + } + this._length++; + // Restructure tree to be balanced + while (node !== null) { + node.updateHeight(); + node.updateMax(); + this._rebalance(node); + node = node.parent; + } + return this; + }; + + /** + * Remove an event from the timeline. + * @param {Object} event The event to remove from the timeline + * @return {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.removeEvent = function(event){ + if (this._root !== null){ + var results = []; + this._root.search(event.time, results); + for (var i = 0; i < results.length; i++){ + var node = results[i]; + if (node.event === event){ + this._removeNode(node); + this._length--; + break; + } + } + } + return this; + }; + + /** + * The number of items in the timeline. + * @type {Number} + * @memberOf Tone.IntervalTimeline# + * @name length + * @readOnly + */ + Object.defineProperty(Tone.IntervalTimeline.prototype, "length", { + get : function(){ + return this._length; + } + }); + + /** + * Remove events whose time time is after the given time + * @param {Time} time The time to query. + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.cancel = function(after){ + after = this.toSeconds(after); + this.forEachAfter(after, function(event){ + this.removeEvent(event); + }.bind(this)); + return this; + }; + + /** + * Set the root node as the given node + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._setRoot = function(node){ + this._root = node; + if (this._root !== null){ + this._root.parent = null; + } + }; + + /** + * Replace the references to the node in the node's parent + * with the replacement node. + * @param {IntervalNode} node + * @param {IntervalNode} replacement + * @private + */ + Tone.IntervalTimeline.prototype._replaceNodeInParent = function(node, replacement){ + if (node.parent !== null){ + if (node.isLeftChild()){ + node.parent.left = replacement; + } else { + node.parent.right = replacement; + } + this._rebalance(node.parent); + } else { + this._setRoot(replacement); + } + }; + + /** + * Remove the node from the tree and replace it with + * a successor which follows the schema. + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._removeNode = function(node){ + if (node.left === null && node.right === null){ + this._replaceNodeInParent(node, null); + } else if (node.right === null){ + this._replaceNodeInParent(node, node.left); + } else if (node.left === null){ + this._replaceNodeInParent(node, node.right); + } else { + var balance = node.getBalance(); + var replacement, temp; + if (balance > 0){ + if (node.left.right === null){ + replacement = node.left; + replacement.right = node.right; + temp = replacement; + } else { + replacement = node.left.right; + while (replacement.right !== null){ + replacement = replacement.right; + } + replacement.parent.right = replacement.left; + temp = replacement.parent; + replacement.left = node.left; + replacement.right = node.right; + } + } else { + if (node.right.left === null){ + replacement = node.right; + replacement.left = node.left; + temp = replacement; + } else { + replacement = node.right.left; + while (replacement.left !== null) { + replacement = replacement.left; + } + replacement.parent = replacement.parent; + replacement.parent.left = replacement.right; + temp = replacement.parent; + replacement.left = node.left; + replacement.right = node.right; + } + } + if (node.parent !== null){ + if (node.isLeftChild()){ + node.parent.left = replacement; + } else { + node.parent.right = replacement; + } + } else { + this._setRoot(replacement); + } + // this._replaceNodeInParent(node, replacement); + this._rebalance(temp); + } + node.dispose(); + }; + + /** + * Rotate the tree to the left + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rotateLeft = function(node){ + var parent = node.parent; + var isLeftChild = node.isLeftChild(); + + // Make node.right the new root of this sub tree (instead of node) + var pivotNode = node.right; + node.right = pivotNode.left; + pivotNode.left = node; + + if (parent !== null){ + if (isLeftChild){ + parent.left = pivotNode; + } else{ + parent.right = pivotNode; + } + } else{ + this._setRoot(pivotNode); + } + }; + + /** + * Rotate the tree to the right + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rotateRight = function(node){ + var parent = node.parent; + var isLeftChild = node.isLeftChild(); + + // Make node.left the new root of this sub tree (instead of node) + var pivotNode = node.left; + node.left = pivotNode.right; + pivotNode.right = node; + + if (parent !== null){ + if (isLeftChild){ + parent.left = pivotNode; + } else{ + parent.right = pivotNode; + } + } else{ + this._setRoot(pivotNode); + } + }; + + /** + * Balance the BST + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rebalance = function(node){ + var balance = node.getBalance(); + if (balance > 1){ + if (node.left.getBalance() < 0){ + this._rotateLeft(node.left); + } else { + this._rotateRight(node); + } + } else if (balance < -1) { + if (node.right.getBalance() > 0){ + this._rotateRight(node.right); + } else { + this._rotateLeft(node); + } + } + }; + + /** + * Get an event whose time and duration span the give time. Will + * return the match whose "time" value is closest to the given time. + * @param {Object} event The event to add to the timeline + * @return {Object} The event which spans the desired time + */ + Tone.IntervalTimeline.prototype.getEvent = function(time){ + if (this._root !== null){ + var results = []; + this._root.search(time, results); + if (results.length > 0){ + var max = results[0]; + for (var i = 1; i < results.length; i++){ + if (results[i].low > max.low){ + max = results[i]; + } + } + return max.event; + } + } + return null; + }; + + /** + * Iterate over everything in the timeline. + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEach = function(callback){ + if (this._root !== null){ + var allNodes = []; + if (this._root !== null){ + this._root.traverse(function(node){ + allNodes.push(node); + }); + } + for (var i = 0; i < allNodes.length; i++){ + var ev = allNodes[i].event; + if (ev){ + callback(ev); + } + } + } + return this; + }; + + /** + * Iterate over everything in the array in which the given time + * overlaps with the time and duration time of the event. + * @param {Time} time The time to check if items are overlapping + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEachOverlap = function(time, callback){ + time = this.toSeconds(time); + if (this._root !== null){ + var results = []; + this._root.search(time, results); + for (var i = results.length - 1; i >= 0; i--){ + var ev = results[i].event; + if (ev){ + callback(ev); + } + } + } + return this; + }; + + /** + * Iterate over everything in the array in which the time is greater + * than the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEachAfter = function(time, callback){ + time = this.toSeconds(time); + if (this._root !== null){ + var results = []; + this._root.searchAfter(time, results); + for (var i = results.length - 1; i >= 0; i--){ + var ev = results[i].event; + if (ev){ + callback(ev); + } + } + } + return this; + }; + + /** + * Clean up + * @return {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.dispose = function() { + var allNodes = []; + if (this._root !== null){ + this._root.traverse(function(node){ + allNodes.push(node); + }); + } + for (var i = 0; i < allNodes.length; i++){ + allNodes[i].dispose(); + } + allNodes = null; + this._root = null; + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // INTERVAL NODE HELPER + /////////////////////////////////////////////////////////////////////////// + + /** + * Represents a node in the binary search tree, with the addition + * of a "high" value which keeps track of the highest value of + * its children. + * References: + * https://brooknovak.wordpress.com/2013/12/07/augmented-interval-tree-in-c/ + * http://www.mif.vu.lt/~valdas/ALGORITMAI/LITERATURA/Cormen/Cormen.pdf + * @param {Number} low + * @param {Number} high + * @private + */ + var IntervalNode = function(low, high, event){ + //the event container + this.event = event; + //the low value + this.low = low; + //the high value + this.high = high; + //the high value for this and all child nodes + this.max = this.high; + //the nodes to the left + this._left = null; + //the nodes to the right + this._right = null; + //the parent node + this.parent = null; + //the number of child nodes + this.height = 0; + }; + + /** + * Insert a node into the correct spot in the tree + * @param {IntervalNode} node + */ + IntervalNode.prototype.insert = function(node) { + if (node.low <= this.low){ + if (this.left === null){ + this.left = node; + } else { + this.left.insert(node); + } + } else { + if (this.right === null){ + this.right = node; + } else { + this.right.insert(node); + } + } + }; + + /** + * Search the tree for nodes which overlap + * with the given point + * @param {Number} point The point to query + * @param {Array} results The array to put the results + */ + IntervalNode.prototype.search = function(point, results) { + // If p is to the right of the rightmost point of any interval + // in this node and all children, there won't be any matches. + if (point > this.max){ + return; + } + // Search left children + if (this.left !== null){ + this.left.search(point, results); + } + // Check this node + if (this.low <= point && this.high >= point){ + results.push(this); + } + // If p is to the left of the time of this interval, + // then it can't be in any child to the right. + if (this.low > point){ + return; + } + // Search right children + if (this.right !== null){ + this.right.search(point, results); + } + }; + + /** + * Search the tree for nodes which are less + * than the given point + * @param {Number} point The point to query + * @param {Array} results The array to put the results + */ + IntervalNode.prototype.searchAfter = function(point, results) { + // Check this node + if (this.low >= point){ + results.push(this); + if (this.left !== null){ + this.left.searchAfter(point, results); + } + } + // search the right side + if (this.right !== null){ + this.right.searchAfter(point, results); + } + }; + + /** + * Invoke the callback on this element and both it's branches + * @param {Function} callback + */ + IntervalNode.prototype.traverse = function(callback){ + callback(this); + if (this.left !== null){ + this.left.traverse(callback); + } + if (this.right !== null){ + this.right.traverse(callback); + } + }; + + /** + * Update the height of the node + */ + IntervalNode.prototype.updateHeight = function(){ + if (this.left !== null && this.right !== null){ + this.height = Math.max(this.left.height, this.right.height) + 1; + } else if (this.right !== null){ + this.height = this.right.height + 1; + } else if (this.left !== null){ + this.height = this.left.height + 1; + } else { + this.height = 0; + } + }; + + /** + * Update the height of the node + */ + IntervalNode.prototype.updateMax = function(){ + this.max = this.high; + if (this.left !== null){ + this.max = Math.max(this.max, this.left.max); + } + if (this.right !== null){ + this.max = Math.max(this.max, this.right.max); + } + }; + + /** + * The balance is how the leafs are distributed on the node + * @return {Number} Negative numbers are balanced to the right + */ + IntervalNode.prototype.getBalance = function() { + var balance = 0; + if (this.left !== null && this.right !== null){ + balance = this.left.height - this.right.height; + } else if (this.left !== null){ + balance = this.left.height + 1; + } else if (this.right !== null){ + balance = -(this.right.height + 1); + } + return balance; + }; + + /** + * @returns {Boolean} true if this node is the left child + * of its parent + */ + IntervalNode.prototype.isLeftChild = function() { + return this.parent !== null && this.parent.left === this; + }; + + /** + * get/set the left node + * @type {IntervalNode} + */ + Object.defineProperty(IntervalNode.prototype, "left", { + get : function(){ + return this._left; + }, + set : function(node){ + this._left = node; + if (node !== null){ + node.parent = this; + } + this.updateHeight(); + this.updateMax(); + } + }); + + /** + * get/set the right node + * @type {IntervalNode} + */ + Object.defineProperty(IntervalNode.prototype, "right", { + get : function(){ + return this._right; + }, + set : function(node){ + this._right = node; + if (node !== null){ + node.parent = this; + } + this.updateHeight(); + this.updateMax(); + } + }); + + /** + * null out references. + */ + IntervalNode.prototype.dispose = function() { + this.parent = null; + this._left = null; + this._right = null; + this.event = null; + }; + + /////////////////////////////////////////////////////////////////////////// + // END INTERVAL NODE HELPER + /////////////////////////////////////////////////////////////////////////// + + return Tone.IntervalTimeline; +}); \ No newline at end of file diff --git a/Tone/core/Master.js b/Tone/core/Master.js index e30d7d887..b2d372d75 100644 --- a/Tone/core/Master.js +++ b/Tone/core/Master.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/Volume"], function(Tone){ "use strict"; @@ -39,13 +39,21 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ */ this._muted = false; + /** + * 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 = new Tone.Signal(this.output.gain, Tone.Type.Decibels); + this.volume = this._volume.volume; + this._readOnly("volume"); //connections this.input.chain(this.output, this.context.destination); }; @@ -112,6 +120,18 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ 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 /////////////////////////////////////////////////////////////////////////// diff --git a/Tone/core/Note.js b/Tone/core/Note.js index e6850e42b..fa47b2ebe 100644 --- a/Tone/core/Note.js +++ b/Tone/core/Note.js @@ -55,7 +55,7 @@ define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){ * @returns {Tone.Note} this */ Tone.Note.prototype.dispose = function(){ - Tone.Tranport.clearTimeline(this._timelineID); + Tone.Transport.clearTimeline(this._timelineID); this.value = null; return this; }; @@ -163,6 +163,8 @@ define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){ 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); } @@ -175,141 +177,5 @@ define(["Tone/core/Tone", "Tone/core/Transport"], function(Tone){ return notes; }; - /////////////////////////////////////////////////////////////////////////// - // MUSIC NOTES - // - // Augments Tone.prototype to include note methods - /////////////////////////////////////////////////////////////////////////// - - var noteToIndex = { "c" : 0, "c#" : 1, "db" : 1, "d" : 2, "d#" : 3, "eb" : 3, - "e" : 4, "f" : 5, "f#" : 6, "gb" : 6, "g" : 7, "g#" : 8, "ab" : 8, - "a" : 9, "a#" : 10, "bb" : 10, "b" : 11 - }; - - var noteIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; - - var middleC = 261.6255653005986; - - /** - * Convert a note name to frequency. - * @param {string} note - * @return {number} - * @example - * var freq = tone.noteToFrequency("A4"); //returns 440 - */ - Tone.prototype.noteToFrequency = function(note){ - //break apart the note by frequency and octave - var parts = note.split(/(\d+)/); - if (parts.length === 3){ - var index = noteToIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - var noteNumber = index + parseInt(octave, 10) * 12; - return Math.pow(2, (noteNumber - 48) / 12) * middleC; - } else { - return 0; - } - }; - - /** - * Test if a string is in note format: i.e. "C4". - * @param {string|number} note The note to test - * @return {boolean} true if it's in the form of a note - * @method isNotation - * @lends Tone.prototype.isNote - * @function - */ - Tone.prototype.isNote = ( function(){ - var noteFormat = new RegExp(/[a-g]{1}([b#]{1}|[b#]{0})[0-9]+$/i); - return function(note){ - if (typeof note === "string"){ - note = note.toLowerCase(); - } - return noteFormat.test(note); - }; - })(); - - /** - * A pointer to the previous toFrequency method - * @private - * @function - */ - Tone.prototype._overwrittenToFrequency = Tone.prototype.toFrequency; - - /** - * A method which accepts frequencies in the form - * of notes (`"C#4"`), frequencies as strings ("49hz"), frequency numbers, - * or Time and converts them to their frequency as a number in hertz. - * @param {Frequency} note the note name or notation - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} the frequency as a number - */ - Tone.prototype.toFrequency = function(note, now){ - if (this.isNote(note)){ - note = this.noteToFrequency(note); - } - return this._overwrittenToFrequency(note, now); - }; - - /** - * Convert a 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 / middleC) / Math.LN2; - var noteNumber = Math.round(12 * log) + 48; - var octave = Math.floor(noteNumber/12); - var noteName = noteIndexToNote[noteNumber % 12]; - return noteName + octave.toString(); - }; - - /** - * Convert an interval (in semitones) to a frequency ratio. - * - * @param {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) - 2; - var note = midiNumber % 12; - return noteIndexToNote[note] + octave; - }; - - /** - * Convert a note to it's midi value. - * - * @param {string} note the note name (i.e. "C3") - * @return {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 = noteToIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - return index + (parseInt(octave, 10) + 2) * 12; - } else { - return 0; - } - }; - return Tone.Note; }); \ No newline at end of file diff --git a/Tone/core/Param.js b/Tone/core/Param.js new file mode 100644 index 000000000..1beb12784 --- /dev/null +++ b/Tone/core/Param.js @@ -0,0 +1,322 @@ +define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ + + "use strict"; + + /** + * @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.value = convertedVal; + } + }); + + /** + * Convert the given value from the type specified by Tone.Param.units + * into the destination value (such as Gain or Frequency). + * @private + * @param {*} val the value to convert + * @return {number} the number which the value should be set to + */ + Tone.Param.prototype._fromUnits = function(val){ + if (this.convert || this.isUndef(this.convert)){ + switch(this.units){ + case Tone.Type.Time: + return this.toSeconds(val); + case Tone.Type.Frequency: + return this.toFrequency(val); + case Tone.Type.Decibels: + return this.dbToGain(val); + case Tone.Type.NormalRange: + return Math.min(Math.max(val, 0), 1); + case Tone.Type.AudioRange: + return Math.min(Math.max(val, -1), 1); + case Tone.Type.Positive: + return Math.max(val, 0); + default: + return val; + } + } else { + return val; + } + }; + + /** + * Convert the parameters value into the units specified by Tone.Param.units. + * @private + * @param {number} val the value to convert + * @return {number} + */ + Tone.Param.prototype._toUnits = function(val){ + if (this.convert || this.isUndef(this.convert)){ + switch(this.units){ + case Tone.Type.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; + } + }; + + /** + * the minimum output value + * @type {Number} + * @private + */ + Tone.Param.prototype._minOutput = 0.00001; + + /** + * Schedules a parameter value change at the given time. + * @param {*} value The value to set the signal. + * @param {Time} time The time when the change should occur. + * @returns {Tone.Param} this + * @example + * //set the frequency to "G4" in exactly 1 second from now. + * freq.setValueAtTime("G4", "+1"); + */ + Tone.Param.prototype.setValueAtTime = function(value, time){ + value = this._fromUnits(value); + this._param.setValueAtTime(value, this.toSeconds(time)); + return this; + }; + + /** + * Creates a schedule point with the current value at the current time. + * This is useful for creating an automation anchor point in order to + * schedule changes from the current value. + * + * @param {number=} now (Optionally) pass the now value in. + * @returns {Tone.Param} this + */ + Tone.Param.prototype.setRampPoint = function(now){ + now = this.defaultArg(now, this.now()); + var currentVal = this._param.value; + this._param.setValueAtTime(currentVal, now); + return this; + }; + + /** + * Schedules a linear continuous change in parameter value from the + * previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.Param} this + */ + Tone.Param.prototype.linearRampToValueAtTime = function(value, endTime){ + value = this._fromUnits(value); + this._param.linearRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + + /** + * Schedules an exponential continuous change in parameter value from + * the previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.Param} this + */ + Tone.Param.prototype.exponentialRampToValueAtTime = function(value, endTime){ + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); + return this; + }; + + /** + * Schedules an exponential continuous change in parameter value from + * the current time and current value to the given value over the + * duration of the rampTime. + * + * @param {number} value The value to ramp to. + * @param {Time} rampTime the time that it takes the + * value to ramp from it's current value + * @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)); + return this; + }; + + /** + * Schedules an linear continuous change in parameter value from + * the current time and current value to the given value over the + * duration of the rampTime. + * + * @param {number} value The value to ramp to. + * @param {Time} rampTime the time that it takes the + * value to ramp from it's current value + * @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)); + return this; + }; + + /** + * Start exponentially approaching the target value at the given time with + * a rate having the given time constant. + * @param {number} value + * @param {Time} startTime + * @param {number} timeConstant + * @returns {Tone.Param} this + */ + Tone.Param.prototype.setTargetAtTime = function(value, startTime, timeConstant){ + value = this._fromUnits(value); + // The value will never be able to approach without timeConstant > 0. + // http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation + // is described. 0 results in a division by 0. + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); + return this; + }; + + /** + * Sets an array of arbitrary parameter values starting at the given time + * for the given duration. + * + * @param {Array} values + * @param {Time} startTime + * @param {Time} duration + * @returns {Tone.Param} this + */ + Tone.Param.prototype.setValueCurveAtTime = function(values, startTime, duration){ + for (var i = 0; i < values.length; i++){ + values[i] = this._fromUnits(values[i]); + } + this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); + return this; + }; + + /** + * Cancels all scheduled parameter changes with times greater than or + * equal to startTime. + * + * @param {Time} startTime + * @returns {Tone.Param} this + */ + Tone.Param.prototype.cancelScheduledValues = function(startTime){ + this._param.cancelScheduledValues(this.toSeconds(startTime)); + return this; + }; + + /** + * Ramps to the given value over the duration of the rampTime. + * Automatically selects the best ramp type (exponential or linear) + * depending on the `units` of the signal + * + * @param {number} value + * @param {Time} rampTime the time that it takes the + * value to ramp from it's current value + * @returns {Tone.Param} this + * @example + * //ramp to the value either linearly or exponentially + * //depending on the "units" value of the signal + * signal.rampTo(0, 10); + */ + Tone.Param.prototype.rampTo = function(value, rampTime){ + rampTime = this.defaultArg(rampTime, 0); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM){ + this.exponentialRampToValue(value, rampTime); + } else { + this.linearRampToValue(value, rampTime); + } + return this; + }; + + /** + * Clean up + * @returns {Tone.Param} this + */ + Tone.Param.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._param = null; + return this; + }; + + return Tone.Param; +}); \ No newline at end of file diff --git a/Tone/core/Timeline.js b/Tone/core/Timeline.js new file mode 100644 index 000000000..3379bbf00 --- /dev/null +++ b/Tone/core/Timeline.js @@ -0,0 +1,350 @@ +define(["Tone/core/Tone", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class A Timeline class for scheduling and maintaining state + * along a timeline. All events must have a "time" property. + * Internally, events are stored in time order for fast + * retrieval. + * @extends {Tone} + * @param {Positive} [memory=Infinity] The number of previous events that are retained. + */ + Tone.Timeline = function(){ + + var options = this.optionsObject(arguments, ["memory"], Tone.Timeline.defaults); + + /** + * The array of scheduled timeline events + * @type {Array} + * @private + */ + this._timeline = []; + + /** + * An array of items to remove from the list. + * @type {Array} + * @private + */ + this._toRemove = []; + + /** + * Flag if the tieline is mid iteration + * @private + * @type {Boolean} + */ + this._iterating = false; + + /** + * The memory of the timeline, i.e. + * how many events in the past it will retain + * @type {Positive} + */ + this.memory = options.memory; + }; + + Tone.extend(Tone.Timeline); + + /** + * the default parameters + * @static + * @const + */ + Tone.Timeline.defaults = { + "memory" : Infinity + }; + + /** + * The number of items in the timeline. + * @type {Number} + * @memberOf Tone.Timeline# + * @name length + * @readOnly + */ + Object.defineProperty(Tone.Timeline.prototype, "length", { + get : function(){ + return this._timeline.length; + } + }); + + /** + * Insert an event object onto the timeline. Events must have a "time" attribute. + * @param {Object} event The event object to insert into the + * timeline. + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.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"); + } + event.time = this.toSeconds(event.time); + if (this._timeline.length){ + var index = this._search(event.time); + this._timeline.splice(index + 1, 0, event); + } else { + this._timeline.push(event); + } + //if the length is more than the memory, remove the previous ones + if (this.length > this.memory){ + var diff = this.length - this.memory; + this._timeline.splice(0, diff); + } + return this; + }; + + /** + * Remove an event from the timeline. + * @param {Object} event The event object to remove from the list. + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.removeEvent = function(event){ + if (this._iterating){ + this._toRemove.push(event); + } else { + var index = this._timeline.indexOf(event); + if (index !== -1){ + this._timeline.splice(index, 1); + } + } + return this; + }; + + /** + * Get the 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. + */ + Tone.Timeline.prototype.getEvent = function(time){ + time = this.toSeconds(time); + var index = this._search(time); + if (index !== -1){ + return this._timeline[index]; + } else { + return null; + } + }; + + /** + * Get the event which is scheduled after the given time. + * @param {Number} time The time to query. + * @returns {Object} The event object after the given time + */ + Tone.Timeline.prototype.getEventAfter = function(time){ + time = this.toSeconds(time); + var index = this._search(time); + if (index + 1 < this._timeline.length){ + return this._timeline[index + 1]; + } else { + return null; + } + }; + + /** + * Get the event before the event at the given time. + * @param {Number} time The time to query. + * @returns {Object} The event object before the given time + */ + Tone.Timeline.prototype.getEventBefore = function(time){ + time = this.toSeconds(time); + var index = this._search(time); + if (index - 1 >= 0){ + return this._timeline[index - 1]; + } else { + return null; + } + }; + + /** + * Cancel events after the given time + * @param {Time} time The time to query. + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.cancel = function(after){ + if (this._timeline.length > 1){ + after = this.toSeconds(after); + var index = this._search(after); + if (index >= 0){ + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = []; + } + } else if (this._timeline.length === 1){ + //the first item's time + if (this._timeline[0].time >= after){ + this._timeline = []; + } + } + return this; + }; + + /** + * Cancel events before or equal to the given time. + * @param {Time} time The time to cancel before. + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.cancelBefore = function(time){ + if (this._timeline.length){ + time = this.toSeconds(time); + var index = this._search(time); + if (index >= 0){ + this._timeline = this._timeline.slice(index + 1); + } + } + return this; + }; + + /** + * Does a binary serach on the timeline array and returns the + * event which is after or equal to the time. + * @param {Number} time + * @return {Number} the index in the timeline array + * @private + */ + Tone.Timeline.prototype._search = function(time){ + var beginning = 0; + var len = this._timeline.length; + var end = len; + // continue searching while [imin,imax] is not empty + while (beginning <= end && beginning < len){ + // calculate the midpoint for roughly equal partition + var midPoint = Math.floor(beginning + (end - beginning) / 2); + var event = this._timeline[midPoint]; + if (event.time === time){ + //choose the last one that has the same time + for (var i = midPoint; i < this._timeline.length; i++){ + var testEvent = this._timeline[i]; + if (testEvent.time === time){ + midPoint = i; + } + } + return midPoint; + } else if (event.time > time){ + //search lower + end = midPoint - 1; + } else if (event.time < time){ + //search upper + beginning = midPoint + 1; + } + } + return beginning - 1; + }; + + /** + * Internal iterator. Applies extra safety checks for + * removing items from the array. + * @param {Function} callback + * @param {Number=} lowerBound + * @param {Number=} upperBound + * @private + */ + Tone.Timeline.prototype._iterate = function(callback, lowerBound, upperBound){ + this._iterating = true; + lowerBound = this.defaultArg(lowerBound, 0); + upperBound = this.defaultArg(upperBound, this._timeline.length - 1); + for (var i = lowerBound; i <= upperBound; i++){ + callback(this._timeline[i]); + } + this._iterating = false; + if (this._toRemove.length > 0){ + for (var j = 0; j < this._toRemove.length; j++){ + var index = this._timeline.indexOf(this._toRemove[j]); + if (index !== -1){ + this._timeline.splice(index, 1); + } + } + this._toRemove = []; + } + }; + + /** + * Iterate over everything in the array + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEach = function(callback){ + this._iterate(callback); + return this; + }; + + /** + * Iterate over everything in the array at or before the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEachBefore = function(time, callback){ + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1){ + this._iterate(callback, 0, upperBound); + } + return this; + }; + + /** + * Iterate over everything in the array after the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEachAfter = function(time, callback){ + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var lowerBound = this._search(time); + this._iterate(callback, lowerBound + 1); + return this; + }; + + /** + * Iterate over everything in the array at or after the given time. Similar to + * forEachAfter, but includes the item(s) at the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEachFrom = function(time, callback){ + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var lowerBound = this._search(time); + //work backwards until the event time is less than time + while (lowerBound >= 0 && this._timeline[lowerBound].time >= time){ + lowerBound--; + } + this._iterate(callback, lowerBound + 1); + return this; + }; + + /** + * Iterate over everything in the array at the given time + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEachAtTime = function(time, callback){ + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1){ + this._iterate(function(event){ + if (event.time === time){ + callback(event); + } + }, 0, upperBound); + } + return this; + }; + + /** + * Clean up. + * @return {Tone.Timeline} this + */ + Tone.Timeline.prototype.dispose = function(){ + Tone.prototype.dispose.call(this); + this._timeline = null; + this._toRemove = null; + }; + + return Tone.Timeline; +}); \ No newline at end of file diff --git a/Tone/core/TimelineState.js b/Tone/core/TimelineState.js new file mode 100644 index 000000000..079ddaa94 --- /dev/null +++ b/Tone/core/TimelineState.js @@ -0,0 +1,56 @@ +define(["Tone/core/Tone", "Tone/core/Timeline", "Tone/core/Type"], function (Tone) { + + "use strict"; + + /** + * @class A Timeline State. Provides the methods: setStateAtTime("state", time) + * and getStateAtTime(time). + * + * @extends {Tone.Timeline} + * @param {String} initial The initial state of the TimelineState. + * Defaults to undefined + */ + Tone.TimelineState = function(initial){ + + Tone.Timeline.call(this); + + /** + * The initial state + * @private + * @type {String} + */ + this._initial = initial; + }; + + Tone.extend(Tone.TimelineState, Tone.Timeline); + + /** + * Returns the scheduled state scheduled before or at + * the given time. + * @param {Time} time The time to query. + * @return {String} The name of the state input in setStateAtTime. + */ + Tone.TimelineState.prototype.getStateAtTime = function(time){ + var event = this.getEvent(time); + if (event !== null){ + return event.state; + } else { + return this._initial; + } + }; + + /** + * Returns the scheduled state scheduled before or at + * the given time. + * @param {String} state The name of the state to set. + * @param {Time} time The time to query. + */ + Tone.TimelineState.prototype.setStateAtTime = function(state, time){ + this.addEvent({ + "state" : state, + "time" : this.toSeconds(time) + }); + }; + + return Tone.TimelineState; +}); \ No newline at end of file diff --git a/Tone/core/Tone.js b/Tone/core/Tone.js index c6e0bf8d9..2151b1ddc 100644 --- a/Tone/core/Tone.js +++ b/Tone/core/Tone.js @@ -152,9 +152,9 @@ define(function(){ * }, 3); */ Tone.prototype.set = function(params, value, rampTime){ - if (typeof params === "object"){ + if (this.isObject(params)){ rampTime = value; - } else if (typeof params === "string"){ + } else if (this.isString(params)){ var tmpObj = {}; tmpObj[params] = value; params = tmpObj; @@ -173,7 +173,8 @@ define(function(){ if (isUndef(param)){ continue; } - if (param instanceof Tone.Signal){ + if ((Tone.Signal && param instanceof Tone.Signal) || + (Tone.Param && param instanceof Tone.Param)){ if (param.value !== value){ if (isUndef(rampTime)){ param.value = value; @@ -217,7 +218,7 @@ define(function(){ Tone.prototype.get = function(params){ if (isUndef(params)){ params = this._collectDefaults(this.constructor); - } else if (typeof params === "string"){ + } else if (this.isString(params)){ params = [params]; } var ret = {}; @@ -236,9 +237,11 @@ define(function(){ attr = attrSplit[attrSplit.length - 1]; } var param = parent[attr]; - if (typeof params[attr] === "object"){ + if (this.isObject(params[attr])){ subRet[attr] = param.get(); - } else if (param instanceof Tone.Signal){ + } else if (Tone.Signal && param instanceof Tone.Signal){ + subRet[attr] = param.value; + } else if (Tone.Param && param instanceof Tone.Param){ subRet[attr] = param.value; } else if (param instanceof AudioParam){ subRet[attr] = param.value; @@ -274,18 +277,6 @@ define(function(){ return ret; }; - /** - * Set the preset if it exists. - * @param {string} presetName the name of the preset - * @returns {Tone} this - */ - Tone.prototype.setPreset = function(presetName){ - if (!this.isUndef(this.preset) && this.preset.hasOwnProperty(presetName)){ - this.set(this.preset[presetName]); - } - return this; - }; - /** * @returns {string} returns the name of the class as a string */ @@ -325,12 +316,12 @@ define(function(){ Tone.prototype.bufferSize = 2048; /** - * the delay time of a single buffer frame + * The delay time of a single frame (128 samples according to the spec). * @type {number} * @static * @const */ - Tone.prototype.bufferTime = Tone.prototype.bufferSize / Tone.context.sampleRate; + Tone.prototype.blockTime = 128 / Tone.context.sampleRate; /////////////////////////////////////////////////////////////////////////// // CONNECTIONS @@ -415,7 +406,7 @@ define(function(){ /** * connect together all of the arguments in series - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.connectSeries = function(){ @@ -432,7 +423,7 @@ define(function(){ /** * fan out the connection from the first argument to the rest of the arguments - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.connectParallel = function(){ @@ -468,7 +459,7 @@ define(function(){ /** * connect the output of this node to the rest of the nodes in parallel. - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.fan = function(){ @@ -489,27 +480,28 @@ define(function(){ /////////////////////////////////////////////////////////////////////////// /** - * if a the given is undefined, use the fallback. - * if both given and fallback are objects, given - * will be augmented with whatever properties it's - * missing which are in fallback - * - * warning: if object is self referential, it will go into an an - * infinite recursive loop. + * If the `given` parameter is undefined, use the `fallback`. + * If both `given` and `fallback` are object literals, it will + * return a deep copy which includes all of the parameters from both + * objects. If a parameter is undefined in given, it will return + * the fallback property. + *

+ * WARNING: if object is self referential, it will go into an an + * infinite recursive loop. * * @param {*} given * @param {*} fallback * @return {*} */ Tone.prototype.defaultArg = function(given, fallback){ - if (typeof given === "object" && typeof fallback === "object"){ + if (this.isObject(given) && this.isObject(fallback)){ var ret = {}; //make a deep copy of the given object for (var givenProp in given) { - ret[givenProp] = this.defaultArg(given[givenProp], given[givenProp]); + ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); } - for (var prop in fallback) { - ret[prop] = this.defaultArg(given[prop], fallback[prop]); + for (var fallbackProp in fallback) { + ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); } return ret; } else { @@ -521,7 +513,7 @@ define(function(){ * returns the args as an options object with given arguments * mapped to the names provided. * - * if the args given is an array containing an object, it is assumed + * if the args given is an array containing only one object, it is assumed * that that's already the options object and will just return it. * * @param {Array} values the 'arguments' object of the function @@ -533,7 +525,7 @@ define(function(){ */ Tone.prototype.optionsObject = function(values, keys, defaults){ var options = {}; - if (values.length === 1 && typeof values[0] === "object"){ + if (values.length === 1 && this.isObject(values[0])){ options = values[0]; } else { for (var i = 0; i < keys.length; i++){ @@ -547,6 +539,10 @@ define(function(){ } }; + /////////////////////////////////////////////////////////////////////////// + // TYPE CHECKING + /////////////////////////////////////////////////////////////////////////// + /** * test if the arg is undefined * @param {*} arg the argument to test @@ -563,6 +559,57 @@ define(function(){ */ Tone.prototype.isFunction = isFunction; + /** + * Test if the argument is a number. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a number + */ + Tone.prototype.isNumber = function(arg){ + return (typeof arg === "number"); + }; + + /** + * Test if the given argument is an object literal (i.e. `{}`); + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is an object literal. + */ + Tone.prototype.isObject = function(arg){ + return (Object.prototype.toString.call(arg) === "[object Object]" && arg.constructor === Object); + }; + + /** + * Test if the argument is a boolean. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a boolean + */ + Tone.prototype.isBoolean = function(arg){ + return (typeof arg === "boolean"); + }; + + /** + * Test if the argument is an Array + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is an array + */ + Tone.prototype.isArray = function(arg){ + return (Array.isArray(arg)); + }; + + /** + * Test if the argument is a string. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a string + */ + Tone.prototype.isString = function(arg){ + return (typeof arg === "string"); + }; + + /** + * An empty function. + * @static + */ + Tone.noOp = function(){}; + /** * Make the property not writable. Internal use only. * @private @@ -598,15 +645,24 @@ define(function(){ } }; + /** + * Possible play states. + * @enum {string} + */ + Tone.State = { + Started : "started", + Stopped : "stopped", + Paused : "paused", + }; + /////////////////////////////////////////////////////////////////////////// // GAIN CONVERSIONS /////////////////////////////////////////////////////////////////////////// /** - * equal power gain scale - * good for cross-fading - * @param {number} percent (0-1) - * @return {number} output gain (0-1) + * Equal power gain scale. Good for cross-fading. + * @param {NormalRange} percent (0-1) + * @return {Number} output gain (0-1) */ Tone.prototype.equalPowerScale = function(percent){ var piFactor = 0.5 * Math.PI; @@ -614,18 +670,18 @@ define(function(){ }; /** - * convert db scale to gain scale (0-1) - * @param {number} db - * @return {number} + * Convert decibels into gain. + * @param {Decibels} db + * @return {Number} */ Tone.prototype.dbToGain = function(db) { return Math.pow(2, db / 6); }; /** - * convert gain scale to decibels - * @param {number} gain (0-1) - * @return {number} + * Convert gain to decibels. + * @param {Number} gain (0-1) + * @return {Decibels} */ Tone.prototype.gainToDb = function(gain) { return 20 * (Math.log(gain) / Math.LN10); @@ -636,101 +692,15 @@ define(function(){ /////////////////////////////////////////////////////////////////////////// /** + * 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 */ Tone.prototype.now = function(){ return this.context.currentTime; }; - /** - * convert a sample count to seconds - * @param {number} samples - * @return {number} - */ - Tone.prototype.samplesToSeconds = function(samples){ - return samples / this.context.sampleRate; - }; - - /** - * convert a time into samples - * - * @param {Tone.time} time - * @return {number} - */ - Tone.prototype.toSamples = function(time){ - var seconds = this.toSeconds(time); - return Math.round(seconds * this.context.sampleRate); - }; - - /** - * convert time to seconds - * - * this is a simplified version which only handles numbers and - * 'now' relative numbers. If the Transport is included this - * method is overridden to include many other features including - * notationTime, Frequency, and transportTime - * - * @param {number=} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} seconds in the same timescale as the AudioContext - */ - Tone.prototype.toSeconds = function(time, now){ - now = this.defaultArg(now, this.now()); - if (typeof time === "number"){ - return time; //assuming that it's seconds - } else if (typeof time === "string"){ - var plusTime = 0; - if(time.charAt(0) === "+") { - time = time.slice(1); - plusTime = now; - } - return parseFloat(time) + plusTime; - } else { - return now; - } - }; - - /////////////////////////////////////////////////////////////////////////// - // FREQUENCY CONVERSION - /////////////////////////////////////////////////////////////////////////// - - /** - * true if the input is in the format number+hz - * i.e.: 10hz - * - * @param {number} freq - * @return {boolean} - * @function - */ - Tone.prototype.isFrequency = (function(){ - var freqFormat = new RegExp(/\d*\.?\d+hz$/i); - return function(freq){ - return freqFormat.test(freq); - }; - })(); - - /** - * Convert a frequency into seconds. - * Accepts numbers and strings: i.e. "10hz" or - * 10 both return 0.1. - * - * @param {number|string} freq - * @return {number} - */ - Tone.prototype.frequencyToSeconds = function(freq){ - return 1 / parseFloat(freq); - }; - - /** - * Convert a number in seconds to a frequency. - * @param {number} seconds - * @return {number} - */ - Tone.prototype.secondsToFrequency = function(seconds){ - return 1/seconds; - }; - /////////////////////////////////////////////////////////////////////////// // INHERITANCE /////////////////////////////////////////////////////////////////////////// @@ -760,126 +730,6 @@ define(function(){ child._super = parent; }; - /////////////////////////////////////////////////////////////////////////// - // TYPES / STATES - /////////////////////////////////////////////////////////////////////////// - - /** - * Possible types which a value can take on - * @enum {string} - */ - Tone.Type = { - /** - * The default value is a number which can take on any value between [-Infinity, Infinity] - */ - Default : "number", - /** - * Time can be described in a number of ways. Read more [Time](https://github.com/TONEnoTONE/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", - /** - * Gain is the ratio between the input and the output value of a signal. - * @typedef {Gain} - */ - Gain : "gain", - /** - * Normal values are within the range [0, 1]. - * @typedef {NormalRange} - */ - NormalRange : "normalrange", - /** - * AudioRange values are between [-1, 1]. - * @typedef {AudioRange} - */ - AudioRange : "audiorange", - /** - * Decibels are a logarithmic unit of measurement which is useful for volume - * because of the logarithmic way that we perceive loudness. 0 decibels - * means no change in volume. -10db is approximately half as loud and 10db - * is twice is loud. - * @typedef {Decibels} - */ - Decibels : "db", - /** - * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. - * @typedef {Interval} - */ - Interval : "interval", - /** - * Beats per minute. - * @typedef {BPM} - */ - BPM : "bpm", - /** - * The value must be greater than 0. - * @typedef {Positive} - */ - Positive : "positive", - /** - * A cent is a 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" - }; - - /** - * Possible play states. - * @enum {string} - */ - Tone.State = { - Started : "started", - Stopped : "stopped", - Paused : "paused", - }; - - /** - * An empty function. - * @static - */ - Tone.noOp = function(){}; - /////////////////////////////////////////////////////////////////////////// // CONTEXT /////////////////////////////////////////////////////////////////////////// @@ -943,14 +793,14 @@ define(function(){ //setup the context Tone._initAudioContext(function(audioContext){ - //set the bufferTime - Tone.prototype.bufferTime = Tone.prototype.bufferSize / audioContext.sampleRate; + //set the blockTime + Tone.prototype.blockTime = 128 / audioContext.sampleRate; _silentNode = audioContext.createGain(); _silentNode.gain.value = 0; _silentNode.connect(audioContext.destination); }); - Tone.version = "r5"; + Tone.version = "r6"; console.log("%c * Tone.js " + Tone.version + " * ", "background: #000; color: #fff"); diff --git a/Tone/core/Transport.js b/Tone/core/Transport.js index f61d9f3fd..12ca71deb 100644 --- a/Tone/core/Transport.js +++ b/Tone/core/Transport.js @@ -1,18 +1,21 @@ -define(["Tone/core/Tone", "Tone/core/Clock", "Tone/signal/Signal", "Tone/signal/Multiply"], +define(["Tone/core/Tone", "Tone/core/Clock", "Tone/core/Type", "Tone/core/Timeline", + "Tone/core/Emitter", "Tone/core/Gain", "Tone/core/IntervalTimeline"], function(Tone){ "use strict"; /** - * @class Oscillator-based transport allows for timing musical events. - * Supports tempo curves and time changes. A single transport is created - * on initialization. Unlike browser-based timing (setInterval, requestAnimationFrame) + * @class Transport for timing musical events. + * Supports tempo curves and time changes. Unlike browser-based timing (setInterval, requestAnimationFrame) * Tone.Transport timing events pass in the exact time of the scheduled event * in the argument of the callback function. Pass that time value to the object * you're scheduling.

* A single transport is created for you when the library is initialized. + *

+ * The transport emits the events: "start", "stop", "pause", and "loop" which are + * called with the time of that event as the argument. * - * @extends {Tone} + * @extends {Tone.Emitter} * @singleton * @example * //repeated event every 8th note @@ -32,15 +35,11 @@ function(Tone){ */ Tone.Transport = function(){ - /** - * watches the main oscillator for timing ticks - * initially starts at 120bpm - * - * @private - * @type {Tone.Clock} - */ - this._clock = new Tone.Clock(0, this._processTick.bind(this)); - this._clock.onended = this._onended.bind(this); + Tone.Emitter.call(this); + + /////////////////////////////////////////////////////////////////////// + // LOOPING + ////////////////////////////////////////////////////////////////////// /** * If the transport loops or not. @@ -48,6 +47,41 @@ function(Tone){ */ this.loop = false; + /** + * The loop start position in ticks + * @type {Ticks} + * @private + */ + this._loopStart = 0; + + /** + * The loop end position in ticks + * @type {Ticks} + * @private + */ + this._loopEnd = 0; + + /////////////////////////////////////////////////////////////////////// + // CLOCK/TEMPO + ////////////////////////////////////////////////////////////////////// + + /** + * Pulses per quarter is the number of ticks per quarter note. + * @private + * @type {Number} + */ + this._ppq = TransportConstructor.defaults.PPQ; + + /** + * watches the main oscillator for timing ticks + * initially starts at 120bpm + * @private + * @type {Tone.Clock} + */ + this._clock = new Tone.Clock({ + "callback" : this._processTick.bind(this), + "frequency" : 0, + }); /** * The Beats Per Minute of the Transport. * @type {BPM} @@ -57,26 +91,90 @@ function(Tone){ * //ramp the bpm to 120 over 10 seconds * Tone.Transport.bpm.rampTo(120, 10); */ - this.bpm = new Tone.Signal(120, Tone.Type.BPM); + this.bpm = this._clock.frequency; + this.bpm._toUnits = this._toUnits.bind(this); + this.bpm._fromUnits = this._fromUnits.bind(this); + this.bpm.units = Tone.Type.BPM; + this.bpm.value = TransportConstructor.defaults.bpm; + this._readOnly("bpm"); + + /** + * The time signature, or more accurately the numerator + * of the time signature over a denominator of 4. + * @type {Number} + * @private + */ + this._timeSignature = TransportConstructor.defaults.timeSignature; + + /////////////////////////////////////////////////////////////////////// + // TIMELINE EVENTS + ////////////////////////////////////////////////////////////////////// + + /** + * All the events in an object to keep track by ID + * @type {Object} + * @private + */ + this._scheduledEvents = {}; + + /** + * The event ID counter + * @type {Number} + * @private + */ + this._eventID = 0; + + /** + * The scheduled events. + * @type {Tone.Timeline} + * @private + */ + this._timeline = new Tone.Timeline(); + + /** + * Repeated events + * @type {Array} + * @private + */ + this._repeatedEvents = new Tone.IntervalTimeline(); /** - * the signal scalar - * @type {Tone.Multiply} + * Events that occur once + * @type {Array} * @private */ - this._bpmMult = new Tone.Multiply(1/60 * tatum); + this._onceEvents = new Tone.Timeline(); + + /** + * All of the synced Signals + * @private + * @type {Array} + */ + this._syncedSignals = []; + + /////////////////////////////////////////////////////////////////////// + // 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; /** - * The state of the transport. READ ONLY. - * @type {Tone.State} + * The swing amount + * @type {NormalRange} + * @private */ - this.state = Tone.State.Stopped; + this._swingAmount = 0; - //connect it all up - this.bpm.chain(this._bpmMult, this._clock.frequency); }; - Tone.extend(Tone.Transport); + Tone.extend(Tone.Transport, Tone.Emitter); /** * the defaults @@ -90,106 +188,10 @@ function(Tone){ "swingSubdivision" : "16n", "timeSignature" : 4, "loopStart" : 0, - "loopEnd" : "4m" + "loopEnd" : "4m", + "PPQ" : 48 }; - /** - * @private - * @type {number} - */ - var tatum = 12; - - /** - * @private - * @type {number} - */ - var timelineTicks = 0; - - /** - * @private - * @type {number} - */ - var transportTicks = 0; - - /** - * Which subdivision the swing is applied to. - * defaults to an 16th note - * @private - * @type {number} - */ - var swingSubdivision = "16n"; - - /** - * controls which beat the swing is applied to - * defaults to an 16th note - * @private - * @type {number} - */ - var swingTatum = 3; - - /** - * controls which beat the swing is applied to - * @private - * @type {number} - */ - var swingAmount = 0; - - /** - * @private - * @type {number} - */ - var transportTimeSignature = 4; - - /** - * @private - * @type {number} - */ - var loopStart = 0; - - /** - * @private - * @type {number} - */ - var loopEnd = tatum * 4; - - /** - * @private - * @type {Array} - */ - var intervals = []; - - /** - * @private - * @type {Array} - */ - var timeouts = []; - - /** - * @private - * @type {Array} - */ - var transportTimeline = []; - - /** - * @private - * @type {number} - */ - var timelineProgress = 0; - - /** - * All of the synced components - * @private - * @type {Array} - */ - var SyncedSources = []; - - /** - * All of the synced Signals - * @private - * @type {Array} - */ - var SyncedSignals = []; - /////////////////////////////////////////////////////////////////////////////// // TICKS /////////////////////////////////////////////////////////////////////////////// @@ -200,324 +202,202 @@ function(Tone){ * @private */ Tone.Transport.prototype._processTick = function(tickTime){ - if (this.state === Tone.State.Started){ - if (swingAmount > 0 && - timelineTicks % tatum !== 0 && //not on a downbeat - timelineTicks % swingTatum === 0){ - //add some swing - tickTime += this._ticksToSeconds(swingTatum) * swingAmount; - } - processIntervals(tickTime); - processTimeouts(tickTime); - processTimeline(tickTime); - transportTicks += 1; - timelineTicks += 1; - if (this.loop){ - if (timelineTicks === loopEnd){ - this._setTicks(loopStart); - } - } + //handle swing + if (this._swingAmount > 0 && + this._clock.ticks % this._ppq !== 0 && //not on a downbeat + this._clock.ticks % this._swingTicks === 0){ + //add some swing + tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount; } - }; - - /** - * jump to a specific tick in the timeline - * updates the timeline callbacks - * - * @param {number} ticks the tick to jump to - * @private - */ - Tone.Transport.prototype._setTicks = function(ticks){ - timelineTicks = ticks; - for (var i = 0; i < transportTimeline.length; i++){ - var timeout = transportTimeline[i]; - if (timeout.callbackTick() >= ticks){ - timelineProgress = i; - break; + //do the loop test + if (this.loop){ + if (this._clock.ticks === this._loopEnd){ + this.ticks = this._loopStart; + this.trigger("loop", tickTime); } } - }; - - /////////////////////////////////////////////////////////////////////////////// - // EVENT PROCESSING - /////////////////////////////////////////////////////////////////////////////// - - /** - * process the intervals - * @param {number} time - */ - var processIntervals = function(time){ - for (var i = 0, len = intervals.length; i transportTicks){ - break; - } - } - //remove the timeouts off the front of the array after they've been called - timeouts.splice(0, removeTimeouts); - }; - - /** - * process the transportTimeline events - * @param {number} time - */ - var processTimeline = function(time){ - for (var i = timelineProgress, len = transportTimeline.length; i timelineTicks){ - break; - } - } + }); + //process the single occurrence events + this._onceEvents.forEachBefore(ticks, function(event){ + event.callback(tickTime); + }); + //and clear the single occurrence timeline + this._onceEvents.cancelBefore(ticks); }; /////////////////////////////////////////////////////////////////////////////// - // INTERVAL + // SCHEDULABLE EVENTS /////////////////////////////////////////////////////////////////////////////// /** - * Set a callback for a recurring event. - * @param {function} callback - * @param {Time} interval - * @return {number} the id of the interval + * 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. + * @return {Number} The id of the event which can be used for canceling the event. * @example - * //triggers a callback every 8th note with the exact time of the event - * Tone.Transport.setInterval(function(time){ - * envelope.triggerAttack(time); - * }, "8n"); - */ - Tone.Transport.prototype.setInterval = function(callback, interval, ctx){ - var tickTime = this._toTicks(interval); - var timeout = new TimelineEvent(callback, ctx, tickTime, transportTicks); - intervals.push(timeout); - return timeout.id; - }; - - /** - * 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 + * //trigger the callback when the Transport reaches the desired time + * Tone.Transport.schedule(function(time){ + * envelope.triggerAttack(time); + * }, "128i"); + */ + Tone.Transport.prototype.schedule = function(callback, time){ + var event = { + "time" : this.toTicks(time), + "callback" : callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + "event" : event, + "timeline" : this._timeline + }; + this._timeline.addEvent(event); + return id; + }; + + /** + * Schedule a repeated event along the timeline. The event will fire + * at the `interval` starting at the `startTime` and for the specified + * `duration`. + * @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 + * 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 + * the event. + * @example + * //a callback invoked every eighth note after the first measure + * Tone.Transport.scheduleRepeat(callback, "8n", "1m"); */ - Tone.Transport.prototype.clearInterval = function(rmInterval){ - for (var i = 0; i < intervals.length; i++){ - var interval = intervals[i]; - if (interval.id === rmInterval){ - intervals.splice(i, 1); - return true; - } + Tone.Transport.prototype.scheduleRepeat = function(callback, interval, startTime, duration){ + if (interval <= 0){ + throw new Error("repeat events must have an interval larger than 0"); } - return false; - }; - - /** - * Removes all of the intervals that are currently set. - * @return {boolean} true if the event was removed - */ - Tone.Transport.prototype.clearIntervals = function(){ - var willRemove = intervals.length > 0; - intervals = []; - return willRemove; + var event = { + "time" : this.toTicks(startTime), + "duration" : this.toTicks(this.defaultArg(duration, Infinity)), + "interval" : this.toTicks(interval), + "callback" : callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + "event" : event, + "timeline" : this._repeatedEvents + }; + this._repeatedEvents.addEvent(event); + return id; }; - /////////////////////////////////////////////////////////////////////////////// - // TIMEOUT - /////////////////////////////////////////////////////////////////////////////// - /** - * Set a timeout to occur after time from now. NB: the transport must be - * running for this to be triggered. All timeout events are cleared when the - * transport is stopped. - * - * @param {function} callback - * @param {Time} time 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) + * Schedule an event that will be removed after it is invoked. + * 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. + * @returns {Number} The ID of the scheduled event. */ - Tone.Transport.prototype.setTimeout = function(callback, time, ctx){ - var ticks = this._toTicks(time); - var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0); - //put it in the right spot - for (var i = 0, len = timeouts.length; i timeout.callbackTick()){ - timeouts.splice(i, 0, timeout); - return timeout.id; - } - } - //otherwise push it on the end - timeouts.push(timeout); - return timeout.id; + Tone.Transport.prototype.scheduleOnce = function(callback, time){ + var event = { + "time" : this.toTicks(time), + "callback" : callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + "event" : event, + "timeline" : this._onceEvents + }; + this._onceEvents.addEvent(event); + return id; }; /** - * 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 + * Clear the passed in event id from the timeline + * @param {Number} eventId The id of the event. + * @returns {Tone.Transport} this */ - Tone.Transport.prototype.clearTimeout = function(timeoutID){ - for (var i = 0; i < timeouts.length; i++){ - var testTimeout = timeouts[i]; - if (testTimeout.id === timeoutID){ - timeouts.splice(i, 1); - return true; - } + Tone.Transport.prototype.clear = function(eventId){ + if (this._scheduledEvents.hasOwnProperty(eventId)){ + var item = this._scheduledEvents[eventId.toString()]; + item.timeline.removeEvent(item.event); + delete this._scheduledEvents[eventId.toString()]; } - return false; + return this; }; /** - * Removes all of the timeouts that are currently set. - * @return {boolean} true if the event was removed + * 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 + * this time. + * @returns {Tone.Transport} this */ - Tone.Transport.prototype.clearTimeouts = function(){ - var willRemove = timeouts.length > 0; - timeouts = []; - return willRemove; + Tone.Transport.prototype.cancel = function(after){ + after = this.defaultArg(after, 0); + after = this.toTicks(after); + this._timeline.cancel(after); + this._onceEvents.cancel(after); + this._repeatedEvents.cancel(after); + return this; }; /////////////////////////////////////////////////////////////////////////////// - // TIMELINE + // QUANTIZATION /////////////////////////////////////////////////////////////////////////////// /** - * 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 {Tome.Time} timeout - * @return {number} the id for clearing the transportTimeline event + * 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 - * //trigger the start of a part on the 16th measure - * Tone.Transport.setTimeline(function(time){ - * part.start(time); - * }, "16m"); + * 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.setTimeline = function(callback, timeout, ctx){ - var ticks = this._toTicks(timeout); - var timelineEvnt = new TimelineEvent(callback, ctx, ticks, 0); - //put it in the right spot - for (var i = timelineProgress, len = transportTimeline.length; i timelineEvnt.callbackTick()){ - transportTimeline.splice(i, 0, timelineEvnt); - return timelineEvnt.id; - } + 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; } - //otherwise push it on the end - transportTimeline.push(timelineEvnt); - return timelineEvnt.id; - }; - - /** - * Clear the timeline event. - * @param {number} timelineID - * @return {boolean} true if it was removed - */ - Tone.Transport.prototype.clearTimeline = function(timelineID){ - for (var i = 0; i < transportTimeline.length; i++){ - var testTimeline = transportTimeline[i]; - if (testTimeline.id === timelineID){ - transportTimeline.splice(i, 1); - return true; - } + var now = this.now(); + if (this.state === Tone.State.Started){ + now = this._clock._nextTick; } - return false; - }; - - /** - * Remove all events from the timeline. - * @returns {boolean} true if the events were removed - */ - Tone.Transport.prototype.clearTimelines = function(){ - timelineProgress = 0; - var willRemove = transportTimeline.length > 0; - transportTimeline = []; - return willRemove; + return this.toSeconds(time, now) + this.ticksToSeconds(remainingTicks); }; /////////////////////////////////////////////////////////////////////////////// - // TIME CONVERSIONS + // START/STOP/PAUSE /////////////////////////////////////////////////////////////////////////////// /** - * turns the time into - * @param {Time} time - * @return {number} - * @private - */ - Tone.Transport.prototype._toTicks = function(time){ - //get the seconds - var seconds = this.toSeconds(time); - var quarter = this.notationToSeconds("4n"); - var quarters = seconds / quarter; - var tickNum = quarters * tatum; - //quantize to tick value - return Math.round(tickNum); - }; - - /** - * convert ticks into seconds - * - * @param {number} ticks - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * @private - */ - Tone.Transport.prototype._ticksToSeconds = function(ticks, bpm, timeSignature){ - ticks = Math.floor(ticks); - var quater = this.notationToSeconds("4n", bpm, timeSignature); - return (quater * ticks) / (tatum); - }; - - /** - * Returns the time of the next beat. - * @param {string} [subdivision="4n"] - * @return {number} the time in seconds of the next subdivision + * Returns the playback state of the source, either "started", "stopped", or "paused" + * @type {Tone.State} + * @readOnly + * @memberOf Tone.Transport# + * @name state */ - Tone.Transport.prototype.nextBeat = function(subdivision){ - subdivision = this.defaultArg(subdivision, "4n"); - var tickNum = this._toTicks(subdivision); - var remainingTicks = (transportTicks % tickNum); - var nextTick = remainingTicks; - if (remainingTicks > 0){ - nextTick = tickNum - remainingTicks; + Object.defineProperty(Tone.Transport.prototype, "state", { + get : function(){ + return this._clock.getStateAtTime(this.now()); } - return this._ticksToSeconds(nextTick); - }; - - - /////////////////////////////////////////////////////////////////////////////// - // START/STOP/PAUSE - /////////////////////////////////////////////////////////////////////////////// + }); /** * Start the transport and all sources synced to the transport. @@ -529,24 +409,18 @@ function(Tone){ * Tone.Transport.start("+1", "4:0:0"); */ Tone.Transport.prototype.start = function(time, offset){ - if (this.state === Tone.State.Stopped || this.state === Tone.State.Paused){ - if (!this.isUndef(offset)){ - this._setTicks(this._toTicks(offset)); - } - this.state = Tone.State.Started; - var startTime = this.toSeconds(time); - this._clock.start(startTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - var delay = SyncedSources[i].delay; - source.start(startTime + delay); - } + time = this.toSeconds(time); + if (!this.isUndef(offset)){ + offset = this.toTicks(offset); + } else { + offset = this.defaultArg(offset, this._clock.ticks); } + //start the clock + this._clock.start(time, offset); + this.trigger("start", time, this.ticksToSeconds(offset)); return this; }; - /** * Stop the transport and all sources synced to the transport. * @param {Time} [time=now] The time when the transport should stop. @@ -555,47 +429,21 @@ function(Tone){ * Tone.Transport.stop(); */ Tone.Transport.prototype.stop = function(time){ - if (this.state === Tone.State.Started || this.state === Tone.State.Paused){ - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - source.stop(stopTime); - } - } else { - this._onended(); - } + time = this.toSeconds(time); + this._clock.stop(time); + this.trigger("stop", time); return this; }; - /** - * invoked when the transport is stopped - * @private - */ - Tone.Transport.prototype._onended = function(){ - transportTicks = 0; - this._setTicks(0); - this.clearTimeouts(); - this.state = Tone.State.Stopped; - }; - /** * Pause the transport and all sources synced to the transport. * @param {Time} [time=now] * @returns {Tone.Transport} this */ Tone.Transport.prototype.pause = function(time){ - if (this.state === Tone.State.Started){ - this.state = Tone.State.Paused; - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call pause on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - source.pause(stopTime); - } - } + time = this.toSeconds(time); + this._clock.pause(time); + this.trigger("pause", time); return this; }; @@ -607,20 +455,25 @@ function(Tone){ * The time signature as just the numerator over 4. * For example 4/4 would be just 4 and 6/8 would be 3. * @memberOf Tone.Transport# - * @type {number} + * @type {Number|Array} * @name timeSignature * @example * //common time * Tone.Transport.timeSignature = 4; * // 7/8 - * Tone.Transport.timeSignature = 3.5; + * Tone.Transport.timeSignature = [7, 8]; + * //this will be reduced to a single number + * Tone.Transport.timeSignature; //returns 3.5 */ Object.defineProperty(Tone.Transport.prototype, "timeSignature", { get : function(){ - return transportTimeSignature; + return this._timeSignature; }, - set : function(numerator){ - transportTimeSignature = numerator; + set : function(timeSig){ + if (this.isArray(timeSig)){ + timeSig = (timeSig[0] / timeSig[1]) * 4; + } + this._timeSignature = timeSig; } }); @@ -633,10 +486,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "loopStart", { get : function(){ - return this._ticksToSeconds(loopStart); + return this.ticksToSeconds(this._loopStart); }, set : function(startPosition){ - loopStart = this._toTicks(startPosition); + this._loopStart = this.toTicks(startPosition); } }); @@ -648,10 +501,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "loopEnd", { get : function(){ - return this._ticksToSeconds(loopEnd); + return this.ticksToSeconds(this._loopEnd); }, set : function(endPosition){ - loopEnd = this._toTicks(endPosition); + this._loopEnd = this.toTicks(endPosition); } }); @@ -680,11 +533,11 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swing", { get : function(){ - return swingAmount * 2; + return this._swingAmount * 2; }, set : function(amount){ //scale the values to a normal range - swingAmount = amount * 0.5; + this._swingAmount = amount * 0.5; } }); @@ -699,12 +552,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swingSubdivision", { get : function(){ - return swingSubdivision; + return this.toNotation(this._swingTicks + "i"); }, set : function(subdivision){ - //scale the values to a normal range - swingSubdivision = subdivision; - swingTatum = this._toTicks(subdivision); + this._swingTicks = this.toTicks(subdivision); } }); @@ -718,56 +569,100 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "position", { get : function(){ - var quarters = timelineTicks / tatum; - var measures = Math.floor(quarters / transportTimeSignature); - var sixteenths = Math.floor((quarters % 1) * 4); - quarters = Math.floor(quarters) % transportTimeSignature; + var 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(":"); }, set : function(progress){ - var ticks = this._toTicks(progress); - this._setTicks(ticks); + var ticks = this.toTicks(progress); + this.ticks = ticks; } }); - /////////////////////////////////////////////////////////////////////////////// - // SYNCING - /////////////////////////////////////////////////////////////////////////////// + /** + * The Transport's loop position as a normalized value. Always + * returns 0 if the transport if loop is not true. + * @memberOf Tone.Transport# + * @name progress + * @type {NormalRange} + */ + Object.defineProperty(Tone.Transport.prototype, "progress", { + get : function(){ + if (this.loop){ + return (this.ticks - this._loopStart) / (this._loopEnd - this._loopStart); + } else { + return 0; + } + } + }); /** - * Sync a source to the transport so that - * @param {Tone.Source} source the source to sync to the transport - * @param {Time} delay (optionally) start the source with a delay from the transport - * @returns {Tone.Transport} this - * @example - * Tone.Transport.syncSource(player, "1m"); - * Tone.Transport.start(); - * //the player will start 1 measure after the transport starts + * The transports current tick position. + * + * @memberOf Tone.Transport# + * @type {Ticks} + * @name ticks */ - Tone.Transport.prototype.syncSource = function(source, startDelay){ - SyncedSources.push({ - source : source, - delay : this.toSeconds(this.defaultArg(startDelay, 0)) - }); - return this; - }; + Object.defineProperty(Tone.Transport.prototype, "ticks", { + get : function(){ + return this._clock.ticks; + }, + set : function(t){ + this._clock.ticks = t; + } + }); /** - * Unsync the source from the transport. See Tone.Transport.syncSource. + * Pulses Per Quarter note. This is the smallest resolution + * the Transport timing supports. This should be set once + * on initialization and not set again. Changing this value + * after other objects have been created can cause problems. * - * @param {Tone.Source} source [description] - * @returns {Tone.Transport} this + * @memberOf Tone.Transport# + * @type {Number} + * @name PPQ */ - Tone.Transport.prototype.unsyncSource = function(source){ - for (var i = 0; i < SyncedSources.length; i++){ - if (SyncedSources[i].source === source){ - SyncedSources.splice(i, 1); - } + Object.defineProperty(Tone.Transport.prototype, "PPQ", { + get : function(){ + return this._ppq; + }, + set : function(ppq){ + this._ppq = ppq; + this.bpm.value = this.bpm.value; } - return this; + }); + + /** + * Convert from BPM to frequency (factoring in PPQ) + * @param {BPM} bpm The BPM value to convert to frequency + * @return {Frequency} The BPM as a frequency with PPQ factored in. + * @private + */ + Tone.Transport.prototype._fromUnits = function(bpm){ + return 1 / (60 / bpm / this.PPQ); }; + /** + * Convert from frequency (with PPQ) into BPM + * @param {Frequency} freq The clocks frequency to convert to BPM + * @return {BPM} The frequency value as BPM. + * @private + */ + Tone.Transport.prototype._toUnits = function(freq){ + return (freq / this.PPQ) * 60; + }; + + /////////////////////////////////////////////////////////////////////////////// + // SYNCING + /////////////////////////////////////////////////////////////////////////////// + /** * Attaches the signal to the tempo control signal so that * any changes in the tempo will change the signal in the same @@ -782,21 +677,20 @@ function(Tone){ Tone.Transport.prototype.syncSignal = function(signal, ratio){ if (!ratio){ //get the sync ratio - if (signal._value.value !== 0){ - ratio = signal._value.value / this.bpm.value; + if (signal._param.value !== 0){ + ratio = signal._param.value / this.bpm._param.value; } else { ratio = 0; } } - var ratioSignal = this.context.createGain(); - ratioSignal.gain.value = ratio; - this.bpm.chain(ratioSignal, signal._value); - SyncedSignals.push({ + var ratioSignal = new Tone.Gain(ratio); + this.bpm.chain(ratioSignal, signal._param); + this._syncedSignals.push({ "ratio" : ratioSignal, "signal" : signal, - "initial" : signal._value.value + "initial" : signal._param.value }); - signal._value.value = 0; + signal._param.value = 0; return this; }; @@ -807,12 +701,12 @@ function(Tone){ * @returns {Tone.Transport} this */ Tone.Transport.prototype.unsyncSignal = function(signal){ - for (var i = 0; i < SyncedSignals.length; i++){ - var syncedSignal = SyncedSignals[i]; + for (var i = this._syncedSignals.length - 1; i >= 0; i--){ + var syncedSignal = this._syncedSignals[i]; if (syncedSignal.signal === signal){ - syncedSignal.ratio.disconnect(); - syncedSignal.signal._value.value = syncedSignal.initial; - SyncedSignals.splice(i, 1); + syncedSignal.ratio.dispose(); + syncedSignal.signal._param.value = syncedSignal.initial; + this._syncedSignals.splice(i, 1); } } return this; @@ -824,279 +718,126 @@ function(Tone){ * @private */ Tone.Transport.prototype.dispose = function(){ + Tone.Emitter.prototype.dispose.call(this); this._clock.dispose(); this._clock = null; - this.bpm.dispose(); + this._writable("bpm"); this.bpm = null; - this._bpmMult.dispose(); - this._bpmMult = null; + this._timeline.dispose(); + this._timeline = null; + this._onceEvents.dispose(); + this._onceEvents = null; + this._repeatedEvents.dispose(); + this._repeatedEvents = null; return this; }; /////////////////////////////////////////////////////////////////////////////// - // TIMELINE EVENT + // DEPRECATED FUNCTIONS + // (will be removed in r7) /////////////////////////////////////////////////////////////////////////////// /** - * @static - * @type {number} - */ - var TimelineEventIDCounter = 0; - - /** - * A Timeline event - * - * @constructor + * @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 - * @param {function(number)} callback - * @param {Object} context - * @param {number} tickTime - * @param {number} startTicks - */ - var TimelineEvent = function(callback, context, tickTime, startTicks){ - this.startTicks = startTicks; - this.tickTime = tickTime; - this.callback = callback; - this.context = context; - this.id = TimelineEventIDCounter++; - }; - - /** - * invoke the callback in the correct context - * passes in the playback time - * - * @param {number} playbackTime - */ - TimelineEvent.prototype.doCallback = function(playbackTime){ - this.callback.call(this.context, playbackTime); - }; - - /** - * get the tick which the callback is supposed to occur on - * - * @return {number} */ - TimelineEvent.prototype.callbackTick = function(){ - return this.startTicks + this.tickTime; + Tone.Transport.prototype.setInterval = function(callback, interval){ + console.warn("This method is deprecated. Use Tone.Transport.scheduleRepeat instead."); + return Tone.Transport.scheduleRepeat(callback, interval); }; /** - * test if the tick occurs on the interval - * - * @param {number} tick - * @return {boolean} + * @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 */ - TimelineEvent.prototype.testInterval = function(tick){ - return (tick - this.startTicks) % this.tickTime === 0; + Tone.Transport.prototype.clearInterval = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.clear instead."); + return Tone.Transport.clear(id); }; - - /////////////////////////////////////////////////////////////////////////////// - // AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING - /////////////////////////////////////////////////////////////////////////////// - - /** - * tests if a string is musical notation - * i.e.: - * 4n = quarter note - * 2m = two measures - * 8t = eighth-note triplet - * - * @return {boolean} - * @method isNotation - * @lends Tone.prototype.isNotation - */ - Tone.prototype.isNotation = (function(){ - var notationFormat = new RegExp(/[0-9]+[mnt]$/i); - return function(note){ - return notationFormat.test(note); - }; - })(); - - /** - * tests if a string is transportTime - * i.e. : - * 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes - * - * @return {boolean} - * - * @method isTransportTime - * @lends Tone.prototype.isTransportTime - */ - Tone.prototype.isTransportTime = (function(){ - var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/i); - return function(transportTime){ - return transportTimeFormat.test(transportTime); - }; - })(); - /** + * @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. * - * convert notation format strings to seconds - * - * @param {string} notation - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} - * + * @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.prototype.notationToSeconds = function(notation, bpm, timeSignature){ - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var beatTime = (60 / bpm); - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0){ - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === "t"){ - beats = (4 / subdivision) * 2/3; - } else if (lastLetter === "n"){ - beats = 4 / subdivision; - } else if (lastLetter === "m"){ - beats = subdivision * timeSignature; - } else { - beats = 0; - } - return beatTime * beats; + Tone.Transport.prototype.setTimeout = function(callback, timeout){ + console.warn("This method is deprecated. Use Tone.Transport.scheduleOnce instead."); + return Tone.Transport.scheduleOnce(callback, timeout); }; /** - * convert transportTime into seconds. - * - * ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - * - * @param {string} transportTime - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * - * @lends Tone.prototype.transportTimeToSeconds + * @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.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){ - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(":"); - if (split.length === 2){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1){ - quarters = parseFloat(split[0]); - } else if (split.length === 3){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = (measures * timeSignature + quarters + sixteenths / 4); - return beats * this.notationToSeconds("4n"); + Tone.Transport.prototype.clearTimeout = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.clear instead."); + return Tone.Transport.clear(id); }; /** - * Convert seconds to the closest transportTime in the form - * measures:quarters:sixteenths + * @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. * - * @method toTransportTime - * - * @param {Time} seconds - * @param {number=} bpm - * @param {number=} timeSignature - * @return {string} - * - * @lends Tone.prototype.toTransportTime + * @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.prototype.toTransportTime = function(time, bpm, timeSignature){ - var seconds = this.toSeconds(time, bpm, timeSignature); - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var quarterTime = this.notationToSeconds("4n"); - var quarters = seconds / quarterTime; - var measures = Math.floor(quarters / timeSignature); - var sixteenths = Math.floor((quarters % 1) * 4); - quarters = Math.floor(quarters) % timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); + Tone.Transport.prototype.setTimeline = function(callback, time){ + console.warn("This method is deprecated. Use Tone.Transport.schedule instead."); + return Tone.Transport.schedule(callback, time); }; /** - * 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 + * @deprecated Use Tone.Note instead. + * Clear the timeline event. + * @param {number} id + * @return {boolean} true if it was removed + * @private */ - Tone.prototype.toFrequency = function(freq, now){ - if (this.isFrequency(freq)){ - return parseFloat(freq); - } else if (this.isNotation(freq) || this.isTransportTime(freq)) { - return this.secondsToFrequency(this.toSeconds(freq, now)); - } else { - return freq; - } + Tone.Transport.prototype.clearTimeline = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.clear instead."); + return Tone.Transport.clear(id); }; - /** - * Convert Time into seconds. - * - * Unlike the method which it overrides, this takes into account - * transporttime and musical notation. - * - * Time : 1.40 - * Notation: 4n|1m|2t - * TransportTime: 2:4:1 (measure:quarters:sixteens) - * Now Relative: +3n - * Math: 3n+16n or even very complicated expressions ((3n*2)/6 + 1) - * - * @override - * @param {Time} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} - */ - Tone.prototype.toSeconds = function(time, now){ - now = this.defaultArg(now, this.now()); - if (typeof time === "number"){ - return time; //assuming that it's seconds - } else if (typeof time === "string"){ - var plusTime = 0; - if(time.charAt(0) === "+") { - plusTime = now; - time = time.slice(1); - } - var components = time.split(/[\(\)\-\+\/\*]/); - if (components.length > 1){ - var originalTime = time; - for(var i = 0; i < components.length; i++){ - var symb = components[i].trim(); - if (symb !== ""){ - var val = this.toSeconds(symb); - time = time.replace(symb, val); - } - } - try { - //i know eval is evil, but i think it's safe here - time = eval(time); // jshint ignore:line - } catch (e){ - throw new EvalError("problem evaluating Tone.Type.Time: "+originalTime); - } - } else if (this.isNotation(time)){ - time = this.notationToSeconds(time); - } else if (this.isTransportTime(time)){ - time = this.transportTimeToSeconds(time); - } else if (this.isFrequency(time)){ - time = this.frequencyToSeconds(time); - } else { - time = parseFloat(time); - } - return time + plusTime; - } else { - return now; - } - }; + /////////////////////////////////////////////////////////////////////////////// + // INITIALIZATION + /////////////////////////////////////////////////////////////////////////////// var TransportConstructor = Tone.Transport; @@ -1107,14 +848,14 @@ function(Tone){ } else { //stop the clock Tone.Transport.stop(); - //get the previous bpm - var bpm = Tone.Transport.bpm.value; - //destory the old clock - Tone.Transport._clock.dispose(); + //get the previous values + var prevSettings = Tone.Transport.get(); + //destory the old transport + Tone.Transport.dispose(); //make new Transport insides TransportConstructor.call(Tone.Transport); - //set the bpm - Tone.Transport.bpm.value = bpm; + //set the previous config + Tone.Transport.set(prevSettings); } }); diff --git a/Tone/core/Type.js b/Tone/core/Type.js new file mode 100644 index 000000000..c05b1c32a --- /dev/null +++ b/Tone/core/Type.js @@ -0,0 +1,816 @@ +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). + * + *
    + *
  • Numbers, which will be taken literally as the time (in seconds).
  • + *
  • Notation, ("4n", "8t") describes time in BPM and time signature relative values.
  • + *
  • TransportTime, ("4:3:2") will also provide tempo and time signature relative times + * in the form BARS:QUARTERS:SIXTEENTHS.
  • + *
  • Frequency, ("8hz") is converted to the length of the cycle in seconds.
  • + *
  • Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as + * "the current time plus whatever expression follows".
  • + *
  • Expressions, ("3:0 + 2 - (1m / 7)") any of the above can also be combined + * into a mathematical expression which will be evaluated to compute the desired time.
  • + *
  • No Argument, for methods which accept time, no argument will be interpreted as + * "now" (i.e. the currentTime).
  • + *
+ * + * @typedef {Time} + */ + Time : "time", + /** + * Frequency can be described similar to time, except ultimately the + * values are converted to frequency instead of seconds. A number + * is taken literally as the value in hertz. Additionally any of the + * Time encodings can be used. Note names in the form + * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their + * frequency value. + * @typedef {Frequency} + */ + Frequency : "frequency", + /** + * 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. + *
    + *
  • "4n" = quarter note
  • + *
  • "2m" = two measures
  • + *
  • "8t" = eighth-note triplet
  • + *
+ * @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.: + *
    + *
  • 4n = quarter note
  • + *
  • 2m = two measures
  • + *
  • 8t = eighth-note triplet
  • + *
+ * + * @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/AutoFilter.js b/Tone/effect/AutoFilter.js index 9e5169451..ecf3f4440 100644 --- a/Tone/effect/AutoFilter.js +++ b/Tone/effect/AutoFilter.js @@ -10,8 +10,8 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp * @constructor * @extends {Tone.Effect} * @param {Time|Object} [frequency] The rate of the LFO. - * @param {Frequency} [min] The lower value of the LFOs oscillation - * @param {Frequency} [max] The upper value of the LFOs oscillation. + * @param {Frequency=} baseFrequency The lower value of the LFOs oscillation + * @param {Frequency=} octaves The number of octaves above the baseFrequency * @example * //create an autofilter and start it's LFO * var autoFilter = new Tone.AutoFilter("4n").toMaster().start(); @@ -20,7 +20,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp */ Tone.AutoFilter = function(){ - var options = this.optionsObject(arguments, ["frequency", "min", "max"], Tone.AutoFilter.defaults); + var options = this.optionsObject(arguments, ["frequency", "baseFrequency", "octaves"], Tone.AutoFilter.defaults); Tone.Effect.call(this, options); /** @@ -31,8 +31,6 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp this._lfo = new Tone.LFO({ "frequency" : options.frequency, "amplitude" : options.depth, - "min" : options.min, - "max" : options.max }); /** @@ -56,11 +54,20 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp */ this.filter = new Tone.Filter(options.filter); + /** + * The octaves placeholder + * @type {Positive} + * @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; }; //extend Effect @@ -75,8 +82,8 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp "frequency" : 1, "type" : "sine", "depth" : 1, - "min" : 200, - "max" : 1200, + "baseFrequency" : 200, + "octaves" : 2.6, "filter" : { "type" : "lowpass", "rolloff" : -12, @@ -141,32 +148,33 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp }); /** - * The minimum value of the LFO attached to the cutoff frequency of the filter. + * The minimum value of the filter's cutoff frequency. * @memberOf Tone.AutoFilter# * @type {Frequency} * @name min */ - Object.defineProperty(Tone.AutoFilter.prototype, "min", { + Object.defineProperty(Tone.AutoFilter.prototype, "baseFrequency", { get : function(){ return this._lfo.min; }, - set : function(min){ - this._lfo.min = min; + set : function(freq){ + this._lfo.min = this.toFrequency(freq); } }); /** - * The minimum value of the LFO attached to the cutoff frequency of the filter. + * The maximum value of the filter's cutoff frequency. * @memberOf Tone.AutoFilter# - * @type {Frequency} - * @name max + * @type {Positive} + * @name octaves */ - Object.defineProperty(Tone.AutoFilter.prototype, "max", { + Object.defineProperty(Tone.AutoFilter.prototype, "octaves", { get : function(){ - return this._lfo.max; + return this._octaves; }, - set : function(max){ - this._lfo.max = max; + set : function(oct){ + this._octaves = oct; + this._lfo.max = this.baseFrequency * Math.pow(2, oct); } }); diff --git a/Tone/effect/AutoPanner.js b/Tone/effect/AutoPanner.js index 7d992c540..60a751400 100644 --- a/Tone/effect/AutoPanner.js +++ b/Tone/effect/AutoPanner.js @@ -30,8 +30,6 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/LFO", "Tone/comp "amplitude" : options.depth, "min" : 0, "max" : 1, - //start at the middle of the cycle - "phase" : 90 }); /** diff --git a/Tone/effect/AutoWah.js b/Tone/effect/AutoWah.js index 322b2dbd4..e4010cc23 100644 --- a/Tone/effect/AutoWah.js +++ b/Tone/effect/AutoWah.js @@ -84,7 +84,7 @@ function(Tone){ /** * The gain of the filter. - * @type {Gain} + * @type {Number} * @signal */ this.gain = this._peaking.gain; diff --git a/Tone/effect/Chebyshev.js b/Tone/effect/Chebyshev.js index ffd7c4a76..879ba6645 100644 --- a/Tone/effect/Chebyshev.js +++ b/Tone/effect/Chebyshev.js @@ -21,7 +21,7 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/WaveShaper"], funct Tone.Chebyshev = function(){ var options = this.optionsObject(arguments, ["order"], Tone.Chebyshev.defaults); - Tone.Effect.call(this); + Tone.Effect.call(this, options); /** * @type {WaveShaperNode} diff --git a/Tone/effect/Chorus.js b/Tone/effect/Chorus.js index 23fd9a5b1..76a7f28c7 100644 --- a/Tone/effect/Chorus.js +++ b/Tone/effect/Chorus.js @@ -12,7 +12,7 @@ function(Tone){ * @constructor * @extends {Tone.StereoXFeedbackEffect} * @param {Frequency|Object} [frequency] The frequency of the LFO. - * @param {Number} [delayTime] The delay of the chorus effect in ms. + * @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); @@ -43,15 +43,23 @@ function(Tone){ * @type {Tone.LFO} * @private */ - this._lfoL = new Tone.LFO(options.rate, 0, 1); + this._lfoL = new Tone.LFO({ + "frequency" : options.frequency, + "min" : 0, + "max" : 1, + }); /** * another LFO for the right side with a 180 degree phase diff * @type {Tone.LFO} * @private */ - this._lfoR = new Tone.LFO(options.rate, 0, 1); - this._lfoR.phase = 180; + this._lfoR = new Tone.LFO({ + "frequency" : options.frequency, + "min" : 0, + "max" : 1, + "phase" : 180 + }); /** * delay for left @@ -75,10 +83,11 @@ function(Tone){ this.frequency = this._lfoL.frequency; //connections - this.connectSeries(this.effectSendL, this._delayNodeL, this.effectReturnL); - this.connectSeries(this.effectSendR, this._delayNodeR, this.effectReturnR); + this.effectSendL.chain(this._delayNodeL, this.effectReturnL); + this.effectSendR.chain(this._delayNodeR, this.effectReturnR); //and pass through to make the detune apparent - this.input.connect(this.output); + this.effectSendL.connect(this.effectReturnL); + this.effectSendR.connect(this.effectReturnR); //lfo setup this._lfoL.connect(this._delayNodeL.delayTime); this._lfoR.connect(this._delayNodeR.delayTime); @@ -91,8 +100,8 @@ function(Tone){ this.depth = this._depth; this.frequency.value = options.frequency; this.type = options.type; - this._readOnly(["frequency"]); + this.spread = options.spread; }; Tone.extend(Tone.Chorus, Tone.StereoXFeedbackEffect); @@ -106,7 +115,8 @@ function(Tone){ "delayTime" : 3.5, "depth" : 0.7, "feedback" : 0.1, - "type" : "sine" + "type" : "sine", + "spread" : 180 }; /** @@ -135,7 +145,7 @@ function(Tone){ * will give a more pronounced effect. Nominal range a delayTime * is between 2 and 20ms. * @memberOf Tone.Chorus# - * @type {Number} + * @type {Milliseconds} * @name delayTime */ Object.defineProperty(Tone.Chorus.prototype, "delayTime", { @@ -164,6 +174,23 @@ function(Tone){ } }); + /** + * 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 + */ + 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.Chorus} this diff --git a/Tone/effect/Distortion.js b/Tone/effect/Distortion.js index 2b6e7e636..e3174d7df 100644 --- a/Tone/effect/Distortion.js +++ b/Tone/effect/Distortion.js @@ -52,9 +52,9 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/signal/WaveShaper"], funct }; /** - * The amount of distortion. Range between 0-1. + * The amount of distortion. * @memberOf Tone.Distortion# - * @type {number} + * @type {NormalRange} * @name distortion */ Object.defineProperty(Tone.Distortion.prototype, "distortion", { diff --git a/Tone/effect/MidSideEffect.js b/Tone/effect/MidSideEffect.js index bd20f0bf3..f85fb560d 100644 --- a/Tone/effect/MidSideEffect.js +++ b/Tone/effect/MidSideEffect.js @@ -17,7 +17,8 @@ define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/component/MidSideSplit", " * @constructor */ Tone.MidSideEffect = function(){ - Tone.Effect.call(this); + + Tone.Effect.apply(this, arguments); /** * The mid/side split diff --git a/Tone/effect/Phaser.js b/Tone/effect/Phaser.js index 96ac12665..4d28f83e6 100644 --- a/Tone/effect/Phaser.js +++ b/Tone/effect/Phaser.js @@ -12,12 +12,12 @@ function(Tone){ * @extends {Tone.StereoEffect} * @constructor * @param {Frequency|Object} [frequency] The speed of the phasing. - * @param {number} [depth] The depth of the effect. + * @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, - * "depth" : 5, + * "octaves" : 5, * "baseFrequency" : 1000 * }).toMaster(); * var synth = new Tone.FMSynth().connect(phaser); @@ -26,7 +26,7 @@ function(Tone){ Tone.Phaser = function(){ //set the defaults - var options = this.optionsObject(arguments, ["frequency", "depth", "baseFrequency"], Tone.Phaser.defaults); + var options = this.optionsObject(arguments, ["frequency", "octaves", "baseFrequency"], Tone.Phaser.defaults); Tone.StereoEffect.call(this, options); /** @@ -52,11 +52,11 @@ function(Tone){ this._baseFrequency = options.baseFrequency; /** - * the depth of the phasing + * the octaves of the phasing * @type {number} * @private */ - this._depth = options.depth; + this._octaves = options.octaves; /** * The quality factor of the filters @@ -95,7 +95,7 @@ function(Tone){ this._lfoL.frequency.connect(this._lfoR.frequency); //set the options this.baseFrequency = options.baseFrequency; - this.depth = options.depth; + this.octaves = options.octaves; //start the lfo this._lfoL.start(); this._lfoR.start(); @@ -111,7 +111,7 @@ function(Tone){ */ Tone.Phaser.defaults = { "frequency" : 0.5, - "depth" : 10, + "octaves" : 3, "stages" : 10, "Q" : 10, "baseFrequency" : 350, @@ -137,18 +137,19 @@ function(Tone){ }; /** - * The depth of the effect. + * The number of octaves the phase goes above + * the baseFrequency * @memberOf Tone.Phaser# - * @type {number} - * @name depth + * @type {Positive} + * @name octaves */ - Object.defineProperty(Tone.Phaser.prototype, "depth", { + Object.defineProperty(Tone.Phaser.prototype, "octaves", { get : function(){ - return this._depth; + return this._octaves; }, - set : function(depth){ - this._depth = depth; - var max = this._baseFrequency + this._baseFrequency * depth; + set : function(octaves){ + this._octaves = octaves; + var max = this._baseFrequency * Math.pow(2, octaves); this._lfoL.max = max; this._lfoR.max = max; } @@ -168,7 +169,7 @@ function(Tone){ this._baseFrequency = freq; this._lfoL.min = freq; this._lfoR.min = freq; - this.depth = this._depth; + this.octaves = this._octaves; } }); diff --git a/Tone/effect/PitchShift.js b/Tone/effect/PitchShift.js new file mode 100644 index 000000000..2257ea0e7 --- /dev/null +++ b/Tone/effect/PitchShift.js @@ -0,0 +1,234 @@ +define(["Tone/core/Tone", "Tone/component/LFO", "Tone/component/CrossFade", + "Tone/signal/Signal", "Tone/effect/FeedbackEffect", "Tone/core/Delay"], +function (Tone) { + + "use strict"; + + /** + * @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.PitchShift = function(){ + + var options = this.optionsObject(arguments, ["pitch"], Tone.PitchShift.defaults); + Tone.FeedbackEffect.call(this, options); + + /** + * The pitch signal + * @type {Tone.Signal} + * @private + */ + this._frequency = new Tone.Signal(0); + + /** + * Uses two DelayNodes to cover up the jump in + * the sawtooth wave. + * @type {DelayNode} + * @private + */ + this._delayA = new Tone.Delay(0, 1); + + /** + * The first LFO. + * @type {Tone.LFO} + * @private + */ + this._lfoA = new Tone.LFO({ + "min" : 0, + "max" : 0.1, + "type" : "sawtooth" + }).connect(this._delayA.delayTime); + + + /** + * The second DelayNode + * @type {DelayNode} + * @private + */ + this._delayB = new Tone.Delay(0, 1); + + /** + * The first LFO. + * @type {Tone.LFO} + * @private + */ + 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.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; + */ + 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. + * @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; +}); \ No newline at end of file diff --git a/Tone/effect/StereoWidener.js b/Tone/effect/StereoWidener.js index 0c898ab6f..1afb66b2b 100644 --- a/Tone/effect/StereoWidener.js +++ b/Tone/effect/StereoWidener.js @@ -28,7 +28,7 @@ define(["Tone/core/Tone", "Tone/effect/MidSideEffect", "Tone/signal/Signal", * @type {NormalRange} * @signal */ - this.width = new Tone.Signal(0.5, Tone.Type.NormalRange); + this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); /** * Mid multiplier diff --git a/Tone/effect/Tremolo.js b/Tone/effect/Tremolo.js index 122cb290f..e3546bb22 100644 --- a/Tone/effect/Tremolo.js +++ b/Tone/effect/Tremolo.js @@ -1,17 +1,17 @@ -define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function(Tone){ +define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/StereoEffect"], function(Tone){ "use strict"; /** - * @class Tone.Tremelo modulates the amplitude of an incoming signal using a Tone.LFO. - * The type, frequency, and depth of the LFO is controllable. + * @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.Effect} + * @extends {Tone.StereoEffect} * @constructor - * @param {Frequency|Object} [frequency] The rate of the effect. - * @param {NormalRange} [depth] The depth of the wavering. + * @param {Frequency} [frequency] The rate of the effect. + * @param {NormalRange} [depth] The depth of the effect. * @example - * //create an tremolo and start it's LFO + * //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(); @@ -19,50 +19,72 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( Tone.Tremolo = function(){ var options = this.optionsObject(arguments, ["frequency", "depth"], Tone.Tremolo.defaults); - Tone.Effect.call(this, options); + Tone.StereoEffect.call(this, options); /** - * The tremelo LFO + * The tremelo LFO in the left channel * @type {Tone.LFO} * @private */ - this._lfo = new Tone.LFO({ - "frequency" : options.frequency, - "amplitude" : options.depth, + this._lfoL = new Tone.LFO({ + "phase" : options.spread, "min" : 1, - "max" : 0 + "max" : 0, + }); + + /** + * The tremelo LFO in the left channel + * @type {Tone.LFO} + * @private + */ + this._lfoR = new Tone.LFO({ + "phase" : options.spread, + "min" : 1, + "max" : 0, }); /** * Where the gain is multiplied - * @type {GainNode} + * @type {Tone.Gain} * @private */ - this._amplitude = this.context.createGain(); + this._amplitudeL = new Tone.Gain(); /** - * The frequency of the tremolo. + * Where the gain is multiplied + * @type {Tone.Gain} + * @private + */ + this._amplitudeR = new Tone.Gain(); + + /** + * The frequency of the tremolo. * @type {Frequency} * @signal */ - this.frequency = this._lfo.frequency; + 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. + * modulate fully between 0 and 1. * @type {NormalRange} * @signal */ - this.depth = this._lfo.amplitude; + this.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange); this._readOnly(["frequency", "depth"]); - this.connectEffect(this._amplitude); - this._lfo.connect(this._amplitude.gain); + 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.Tremolo, Tone.Effect); + Tone.extend(Tone.Tremolo, Tone.StereoEffect); /** * @static @@ -72,7 +94,8 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( Tone.Tremolo.defaults = { "frequency" : 10, "type" : "sine", - "depth" : 0.5 + "depth" : 0.5, + "spread" : 180, }; /** @@ -81,7 +104,8 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( * @returns {Tone.Tremolo} this */ Tone.Tremolo.prototype.start = function(time){ - this._lfo.start(time); + this._lfoL.start(time); + this._lfoR.start(time); return this; }; @@ -91,18 +115,20 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( * @returns {Tone.Tremolo} this */ Tone.Tremolo.prototype.stop = function(time){ - this._lfo.stop(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. + * Transport has started. * @returns {Tone.AutoFilter} this */ Tone.Tremolo.prototype.sync = function(delay){ - this._lfo.sync(delay); + this._lfoL.sync(delay); + this._lfoR.sync(delay); return this; }; @@ -111,22 +137,41 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( * @returns {Tone.Tremolo} this */ Tone.Tremolo.prototype.unsync = function(){ - this._lfo.unsync(); + this._lfoL.unsync(); + this._lfoR.unsync(); return this; }; /** - * Type of oscillator attached to the Tremolo. + * The Tremolo's oscillator type. * @memberOf Tone.Tremolo# * @type {string} * @name type */ Object.defineProperty(Tone.Tremolo.prototype, "type", { get : function(){ - return this._lfo.type; + return this._lfoL.type; }, set : function(type){ - this._lfo.type = 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.Tremolo# + * @type {Degrees} + * @name spread + */ + Object.defineProperty(Tone.Tremolo.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; } }); @@ -135,12 +180,16 @@ define(["Tone/core/Tone", "Tone/component/LFO", "Tone/effect/Effect"], function( * @returns {Tone.Tremolo} this */ Tone.Tremolo.prototype.dispose = function(){ - Tone.Effect.prototype.dispose.call(this); + Tone.StereoEffect.prototype.dispose.call(this); this._writable(["frequency", "depth"]); - this._lfo.dispose(); - this._lfo = null; - this._amplitude.disconnect(); - this._amplitude = null; + 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; diff --git a/Tone/effect/Vibrato.js b/Tone/effect/Vibrato.js new file mode 100644 index 000000000..86620578a --- /dev/null +++ b/Tone/effect/Vibrato.js @@ -0,0 +1,102 @@ +define(["Tone/core/Tone", "Tone/effect/Effect", "Tone/core/Delay", "Tone/component/LFO"], function (Tone) { + + "use strict"; + + /** + * @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 + */ + 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.Vibrato; +}); \ No newline at end of file diff --git a/Tone/effect/preset/AutoWah.preset.js b/Tone/effect/preset/AutoWah.preset.js deleted file mode 100644 index dfc63773f..000000000 --- a/Tone/effect/preset/AutoWah.preset.js +++ /dev/null @@ -1,49 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/AutoWah"], function(Tone){ - - /** - * named presets for the AutoWah - * @const - * @static - * @type {Object} - */ - Tone.AutoWah.prototype.preset = { - "Talker" : { - "baseFrequency" : 100, - "octaves" : 4, - "sensitivity" : 0, - "Q" : 2, - "gain" : 10, - "rolloff" : -12, - "follower" : { - "attack" : 0.05, - "release" : 0.2 - } - }, - "Yes" : { - "baseFrequency" : 250, - "octaves" : 5, - "sensitivity" : 0, - "Q" : 2, - "gain" : 20, - "rolloff" : -24, - "follower" : { - "attack" : 0.1, - "release" : 0.2 - } - }, - "Springy" : { - "baseFrequency" : 10, - "octaves" : 8, - "sensitivity" : 0, - "Q" : 1, - "gain" : 10, - "rolloff" : -48, - "follower" : { - "attack" : 0.02, - "release" : 1 - } - } - }; - - return Tone.AutoWah.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/Chebyshev.preset.js b/Tone/effect/preset/Chebyshev.preset.js deleted file mode 100644 index 0deb3f470..000000000 --- a/Tone/effect/preset/Chebyshev.preset.js +++ /dev/null @@ -1,25 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/Chebyshev"], function(Tone){ - - /** - * named presets for the Chebyshev - * @const - * @static - * @type {Object} - */ - Tone.Chebyshev.prototype.preset = { - "Hornsy" : { - "order" : 50, - "oversample" : "none" - }, - "Peaker" : { - "order" : 11, - "oversample" : "2x" - }, - "CoinOperated" : { - "order" : 108, - "oversample" : "none" - } - }; - - return Tone.Chebyshev.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/Chorus.preset.js b/Tone/effect/preset/Chorus.preset.js deleted file mode 100644 index 8943fb3f4..000000000 --- a/Tone/effect/preset/Chorus.preset.js +++ /dev/null @@ -1,34 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/Chorus"], function(Tone){ - - /** - * named presets for the Chorus - * @const - * @static - * @type {Object} - */ - Tone.Chorus.prototype.preset = { - "Ether" : { - "rate" : 0.3, - "delayTime" : 8, - "type" : "triangle", - "depth" : 0.8, - "feedback" : 0.2 - }, - "Harmony" : { - "rate" : 12, - "delayTime" : 3.5, - "type" : "sine", - "depth" : 0.8, - "feedback" : 0.1 - }, - "Rattler" : { - "rate" : "16n", - "delayTime" : 15, - "type" : "square", - "depth" : 0.2, - "feedback" : 0.3 - } - }; - - return Tone.Chorus.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/Distortion.preset.js b/Tone/effect/preset/Distortion.preset.js deleted file mode 100644 index 0752ef07f..000000000 --- a/Tone/effect/preset/Distortion.preset.js +++ /dev/null @@ -1,25 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/Distortion"], function(Tone){ - - /** - * named presets for Distortion - * @const - * @static - * @type {Object} - */ - Tone.Distortion.prototype.preset = { - "Clean" : { - "distortion" : 0.08, - "oversample" : "4x" - }, - "Thick" : { - "distortion" : 0.6, - "oversample" : "none" - }, - "Growl" : { - "distortion" : 1.4, - "oversample" : "2x" - } - }; - - return Tone.Distortion.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/FeedbackDelay.preset.js b/Tone/effect/preset/FeedbackDelay.preset.js deleted file mode 100644 index b13760547..000000000 --- a/Tone/effect/preset/FeedbackDelay.preset.js +++ /dev/null @@ -1,25 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/FeedbackDelay"], function(Tone){ - - /** - * named presets for FeedbackDelay - * @const - * @static - * @type {Object} - */ - Tone.FeedbackDelay.prototype.preset = { - "DecayDelay" : { - "delayTime" : "8n", - "feedback" : 0.4 - }, - "Minimalism" : { - "delayTime" : "4n", - "feedback" : 0.7 - }, - "CounterPoints" : { - "delayTime" : "8t", - "feedback" : 0.2 - }, - }; - - return Tone.FeedbackDelay.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/Freeverb.preset.js b/Tone/effect/preset/Freeverb.preset.js deleted file mode 100644 index c94995f7e..000000000 --- a/Tone/effect/preset/Freeverb.preset.js +++ /dev/null @@ -1,25 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/Freeverb"], function(Tone){ - - /** - * named presets for Freeverb - * @const - * @static - * @type {Object} - */ - Tone.Freeverb.prototype.preset = { - "Sewer" : { - "roomSize" : 0.8, - "dampening" : 0.05 - }, - "Glassroom" : { - "roomSize" : 0.6, - "dampening" : 0.9 - }, - "Bigplate" : { - "roomSize" : 0.9, - "dampening" : 0.2 - } - }; - - return Tone.Freeverb.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/JCReverb.preset.js b/Tone/effect/preset/JCReverb.preset.js deleted file mode 100644 index a959dc26a..000000000 --- a/Tone/effect/preset/JCReverb.preset.js +++ /dev/null @@ -1,22 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/JCReverb"], function(Tone){ - - /** - * named presets for the JCReverb - * @const - * @static - * @type {Object} - */ - Tone.JCReverb.prototype.preset = { - "QuickSlap" : { - "roomSize" : 0.1, - }, - "BounceHall" : { - "roomSize" : 0.8, - }, - "NotNormal" : { - "roomSize" : 0.5, - }, - }; - - return Tone.JCReverb.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/Phaser.preset.js b/Tone/effect/preset/Phaser.preset.js deleted file mode 100644 index f00672981..000000000 --- a/Tone/effect/preset/Phaser.preset.js +++ /dev/null @@ -1,31 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/Phaser"], function(Tone){ - - /** - * named presets for the Phaser - * @const - * @static - * @type {Object} - */ - Tone.Phaser.prototype.preset = { - "Testing" : { - "rate" : 10, - "depth" : 0.2, - "Q" : 2, - "baseFrequency" : 700, - }, - "Landing" : { - "rate" : 4, - "depth" : 1.2, - "Q" : 20, - "baseFrequency" : 800, - }, - "Bubbles" : { - "rate" : 0.5, - "depth" : 5, - "Q" : 8, - "baseFrequency" : 250, - } - }; - - return Tone.Phaser.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/effect/preset/PingPongDelay.preset.js b/Tone/effect/preset/PingPongDelay.preset.js deleted file mode 100644 index 6a57a0528..000000000 --- a/Tone/effect/preset/PingPongDelay.preset.js +++ /dev/null @@ -1,25 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/PingPongDelay"], function(Tone){ - - /** - * named presets for PingPongDelay - * @const - * @static - * @type {Object} - */ - Tone.PingPongDelay.prototype.preset = { - "SlowSteady" : { - "delayTime" : "4n", - "feedback" : 0.2 - }, - "ThickStereo" : { - "delayTime" : "16t", - "feedback" : 0.3 - }, - "RhythmicDelay" : { - "delayTime" : "8n", - "feedback" : 0.6 - }, - }; - - return Tone.PingPongDelay.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/event/Event.js b/Tone/event/Event.js new file mode 100644 index 000000000..66d56537d --- /dev/null +++ b/Tone/event/Event.js @@ -0,0 +1,405 @@ +define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Type", "Tone/core/TimelineState"], function (Tone) { + + "use strict"; + + /** + * @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.Event = function(){ + + var options = this.optionsObject(arguments, ["callback", "value"], Tone.Event.defaults); + + /** + * Loop value + * @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} + * @private + */ + this._loopStart = this.toTicks(options.loopStart); + + /** + * When the note is scheduled to start. + * @type {Number} + * @private + */ + this._loopEnd = this.toTicks(options.loopEnd); + + /** + * Tracks the scheduled events + * @type {Tone.TimelineState} + * @private + */ + this._state = new Tone.TimelineState(Tone.State.Stopped); + + /** + * The playback speed of the note. A speed of 1 + * is no change. + * @private + * @type {Positive} + */ + this._playbackRate = 1; + + /** + * A delay time from when the event is scheduled to start + * @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; + */ + this.probability = options.probability; + + /** + * 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.humanize = options.humanize; + + /** + * If mute is true, the callback won't be + * invoked. + * @type {Boolean} + */ + this.mute = options.mute; + + //set the initial values + this.playbackRate = options.playbackRate; + }; + + Tone.extend(Tone.Event); + + /** + * The default values + * @type {Object} + * @const + */ + 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 - 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"); + } + } + }.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 {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._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 + */ + 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); + }; + + /** + * 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(); + } + }); + + /** + * 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 + */ + 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(); + } + } + }); + + /** + * The time when the loop should start. + * @memberOf Tone.Event# + * @type {Boolean|Positive} + * @name loopStart + */ + 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(); + } + } + }); + + /** + * 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 + */ + 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 + * @return {Tone.Event} this + */ + Tone.Event.prototype.dispose = function(){ + this.cancel(); + this._state.dispose(); + this._state = null; + this.callback = null; + this.value = null; + }; + + return Tone.Event; +}); \ No newline at end of file diff --git a/Tone/event/Loop.js b/Tone/event/Loop.js new file mode 100644 index 000000000..b596be65e --- /dev/null +++ b/Tone/event/Loop.js @@ -0,0 +1,242 @@ +define(["Tone/core/Tone", "Tone/event/Event"], 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. + */ + 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 + }); + + /** + * 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 + }; + + /** + * Start the loop at the specified time along the Transport's + * timeline. + * @param {Time=} time When to start the Loop. + * @return {Tone.Loop} this + */ + Tone.Loop.prototype.start = function(time){ + this._event.start(time); + return this; + }; + + /** + * Stop the loop at the given time. + * @param {Time=} time When to stop the Arpeggio + * @return {Tone.Loop} this + */ + Tone.Loop.prototype.stop = function(time){ + this._event.stop(time); + 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 + */ + Tone.Loop.prototype.cancel = function(time){ + this._event.cancel(time); + return this; + }; + + /** + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs + * @private + */ + Tone.Loop.prototype._tick = function(time){ + this.callback(time); + }; + + /** + * 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.Loop.prototype, "probability", { + get : function(){ + return this._event.probability; + }, + set : function(prob){ + this._event.probability = prob; + } + }); + + /** + * Muting the Loop means that no callbacks are invoked. + * @memberOf Tone.Loop# + * @type {Boolean} + * @name mute + */ + Object.defineProperty(Tone.Loop.prototype, "mute", { + get : function(){ + return this._event.mute; + }, + set : function(mute){ + this._event.mute = mute; + } + }); + + /** + * The number of iterations of the loop. The default + * value is Infinity (loop forever). + * @memberOf Tone.Loop# + * @type {Positive} + * @name iterations + */ + Object.defineProperty(Tone.Loop.prototype, "iterations", { + 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; + } + } + }); + + /** + * Clean up + * @return {Tone.Loop} this + */ + Tone.Loop.prototype.dispose = function(){ + this._event.dispose(); + this._event = null; + this.callback = null; + }; + + return Tone.Loop; +}); \ No newline at end of file diff --git a/Tone/event/Part.js b/Tone/event/Part.js new file mode 100644 index 000000000..768e1de69 --- /dev/null +++ b/Tone/event/Part.js @@ -0,0 +1,585 @@ +define(["Tone/core/Tone", "Tone/event/Event", "Tone/core/Type", "Tone/core/Transport"], function (Tone) { + + "use strict"; + + /** + * @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); + + /** + * An array of Objects. + * @type {Array} + * @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]); + } + } + } + }; + + Tone.extend(Tone.Part, Tone.Event); + + /** + * The default values + * @type {Object} + * @const + */ + Tone.Part.defaults = { + "callback" : Tone.noOp, + "loop" : false, + "loopEnd" : "1m", + "loopStart" : 0, + "playbackRate" : 1, + "probability" : 1, + "humanize" : false, + "mute" : false, + }; + + /** + * 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 + */ + 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; + }; + + /** + * 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 + */ + 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"); + } + } + }; + + /** + * The start from the scheduled start time + * @type {Ticks} + * @memberOf Tone.Part# + * @name startOffset + * @private + */ + Object.defineProperty(Tone.Part.prototype, "startOffset", { + get : function(){ + return this._startOffset; + }, + set : function(offset){ + this._startOffset = offset; + this._forEach(function(event){ + event.startOffset += this._startOffset; + }); + } + }); + + /** + * Stop the part at the given time. + * @param {Time} 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); + }); + } + return this; + }; + + /** + * 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 {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 + */ + 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; + } + } + //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; + } + }; + + /** + * 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"); + */ + 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; + }; + + /** + * Restart the given event + * @param {Tone.Event} event + * @private + */ + 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); + } + }; + + /** + * 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 + */ + 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 { + if (event.startOffset === time){ + if (this.isUndef(value) || (!this.isUndef(value) && event.value === value)){ + this._events.splice(i, 1); + event.dispose(); + } + } + } + } + return this; + }; + + /** + * Remove all of the notes from the group. + * @return {Tone.Part} this + */ + Tone.Part.prototype.removeAll = function(){ + this._forEach(function(event){ + event.dispose(); + }); + this._events = []; + 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 + */ + Tone.Part.prototype.cancel = function(after){ + this._forEach(function(event){ + event.cancel(after); + }); + this._state.cancel(after); + return this; + }; + + /** + * Iterate over all of the events + * @param {Function} callback + * @param {Object} ctx The context + * @private + */ + 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; + }; + + /** + * 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.Part.prototype._setAll = function(attr, value){ + this._forEach(function(event){ + event[attr] = value; + }); + }; + + /** + * Internal tick method + * @param {Number} time The time of the event in seconds + * @private + */ + Tone.Part.prototype._tick = function(time, value){ + if (!this.mute){ + this.callback(time, value); + } + }; + + /** + * Determine if the event should be currently looping + * given the loop boundries of this Part. + * @param {Tone.Event} event The event to test + * @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); + } + } + }; + + /** + * 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 + */ + Object.defineProperty(Tone.Part.prototype, "loopStart", { + get : function(){ + return this.toNotation(this._loopStart + "i"); + }, + set : function(loopStart){ + this._loopStart = this.toTicks(loopStart); + if (this._loop){ + this._forEach(function(event){ + event.loopStart = this.loopStart; + this._testLoopBoundries(event); + }); + } + } + }); + + /** + * The playback rate of the part + * @memberOf Tone.Part# + * @type {Positive} + * @name playbackRate + */ + Object.defineProperty(Tone.Part.prototype, "playbackRate", { + get : function(){ + return this._playbackRate; + }, + set : function(rate){ + this._playbackRate = rate; + this._setAll("playbackRate", rate); + } + }); + + /** + * The number of scheduled notes in the part. + * @memberOf Tone.Part# + * @type {Positive} + * @name length + * @readOnly + */ + Object.defineProperty(Tone.Part.prototype, "length", { + get : function(){ + return this._events.length; + } + }); + + /** + * Clean up + * @return {Tone.Part} this + */ + Tone.Part.prototype.dispose = function(){ + this.removeAll(); + this._state.dispose(); + this._state = null; + this.callback = null; + this._events = null; + return this; + }; + + return Tone.Part; +}); \ No newline at end of file diff --git a/Tone/event/Pattern.js b/Tone/event/Pattern.js new file mode 100644 index 000000000..4733d3aaf --- /dev/null +++ b/Tone/event/Pattern.js @@ -0,0 +1,126 @@ +define(["Tone/core/Tone", "Tone/event/Loop", "Tone/control/CtrlPattern"], 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} events The events to arpeggiate over. + */ + Tone.Pattern = function(){ + + var options = this.optionsObject(arguments, ["callback", "events", "pattern"], Tone.Pattern.defaults); + + Tone.Loop.call(this, options); + + /** + * The pattern manager + * @type {Tone.CtrlPattern} + * @private + */ + this._pattern = new Tone.CtrlPattern({ + "values" : options.events, + "type" : options.pattern, + "index" : options.index + }); + + }; + + Tone.extend(Tone.Pattern, Tone.Loop); + + /** + * The defaults + * @const + * @type {Object} + */ + Tone.Pattern.defaults = { + "pattern" : Tone.CtrlPattern.Type.Up, + "events" : [], + }; + + /** + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs + * @private + */ + Tone.Pattern.prototype._tick = function(time){ + this.callback(time, this._pattern.value); + this._pattern.next(); + }; + + /** + * The current index in the events array. + * @memberOf Tone.Pattern# + * @type {Positive} + * @name index + */ + Object.defineProperty(Tone.Pattern.prototype, "index", { + get : function(){ + return this._pattern.index; + }, + set : function(i){ + this._pattern.index = i; + } + }); + + /** + * The array of events. + * @memberOf Tone.Pattern# + * @type {Array} + * @name events + */ + Object.defineProperty(Tone.Pattern.prototype, "events", { + get : function(){ + return this._pattern.values; + }, + set : function(vals){ + this._pattern.values = vals; + } + }); + + /** + * The current value of the pattern. + * @memberOf Tone.Pattern# + * @type {*} + * @name value + * @readOnly + */ + Object.defineProperty(Tone.Pattern.prototype, "value", { + get : function(){ + return this._pattern.value; + } + }); + + /** + * 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; +}); \ No newline at end of file diff --git a/Tone/event/Sequence.js b/Tone/event/Sequence.js new file mode 100644 index 000000000..bd3653ba0 --- /dev/null +++ b/Tone/event/Sequence.js @@ -0,0 +1,162 @@ +define(["Tone/core/Tone", "Tone/event/Part", "Tone/core/Transport"], function (Tone) { + + "use strict"; + + /** + * @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"); + * @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.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); + } + //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); + + /** + * The default values. + * @type {Object} + */ + 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 this.toNotation(this._subdivision + "i"); + } + }); + + /** + * 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.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); + }; + + /** + * 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.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); + } + 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 + */ + Tone.Sequence.prototype._indexTime = function(index){ + if (this.isTicks(index)){ + return index; + } else { + return (index * this._subdivision + this.startOffset) + "i"; + } + }; + + /** + * Clean up. + * @return {Tone.Sequence} this + */ + Tone.Sequence.prototype.dispose = function(){ + Tone.Part.prototype.dispose.call(this); + return this; + }; + + return Tone.Sequence; +}); \ No newline at end of file diff --git a/Tone/instrument/AMSynth.js b/Tone/instrument/AMSynth.js index 552315692..d8415e800 100644 --- a/Tone/instrument/AMSynth.js +++ b/Tone/instrument/AMSynth.js @@ -104,8 +104,8 @@ function(Tone){ "decay" : 0.0, "sustain" : 1, "release" : 0.5, - "min" : 20000, - "max" : 20000 + "baseFrequency" : 20000, + "octaves" : 0 }, "filter" : { "Q" : 6, @@ -129,8 +129,8 @@ function(Tone){ "decay" : 0.2, "sustain" : 0.5, "release" : 0.5, - "min" : 20, - "max" : 1500 + "baseFrequency" : 20, + "octaves" : 6 }, "filter" : { "Q" : 6, diff --git a/Tone/instrument/DuoSynth.js b/Tone/instrument/DuoSynth.js index d55cdbd8e..e8a72ab56 100644 --- a/Tone/instrument/DuoSynth.js +++ b/Tone/instrument/DuoSynth.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/component/LFO", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic"], +define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/component/LFO", "Tone/signal/Signal", + "Tone/signal/Multiply", "Tone/instrument/Monophonic", "Tone/core/Param"], function(Tone){ "use strict"; @@ -60,11 +61,14 @@ function(Tone){ /** * The amount of vibrato - * @type {Gain} + * @type {Positive} * @signal */ - this.vibratoAmount = new Tone.Signal(this._vibratoGain.gain, Tone.Type.Gain); - this.vibratoAmount.value = options.vibratoAmount; + this.vibratoAmount = new Tone.Param({ + "param" : this._vibratoGain.gain, + "units" : Tone.Type.Positive, + "value" : options.vibratoAmount + }); /** * the delay before the vibrato starts diff --git a/Tone/instrument/FMSynth.js b/Tone/instrument/FMSynth.js index d84145567..3689bb775 100644 --- a/Tone/instrument/FMSynth.js +++ b/Tone/instrument/FMSynth.js @@ -110,8 +110,8 @@ function(Tone){ "decay" : 0.0, "sustain" : 1, "release" : 0.5, - "min" : 20000, - "max" : 20000 + "baseFrequency" : 200, + "octaves" : 8 } }, "modulator" : { @@ -131,8 +131,8 @@ function(Tone){ "decay" : 0.0, "sustain" : 1, "release" : 0.5, - "min" : 20000, - "max" : 20000 + "baseFrequency" : 600, + "octaves" : 5 } } }; diff --git a/Tone/instrument/Instrument.js b/Tone/instrument/Instrument.js index 7700b1f69..1900f4549 100644 --- a/Tone/instrument/Instrument.js +++ b/Tone/instrument/Instrument.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/core/Master", "Tone/core/Note"], function(Tone){ +define(["Tone/core/Tone", "Tone/core/Type"], function(Tone){ "use strict"; @@ -14,23 +14,21 @@ define(["Tone/core/Tone", "Tone/core/Master", "Tone/core/Note"], function(Tone){ options = this.defaultArg(options, Tone.Instrument.defaults); /** - * the output - * @type {GainNode} + * The output and volume triming node + * @type {Tone.Volume} * @private */ - this.output = this.context.createGain(); + this._volume = this.output = new Tone.Volume(options.volume); /** - * The volume of the instrument. + * The volume of the output in decibels. * @type {Decibels} * @signal + * @example + * source.volume.value = -6; */ - this.volume = new Tone.Signal({ - "param" : this.output.gain, - "units" : Tone.Type.Decibels, - "value" : options.volume - }); - this._readOnly(["volume"]); + this.volume = this._volume.volume; + this._readOnly("volume"); }; Tone.extend(Tone.Instrument); @@ -84,8 +82,9 @@ define(["Tone/core/Tone", "Tone/core/Master", "Tone/core/Note"], function(Tone){ */ Tone.Instrument.prototype.dispose = function(){ Tone.prototype.dispose.call(this); + this._volume.dispose(); + this._volume = null; this._writable(["volume"]); - this.volume.dispose(); this.volume = null; return this; }; diff --git a/Tone/instrument/MonoSynth.js b/Tone/instrument/MonoSynth.js index fa0d98d75..b4dde8df9 100644 --- a/Tone/instrument/MonoSynth.js +++ b/Tone/instrument/MonoSynth.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/component/ScaledEnvelope", +define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/component/FrequencyEnvelope", "Tone/source/OmniOscillator", "Tone/signal/Signal", "Tone/component/Filter", "Tone/instrument/Monophonic"], function(Tone){ @@ -59,9 +59,9 @@ function(Tone){ /** * The filter envelope. - * @type {Tone.ScaledEnvelope} + * @type {Tone.FrequencyEnvelope} */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** * The amplitude envelope. @@ -107,8 +107,8 @@ function(Tone){ "decay" : 0.2, "sustain" : 0.5, "release" : 2, - "min" : 20, - "max" : 4000, + "baseFrequency" : 200, + "octaves" : 7, "exponent" : 2 } }; diff --git a/Tone/instrument/Monophonic.js b/Tone/instrument/Monophonic.js index d739657ff..37025a08e 100644 --- a/Tone/instrument/Monophonic.js +++ b/Tone/instrument/Monophonic.js @@ -3,7 +3,9 @@ define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/signal/Signal"], f "use strict"; /** - * @class This is a base class for monophonic instruments. + * @class This is an abstract base class for other monophonic instruments to + * extend. IMPORTANT: It does not make any sound on its own and + * shouldn't be directly instantiated. * * @constructor * @abstract diff --git a/Tone/instrument/NoiseSynth.js b/Tone/instrument/NoiseSynth.js index 14ba78d1c..785a2d438 100644 --- a/Tone/instrument/NoiseSynth.js +++ b/Tone/instrument/NoiseSynth.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/component/ScaledEnvelope", +define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/component/FrequencyEnvelope", "Tone/source/Noise", "Tone/signal/Signal", "Tone/component/Filter", "Tone/instrument/Instrument"], function(Tone){ @@ -40,9 +40,9 @@ function(Tone){ /** * The filter envelope. - * @type {Tone.ScaledEnvelope} + * @type {Tone.FrequencyEnvelope} */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** * The amplitude envelope. @@ -85,9 +85,8 @@ function(Tone){ "decay" : 0.2, "sustain" : 0, "release" : 2, - "min" : 20, - "max" : 4000, - "exponent" : 2 + "baseFrequency" : 20, + "octaves" : 5, } }; diff --git a/Tone/instrument/PolySynth.js b/Tone/instrument/PolySynth.js index d963d98ac..5a30eee55 100644 --- a/Tone/instrument/PolySynth.js +++ b/Tone/instrument/PolySynth.js @@ -35,6 +35,13 @@ 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 * @private @@ -92,12 +99,19 @@ function(Tone){ for (var i = 0; i < notes.length; i++){ var val = notes[i]; var stringified = JSON.stringify(val); - if (this._activeVoices[stringified]){ + //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; + } } } return this; @@ -131,7 +145,7 @@ function(Tone){ * @param {Time} [time=now] When the release will be triggered. * @returns {Tone.PolySynth} this * @example - * poly.triggerAttack(["Ab3", "C4", "F5"]); + * poly.triggerRelease(["Ab3", "C4", "F5"], "+2n"); */ Tone.PolySynth.prototype.triggerRelease = function(notes, time){ if (!Array.isArray(notes)){ @@ -188,13 +202,13 @@ function(Tone){ }; /** - * @param {string} presetName the preset name - * @returns {Tone.PolySynth} this - * @private + * Trigger the release portion of all the currently active voices. + * @param {Time} [time=now] When the notes should be released. + * @return {Tone.PolySynth} this */ - Tone.PolySynth.prototype.setPreset = function(presetName){ + Tone.PolySynth.prototype.releaseAll = function(time){ for (var i = 0; i < this.voices.length; i++){ - this.voices[i].setPreset(presetName); + this.voices[i].triggerRelease(time); } return this; }; diff --git a/Tone/instrument/Sampler.js b/Tone/instrument/Sampler.js index 4e1aebab6..964d2de01 100644 --- a/Tone/instrument/Sampler.js +++ b/Tone/instrument/Sampler.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/component/ScaledEnvelope", +define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/component/FrequencyEnvelope", "Tone/component/Filter", "Tone/instrument/Instrument"], function(Tone){ @@ -19,7 +19,7 @@ function(Tone){ * @example * var sampler = new Sampler({ * A : { - * 1 : {"./audio/casio/A1.mp3", + * 1 : "./audio/casio/A1.mp3", * 2 : "./audio/casio/A2.mp3", * }, * "B.1" : "./audio/casio/B1.mp3", @@ -57,9 +57,9 @@ function(Tone){ /** * The filter envelope. - * @type {Tone.ScaledEnvelope} + * @type {Tone.FrequencyEnvelope} */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** * The name of the current sample. @@ -112,9 +112,8 @@ function(Tone){ "decay" : 0.001, "sustain" : 1, "release" : 0.5, - "min" : 20, - "max" : 20000, - "exponent" : 2, + "baseFrequency" : 20, + "octaves" : 10, }, "filter" : { "type" : "lowpass" @@ -127,7 +126,7 @@ function(Tone){ * @private */ Tone.Sampler.prototype._loadBuffers = function(urls){ - if (typeof urls === "string"){ + if (this.isString(urls)){ this._buffers["0"] = new Tone.Buffer(urls, function(){ this.sample = "0"; }.bind(this)); @@ -152,7 +151,7 @@ function(Tone){ var toReturn = {}; for (var i in ob) { if (!ob.hasOwnProperty(i)) continue; - if ((typeof ob[i]) == "object") { + if (this.isObject(ob[i])) { var flatObject = this._flattenUrls(ob[i]); for (var x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; diff --git a/Tone/instrument/preset/AMSynth.preset.js b/Tone/instrument/preset/AMSynth.preset.js deleted file mode 100644 index 4999b91be..000000000 --- a/Tone/instrument/preset/AMSynth.preset.js +++ /dev/null @@ -1,82 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/AMSynth"], function(Tone){ - - /** - * named presets for the AMSynth - * @const - * @static - * @type {Object} - */ - Tone.AMSynth.prototype.preset = { - "Sand" : { - "harmonicity": 3, - "carrier": { - "oscillator": { - "frequency": 0, - "detune": 0, - "type": "square", - "phase": 0, - "volume": 0 - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 0.93, - "gain": 0 - }, - "envelope": { - "attack": 0.003, - "decay": 0.305, - "sustain": 0.7, - "release": 0.828 - }, - "filterEnvelope": { - "min": 1509.0, - "max": 7212.8, - "exponent": 2, - "attack": 0.0035, - "decay": 0.0006, - "sustain": 0.57, - "release": 0.8 - }, - "portamento": 0 - }, - "modulator": { - "oscillator": { - "frequency": 0, - "detune": 0, - "type": "sine", - "phase": 0, - "volume": 0 - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 0.6, - "gain": 0 - }, - "envelope": { - "attack": 0.043, - "decay": 0.13, - "sustain": 0.047, - "release": 0.040 - }, - "filterEnvelope": { - "min": 1132.80, - "max": 557.38, - "exponent": 2, - "attack": 0.17, - "decay": 0.023, - "sustain": 0.036, - "release": 1 - }, - "portamento": 0 - }, - "portamento": 0 - } - }; - - - return Tone.AMSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/instrument/preset/DuoSynth.preset.js b/Tone/instrument/preset/DuoSynth.preset.js deleted file mode 100644 index 5e10d8e44..000000000 --- a/Tone/instrument/preset/DuoSynth.preset.js +++ /dev/null @@ -1,191 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/DuoSynth"], function(Tone){ - - /** - * named presets for the DuoSynth - * @const - * @static - * @type {Object} - */ - Tone.DuoSynth.prototype.preset = { - "Steely" : { - "vibratoAmount" : 0.0, - "vibratoRate" : 5, - "vibratoDelay" : 1, - "portamento" : 0, - "harmonicity" : 1.99, - "voice0" : { - "volume" : -8, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 1, - "sustain" : 0, - "release" : 0.4 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.01, - "sustain" : 0.35, - "release" : 1, - "min" : 20, - "max" : 8000 - } - }, - "voice1" : { - "volume" : -1, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.25, - "decay" : 4, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 1000, - "max" : 2500 - } - } - }, - "Unicorn" : { - "vibratoAmount" : 0.5, - "vibratoRate" : 5, - "vibratoDelay" : 1, - "portamento" : 0.1, - "harmonicity" : 1.005, - "voice0" : { - "volume" : -2, - "portamento" : 0, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 1, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.25, - "sustain" : 0.4, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.05, - "sustain" : 0.3, - "release" : 2, - "min" : 100, - "max" : 10000 - } - }, - "voice1" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "bandpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.25, - "decay" : 4, - "sustain" : 0.1, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.05, - "sustain" : 0.7, - "release" : 2, - "min" : 5000, - "max" : 2000 - } - } - }, - "Organic" : { - "vibratoAmount": 0.3, - "vibratoRate": 3.5, - "harmonicity": 1.5, - "voice0": { - "volume" : -6, - "oscillator": { - "frequency": 0, - "type": "sine", - }, - "filter": { - "type": "lowpass", - "rolloff": -12, - "Q": 2.8, - }, - "envelope": { - "attack": 0.0070, - "decay": 0, - "sustain": 1, - "release": 0.056682076 - }, - "filterEnvelope": { - "min": 219.80, - "max": 1049.54942, - "exponent": 2, - "attack": 0.00704, - "decay": 0.0278, - "sustain": 0.065, - "release": 0.0749 - }, - }, - "voice1": { - "volume" : -20, - "oscillator": { - "type": "sine", - }, - "filter": { - "type": "highpass", - "rolloff": -12, - "Q": 4.55, - }, - "envelope": { - "attack": 0.011, - "decay": 0.016, - "sustain": 0.7464, - "release": 0.074 - }, - "filterEnvelope": { - "min": 298.20, - "max": 80.43, - "exponent": 2, - "attack": 0.0035, - "decay": 0.0060, - "sustain": 1, - "release": 0.108 - }, - }, - "portamento": 0 - } - }; - - - return Tone.DuoSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/instrument/preset/FMSynth.preset.js b/Tone/instrument/preset/FMSynth.preset.js deleted file mode 100644 index 1000f20f7..000000000 --- a/Tone/instrument/preset/FMSynth.preset.js +++ /dev/null @@ -1,242 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/FMSynth"], function(Tone){ - - /** - * named presets for the FMSynth - * @const - * @static - * @type {Object} - */ - Tone.FMSynth.prototype.preset = { - "Trumpet" : { - "portamento" : 0, - "harmonicity" : 1, - "modulationIndex" : 4, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 1, - "sustain" : 0.7, - "release" : 0.4 - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.07, - "sustain" : 0.35, - "release" : 0.8, - "min" : 3000, - "max" : 6500 - } - }, - "modulator" : { - "volume" : -6, - "portamento" : 0, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 0, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.15, - "decay" : 0.3, - "sustain" : 1, - "release" : 1.5 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 20000, - "max" : 20000 - } - } - }, - "Koto" : { - "portamento" : 0, - "harmonicity" : 3.01, - "modulationIndex" : 12.7, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 2, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.07, - "sustain" : 0.35, - "release" : 0.8, - "min" : 20000, - "max" : 20000 - } - }, - "modulator" : { - "volume" : -1, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 0, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.02, - "decay" : 2, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 20000, - "max" : 20000 - } - } - }, - "ScratchAttack" : { - "portamento" : 0, - "harmonicity" : 10, - "modulationIndex" : 50, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.08, - "decay" : 0.3, - "sustain" : 0, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0, - "release" : 0.2, - "min" : 200, - "max" : 10000 - } - }, - "modulator" : { - "volume" : -6, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 1, - "type" : "highpass", - "rolloff" : -48 - }, - "envelope" : { - "attack" : 0.1, - "decay" : 0.2, - "sustain" : 0.3, - "release" : 0.01 - }, - "filterEnvelope" : { - "attack" : 0.2, - "decay" : 0.2, - "sustain" : 0.8, - "release" : 0.01, - "min" : 20, - "max" : 2000 - } - } - }, - "DistGit" : { - "portamento" : 0, - "harmonicity" : 1, - "modulationIndex" : 10, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.001, - "decay" : 3.3, - "sustain" : 0, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.15, - "sustain" : 1, - "release" : 1.5, - "min" : 400, - "max" : 4000 - } - }, - "modulator" : { - "volume" : -3, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 1, - "type" : "lowpass", - "rolloff" : -48 - }, - "envelope" : { - "attack" : 0.3, - "decay" : 0.4, - "sustain" : 1, - "release" : 1.7 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.02, - "sustain" : 0.1, - "release" : 1.5, - "min" : 200, - "max" : 200 - } - } - }, - }; - - - return Tone.FMSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/instrument/preset/MonoSynth.preset.js b/Tone/instrument/preset/MonoSynth.preset.js deleted file mode 100644 index 8b3379e61..000000000 --- a/Tone/instrument/preset/MonoSynth.preset.js +++ /dev/null @@ -1,189 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/MonoSynth"], function(Tone){ - - /** - * named presets for the MonoSynth - * @const - * @static - * @type {Object} - */ - Tone.MonoSynth.prototype.preset = { - "CoolGuy" : { - "portamento" : 0.0, - "oscillator" : { - "type" : "pwm", - "modulationFrequency" : 1 - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.025, - "decay" : 0.3, - "sustain" : 0.9, - "release" : 2 - }, - "filterEnvelope" : { - "attack" : 0.245, - "decay" : 0.131, - "sustain" : 0.5, - "release" : 2, - "min" : 20, - "max" : 3000 - } - }, - "Pianoetta" : { - "portamento" : 0.0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.005, - "decay" : 3, - "sustain" : 0, - "release" : 0.45 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.32, - "sustain" : 0.9, - "release" : 3, - "min" : 700, - "max" : 3500 - } - }, - "Barky" : { - "portamento" : 0.01, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 3, - "type" : "highpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.05, - "decay" : 0.15, - "sustain" : 0.6, - "release" : 1 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.2, - "sustain" : 0.8, - "release" : 1.5, - "min" : 3000, - "max" : 250 - } - }, - "Bassy" : { - "portamento" : 0.08, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 4, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.04, - "decay" : 0.06, - "sustain" : 0.4, - "release" : 1 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.6, - "release" : 1.5, - "min" : 50, - "max" : 350 - } - }, - "BrassCircuit" : { - "portamento" : 0.01, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.1, - "decay" : 0.1, - "sustain" : 0.6, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.8, - "sustain" : 0.4, - "release" : 1.5, - "min" : 2000, - "max" : 5000 - } - }, - "Pizz" : { - "portamento" : 0.00, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "highpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.2, - "sustain" : 0.0, - "release" : 0.2 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.0, - "release" : 0.1, - "min" : 900, - "max" : 500 - } - }, - "LaserSteps" : { - "portamento" : 0.00, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "bandpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.2, - "release" : 0.6 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.4, - "sustain" : 1, - "release" : 0.2, - "min" : 0, - "max" : 7500 - } - } - }; - - return Tone.MonoSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/instrument/preset/NoiseSynth.preset.js b/Tone/instrument/preset/NoiseSynth.preset.js deleted file mode 100644 index ab162459a..000000000 --- a/Tone/instrument/preset/NoiseSynth.preset.js +++ /dev/null @@ -1,90 +0,0 @@ -define(["Tone/core/Tone", "Tone/instrument/NoiseSynth"], function(Tone){ - - /** - * named presets for the NoiseSynth - * @const - * @static - * @type {Object} - */ - Tone.NoiseSynth.prototype.preset = { - "LaserWind" : { - "noise": { - "type": "white", - }, - "filter": { - "type": "highpass", - "rolloff": -24, - "Q": 6, - }, - "envelope": { - "attack": 0.005, - "decay": 0.1, - "sustain": 0, - "release": 1 - }, - "filterEnvelope": { - "min": 20, - "max": 4000, - "exponent": 2, - "attack": 0.06, - "decay": 0.2, - "sustain": 0, - "release": 2 - } - }, - "WindWind" : { - "noise": { - "type": "brown", - }, - "filter": { - "type": "lowpass", - "rolloff": -24, - "Q": 6, - }, - "envelope": { - "attack": 0.033, - "decay": 0.15, - "sustain": 0.38, - "release": 1 - }, - "filterEnvelope": { - "min": 1509.08, - "max": 3976.53, - "exponent": 2, - "attack": 0.61, - "decay": 0.76, - "sustain": 0.20, - "release": 0.33 - } - }, - "Snare" : { - "noise": { - "type": "pink", - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 3.7, - "gain": 0 - }, - "envelope": { - "attack": 0.024, - "decay": 0.111, - "sustain": 0, - "release": 0.22 - }, - "filterEnvelope": { - "min": 819.20, - "max": 3510.98, - "exponent": 2, - "attack": 0.002, - "decay": 0.02, - "sustain": 0.02, - "release": 0.013 - } - } - }; - - return Tone.NoiseSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/instrument/preset/PluckSynth.preset.js b/Tone/instrument/preset/PluckSynth.preset.js deleted file mode 100644 index 05eb56c3b..000000000 --- a/Tone/instrument/preset/PluckSynth.preset.js +++ /dev/null @@ -1,28 +0,0 @@ -define(["Tone/core/Tone", "Tone/effect/PluckSynth"], function(Tone){ - - /** - * named presets for the PluckSynth - * @const - * @static - * @type {Object} - */ - Tone.PluckSynth.prototype.preset = { - "Glassy" : { - "attackNoise": 4, - "dampening": 9200, - "resonance": 1 - }, - "Violin" : { - "attackNoise": 8, - "dampening": 3200, - "resonance": 0.8 - }, - "Plucky" : { - "attackNoise": 0.8, - "dampening": 2600, - "resonance": 0.54 - } - }; - - return Tone.PluckSynth.prototype.preset; -}); \ No newline at end of file diff --git a/Tone/signal/Add.js b/Tone/signal/Add.js index 2bd5cec05..906def29d 100644 --- a/Tone/signal/Add.js +++ b/Tone/signal/Add.js @@ -40,9 +40,9 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * @private * @type {Tone.Signal} */ - this._value = this.input[1] = new Tone.Signal(value); + this._param = this.input[1] = new Tone.Signal(value); - this._value.connect(this._sum); + this._param.connect(this._sum); }; Tone.extend(Tone.Add, Tone.Signal); @@ -55,8 +55,8 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ Tone.prototype.dispose.call(this); this._sum.disconnect(); this._sum = null; - this._value.dispose(); - this._value = null; + this._param.dispose(); + this._param = null; return this; }; diff --git a/Tone/signal/Expr.js b/Tone/signal/Expr.js index ad4815a09..d9323eb3e 100644 --- a/Tone/signal/Expr.js +++ b/Tone/signal/Expr.js @@ -2,7 +2,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa "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/Min", "Tone/signal/Modulo", "Tone/signal/Pow", "Tone/signal/AudioToGain"], function(Tone){ "use strict"; @@ -169,6 +169,14 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa 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" : { diff --git a/Tone/signal/GreaterThan.js b/Tone/signal/GreaterThan.js index 0d1001bf0..dd432cc37 100644 --- a/Tone/signal/GreaterThan.js +++ b/Tone/signal/GreaterThan.js @@ -24,8 +24,8 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThanZero", "Tone/signal/Subtract", * @type {Tone.Subtract} * @private */ - this._value = this.input[0] = new Tone.Subtract(value); - this.input[1] = this._value.input[1]; + this._param = this.input[0] = new Tone.Subtract(value); + this.input[1] = this._param.input[1]; /** * compare that amount to zero @@ -35,7 +35,7 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThanZero", "Tone/signal/Subtract", this._gtz = this.output = new Tone.GreaterThanZero(); //connect - this._value.connect(this._gtz); + this._param.connect(this._gtz); }; Tone.extend(Tone.GreaterThan, Tone.Signal); @@ -46,8 +46,8 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThanZero", "Tone/signal/Subtract", */ Tone.GreaterThan.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._value.dispose(); - this._value = null; + this._param.dispose(); + this._param = null; this._gtz.dispose(); this._gtz = null; return this; diff --git a/Tone/signal/LessThan.js b/Tone/signal/LessThan.js index b119a4654..ba21ede29 100644 --- a/Tone/signal/LessThan.js +++ b/Tone/signal/LessThan.js @@ -47,11 +47,11 @@ function(Tone){ * @private * @type {Tone.Signal} */ - this._value = this.input[1] = new Tone.Signal(value); + this._param = this.input[1] = new Tone.Signal(value); //connect this._neg.connect(this._gt); - this._value.connect(this._rhNeg); + this._param.connect(this._rhNeg); this._rhNeg.connect(this._gt, 0, 1); }; @@ -69,8 +69,8 @@ function(Tone){ this._gt = null; this._rhNeg.dispose(); this._rhNeg = null; - this._value.dispose(); - this._value = null; + this._param.dispose(); + this._param = null; return this; }; diff --git a/Tone/signal/Max.js b/Tone/signal/Max.js index 1570ea4c0..29470027d 100644 --- a/Tone/signal/Max.js +++ b/Tone/signal/Max.js @@ -34,7 +34,7 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/IfThenElse", " * @type {Tone.Signal} * @private */ - this._value = this.input[1] = new Tone.Signal(max); + this._param = this.input[1] = new Tone.Signal(max); /** * @type {Tone.Select} @@ -51,8 +51,8 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/IfThenElse", " //connections this.input[0].chain(this._gt, this._ifThenElse.if); this.input[0].connect(this._ifThenElse.then); - this._value.connect(this._ifThenElse.else); - this._value.connect(this._gt, 0, 1); + this._param.connect(this._ifThenElse.else); + this._param.connect(this._gt, 0, 1); }; Tone.extend(Tone.Max, Tone.Signal); @@ -63,10 +63,10 @@ define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/IfThenElse", " */ Tone.Max.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._value.dispose(); + this._param.dispose(); this._ifThenElse.dispose(); this._gt.dispose(); - this._value = null; + this._param = null; this._ifThenElse = null; this._gt = null; return this; diff --git a/Tone/signal/Min.js b/Tone/signal/Min.js index cf72c80a9..e233086d5 100644 --- a/Tone/signal/Min.js +++ b/Tone/signal/Min.js @@ -45,13 +45,13 @@ define(["Tone/core/Tone", "Tone/signal/LessThan", "Tone/signal/IfThenElse", "Ton * @type {Tone.Signal} * @private */ - this._value = this.input[1] = new Tone.Signal(min); + 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._value.connect(this._ifThenElse.else); - this._value.connect(this._lt, 0, 1); + this._param.connect(this._ifThenElse.else); + this._param.connect(this._lt, 0, 1); }; Tone.extend(Tone.Min, Tone.Signal); @@ -62,10 +62,10 @@ define(["Tone/core/Tone", "Tone/signal/LessThan", "Tone/signal/IfThenElse", "Ton */ Tone.Min.prototype.dispose = function(){ Tone.prototype.dispose.call(this); - this._value.dispose(); + this._param.dispose(); this._ifThenElse.dispose(); this._lt.dispose(); - this._value = null; + this._param = null; this._ifThenElse = null; this._lt = null; return this; diff --git a/Tone/signal/Multiply.js b/Tone/signal/Multiply.js index 3fcda6f9b..420b35592 100644 --- a/Tone/signal/Multiply.js +++ b/Tone/signal/Multiply.js @@ -40,9 +40,9 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ * @type {AudioParam} * @private */ - this._value = this.input[1] = this.output.gain; + this._param = this.input[1] = this.output.gain; - this._value.value = this.defaultArg(value, 0); + this._param.value = this.defaultArg(value, 0); }; Tone.extend(Tone.Multiply, Tone.Signal); @@ -55,7 +55,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ Tone.prototype.dispose.call(this); this._mult.disconnect(); this._mult = null; - this._value = null; + this._param = null; return this; }; diff --git a/Tone/signal/Pow.js b/Tone/signal/Pow.js index 250e781a8..4502401ee 100644 --- a/Tone/signal/Pow.js +++ b/Tone/signal/Pow.js @@ -8,7 +8,7 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * * @extends {Tone.SignalBase} * @constructor - * @param {number} exp The exponent to apply to the incoming signal, must be at least 2. + * @param {Positive} exp The exponent to apply to the incoming signal, must be at least 2. * @example * var pow = new Tone.Pow(2); * var sig = new Tone.Signal(0.5).connect(pow); diff --git a/Tone/signal/Signal.js b/Tone/signal/Signal.js index 6dbc1eb55..c0ab1e590 100644 --- a/Tone/signal/Signal.js +++ b/Tone/signal/Signal.js @@ -1,4 +1,4 @@ -define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ +define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/core/Type", "Tone/core/Param", "Tone/core/Gain"], function(Tone){ "use strict"; @@ -8,10 +8,10 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * has all of the methods available to native Web Audio * [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface) * as well as additional conveniences. Read more about working with signals - * [here](https://github.com/TONEnoTONE/Tone.js/wiki/Signals). + * [here](https://github.com/Tonejs/Tone.js/wiki/Signals). * * @constructor - * @extends {Tone.SignalBase} + * @extends {Tone.Param} * @param {Number|AudioParam} [value] Initial value of the signal. If an AudioParam * is passed in, that parameter will be wrapped * and controlled by the Signal. @@ -23,61 +23,28 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ var options = this.optionsObject(arguments, ["value", "units"], Tone.Signal.defaults); - /** - * The units of the signal. - * @type {string} - */ - this.units = options.units; - - /** - * When true, converts the set value - * based on the units given. When false, - * applies no conversion and the units - * are merely used as a label. - * @type {boolean} - */ - this.convert = options.convert; - - /** - * True if the signal value is being overridden by - * a connected signal. - * @readOnly - * @type {boolean} - * @private - */ - this.overridden = false; - /** * The node where the constant signal value is scaled. * @type {GainNode} * @private */ - this.output = this._scaler = this.context.createGain(); + this.output = this._gain = this.context.createGain(); + + options.param = this._gain.gain; + Tone.Param.call(this, options); /** * The node where the value is set. - * @type {AudioParam} + * @type {Tone.Param} * @private */ - this.input = this._value = this._scaler.gain; + this.input = this._param = this._gain.gain; - if (options.value instanceof AudioParam){ - this._scaler.connect(options.value); - //zero out the value - options.value.value = 0; - } else { - if (!this.isUndef(options.param)){ - this._scaler.connect(options.param); - options.param.value = 0; - } - this.value = options.value; - } - - //connect the constant 1 output to the node output - Tone.Signal._constant.chain(this._scaler); + //connect the const output to the node output + Tone.Signal._constant.chain(this._gain); }; - Tone.extend(Tone.Signal, Tone.SignalBase); + Tone.extend(Tone.Signal, Tone.Param); /** * The default values @@ -87,256 +54,33 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ */ Tone.Signal.defaults = { "value" : 0, - "param" : undefined, "units" : Tone.Type.Default, "convert" : true, }; /** - * The current value of the signal. - * @memberOf Tone.Signal# - * @type {Number} - * @name value - */ - Object.defineProperty(Tone.Signal.prototype, "value", { - get : function(){ - return this._toUnits(this._value.value); - }, - set : function(value){ - var convertedVal = this._fromUnits(value); - //is this what you want? - this.cancelScheduledValues(0); - this._value.value = convertedVal; - } - }); - - /** - * @private - * @param {*} val the value to convert - * @return {number} the number which the value should be set to - */ - Tone.Signal.prototype._fromUnits = function(val){ - if (this.convert || this.isUndef(this.convert)){ - switch(this.units){ - case Tone.Type.Time: - return this.toSeconds(val); - case Tone.Type.Frequency: - return this.toFrequency(val); - case Tone.Type.Decibels: - return this.dbToGain(val); - case Tone.Type.NormalRange: - return Math.min(Math.max(val, 0), 1); - case Tone.Type.AudioRange: - return Math.min(Math.max(val, -1), 1); - case Tone.Type.Positive: - return Math.max(val, 0); - default: - return val; - } - } else { - return val; - } - }; - - /** - * convert to the desired units - * @private - * @param {number} val the value to convert - * @return {number} - */ - Tone.Signal.prototype._toUnits = function(val){ - if (this.convert || this.isUndef(this.convert)){ - switch(this.units){ - case Tone.Type.Decibels: - return this.gainToDb(val); - default: - return val; - } - } else { - return val; - } - }; - - /** - * Schedules a parameter value change at the given time. - * @param {*} value The value to set the signal. - * @param {Time} time The time when the change should occur. - * @returns {Tone.Signal} this - * @example - * //set the frequency to "G4" in exactly 1 second from now. - * freq.setValueAtTime("G4", "+1"); - */ - Tone.Signal.prototype.setValueAtTime = function(value, time){ - value = this._fromUnits(value); - this._value.setValueAtTime(value, this.toSeconds(time)); - return this; - }; - - /** - * Creates a schedule point with the current value at the current time. - * This is useful for creating an automation anchor point in order to - * schedule changes from the current value. + * When signals connect to other signals or AudioParams, + * they take over the output value of that signal or AudioParam. + * For all other nodes, the behavior is the same as a default connect. * - * @param {number=} now (Optionally) pass the now value in. - * @returns {Tone.Signal} this - */ - Tone.Signal.prototype.setCurrentValueNow = function(now){ - now = this.defaultArg(now, this.now()); - var currentVal = this._value.value; - this.cancelScheduledValues(now); - this._value.setValueAtTime(currentVal, now); - return this; - }; - - /** - * Schedules a linear continuous change in parameter value from the - * previous scheduled parameter value to the given value. - * - * @param {number} value - * @param {Time} endTime - * @returns {Tone.Signal} this - */ - Tone.Signal.prototype.linearRampToValueAtTime = function(value, endTime){ - value = this._fromUnits(value); - this._value.linearRampToValueAtTime(value, this.toSeconds(endTime)); - return this; - }; - - /** - * Schedules an exponential continuous change in parameter value from - * the previous scheduled parameter value to the given value. - * - * @param {number} value - * @param {Time} endTime - * @returns {Tone.Signal} this + * @override + * @param {AudioParam|AudioNode|Tone.Signal|Tone} node + * @param {number} [outputNumber=0] The output number to connect from. + * @param {number} [inputNumber=0] The input number to connect to. + * @returns {Tone.SignalBase} this + * @method */ - Tone.Signal.prototype.exponentialRampToValueAtTime = function(value, endTime){ - value = this._fromUnits(value); - value = Math.max(0.00001, value); - this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); - return this; - }; - - /** - * Schedules an exponential continuous change in parameter value from - * the current time and current value to the given value. - * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //exponentially ramp to the value 2 over 4 seconds. - * signal.exponentialRampToValueNow(2, 4); - */ - Tone.Signal.prototype.exponentialRampToValueNow = function(value, rampTime){ - var now = this.now(); - // exponentialRampToValueAt cannot ever ramp from 0, apparently. - // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 - var currentVal = this.value; - this.setValueAtTime(Math.max(currentVal, 0.0001), now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); - return this; - }; - - /** - * Schedules an linear continuous change in parameter value from - * the current time and current value to the given value at the given time. - * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //linearly ramp to the value 4 over 3 seconds. - * signal.linearRampToValueNow(4, 3); - */ - Tone.Signal.prototype.linearRampToValueNow = function(value, rampTime){ - var now = this.now(); - this.setCurrentValueNow(now); - this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); - return this; - }; - - /** - * Start exponentially approaching the target value at the given time with - * a rate having the given time constant. - * @param {number} value - * @param {Time} startTime - * @param {number} timeConstant - * @returns {Tone.Signal} this - */ - Tone.Signal.prototype.setTargetAtTime = function(value, startTime, timeConstant){ - value = this._fromUnits(value); - // The value will never be able to approach without timeConstant > 0. - // http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation - // is described. 0 results in a division by 0. - timeConstant = Math.max(0.00001, timeConstant); - this._value.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); - return this; - }; - - /** - * Sets an array of arbitrary parameter values starting at the given time - * for the given duration. - * - * @param {Array} values - * @param {Time} startTime - * @param {Time} duration - * @returns {Tone.Signal} this - */ - Tone.Signal.prototype.setValueCurveAtTime = function(values, startTime, duration){ - for (var i = 0; i < values.length; i++){ - values[i] = this._fromUnits(values[i]); - } - this._value.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); - return this; - }; - - /** - * Cancels all scheduled parameter changes with times greater than or - * equal to startTime. - * - * @param {Time} startTime - * @returns {Tone.Signal} this - */ - Tone.Signal.prototype.cancelScheduledValues = function(startTime){ - this._value.cancelScheduledValues(this.toSeconds(startTime)); - return this; - }; - - /** - * Ramps to the given value over the duration of the rampTime. - * Automatically selects the best ramp type (exponential or linear) - * depending on the `units` of the signal - * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //ramp to the value either linearly or exponentially - * //depending on the "units" value of the signal - * signal.rampTo(0, 10); - */ - Tone.Signal.prototype.rampTo = function(value, rampTime){ - rampTime = this.defaultArg(rampTime, 0); - if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM){ - this.exponentialRampToValueNow(value, rampTime); - } else { - this.linearRampToValueNow(value, rampTime); - } - return this; - }; + Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect; /** * dispose and disconnect * @returns {Tone.Signal} this */ Tone.Signal.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); - this._value = null; - this._scaler = null; + Tone.Param.prototype.dispose.call(this); + this._param = null; + this._gain.disconnect(); + this._gain = null; return this; }; @@ -345,21 +89,11 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ /////////////////////////////////////////////////////////////////////////// /** - * the constant signal generator - * @static - * @private - * @const - * @type {OscillatorNode} - */ - Tone.Signal._generator = null; - - /** - * the signal generator waveshaper. makes the incoming signal - * only output 1 for all inputs. + * Generates a constant output of 1. * @static * @private * @const - * @type {Tone.WaveShaper} + * @type {AudioBufferSourceNode} */ Tone.Signal._constant = null; @@ -367,11 +101,18 @@ define(["Tone/core/Tone", "Tone/signal/WaveShaper"], function(Tone){ * initializer function */ Tone._initAudioContext(function(audioContext){ - Tone.Signal._generator = audioContext.createOscillator(); - Tone.Signal._constant = new Tone.WaveShaper([1,1]); - Tone.Signal._generator.connect(Tone.Signal._constant); - Tone.Signal._generator.start(0); - Tone.Signal._generator.noGC(); + var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate); + var arr = buffer.getChannelData(0); + for (var i = 0; i < arr.length; i++){ + arr[i] = 1; + } + Tone.Signal._constant = audioContext.createBufferSource(); + Tone.Signal._constant.channelCount = 1; + Tone.Signal._constant.channelCountMode = "explicit"; + Tone.Signal._constant.buffer = buffer; + Tone.Signal._constant.loop = true; + Tone.Signal._constant.start(0); + Tone.Signal._constant.noGC(); }); return Tone.Signal; diff --git a/Tone/signal/SignalBase.js b/Tone/signal/SignalBase.js index ed670d6ae..d82a50d1f 100644 --- a/Tone/signal/SignalBase.js +++ b/Tone/signal/SignalBase.js @@ -25,11 +25,13 @@ define(["Tone/core/Tone"], function(Tone){ */ Tone.SignalBase.prototype.connect = function(node, outputNumber, inputNumber){ //zero it out so that the signal can have full control - if (node.constructor === Tone.Signal){ + if ((Tone.Signal && Tone.Signal === node.constructor) || + (Tone.Param && Tone.Param === node.constructor) || + (Tone.TimelineSignal && Tone.TimelineSignal === node.constructor)){ //cancel changes - node._value.cancelScheduledValues(0); + node._param.cancelScheduledValues(0); //reset the value - node._value.value = 0; + node._param.value = 0; //mark the value as overridden node.overridden = true; } else if (node instanceof AudioParam){ diff --git a/Tone/signal/Subtract.js b/Tone/signal/Subtract.js index 21aaa8e9c..102da1ede 100644 --- a/Tone/signal/Subtract.js +++ b/Tone/signal/Subtract.js @@ -47,9 +47,9 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Negate", "Tone/signal/ * @private * @type {Tone.Signal} */ - this._value = this.input[1] = new Tone.Signal(value); + this._param = this.input[1] = new Tone.Signal(value); - this._value.chain(this._neg, this._sum); + this._param.chain(this._neg, this._sum); }; Tone.extend(Tone.Subtract, Tone.Signal); @@ -64,8 +64,8 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Negate", "Tone/signal/ this._neg = null; this._sum.disconnect(); this._sum = null; - this._value.dispose(); - this._value = null; + this._param.dispose(); + this._param = null; return this; }; diff --git a/Tone/signal/TimelineSignal.js b/Tone/signal/TimelineSignal.js new file mode 100644 index 000000000..593179dfd --- /dev/null +++ b/Tone/signal/TimelineSignal.js @@ -0,0 +1,341 @@ +define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function (Tone) { + + "use strict"; + + /** + * @class A signal which adds the method getValueAtTime. + * Code and inspiration from https://github.com/jsantell/web-audio-automation-timeline + * @extends {Tone.Param} + * @param {Number=} value The initial value of the signal + * @param {String=} units The conversion units of the signal. + */ + 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} + * @private + */ + this._events = new Tone.Timeline(10); + + /** + * The initial scheduled value + * @type {Number} + * @private + */ + this._initial = this._fromUnits(this._param.value); + }; + + Tone.extend(Tone.TimelineSignal, Tone.Param); + + /** + * The event types of a schedulable signal. + * @enum {String} + */ + Tone.TimelineSignal.Type = { + Linear : "linear", + Exponential : "exponential", + Target : "target", + Set : "set" + }; + + /** + * The current value of the signal. + * @memberOf Tone.TimelineSignal# + * @type {Number} + * @name value + */ + Object.defineProperty(Tone.TimelineSignal.prototype, "value", { + get : function(){ + return this._toUnits(this._param.value); + }, + set : function(value){ + var convertedVal = this._fromUnits(value); + this._initial = convertedVal; + this._param.value = convertedVal; + } + }); + + /////////////////////////////////////////////////////////////////////////// + // SCHEDULING + /////////////////////////////////////////////////////////////////////////// + + /** + * Schedules a parameter value change at the given time. + * @param {*} value The value to set the signal. + * @param {Time} time The time when the change should occur. + * @returns {Tone.TimelineSignal} this + * @example + * //set the frequency to "G4" in exactly 1 second from now. + * freq.setValueAtTime("G4", "+1"); + */ + Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) { + value = this._fromUnits(value); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + "type" : Tone.TimelineSignal.Type.Set, + "value" : value, + "time" : startTime + }); + //invoke the original event + this._param.setValueAtTime(value, startTime); + return this; + }; + + /** + * Schedules a linear continuous change in parameter value from the + * previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + "type" : Tone.TimelineSignal.Type.Linear, + "value" : value, + "time" : endTime + }); + this._param.linearRampToValueAtTime(value, endTime); + return this; + }; + + /** + * Schedules an exponential continuous change in parameter value from + * the previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + "type" : Tone.TimelineSignal.Type.Exponential, + "value" : value, + "time" : endTime + }); + this._param.exponentialRampToValueAtTime(value, endTime); + return this; + }; + + /** + * Start exponentially approaching the target value at the given time with + * a rate having the given time constant. + * @param {number} value + * @param {Time} startTime + * @param {number} timeConstant + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + "type" : Tone.TimelineSignal.Type.Target, + "value" : value, + "time" : startTime, + "constant" : timeConstant + }); + this._param.setTargetAtTime(value, startTime, timeConstant); + return this; + }; + + /** + * Cancels all scheduled parameter changes with times greater than or + * equal to startTime. + * + * @param {Time} startTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) { + this._events.cancel(after); + this._param.cancelScheduledValues(this.toSeconds(after)); + return this; + }; + + /** + * Sets the computed value at the given time. This provides + * a point from which a linear or exponential curve + * can be scheduled after. Will cancel events after + * the given time and shorten the currently scheduled + * linear or exponential ramp so that it ends at `time` . + * This is to avoid discontinuities and clicks in envelopes. + * @param {Time} time When to set the ramp point + * @returns {Tone.TimelineSignal} this + */ + 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) + 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; + }; + + /** + * Do a linear ramp to the given value between the start and finish times. + * @param {Number} value The value to ramp to. + * @param {Time} start The beginning anchor point to do the linear ramp + * @param {Time} finish The ending anchor point by which the value of + * the signal will equal the given value. + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.linearRampToValueAtTime(value, finish); + return this; + }; + + /** + * Do a exponential ramp to the given value between the start and finish times. + * @param {Number} value The value to ramp to. + * @param {Time} start The beginning anchor point to do the exponential ramp + * @param {Time} finish The ending anchor point by which the value of + * the signal will equal the given value. + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.exponentialRampToValueAtTime(value, finish); + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // GETTING SCHEDULED VALUES + /////////////////////////////////////////////////////////////////////////// + + /** + * Returns the value before or equal to the given time + * @param {Number} time The time to query + * @return {Object} The event at or before the given time. + * @private + */ + Tone.TimelineSignal.prototype._searchBefore = function(time){ + return this._events.getEvent(time); + }; + + /** + * The event after the given time + * @param {Number} time The time to query. + * @return {Object} The next event after the given time + * @private + */ + Tone.TimelineSignal.prototype._searchAfter = function(time){ + return this._events.getEventAfter(time); + }; + + /** + * 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.TimelineSignal.prototype.getValueAtTime = function(time){ + var after = this._searchAfter(time); + var before = this._searchBefore(time); + var value = this._initial; + //if it was set by + if (before === null){ + value = this._initial; + } else if (before.type === Tone.TimelineSignal.Type.Target){ + var previous = this._events.getEventBefore(before.time); + var previouVal; + if (previous === null){ + previouVal = this._initial; + } else { + previouVal = previous.value; + } + value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (after === null){ + value = before.value; + } else if (after.type === Tone.TimelineSignal.Type.Linear){ + value = this._linearInterpolate(before.time, before.value, after.time, after.value, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential){ + value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time); + } else { + value = before.value; + } + return value; + }; + + /** + * When signals connect to other signals or AudioParams, + * they take over the output value of that signal or AudioParam. + * For all other nodes, the behavior is the same as a default connect. + * + * @override + * @param {AudioParam|AudioNode|Tone.Signal|Tone} node + * @param {number} [outputNumber=0] The output number to connect from. + * @param {number} [inputNumber=0] The input number to connect to. + * @returns {Tone.TimelineSignal} this + * @method + */ + Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect; + + + /////////////////////////////////////////////////////////////////////////// + // AUTOMATION CURVE CALCULATIONS + // MIT License, copyright (c) 2014 Jordan Santell + /////////////////////////////////////////////////////////////////////////// + + /** + * Calculates the the value along the curve produced by setTargetAtTime + * @private + */ + Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + + /** + * Calculates the the value along the curve produced by linearRampToValueAtTime + * @private + */ + Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + + /** + * Calculates the the value along the curve produced by exponentialRampToValueAtTime + * @private + */ + Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { + v0 = Math.max(this._minOutput, v0); + return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); + }; + + /** + * Clean up. + * @return {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.dispose = function(){ + Tone.Signal.prototype.dispose.call(this); + Tone.Param.prototype.dispose.call(this); + this._events.dispose(); + this._events = null; + }; + + return Tone.TimelineSignal; +}); \ No newline at end of file diff --git a/Tone/signal/WaveShaper.js b/Tone/signal/WaveShaper.js index 7c7cb71ee..779faee75 100644 --- a/Tone/signal/WaveShaper.js +++ b/Tone/signal/WaveShaper.js @@ -90,11 +90,6 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){ return this._shaper.curve; }, set : function(mapping){ - //fixes safari WaveShaperNode bug - if (this._isSafari()){ - var first = mapping[0]; - mapping.unshift(first); - } this._curve = new Float32Array(mapping); this._shaper.curve = this._curve; } @@ -112,20 +107,14 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){ return this._shaper.oversample; }, set : function(oversampling){ - this._shaper.oversample = oversampling; + if (["none", "2x", "4x"].indexOf(oversampling) !== -1){ + this._shaper.oversample = oversampling; + } else { + throw new Error("invalid oversampling: "+oversampling); + } } }); - /** - * returns true if the browser is safari - * @return {boolean} - * @private - */ - Tone.WaveShaper.prototype._isSafari = function(){ - var ua = navigator.userAgent.toLowerCase(); - return ua.indexOf("safari") !== -1 && ua.indexOf("chrome") === -1; - }; - /** * Clean up. * @returns {Tone.WaveShaper} this diff --git a/Tone/source/ExternalInput.js b/Tone/source/ExternalInput.js new file mode 100644 index 000000000..36b27d92d --- /dev/null +++ b/Tone/source/ExternalInput.js @@ -0,0 +1,268 @@ +define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone){ + + "use strict"; + + //polyfill for getUserMedia + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || navigator.msGetUserMedia; + + /** + * @class Tone.ExternalInput is a WebRTC Audio Input. Check + * [Media Stream API Support](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_API) + * to see which browsers are supported. As of + * writing this, Chrome, Firefox, and Opera + * support Media Stream. Chrome allows enumeration + * of the sources, and access to device name over a + * secure (HTTPS) connection. See [https://simpl.info](https://simpl.info/getusermedia/sources/index.html) + * vs [http://simple.info](https://simpl.info/getusermedia/sources/index.html) + * on a Chrome browser for the difference. + * + * @constructor + * @extends {Tone.Source} + * @param {number} [inputNum=0] If multiple inputs are present, select the input number. Chrome only. + * @example + * //select the third input + * var motu = new Tone.ExternalInput(3); + * + * //opening the input asks the user to activate their mic + * motu.open(function(){ + * //opening is activates the microphone + * //starting lets audio through + * motu.start(10); + * }); + */ + + Tone.ExternalInput = function(){ + + var options = this.optionsObject(arguments, ["inputNum"], Tone.ExternalInput.defaults); + Tone.Source.call(this, options); + + /** + * The MediaStreamNode + * @type {MediaStreamAudioSourceNode} + * @private + */ + this._mediaStream = null; + + /** + * The media stream created by getUserMedia. + * @type {LocalMediaStream} + * @private + */ + this._stream = null; + + /** + * The constraints argument for getUserMedia + * @type {Object} + * @private + */ + this._constraints = {"audio" : true}; + + /** + * The input source position in Tone.ExternalInput.sources. + * Set before ExternalInput.open(). + * @type {Number} + * @private + */ + this._inputNum = options.inputNum; + + /** + * Gates the input signal for start/stop. + * Initially closed. + * @type {GainNode} + * @private + */ + this._gate = new Tone.Gain(0).connect(this.output); + }; + + Tone.extend(Tone.ExternalInput, Tone.Source); + + /** + * the default parameters + * @type {Object} + */ + Tone.ExternalInput.defaults = { + "inputNum" : 0 + }; + + /** + * wrapper for getUserMedia function + * @param {function} callback + * @private + */ + Tone.ExternalInput.prototype._getUserMedia = function(callback){ + if (!Tone.ExternalInput.supported){ + throw new Error("browser does not support 'getUserMedia'"); + } + if (Tone.ExternalInput.sources[this._inputNum]){ + this._constraints = { + audio : { + optional : [{sourceId: Tone.ExternalInput.sources[this._inputNum].id}] + } + }; + } + navigator.getUserMedia(this._constraints, function(stream){ + this._onStream(stream); + callback(); + }.bind(this), function(err){ + callback(err); + }); + }; + + /** + * called when the stream is successfully setup + * @param {LocalMediaStream} stream + * @private + */ + Tone.ExternalInput.prototype._onStream = function(stream){ + if (!this.isFunction(this.context.createMediaStreamSource)){ + throw new Error("browser does not support the 'MediaStreamSourceNode'"); + } + //can only start a new source if the previous one is closed + if (!this._stream){ + this._stream = stream; + //Wrap a MediaStreamSourceNode around the live input stream. + this._mediaStream = this.context.createMediaStreamSource(stream); + //Connect the MediaStreamSourceNode to a gate gain node + this._mediaStream.connect(this._gate); + } + }; + + /** + * Open the media stream + * @param {function=} callback The callback function to + * execute when the stream is open + * @return {Tone.ExternalInput} this + */ + Tone.ExternalInput.prototype.open = function(callback){ + callback = this.defaultArg(callback, Tone.noOp); + Tone.ExternalInput.getSources(function(){ + this._getUserMedia(callback); + }.bind(this)); + return this; + }; + + /** + * Close the media stream + * @return {Tone.ExternalInput} this + */ + Tone.ExternalInput.prototype.close = function(){ + if(this._stream){ + var track = this._stream.getTracks()[this._inputNum]; + if (!this.isUndef(track)){ + track.stop(); + } + this._stream = null; + } + return this; + }; + + /** + * Start the stream + * @private + */ + Tone.ExternalInput.prototype._start = function(time){ + time = this.toSeconds(time); + this._gate.gain.setValueAtTime(1, time); + return this; + }; + + /** + * Stops the stream. + * @private + */ + Tone.ExternalInput.prototype._stop = function(time){ + time = this.toSeconds(time); + this._gate.gain.setValueAtTime(0, time); + return this; + }; + + /** + * Clean up. + * @return {Tone.ExternalInput} this + */ + Tone.ExternalInput.prototype.dispose = function(){ + Tone.Source.prototype.dispose.call(this); + this.close(); + if (this._mediaStream){ + this._mediaStream.disconnect(); + this._mediaStream = null; + } + this._constraints = null; + this._gate.dispose(); + this._gate = null; + return this; + }; + + /////////////////////////////////////////////////////////////////////////// + // STATIC METHODS + /////////////////////////////////////////////////////////////////////////// + + /** + * The array of available sources, different depending on whether connection is secure + * @type {Array} + * @static + */ + Tone.ExternalInput.sources = []; + + /** + * indicates whether browser supports MediaStreamTrack.getSources (i.e. Chrome vs Firefox) + * @type {Boolean} + * @private + */ + Tone.ExternalInput._canGetSources = !Tone.prototype.isUndef(window.MediaStreamTrack) && Tone.prototype.isFunction(MediaStreamTrack.getSources); + + /** + * If getUserMedia is supported by the browser. + * @type {Boolean} + * @memberOf Tone.ExternalInput# + * @name supported + * @static + * @readOnly + */ + Object.defineProperty(Tone.ExternalInput, "supported", { + get : function(){ + return Tone.prototype.isFunction(navigator.getUserMedia); + } + }); + + /** + * Populates the source list. Invokes the callback with an array of + * possible audio sources. + * @param {function=} callback Callback to be executed after populating list + * @return {Tone.ExternalInput} this + * @static + * @example + * var soundflower = new Tone.ExternalInput(); + * Tone.ExternalInput.getSources(selectSoundflower); + * + * function selectSoundflower(sources){ + * for(var i = 0; i < sources.length; i++){ + * if(sources[i].label === "soundflower"){ + * soundflower.inputNum = i; + * soundflower.open(function(){ + * soundflower.start(); + * }); + * break; + * } + * } + * }; + */ + Tone.ExternalInput.getSources = function(callback){ + if(Tone.ExternalInput.sources.length === 0 && Tone.ExternalInput._canGetSources){ + MediaStreamTrack.getSources(function (media_sources){ + for(var i = 0; i < media_sources.length; i++) { + if(media_sources[i].kind === "audio"){ + Tone.ExternalInput.sources[i] = media_sources[i]; + } + } + callback(Tone.ExternalInput.sources); + }); + } else { + callback(Tone.ExternalInput.sources); + } + return this; + }; + + return Tone.ExternalInput; +}); \ No newline at end of file diff --git a/Tone/source/Microphone.js b/Tone/source/Microphone.js index 17ca3b6b3..289756bb1 100644 --- a/Tone/source/Microphone.js +++ b/Tone/source/Microphone.js @@ -1,111 +1,43 @@ -define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ +define(["Tone/core/Tone", "Tone/source/ExternalInput"], function(Tone){ "use strict"; /** - * @class Tone.Microphone is a WebRTC Microphone. Check - * [Media Stream API Support](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_API) - * to see which browsers are supported. + * @class Opens up the default source (typically the microphone). * * @constructor - * @extends {Tone.Source} - * @param {number} [inputNum=0] If multiple inputs are present, select the input number. + * @extends {Tone.ExternalInput} * @example - * //mic will feedback if played through master - * var mic = new Tone.Microphone(); - * mic.start(); + * //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.Microphone = function(inputNum){ - Tone.Source.call(this); + Tone.Microphone = function(){ - /** - * @type {MediaStreamAudioSourceNode} - * @private - */ - this._mediaStream = null; - - /** - * @type {LocalMediaStream} - * @private - */ - this._stream = null; - - /** - * @type {Object} - * @private - */ - this._constraints = {"audio" : true}; + Tone.ExternalInput.call(this, 0); - //get the option - var self = this; - MediaStreamTrack.getSources(function (media_sources) { - if (inputNum < media_sources.length){ - self.constraints.audio = { - optional : [{ sourceId: media_sources[inputNum].id}] - }; - } - }); }; - Tone.extend(Tone.Microphone, Tone.Source); + Tone.extend(Tone.Microphone, Tone.ExternalInput); /** - * start the stream. - * @private + * If getUserMedia is supported by the browser. + * @type {Boolean} + * @memberOf Tone.Microphone# + * @name supported + * @static + * @readOnly */ - Tone.Microphone.prototype._start = function(){ - navigator.getUserMedia(this._constraints, - this._onStream.bind(this), this._onStreamError.bind(this)); - }; - - /** - * stop the stream. - * @private - */ - Tone.Microphone.prototype._stop = function(){ - this._stream.stop(); - return this; - }; - - /** - * called when the stream is successfully setup - * @param {LocalMediaStream} stream - * @private - */ - Tone.Microphone.prototype._onStream = function(stream) { - this._stream = stream; - // Wrap a MediaStreamSourceNode around the live input stream. - this._mediaStream = this.context.createMediaStreamSource(stream); - this._mediaStream.connect(this.output); - }; - - /** - * called on error - * @param {Error} e - * @private - */ - Tone.Microphone.prototype._onStreamError = function(e) { - console.error(e); - }; - - /** - * Clean up. - * @return {Tone.Microphone} this - */ - Tone.Microphone.prototype.dispose = function() { - Tone.Source.prototype.dispose.call(this); - if (this._mediaStream){ - this._mediaStream.disconnect(); - this._mediaStream = null; + Object.defineProperty(Tone.Microphone, "supported", { + get : function(){ + return Tone.ExternalInput.supported; } - this._stream = null; - this._constraints = null; - return this; - }; - - //polyfill - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || navigator.msGetUserMedia; + }); return Tone.Microphone; }); \ No newline at end of file diff --git a/Tone/source/Noise.js b/Tone/source/Noise.js index cd1e9c3e1..94d308ad4 100644 --- a/Tone/source/Noise.js +++ b/Tone/source/Noise.js @@ -44,6 +44,14 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ */ this._buffer = null; + /** + * The playback rate of the noise. Affects + * the "frequency" of the noise. + * @type {Positive} + * @signal + */ + this._playbackRate = options.playbackRate; + this.type = options.type; }; @@ -58,6 +66,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ */ Tone.Noise.defaults = { "type" : "white", + "playbackRate" : 1 }; /** @@ -91,13 +100,12 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ this._buffer = _brownNoise; break; default : - this._buffer = _whiteNoise; + throw new Error("invalid noise type: "+type) } //if it's playing, stop and restart it if (this.state === Tone.State.Started){ - var now = this.now() + this.bufferTime; + var now = this.now() + this.blockTime; //remove the listener - this._source.onended = undefined; this._stop(now); this._start(now); } @@ -105,6 +113,24 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ } }); + /** + * The playback rate of the noise. Affects + * the "frequency" of the noise. + * @type {Positive} + * @signal + */ + Object.defineProperty(Tone.Noise.prototype, "playbackRate", { + get : function(){ + return this._playbackRate; + }, + set : function(rate){ + this._playbackRate = rate; + if (this._source) { + this._source.playbackRate.value = rate; + } + } + }); + /** * internal start method * @@ -115,9 +141,9 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){ this._source = this.context.createBufferSource(); this._source.buffer = this._buffer; this._source.loop = true; - this.connectSeries(this._source, this.output); + this._source.playbackRate.value = this._playbackRate; + this._source.connect(this.output); this._source.start(this.toSeconds(time)); - this._source.onended = this.onended; }; /** diff --git a/Tone/source/OmniOscillator.js b/Tone/source/OmniOscillator.js index 2408c2f4e..c118a2782 100644 --- a/Tone/source/OmniOscillator.js +++ b/Tone/source/OmniOscillator.js @@ -54,7 +54,11 @@ function(Tone){ //set the oscillator this.type = options.type; + this.phase = options.phase; this._readOnly(["frequency", "detune"]); + if (this.isArray(options.partials)){ + this.partials = options.partials; + } }; Tone.extend(Tone.OmniOscillator, Tone.Oscillator); @@ -69,6 +73,7 @@ function(Tone){ "frequency" : 440, "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", }; @@ -113,7 +118,7 @@ function(Tone){ }, set : function(type){ if (type.indexOf("sine") === 0 || type.indexOf("square") === 0 || - type.indexOf("triangle") === 0 || type.indexOf("sawtooth") === 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); @@ -130,8 +135,33 @@ function(Tone){ this._createNewOscillator(Tone.PulseOscillator); } } else { - throw new TypeError("Tone.OmniOscillator does not support type "+type); + throw new Error("Tone.OmniOscillator does not support type "+type); + } + } + }); + + /** + * The partials of the 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.OmniOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.OmniOscillator.prototype, "partials", { + get : function(){ + return this._oscillator.partials; + }, + set : function(partials){ + if (this._sourceType !== OmniOscType.Oscillator){ + this.type = Tone.Oscillator.Type.Custom; } + this._oscillator.partials = partials; } }); @@ -141,14 +171,15 @@ function(Tone){ */ Tone.OmniOscillator.prototype._createNewOscillator = function(OscillatorConstructor){ //short delay to avoid clicks on the change - var now = this.now() + this.bufferTime; + var now = this.now() + this.blockTime; if (this._oscillator !== null){ var oldOsc = this._oscillator; oldOsc.stop(now); - oldOsc.onended = function(){ + //dispose the old one + setTimeout(function(){ oldOsc.dispose(); oldOsc = null; - }; + }, this.blockTime * 1000); } this._oscillator = new OscillatorConstructor(); this.frequency.connect(this._oscillator.frequency); diff --git a/Tone/source/Oscillator.js b/Tone/source/Oscillator.js index c9adb56ae..2f8203704 100644 --- a/Tone/source/Oscillator.js +++ b/Tone/source/Oscillator.js @@ -49,6 +49,13 @@ function(Tone){ */ this._wave = null; + /** + * The partials of the oscillator + * @type {Array} + * @private + */ + this._partials = this.defaultArg(options.partials, [1]); + /** * the phase of the oscillator * between 0 - 360 @@ -80,7 +87,20 @@ function(Tone){ "type" : "sine", "frequency" : 440, "detune" : 0, - "phase" : 0 + "phase" : 0, + "partials" : [] + }; + + /** + * The Oscillator types + * @enum {String} + */ + Tone.Oscillator.Type = { + Sine : "sine", + Triangle : "triangle", + Sawtooth : "sawtooth", + Square : "square", + Custom : "custom" }; /** @@ -166,62 +186,139 @@ function(Tone){ return this._type; }, set : function(type){ + var coefs = this._getRealImaginary(type, this._phase); + var periodicWave = this.context.createPeriodicWave(coefs[0], coefs[1]); + this._wave = periodicWave; + if (this._oscillator !== null){ + this._oscillator.setPeriodicWave(this._wave); + } + this._type = type; + } + }); - var originalType = type; - - var fftSize = 4096; - var periodicWaveSize = fftSize / 2; + /** + * Returns the real and imaginary components based + * on the oscillator type. + * @returns {Array} [real, imaginary] + * @private + */ + Tone.Oscillator.prototype._getRealImaginary = function(type, phase){ + var fftSize = 4096; + var periodicWaveSize = fftSize / 2; - var real = new Float32Array(periodicWaveSize); - var imag = new Float32Array(periodicWaveSize); - - var partialCount = 1; - var partial = /(sine|triangle|square|sawtooth)(\d+)$/.exec(type); + var real = new Float32Array(periodicWaveSize); + var imag = new Float32Array(periodicWaveSize); + + var partialCount = 1; + if (type === Tone.Oscillator.Type.Custom){ + partialCount = this._partials.length + 1; + periodicWaveSize = partialCount; + } else { + var partial = /^(sine|triangle|square|sawtooth)(\d+)$/.exec(type); if (partial){ - partialCount = parseInt(partial[2]); + partialCount = parseInt(partial[2]) + 1; type = partial[1]; partialCount = Math.max(partialCount, 2); periodicWaveSize = partialCount; } + } - var shift = this._phase; - for (var n = 1; n < periodicWaveSize; ++n) { - var piFactor = 2 / (n * Math.PI); - var b; - switch (type) { - case "sine": - b = (n <= partialCount) ? 1 : 0; - break; - case "square": - b = (n & 1) ? 2 * piFactor : 0; - break; - case "sawtooth": - b = piFactor * ((n & 1) ? 1 : -1); - break; - case "triangle": - if (n & 1) { - b = 2 * (piFactor * piFactor) * ((((n - 1) >> 1) & 1) ? -1 : 1); - } else { - b = 0; - } - break; - default: - throw new TypeError("invalid oscillator type: "+type); - } - if (b !== 0){ - real[n] = -b * Math.sin(shift * n); - imag[n] = b * Math.cos(shift * n); - } else { - real[n] = 0; - imag[n] = 0; - } + for (var n = 1; n < periodicWaveSize; ++n) { + var piFactor = 2 / (n * Math.PI); + var b; + switch (type) { + case Tone.Oscillator.Type.Sine: + b = (n <= partialCount) ? 1 : 0; + break; + case Tone.Oscillator.Type.Square: + b = (n & 1) ? 2 * piFactor : 0; + break; + case Tone.Oscillator.Type.Sawtooth: + b = piFactor * ((n & 1) ? 1 : -1); + break; + case Tone.Oscillator.Type.Triangle: + if (n & 1) { + b = 2 * (piFactor * piFactor) * ((((n - 1) >> 1) & 1) ? -1 : 1); + } else { + b = 0; + } + break; + case Tone.Oscillator.Type.Custom: + b = this._partials[n - 1]; + break; + default: + throw new Error("invalid oscillator type: "+type); } - var periodicWave = this.context.createPeriodicWave(real, imag); - this._wave = periodicWave; - if (this._oscillator !== null){ - this._oscillator.setPeriodicWave(this._wave); + if (b !== 0){ + real[n] = -b * Math.sin(phase * n); + imag[n] = b * Math.cos(phase * n); + } else { + real[n] = 0; + imag[n] = 0; } - this._type = originalType; + } + return [real, imag]; + }; + + /** + * Compute the inverse FFT for a given phase. + * @param {Float32Array} real + * @param {Float32Array} imag + * @param {NormalRange} phase + * @return {AudioRange} + * @private + */ + Tone.Oscillator.prototype._inverseFFT = function(real, imag, phase){ + var sum = 0; + var len = real.length; + for (var i = 0; i < len; i++){ + sum += real[i] * Math.cos(i * phase) + imag[i] * Math.sin(i * phase); + } + return sum; + }; + + /** + * Returns the initial value of the oscillator. + * @return {AudioRange} + * @private + */ + Tone.Oscillator.prototype._getInitialValue = function(){ + var coefs = this._getRealImaginary(this._type, 0); + var real = coefs[0]; + var imag = coefs[1]; + var maxValue = 0; + var twoPi = Math.PI * 2; + //check for peaks in 8 places + for (var i = 0; i < 8; i++){ + maxValue = Math.max(this._inverseFFT(real, imag, (i / 8) * twoPi), maxValue); + } + return -this._inverseFFT(real, imag, this._phase) / maxValue; + }; + + /** + * The partials of the 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.Oscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.Oscillator.prototype, "partials", { + get : function(){ + if (this._type !== Tone.Oscillator.Type.Custom){ + return []; + } else { + return this._partials; + } + }, + set : function(partials){ + this._partials = partials; + this.type = Tone.Oscillator.Type.Custom; } }); @@ -260,6 +357,7 @@ function(Tone){ this.frequency = null; this.detune.dispose(); this.detune = null; + this._partials = null; return this; }; diff --git a/Tone/source/PWMOscillator.js b/Tone/source/PWMOscillator.js index 3d8c4ca73..641a5d67b 100644 --- a/Tone/source/PWMOscillator.js +++ b/Tone/source/PWMOscillator.js @@ -36,7 +36,8 @@ function(Tone){ */ this._modulator = new Tone.Oscillator({ "frequency" : options.frequency, - "detune" : options.detune + "detune" : options.detune, + "phase" : options.phase }); /** @@ -85,6 +86,7 @@ function(Tone){ Tone.PWMOscillator.defaults = { "frequency" : 440, "detune" : 0, + "phase" : 0, "modulationFrequency" : 0.4, }; @@ -123,6 +125,19 @@ function(Tone){ } }); + /** + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PWMOscillator# + * @type {Array} + * @name partials + * @private + */ + Object.defineProperty(Tone.PWMOscillator.prototype, "partials", { + get : function(){ + return []; + } + }); + /** * The phase of the oscillator in degrees. * @memberOf Tone.PWMOscillator# diff --git a/Tone/source/Player.js b/Tone/source/Player.js index 4e9f520fa..82ca2d8b9 100644 --- a/Tone/source/Player.js +++ b/Tone/source/Player.js @@ -17,9 +17,15 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To * player.start(); * } */ - Tone.Player = function(){ - - var options = this.optionsObject(arguments, ["url", "onload"], Tone.Player.defaults); + Tone.Player = function(url){ + + var options; + if (url instanceof Tone.Buffer){ + url = url.get(); + options = Tone.Player.defaults; + } else { + options = this.optionsObject(arguments, ["url", "onload"], Tone.Player.defaults); + } Tone.Source.call(this, options); /** @@ -51,6 +57,9 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To "onload" : this._onload.bind(this, options.onload), "reverse" : options.reverse }); + if (url instanceof AudioBuffer){ + this._buffer.set(url); + } /** * if the buffer should loop once it's over @@ -113,7 +122,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To * Load the audio file as an audio buffer. * Decodes the audio asynchronously and invokes * the callback once the audio buffer loads. - * Note: this does not need to be called, if a url + * Note: this does not need to be called if a url * was passed in to the constructor. Only use this * if you want to manually load a new url. * @param {string} url The url of the buffer to load. @@ -173,18 +182,19 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To this._source.loop = this._loop; this._source.loopStart = this.toSeconds(this._loopStart); this._source.loopEnd = this.toSeconds(this._loopEnd); - // this fixes a bug in chrome 42 that breaks looping - // https://code.google.com/p/chromium/issues/detail?id=457099 - duration = 65536; } else { - this._nextStop = startTime + duration; + //if it's not looping, set the state change at the end of the sample + this._state.setStateAtTime(Tone.State.Stopped, startTime + duration); } //and other properties this._source.playbackRate.value = this._playbackRate; - this._source.onended = this.onended; this._source.connect(this.output); //start it - this._source.start(startTime, offset, duration); + if (this._loop){ + this._source.start(startTime, offset); + } else { + this._source.start(startTime, offset, duration); + } } else { throw Error("tried to start Player before the buffer was loaded"); } @@ -261,7 +271,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To /** * The audio buffer belonging to the player. * @memberOf Tone.Player# - * @type {AudioBuffer} + * @type {Tone.Buffer} * @name buffer */ Object.defineProperty(Tone.Player.prototype, "buffer", { @@ -292,10 +302,8 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To }); /** - * The playback speed. 1 is normal speed. - * Note that this is not a Tone.Signal because of a bug in Blink. - * Please star [this issue](https://code.google.com/p/chromium/issues/detail?id=311284) - * if this an important thing to you. + * The playback speed. 1 is normal speed. This is not a signal because + * Safari and iOS currently don't support playbackRate as a signal. * @memberOf Tone.Player# * @type {number} * @name playbackRate diff --git a/Tone/source/PulseOscillator.js b/Tone/source/PulseOscillator.js index 41a85281b..d93db8130 100644 --- a/Tone/source/PulseOscillator.js +++ b/Tone/source/PulseOscillator.js @@ -148,6 +148,19 @@ function(Tone){ } }); + /** + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PulseOscillator# + * @type {Array} + * @name partials + * @private + */ + Object.defineProperty(Tone.PulseOscillator.prototype, "partials", { + get : function(){ + return []; + } + }); + /** * Clean up method. * @return {Tone.PulseOscillator} this diff --git a/Tone/source/Source.js b/Tone/source/Source.js index 10bc9b599..e5312d5f1 100644 --- a/Tone/source/Source.js +++ b/Tone/source/Source.js @@ -1,69 +1,89 @@ -define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(Tone){ +define(["Tone/core/Tone", "Tone/core/Transport", "Tone/component/Volume", + "Tone/core/Type", "Tone/core/TimelineState", "Tone/signal/Signal"], +function(Tone){ "use strict"; /** * @class Base class for sources. Sources have start/stop methods * and the ability to be synced to the - * start/stop of Tone.Transport. + * start/stop of Tone.Transport. * * @constructor * @extends {Tone} + * @example + * //Multiple state change events can be chained together, + * //but must be set in the correct order and with ascending times + * + * // OK + * state.start().stop("+0.2"); + * // AND + * state.start().stop("+0.2").start("+0.4").stop("+0.7") + * + * // BAD + * state.stop("+0.2").start(); + * // OR + * state.start("+0.3").stop("+0.2"); + * */ Tone.Source = function(options){ - //unlike most ToneNodes, Sources only have an output and no input - Tone.call(this, 0, 1); + //Sources only have an output and no input + Tone.call(this); + options = this.defaultArg(options, Tone.Source.defaults); /** - * Callback is invoked when the source is done playing. - * @type {function} + * 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.onended = function(){ - * console.log("the source is done playing"); - * } + * source.volume.value = -6; */ - this.onended = options.onended; + this.volume = this._volume.volume; + this._readOnly("volume"); /** - * the next time the source is started - * @type {number} + * Keep track of the scheduled state. + * @type {Tone.TimelineState} * @private */ - this._nextStart = Infinity; + this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * the next time the source is stopped - * @type {number} + * The synced `start` callback function from the transport + * @type {Function} * @private */ - this._nextStop = Infinity; + this._syncStart = function(time, offset){ + time = this.toSeconds(time); + time += this.toSeconds(this._startDelay); + this.start(time, offset); + }.bind(this); /** - * The volume of the output in decibels. - * @type {Decibels} - * @signal - * @example - * source.volume.value = -6; + * The synced `stop` callback function from the transport + * @type {Function} + * @private */ - this.volume = new Tone.Signal({ - "param" : this.output.gain, - "value" : options.volume, - "units" : Tone.Type.Decibels - }); - this._readOnly("volume"); + this._syncStop = this.stop.bind(this); /** - * keeps track of the timeout for chaning the state - * and calling the onended - * @type {number} + * The offset from the start of the Transport `start` + * @type {Time} * @private */ - this._timeout = -1; + this._startDelay = 0; //make the output explicitly stereo - this.output.channelCount = 2; - this.output.channelCountMode = "explicit"; + this._volume.output.output.channelCount = 2; + this._volume.output.output.channelCountMode = "explicit"; }; Tone.extend(Tone.Source); @@ -75,7 +95,6 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * @type {Object} */ Tone.Source.defaults = { - "onended" : Tone.noOp, "volume" : 0, }; @@ -88,27 +107,10 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T */ Object.defineProperty(Tone.Source.prototype, "state", { get : function(){ - return this._stateAtTime(this.now()); + return this._state.getStateAtTime(this.now()); } }); - /** - * Get the state of the source at the specified time. - * @param {Time} time - * @return {Tone.State} - * @private - */ - Tone.Source.prototype._stateAtTime = function(time){ - time = this.toSeconds(time); - if (this._nextStart <= time && this._nextStop > time){ - return Tone.State.Started; - } else if (this._nextStop <= time){ - return Tone.State.Stopped; - } else { - return Tone.State.Stopped; - } - }; - /** * Start the source at the specified time. If no time is given, * start the source now. @@ -119,10 +121,11 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T */ Tone.Source.prototype.start = function(time){ time = this.toSeconds(time); - if (this._stateAtTime(time) !== Tone.State.Started || this.retrigger){ - this._nextStart = time; - this._nextStop = Infinity; - this._start.apply(this, arguments); + if (this._state.getStateAtTime(time) !== Tone.State.Started || this.retrigger){ + this._state.setStateAtTime(Tone.State.Started, time); + if (this._start){ + this._start.apply(this, arguments); + } } return this; }; @@ -136,36 +139,16 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * source.stop(); // stops the source immediately */ Tone.Source.prototype.stop = function(time){ - var now = this.now(); - time = this.toSeconds(time, now); - if (this._stateAtTime(time) === Tone.State.Started){ - this._nextStop = this.toSeconds(time); - clearTimeout(this._timeout); - var diff = time - now; - if (diff > 0){ - //add a small buffer before invoking the callback - this._timeout = setTimeout(this.onended, diff * 1000 + 20); - } else { - this.onended(); + 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._stop.apply(this, arguments); } return this; }; - - /** - * Not ready yet. - * @private - * @abstract - * @param {Time} time - * @returns {Tone.Source} this - */ - Tone.Source.prototype.pause = function(time){ - //if there is no pause, just stop it - this.stop(time); - return this; - }; - + /** * Sync the source to the Transport so that when the transport * is started, this source is started and when the transport is stopped @@ -181,7 +164,9 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * Tone.Transport.start(); */ Tone.Source.prototype.sync = function(delay){ - Tone.Transport.syncSource(this, delay); + this._startDelay = this.defaultArg(delay, 0); + Tone.Transport.on("start", this._syncStart); + Tone.Transport.on("stop pause", this._syncStop); return this; }; @@ -190,7 +175,9 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * @returns {Tone.Source} this */ Tone.Source.prototype.unsync = function(){ - Tone.Transport.unsyncSource(this); + this._startDelay = 0; + Tone.Transport.off("start", this._syncStart); + Tone.Transport.off("stop pause", this._syncStop); return this; }; @@ -199,13 +186,17 @@ define(["Tone/core/Tone", "Tone/core/Transport", "Tone/core/Master"], function(T * @return {Tone.Source} this */ Tone.Source.prototype.dispose = function(){ - Tone.prototype.dispose.call(this); this.stop(); - clearTimeout(this._timeout); - this.onended = Tone.noOp; + Tone.prototype.dispose.call(this); + this.unsync(); this._writable("volume"); - this.volume.dispose(); + this._volume.dispose(); + this._volume = null; this.volume = null; + this._state.dispose(); + this._state = null; + this._syncStart = null; + this._syncStart = null; }; return Tone.Source; diff --git a/build/Tone.Preset.js b/build/Tone.Preset.js deleted file mode 100644 index ae3c7cdf8..000000000 --- a/build/Tone.Preset.js +++ /dev/null @@ -1,1091 +0,0 @@ -(function (root) { - - "use strict"; - - function TonePreset(func){ - func(root.Tone); - } - TonePreset( function(Tone){ - - /** - * named presets for the AutoWah - * @const - * @static - * @type {Object} - */ - Tone.AutoWah.prototype.preset = { - "Talker" : { - "baseFrequency" : 100, - "octaves" : 4, - "sensitivity" : 0, - "Q" : 2, - "gain" : 10, - "rolloff" : -12, - "follower" : { - "attack" : 0.05, - "release" : 0.2 - } - }, - "Yes" : { - "baseFrequency" : 250, - "octaves" : 5, - "sensitivity" : 0, - "Q" : 2, - "gain" : 20, - "rolloff" : -24, - "follower" : { - "attack" : 0.1, - "release" : 0.2 - } - }, - "Springy" : { - "baseFrequency" : 10, - "octaves" : 8, - "sensitivity" : 0, - "Q" : 1, - "gain" : 10, - "rolloff" : -48, - "follower" : { - "attack" : 0.02, - "release" : 1 - } - } - }; - - return Tone.AutoWah.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the Chebyshev - * @const - * @static - * @type {Object} - */ - Tone.Chebyshev.prototype.preset = { - "Hornsy" : { - "order" : 50, - "oversample" : "none" - }, - "Peaker" : { - "order" : 11, - "oversample" : "2x" - }, - "CoinOperated" : { - "order" : 108, - "oversample" : "none" - } - }; - - return Tone.Chebyshev.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the Chorus - * @const - * @static - * @type {Object} - */ - Tone.Chorus.prototype.preset = { - "Ether" : { - "rate" : 0.3, - "delayTime" : 8, - "type" : "triangle", - "depth" : 0.8, - "feedback" : 0.2 - }, - "Harmony" : { - "rate" : 12, - "delayTime" : 3.5, - "type" : "sine", - "depth" : 0.8, - "feedback" : 0.1 - }, - "Rattler" : { - "rate" : "16n", - "delayTime" : 15, - "type" : "square", - "depth" : 0.2, - "feedback" : 0.3 - } - }; - - return Tone.Chorus.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for Distortion - * @const - * @static - * @type {Object} - */ - Tone.Distortion.prototype.preset = { - "Clean" : { - "distortion" : 0.08, - "oversample" : "4x" - }, - "Thick" : { - "distortion" : 0.6, - "oversample" : "none" - }, - "Growl" : { - "distortion" : 1.4, - "oversample" : "2x" - } - }; - - return Tone.Distortion.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for FeedbackDelay - * @const - * @static - * @type {Object} - */ - Tone.FeedbackDelay.prototype.preset = { - "DecayDelay" : { - "delayTime" : "8n", - "feedback" : 0.4 - }, - "Minimalism" : { - "delayTime" : "4n", - "feedback" : 0.7 - }, - "CounterPoints" : { - "delayTime" : "8t", - "feedback" : 0.2 - }, - }; - - return Tone.FeedbackDelay.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for Freeverb - * @const - * @static - * @type {Object} - */ - Tone.Freeverb.prototype.preset = { - "Sewer" : { - "roomSize" : 0.8, - "dampening" : 0.05 - }, - "Glassroom" : { - "roomSize" : 0.6, - "dampening" : 0.9 - }, - "Bigplate" : { - "roomSize" : 0.9, - "dampening" : 0.2 - } - }; - - return Tone.Freeverb.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the JCReverb - * @const - * @static - * @type {Object} - */ - Tone.JCReverb.prototype.preset = { - "QuickSlap" : { - "roomSize" : 0.1, - }, - "BounceHall" : { - "roomSize" : 0.8, - }, - "NotNormal" : { - "roomSize" : 0.5, - }, - }; - - return Tone.JCReverb.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the Phaser - * @const - * @static - * @type {Object} - */ - Tone.Phaser.prototype.preset = { - "Testing" : { - "rate" : 10, - "depth" : 0.2, - "Q" : 2, - "baseFrequency" : 700, - }, - "Landing" : { - "rate" : 4, - "depth" : 1.2, - "Q" : 20, - "baseFrequency" : 800, - }, - "Bubbles" : { - "rate" : 0.5, - "depth" : 5, - "Q" : 8, - "baseFrequency" : 250, - } - }; - - return Tone.Phaser.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for PingPongDelay - * @const - * @static - * @type {Object} - */ - Tone.PingPongDelay.prototype.preset = { - "SlowSteady" : { - "delayTime" : "4n", - "feedback" : 0.2 - }, - "ThickStereo" : { - "delayTime" : "16t", - "feedback" : 0.3 - }, - "RhythmicDelay" : { - "delayTime" : "8n", - "feedback" : 0.6 - }, - }; - - return Tone.PingPongDelay.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the AMSynth - * @const - * @static - * @type {Object} - */ - Tone.AMSynth.prototype.preset = { - "Sand" : { - "harmonicity": 3, - "carrier": { - "oscillator": { - "frequency": 0, - "detune": 0, - "type": "square", - "phase": 0, - "volume": 0 - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 0.93, - "gain": 0 - }, - "envelope": { - "attack": 0.003, - "decay": 0.305, - "sustain": 0.7, - "release": 0.828 - }, - "filterEnvelope": { - "min": 1509.0, - "max": 7212.8, - "exponent": 2, - "attack": 0.0035, - "decay": 0.0006, - "sustain": 0.57, - "release": 0.8 - }, - "portamento": 0 - }, - "modulator": { - "oscillator": { - "frequency": 0, - "detune": 0, - "type": "sine", - "phase": 0, - "volume": 0 - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 0.6, - "gain": 0 - }, - "envelope": { - "attack": 0.043, - "decay": 0.13, - "sustain": 0.047, - "release": 0.040 - }, - "filterEnvelope": { - "min": 1132.80, - "max": 557.38, - "exponent": 2, - "attack": 0.17, - "decay": 0.023, - "sustain": 0.036, - "release": 1 - }, - "portamento": 0 - }, - "portamento": 0 - } - }; - - - return Tone.AMSynth.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the DuoSynth - * @const - * @static - * @type {Object} - */ - Tone.DuoSynth.prototype.preset = { - "Steely" : { - "vibratoAmount" : 0.0, - "vibratoRate" : 5, - "vibratoDelay" : 1, - "portamento" : 0, - "harmonicity" : 1.99, - "voice0" : { - "volume" : -8, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 1, - "sustain" : 0, - "release" : 0.4 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.01, - "sustain" : 0.35, - "release" : 1, - "min" : 20, - "max" : 8000 - } - }, - "voice1" : { - "volume" : -1, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.25, - "decay" : 4, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 1000, - "max" : 2500 - } - } - }, - "Unicorn" : { - "vibratoAmount" : 0.5, - "vibratoRate" : 5, - "vibratoDelay" : 1, - "portamento" : 0.1, - "harmonicity" : 1.005, - "voice0" : { - "volume" : -2, - "portamento" : 0, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 1, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.25, - "sustain" : 0.4, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.05, - "sustain" : 0.3, - "release" : 2, - "min" : 100, - "max" : 10000 - } - }, - "voice1" : { - "volume" : -10, - "portamento" : 0, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "bandpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.25, - "decay" : 4, - "sustain" : 0.1, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.05, - "sustain" : 0.7, - "release" : 2, - "min" : 5000, - "max" : 2000 - } - } - }, - "Organic" : { - "vibratoAmount": 0.3, - "vibratoRate": 3.5, - "harmonicity": 1.5, - "voice0": { - "volume" : -6, - "oscillator": { - "frequency": 0, - "type": "sine", - }, - "filter": { - "type": "lowpass", - "rolloff": -12, - "Q": 2.8, - }, - "envelope": { - "attack": 0.0070, - "decay": 0, - "sustain": 1, - "release": 0.056682076 - }, - "filterEnvelope": { - "min": 219.80, - "max": 1049.54942, - "exponent": 2, - "attack": 0.00704, - "decay": 0.0278, - "sustain": 0.065, - "release": 0.0749 - }, - }, - "voice1": { - "volume" : -20, - "oscillator": { - "type": "sine", - }, - "filter": { - "type": "highpass", - "rolloff": -12, - "Q": 4.55, - }, - "envelope": { - "attack": 0.011, - "decay": 0.016, - "sustain": 0.7464, - "release": 0.074 - }, - "filterEnvelope": { - "min": 298.20, - "max": 80.43, - "exponent": 2, - "attack": 0.0035, - "decay": 0.0060, - "sustain": 1, - "release": 0.108 - }, - }, - "portamento": 0 - } - }; - - - return Tone.DuoSynth.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the FMSynth - * @const - * @static - * @type {Object} - */ - Tone.FMSynth.prototype.preset = { - "Trumpet" : { - "portamento" : 0, - "harmonicity" : 1, - "modulationIndex" : 4, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 1, - "sustain" : 0.7, - "release" : 0.4 - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.07, - "sustain" : 0.35, - "release" : 0.8, - "min" : 3000, - "max" : 6500 - } - }, - "modulator" : { - "volume" : -6, - "portamento" : 0, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 0, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.15, - "decay" : 0.3, - "sustain" : 1, - "release" : 1.5 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 20000, - "max" : 20000 - } - } - }, - "Koto" : { - "portamento" : 0, - "harmonicity" : 3.01, - "modulationIndex" : 12.7, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 2, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.06, - "decay" : 0.07, - "sustain" : 0.35, - "release" : 0.8, - "min" : 20000, - "max" : 20000 - } - }, - "modulator" : { - "volume" : -1, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 0, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.02, - "decay" : 2, - "sustain" : 0, - "release" : 0.8 - }, - "filterEnvelope" : { - "attack" : 0.03, - "decay" : 0.25, - "sustain" : 0.7, - "release" : 1, - "min" : 20000, - "max" : 20000 - } - } - }, - "ScratchAttack" : { - "portamento" : 0, - "harmonicity" : 10, - "modulationIndex" : 50, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.08, - "decay" : 0.3, - "sustain" : 0, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0, - "release" : 0.2, - "min" : 200, - "max" : 10000 - } - }, - "modulator" : { - "volume" : -6, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 1, - "type" : "highpass", - "rolloff" : -48 - }, - "envelope" : { - "attack" : 0.1, - "decay" : 0.2, - "sustain" : 0.3, - "release" : 0.01 - }, - "filterEnvelope" : { - "attack" : 0.2, - "decay" : 0.2, - "sustain" : 0.8, - "release" : 0.01, - "min" : 20, - "max" : 2000 - } - } - }, - "DistGit" : { - "portamento" : 0, - "harmonicity" : 1, - "modulationIndex" : 10, - "carrier" : { - "volume" : 0, - "portamento" : 0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.001, - "decay" : 3.3, - "sustain" : 0, - "release" : 1.2 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.15, - "sustain" : 1, - "release" : 1.5, - "min" : 400, - "max" : 4000 - } - }, - "modulator" : { - "volume" : -3, - "portamento" : 0, - "oscillator" : { - "type" : "sine" - }, - "filter" : { - "Q" : 1, - "type" : "lowpass", - "rolloff" : -48 - }, - "envelope" : { - "attack" : 0.3, - "decay" : 0.4, - "sustain" : 1, - "release" : 1.7 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.02, - "sustain" : 0.1, - "release" : 1.5, - "min" : 200, - "max" : 200 - } - } - }, - }; - - - return Tone.FMSynth.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the MonoSynth - * @const - * @static - * @type {Object} - */ - Tone.MonoSynth.prototype.preset = { - "CoolGuy" : { - "portamento" : 0.0, - "oscillator" : { - "type" : "pwm", - "modulationFrequency" : 1 - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.025, - "decay" : 0.3, - "sustain" : 0.9, - "release" : 2 - }, - "filterEnvelope" : { - "attack" : 0.245, - "decay" : 0.131, - "sustain" : 0.5, - "release" : 2, - "min" : 20, - "max" : 3000 - } - }, - "Pianoetta" : { - "portamento" : 0.0, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 6, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.005, - "decay" : 3, - "sustain" : 0, - "release" : 0.45 - }, - "filterEnvelope" : { - "attack" : 0.001, - "decay" : 0.32, - "sustain" : 0.9, - "release" : 3, - "min" : 700, - "max" : 3500 - } - }, - "Barky" : { - "portamento" : 0.01, - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 3, - "type" : "highpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.05, - "decay" : 0.15, - "sustain" : 0.6, - "release" : 1 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.2, - "sustain" : 0.8, - "release" : 1.5, - "min" : 3000, - "max" : 250 - } - }, - "Bassy" : { - "portamento" : 0.08, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 4, - "type" : "lowpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.04, - "decay" : 0.06, - "sustain" : 0.4, - "release" : 1 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.6, - "release" : 1.5, - "min" : 50, - "max" : 350 - } - }, - "BrassCircuit" : { - "portamento" : 0.01, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "lowpass", - "rolloff" : -12 - }, - "envelope" : { - "attack" : 0.1, - "decay" : 0.1, - "sustain" : 0.6, - "release" : 0.5 - }, - "filterEnvelope" : { - "attack" : 0.05, - "decay" : 0.8, - "sustain" : 0.4, - "release" : 1.5, - "min" : 2000, - "max" : 5000 - } - }, - "Pizz" : { - "portamento" : 0.00, - "oscillator" : { - "type" : "square" - }, - "filter" : { - "Q" : 2, - "type" : "highpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.2, - "sustain" : 0.0, - "release" : 0.2 - }, - "filterEnvelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.0, - "release" : 0.1, - "min" : 900, - "max" : 500 - } - }, - "LaserSteps" : { - "portamento" : 0.00, - "oscillator" : { - "type" : "sawtooth" - }, - "filter" : { - "Q" : 2, - "type" : "bandpass", - "rolloff" : -24 - }, - "envelope" : { - "attack" : 0.01, - "decay" : 0.1, - "sustain" : 0.2, - "release" : 0.6 - }, - "filterEnvelope" : { - "attack" : 0.02, - "decay" : 0.4, - "sustain" : 1, - "release" : 0.2, - "min" : 0, - "max" : 7500 - } - } - }; - - return Tone.MonoSynth.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the NoiseSynth - * @const - * @static - * @type {Object} - */ - Tone.NoiseSynth.prototype.preset = { - "LaserWind" : { - "noise": { - "type": "white", - }, - "filter": { - "type": "highpass", - "rolloff": -24, - "Q": 6, - }, - "envelope": { - "attack": 0.005, - "decay": 0.1, - "sustain": 0, - "release": 1 - }, - "filterEnvelope": { - "min": 20, - "max": 4000, - "exponent": 2, - "attack": 0.06, - "decay": 0.2, - "sustain": 0, - "release": 2 - } - }, - "WindWind" : { - "noise": { - "type": "brown", - }, - "filter": { - "type": "lowpass", - "rolloff": -24, - "Q": 6, - }, - "envelope": { - "attack": 0.033, - "decay": 0.15, - "sustain": 0.38, - "release": 1 - }, - "filterEnvelope": { - "min": 1509.08, - "max": 3976.53, - "exponent": 2, - "attack": 0.61, - "decay": 0.76, - "sustain": 0.20, - "release": 0.33 - } - }, - "Snare" : { - "noise": { - "type": "pink", - }, - "filter": { - "type": "highpass", - "frequency": 0, - "rolloff": -12, - "Q": 3.7, - "gain": 0 - }, - "envelope": { - "attack": 0.024, - "decay": 0.111, - "sustain": 0, - "release": 0.22 - }, - "filterEnvelope": { - "min": 819.20, - "max": 3510.98, - "exponent": 2, - "attack": 0.002, - "decay": 0.02, - "sustain": 0.02, - "release": 0.013 - } - } - }; - - return Tone.NoiseSynth.prototype.preset; - }); - TonePreset( function(Tone){ - - /** - * named presets for the PluckSynth - * @const - * @static - * @type {Object} - */ - Tone.PluckSynth.prototype.preset = { - "Glassy" : { - "attackNoise": 4, - "dampening": 9200, - "resonance": 1 - }, - "Violin" : { - "attackNoise": 8, - "dampening": 3200, - "resonance": 0.8 - }, - "Plucky" : { - "attackNoise": 0.8, - "dampening": 2600, - "resonance": 0.54 - } - }; - - return Tone.PluckSynth.prototype.preset; - }); -} (this)); \ No newline at end of file diff --git a/build/Tone.js b/build/Tone.js index 47f174107..a6d5bff5b 100644 --- a/build/Tone.js +++ b/build/Tone.js @@ -149,9 +149,9 @@ * }, 3); */ Tone.prototype.set = function (params, value, rampTime) { - if (typeof params === 'object') { + if (this.isObject(params)) { rampTime = value; - } else if (typeof params === 'string') { + } else if (this.isString(params)) { var tmpObj = {}; tmpObj[params] = value; params = tmpObj; @@ -170,7 +170,7 @@ if (isUndef(param)) { continue; } - if (param instanceof Tone.Signal) { + if (Tone.Signal && param instanceof Tone.Signal || Tone.Param && param instanceof Tone.Param) { if (param.value !== value) { if (isUndef(rampTime)) { param.value = value; @@ -213,7 +213,7 @@ Tone.prototype.get = function (params) { if (isUndef(params)) { params = this._collectDefaults(this.constructor); - } else if (typeof params === 'string') { + } else if (this.isString(params)) { params = [params]; } var ret = {}; @@ -232,9 +232,11 @@ attr = attrSplit[attrSplit.length - 1]; } var param = parent[attr]; - if (typeof params[attr] === 'object') { + if (this.isObject(params[attr])) { subRet[attr] = param.get(); - } else if (param instanceof Tone.Signal) { + } else if (Tone.Signal && param instanceof Tone.Signal) { + subRet[attr] = param.value; + } else if (Tone.Param && param instanceof Tone.Param) { subRet[attr] = param.value; } else if (param instanceof AudioParam) { subRet[attr] = param.value; @@ -268,17 +270,6 @@ } return ret; }; - /** - * Set the preset if it exists. - * @param {string} presetName the name of the preset - * @returns {Tone} this - */ - Tone.prototype.setPreset = function (presetName) { - if (!this.isUndef(this.preset) && this.preset.hasOwnProperty(presetName)) { - this.set(this.preset[presetName]); - } - return this; - }; /** * @returns {string} returns the name of the class as a string */ @@ -313,12 +304,12 @@ */ Tone.prototype.bufferSize = 2048; /** - * the delay time of a single buffer frame + * The delay time of a single frame (128 samples according to the spec). * @type {number} * @static * @const */ - Tone.prototype.bufferTime = Tone.prototype.bufferSize / Tone.context.sampleRate; + Tone.prototype.blockTime = 128 / Tone.context.sampleRate; /////////////////////////////////////////////////////////////////////////// // CONNECTIONS /////////////////////////////////////////////////////////////////////////// @@ -395,7 +386,7 @@ }; /** * connect together all of the arguments in series - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.connectSeries = function () { @@ -411,7 +402,7 @@ }; /** * fan out the connection from the first argument to the rest of the arguments - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.connectParallel = function () { @@ -445,7 +436,7 @@ }; /** * connect the output of this node to the rest of the nodes in parallel. - * @param {...AudioParam|Tone|AudioNode} + * @param {...AudioParam|Tone|AudioNode} nodes * @returns {Tone} this */ Tone.prototype.fan = function () { @@ -463,27 +454,28 @@ // UTILITIES / HELPERS / MATHS /////////////////////////////////////////////////////////////////////////// /** - * if a the given is undefined, use the fallback. - * if both given and fallback are objects, given - * will be augmented with whatever properties it's - * missing which are in fallback - * - * warning: if object is self referential, it will go into an an - * infinite recursive loop. + * If the `given` parameter is undefined, use the `fallback`. + * If both `given` and `fallback` are object literals, it will + * return a deep copy which includes all of the parameters from both + * objects. If a parameter is undefined in given, it will return + * the fallback property. + *

+ * WARNING: if object is self referential, it will go into an an + * infinite recursive loop. * * @param {*} given * @param {*} fallback * @return {*} */ Tone.prototype.defaultArg = function (given, fallback) { - if (typeof given === 'object' && typeof fallback === 'object') { + if (this.isObject(given) && this.isObject(fallback)) { var ret = {}; //make a deep copy of the given object for (var givenProp in given) { - ret[givenProp] = this.defaultArg(given[givenProp], given[givenProp]); + ret[givenProp] = this.defaultArg(fallback[givenProp], given[givenProp]); } - for (var prop in fallback) { - ret[prop] = this.defaultArg(given[prop], fallback[prop]); + for (var fallbackProp in fallback) { + ret[fallbackProp] = this.defaultArg(given[fallbackProp], fallback[fallbackProp]); } return ret; } else { @@ -494,7 +486,7 @@ * returns the args as an options object with given arguments * mapped to the names provided. * - * if the args given is an array containing an object, it is assumed + * if the args given is an array containing only one object, it is assumed * that that's already the options object and will just return it. * * @param {Array} values the 'arguments' object of the function @@ -506,7 +498,7 @@ */ Tone.prototype.optionsObject = function (values, keys, defaults) { var options = {}; - if (values.length === 1 && typeof values[0] === 'object') { + if (values.length === 1 && this.isObject(values[0])) { options = values[0]; } else { for (var i = 0; i < keys.length; i++) { @@ -519,6 +511,9 @@ return options; } }; + /////////////////////////////////////////////////////////////////////////// + // TYPE CHECKING + /////////////////////////////////////////////////////////////////////////// /** * test if the arg is undefined * @param {*} arg the argument to test @@ -533,6 +528,52 @@ * @function */ Tone.prototype.isFunction = isFunction; + /** + * Test if the argument is a number. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a number + */ + Tone.prototype.isNumber = function (arg) { + return typeof arg === 'number'; + }; + /** + * Test if the given argument is an object literal (i.e. `{}`); + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is an object literal. + */ + Tone.prototype.isObject = function (arg) { + return Object.prototype.toString.call(arg) === '[object Object]' && arg.constructor === Object; + }; + /** + * Test if the argument is a boolean. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a boolean + */ + Tone.prototype.isBoolean = function (arg) { + return typeof arg === 'boolean'; + }; + /** + * Test if the argument is an Array + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is an array + */ + Tone.prototype.isArray = function (arg) { + return Array.isArray(arg); + }; + /** + * Test if the argument is a string. + * @param {*} arg the argument to test + * @returns {boolean} true if the arg is a string + */ + Tone.prototype.isString = function (arg) { + return typeof arg === 'string'; + }; + /** + * An empty function. + * @static + */ + Tone.noOp = function () { + }; /** * Make the property not writable. Internal use only. * @private @@ -564,31 +605,39 @@ Object.defineProperty(this, property, { writable: true }); } }; + /** + * Possible play states. + * @enum {string} + */ + Tone.State = { + Started: 'started', + Stopped: 'stopped', + Paused: 'paused' + }; /////////////////////////////////////////////////////////////////////////// // GAIN CONVERSIONS /////////////////////////////////////////////////////////////////////////// /** - * equal power gain scale - * good for cross-fading - * @param {number} percent (0-1) - * @return {number} output gain (0-1) + * Equal power gain scale. Good for cross-fading. + * @param {NormalRange} percent (0-1) + * @return {Number} output gain (0-1) */ Tone.prototype.equalPowerScale = function (percent) { var piFactor = 0.5 * Math.PI; return Math.sin(percent * piFactor); }; /** - * convert db scale to gain scale (0-1) - * @param {number} db - * @return {number} + * Convert decibels into gain. + * @param {Decibels} db + * @return {Number} */ Tone.prototype.dbToGain = function (db) { return Math.pow(2, db / 6); }; /** - * convert gain scale to decibels - * @param {number} gain (0-1) - * @return {number} + * Convert gain to decibels. + * @param {Number} gain (0-1) + * @return {Decibels} */ Tone.prototype.gainToDb = function (gain) { return 20 * (Math.log(gain) / Math.LN10); @@ -597,93 +646,14 @@ // 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 */ Tone.prototype.now = function () { return this.context.currentTime; }; - /** - * convert a sample count to seconds - * @param {number} samples - * @return {number} - */ - Tone.prototype.samplesToSeconds = function (samples) { - return samples / this.context.sampleRate; - }; - /** - * convert a time into samples - * - * @param {Tone.time} time - * @return {number} - */ - Tone.prototype.toSamples = function (time) { - var seconds = this.toSeconds(time); - return Math.round(seconds * this.context.sampleRate); - }; - /** - * convert time to seconds - * - * this is a simplified version which only handles numbers and - * 'now' relative numbers. If the Transport is included this - * method is overridden to include many other features including - * notationTime, Frequency, and transportTime - * - * @param {number=} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} seconds in the same timescale as the AudioContext - */ - Tone.prototype.toSeconds = function (time, now) { - now = this.defaultArg(now, this.now()); - if (typeof time === 'number') { - return time; //assuming that it's seconds - } else if (typeof time === 'string') { - var plusTime = 0; - if (time.charAt(0) === '+') { - time = time.slice(1); - plusTime = now; - } - return parseFloat(time) + plusTime; - } else { - return now; - } - }; - /////////////////////////////////////////////////////////////////////////// - // FREQUENCY CONVERSION - /////////////////////////////////////////////////////////////////////////// - /** - * true if the input is in the format number+hz - * i.e.: 10hz - * - * @param {number} freq - * @return {boolean} - * @function - */ - Tone.prototype.isFrequency = function () { - var freqFormat = new RegExp(/\d*\.?\d+hz$/i); - return function (freq) { - return freqFormat.test(freq); - }; - }(); - /** - * Convert a frequency into seconds. - * Accepts numbers and strings: i.e. "10hz" or - * 10 both return 0.1. - * - * @param {number|string} freq - * @return {number} - */ - Tone.prototype.frequencyToSeconds = function (freq) { - return 1 / parseFloat(freq); - }; - /** - * Convert a number in seconds to a frequency. - * @param {number} seconds - * @return {number} - */ - Tone.prototype.secondsToFrequency = function (seconds) { - return 1 / seconds; - }; /////////////////////////////////////////////////////////////////////////// // INHERITANCE /////////////////////////////////////////////////////////////////////////// @@ -713,123 +683,6 @@ child._super = parent; }; /////////////////////////////////////////////////////////////////////////// - // TYPES / STATES - /////////////////////////////////////////////////////////////////////////// - /** - * Possible types which a value can take on - * @enum {string} - */ - Tone.Type = { - /** - * The default value is a number which can take on any value between [-Infinity, Infinity] - */ - Default: 'number', - /** - * Time can be described in a number of ways. Read more [Time](https://github.com/TONEnoTONE/Tone.js/wiki/Time). - * - *
    - *
  • Numbers, which will be taken literally as the time (in seconds).
  • - *
  • Notation, ("4n", "8t") describes time in BPM and time signature relative values.
  • - *
  • TransportTime, ("4:3:2") will also provide tempo and time signature relative times - * in the form BARS:QUARTERS:SIXTEENTHS.
  • - *
  • Frequency, ("8hz") is converted to the length of the cycle in seconds.
  • - *
  • Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as - * "the current time plus whatever expression follows".
  • - *
  • Expressions, ("3:0 + 2 - (1m / 7)") any of the above can also be combined - * into a mathematical expression which will be evaluated to compute the desired time.
  • - *
  • No Argument, for methods which accept time, no argument will be interpreted as - * "now" (i.e. the currentTime).
  • - *
- * - * @typedef {Time} - */ - Time: 'time', - /** - * Frequency can be described similar to time, except ultimately the - * values are converted to frequency instead of seconds. A number - * is taken literally as the value in hertz. Additionally any of the - * Time encodings can be used. Note names in the form - * of NOTE OCTAVE (i.e. C4) are also accepted and converted to their - * frequency value. - * @typedef {Frequency} - */ - Frequency: 'frequency', - /** - * Gain is the ratio between the input and the output value of a signal. - * @typedef {Gain} - */ - Gain: 'gain', - /** - * Normal values are within the range [0, 1]. - * @typedef {NormalRange} - */ - NormalRange: 'normalrange', - /** - * AudioRange values are between [-1, 1]. - * @typedef {AudioRange} - */ - AudioRange: 'audiorange', - /** - * Decibels are a logarithmic unit of measurement which is useful for volume - * because of the logarithmic way that we perceive loudness. 0 decibels - * means no change in volume. -10db is approximately half as loud and 10db - * is twice is loud. - * @typedef {Decibels} - */ - Decibels: 'db', - /** - * Half-step note increments, i.e. 12 is an octave above the root. and 1 is a half-step up. - * @typedef {Interval} - */ - Interval: 'interval', - /** - * Beats per minute. - * @typedef {BPM} - */ - BPM: 'bpm', - /** - * The value must be greater than 0. - * @typedef {Positive} - */ - Positive: 'positive', - /** - * A cent is a 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' - }; - /** - * Possible play states. - * @enum {string} - */ - Tone.State = { - Started: 'started', - Stopped: 'stopped', - Paused: 'paused' - }; - /** - * An empty function. - * @static - */ - Tone.noOp = function () { - }; - /////////////////////////////////////////////////////////////////////////// // CONTEXT /////////////////////////////////////////////////////////////////////////// /** @@ -887,13 +740,13 @@ }; //setup the context Tone._initAudioContext(function (audioContext) { - //set the bufferTime - Tone.prototype.bufferTime = Tone.prototype.bufferSize / audioContext.sampleRate; + //set the blockTime + Tone.prototype.blockTime = 128 / audioContext.sampleRate; _silentNode = audioContext.createGain(); _silentNode.gain.value = 0; _silentNode.connect(audioContext.destination); }); - Tone.version = 'r5'; + Tone.version = 'r6'; console.log('%c * Tone.js ' + Tone.version + ' * ', 'background: #000; color: #fff'); return Tone; }); @@ -921,11 +774,11 @@ */ Tone.SignalBase.prototype.connect = function (node, outputNumber, inputNumber) { //zero it out so that the signal can have full control - if (node.constructor === Tone.Signal) { + if (Tone.Signal && Tone.Signal === node.constructor || Tone.Param && Tone.Param === node.constructor || Tone.TimelineSignal && Tone.TimelineSignal === node.constructor) { //cancel changes - node._value.cancelScheduledValues(0); + node._param.cancelScheduledValues(0); //reset the value - node._value.value = 0; + node._param.value = 0; //mark the value as overridden node.overridden = true; } else if (node instanceof AudioParam) { @@ -1021,11 +874,6 @@ return this._shaper.curve; }, set: function (mapping) { - //fixes safari WaveShaperNode bug - if (this._isSafari()) { - var first = mapping[0]; - mapping.unshift(first); - } this._curve = new Float32Array(mapping); this._shaper.curve = this._curve; } @@ -1042,18 +890,17 @@ return this._shaper.oversample; }, set: function (oversampling) { - this._shaper.oversample = oversampling; + if ([ + 'none', + '2x', + '4x' + ].indexOf(oversampling) !== -1) { + this._shaper.oversample = oversampling; + } else { + throw new Error('invalid oversampling: ' + oversampling); + } } }); - /** - * returns true if the browser is safari - * @return {boolean} - * @private - */ - Tone.WaveShaper.prototype._isSafari = function () { - var ua = navigator.userAgent.toLowerCase(); - return ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1; - }; /** * Clean up. * @returns {Tone.WaveShaper} this @@ -1069,10402 +916,15603 @@ }); Module(function (Tone) { + /////////////////////////////////////////////////////////////////////////// + // TYPES + /////////////////////////////////////////////////////////////////////////// /** - * @class A signal is an audio-rate value. Tone.Signal is a core component of the library. - * Unlike a number, Signals can be scheduled with sample-level accuracy. Tone.Signal - * has all of the methods available to native Web Audio - * [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface) - * as well as additional conveniences. Read more about working with signals - * [here](https://github.com/TONEnoTONE/Tone.js/wiki/Signals). - * - * @constructor - * @extends {Tone.SignalBase} - * @param {Number|AudioParam} [value] Initial value of the signal. If an AudioParam - * is passed in, that parameter will be wrapped - * and controlled by the Signal. - * @param {string} [units=Number] unit The units the signal is in. - * @example - * var signal = new Tone.Signal(10); + * Units which a value can take on. + * @enum {String} */ - Tone.Signal = function () { - var options = this.optionsObject(arguments, [ - 'value', - 'units' - ], Tone.Signal.defaults); - /** - * The units of the signal. - * @type {string} - */ - this.units = options.units; - /** - * When true, converts the set value - * based on the units given. When false, - * applies no conversion and the units - * are merely used as a label. - * @type {boolean} + Tone.Type = { + /** + * The default value is a number which can take on any value between [-Infinity, Infinity] */ - this.convert = options.convert; + Default: 'number', /** - * True if the signal value is being overridden by - * a connected signal. - * @readOnly - * @type {boolean} - * @private + * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time). + * + *
    + *
  • Numbers, which will be taken literally as the time (in seconds).
  • + *
  • Notation, ("4n", "8t") describes time in BPM and time signature relative values.
  • + *
  • TransportTime, ("4:3:2") will also provide tempo and time signature relative times + * in the form BARS:QUARTERS:SIXTEENTHS.
  • + *
  • Frequency, ("8hz") is converted to the length of the cycle in seconds.
  • + *
  • Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as + * "the current time plus whatever expression follows".
  • + *
  • Expressions, ("3:0 + 2 - (1m / 7)") any of the above can also be combined + * into a mathematical expression which will be evaluated to compute the desired time.
  • + *
  • No Argument, for methods which accept time, no argument will be interpreted as + * "now" (i.e. the currentTime).
  • + *
+ * + * @typedef {Time} */ - this.overridden = false; + Time: 'time', /** - * The node where the constant signal value is scaled. - * @type {GainNode} - * @private + * 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.output = this._scaler = this.context.createGain(); - /** - * The node where the value is set. - * @type {AudioParam} - * @private + Frequency: 'frequency', + /** + * Normal values are within the range [0, 1]. + * @typedef {NormalRange} */ - this.input = this._value = this._scaler.gain; - if (options.value instanceof AudioParam) { - this._scaler.connect(options.value); - //zero out the value - options.value.value = 0; - } else { - if (!this.isUndef(options.param)) { - this._scaler.connect(options.param); - options.param.value = 0; - } - this.value = options.value; - } - //connect the constant 1 output to the node output - Tone.Signal._constant.chain(this._scaler); + 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. + *
    + *
  • "4n" = quarter note
  • + *
  • "2m" = two measures
  • + *
  • "8t" = eighth-note triplet
  • + *
+ * @typedef {Notation} + */ + Notation: 'notation' }; - Tone.extend(Tone.Signal, Tone.SignalBase); + /////////////////////////////////////////////////////////////////////////// + // MATCHING TESTS + /////////////////////////////////////////////////////////////////////////// /** - * The default values - * @type {Object} - * @static - * @const + * 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.Signal.defaults = { - 'value': 0, - 'param': undefined, - 'units': Tone.Type.Default, - 'convert': true - }; + Tone.prototype.isNowRelative = function () { + var nowRelative = new RegExp(/^\s*\+(.)+/i); + return function (note) { + return nowRelative.test(note); + }; + }(); /** - * The current value of the signal. - * @memberOf Tone.Signal# - * @type {Number} - * @name value + * Tests if a string is in Ticks notation. + * + * @param {String} str The string to test + * @return {boolean} + * @method isTicks + * @lends Tone.prototype.isTicks */ - Object.defineProperty(Tone.Signal.prototype, 'value', { - get: function () { - return this._toUnits(this._value.value); - }, - set: function (value) { - var convertedVal = this._fromUnits(value); - //is this what you want? - this.cancelScheduledValues(0); - this._value.value = convertedVal; - } - }); + Tone.prototype.isTicks = function () { + var tickFormat = new RegExp(/^\d+i$/i); + return function (note) { + return tickFormat.test(note); + }; + }(); /** - * @private - * @param {*} val the value to convert - * @return {number} the number which the value should be set to + * Tests if a string is musical notation. + * i.e.: + *
    + *
  • 4n = quarter note
  • + *
  • 2m = two measures
  • + *
  • 8t = eighth-note triplet
  • + *
+ * + * @param {String} str The string to test + * @return {boolean} + * @method isNotation + * @lends Tone.prototype.isNotation */ - Tone.Signal.prototype._fromUnits = function (val) { - if (this.convert || this.isUndef(this.convert)) { - switch (this.units) { - case Tone.Type.Time: - return this.toSeconds(val); - case Tone.Type.Frequency: - return this.toFrequency(val); - case Tone.Type.Decibels: - return this.dbToGain(val); - case Tone.Type.NormalRange: - return Math.min(Math.max(val, 0), 1); - case Tone.Type.AudioRange: - return Math.min(Math.max(val, -1), 1); - case Tone.Type.Positive: - return Math.max(val, 0); - default: - return val; - } - } else { - return val; - } - }; + Tone.prototype.isNotation = function () { + var notationFormat = new RegExp(/^[0-9]+[mnt]$/i); + return function (note) { + return notationFormat.test(note); + }; + }(); /** - * convert to the desired units - * @private - * @param {number} val the value to convert - * @return {number} + * Test if a string is in the transportTime format. + * "Bars:Beats:Sixteenths" + * @param {String} transportTime + * @return {boolean} + * @method isTransportTime + * @lends Tone.prototype.isTransportTime */ - Tone.Signal.prototype._toUnits = function (val) { - if (this.convert || this.isUndef(this.convert)) { - switch (this.units) { - case Tone.Type.Decibels: - return this.gainToDb(val); - default: - return val; - } - } else { - return val; - } - }; + Tone.prototype.isTransportTime = function () { + var transportTimeFormat = new RegExp(/^(\d+(\.\d+)?\:){1,2}(\d+(\.\d+)?)?$/i); + return function (transportTime) { + return transportTimeFormat.test(transportTime); + }; + }(); /** - * Schedules a parameter value change at the given time. - * @param {*} value The value to set the signal. - * @param {Time} time The time when the change should occur. - * @returns {Tone.Signal} this - * @example - * //set the frequency to "G4" in exactly 1 second from now. - * freq.setValueAtTime("G4", "+1"); + * 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.Signal.prototype.setValueAtTime = function (value, time) { - value = this._fromUnits(value); - this._value.setValueAtTime(value, this.toSeconds(time)); - return this; - }; + Tone.prototype.isNote = function () { + var noteFormat = new RegExp(/^[a-g]{1}(b|#|x|bb)?-?[0-9]+$/i); + return function (note) { + return noteFormat.test(note); + }; + }(); /** - * Creates a schedule point with the current value at the current time. - * This is useful for creating an automation anchor point in order to - * schedule changes from the current value. + * Test if the input is in the format of number + hz + * i.e.: 10hz * - * @param {number=} now (Optionally) pass the now value in. - * @returns {Tone.Signal} this + * @param {String} freq + * @return {boolean} + * @function */ - Tone.Signal.prototype.setCurrentValueNow = function (now) { - now = this.defaultArg(now, this.now()); - var currentVal = this._value.value; - this.cancelScheduledValues(now); - this._value.setValueAtTime(currentVal, now); - return this; - }; + Tone.prototype.isFrequency = function () { + var freqFormat = new RegExp(/^\d*\.?\d+hz$/i); + return function (freq) { + return freqFormat.test(freq); + }; + }(); + /////////////////////////////////////////////////////////////////////////// + // TO SECOND CONVERSIONS + /////////////////////////////////////////////////////////////////////////// /** - * Schedules a linear continuous change in parameter value from the - * previous scheduled parameter value to the given value. - * - * @param {number} value - * @param {Time} endTime - * @returns {Tone.Signal} this + * @private + * @return {Object} The Transport's BPM if the Transport exists, + * otherwise returns reasonable defaults. */ - Tone.Signal.prototype.linearRampToValueAtTime = function (value, endTime) { - value = this._fromUnits(value); - this._value.linearRampToValueAtTime(value, this.toSeconds(endTime)); - return this; - }; + function getTransportBpm() { + if (Tone.Transport && Tone.Transport.bpm) { + return Tone.Transport.bpm.value; + } else { + return 120; + } + } /** - * Schedules an exponential continuous change in parameter value from - * the previous scheduled parameter value to the given value. - * - * @param {number} value - * @param {Time} endTime - * @returns {Tone.Signal} this + * @private + * @return {Object} The Transport's Time Signature if the Transport exists, + * otherwise returns reasonable defaults. */ - Tone.Signal.prototype.exponentialRampToValueAtTime = function (value, endTime) { - value = this._fromUnits(value); - value = Math.max(0.00001, value); - this._value.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); - return this; - }; + function getTransportTimeSignature() { + if (Tone.Transport && Tone.Transport.timeSignature) { + return Tone.Transport.timeSignature; + } else { + return 4; + } + } /** - * Schedules an exponential continuous change in parameter value from - * the current time and current value to the given value. + * + * convert notation format strings to seconds * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //exponentially ramp to the value 2 over 4 seconds. - * signal.exponentialRampToValueNow(2, 4); - */ - Tone.Signal.prototype.exponentialRampToValueNow = function (value, rampTime) { - var now = this.now(); - // exponentialRampToValueAt cannot ever ramp from 0, apparently. - // More info: https://bugzilla.mozilla.org/show_bug.cgi?id=1125600#c2 - var currentVal = this.value; - this.setValueAtTime(Math.max(currentVal, 0.0001), now); - this.exponentialRampToValueAtTime(value, now + this.toSeconds(rampTime)); - return this; - }; - /** - * Schedules an linear continuous change in parameter value from - * the current time and current value to the given value at the given time. - * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //linearly ramp to the value 4 over 3 seconds. - * signal.linearRampToValueNow(4, 3); + * @param {String} notation + * @param {BPM=} bpm + * @param {number=} timeSignature + * @return {number} + * */ - Tone.Signal.prototype.linearRampToValueNow = function (value, rampTime) { - var now = this.now(); - this.setCurrentValueNow(now); - this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); - return this; + 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; }; /** - * Start exponentially approaching the target value at the given time with - * a rate having the given time constant. - * @param {number} value - * @param {Time} startTime - * @param {number} timeConstant - * @returns {Tone.Signal} this + * 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.Signal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { - value = this._fromUnits(value); - // The value will never be able to approach without timeConstant > 0. - // http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation - // is described. 0 results in a division by 0. - timeConstant = Math.max(0.00001, timeConstant); - this._value.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); - return this; + 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); }; /** - * Sets an array of arbitrary parameter values starting at the given time - * for the given duration. - * - * @param {Array} values - * @param {Time} startTime - * @param {Time} duration - * @returns {Tone.Signal} this + * Convert ticks into seconds + * @param {Ticks} ticks + * @param {BPM=} bpm + * @return {number} seconds */ - Tone.Signal.prototype.setValueCurveAtTime = function (values, startTime, duration) { - for (var i = 0; i < values.length; i++) { - values[i] = this._fromUnits(values[i]); + Tone.prototype.ticksToSeconds = function (ticks, bpm) { + if (this.isUndef(Tone.Transport)) { + return 0; } - this._value.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); - return this; + ticks = parseFloat(ticks); + bpm = this.defaultArg(bpm, getTransportBpm()); + var tickTime = 60 / bpm / Tone.Transport.PPQ; + return tickTime * ticks; }; /** - * Cancels all scheduled parameter changes with times greater than or - * equal to startTime. + * Convert a frequency into seconds. + * Accepts numbers and strings: i.e. "10hz" or + * 10 both return 0.1. * - * @param {Time} startTime - * @returns {Tone.Signal} this + * @param {Frequency} freq + * @return {number} */ - Tone.Signal.prototype.cancelScheduledValues = function (startTime) { - this._value.cancelScheduledValues(this.toSeconds(startTime)); - return this; + Tone.prototype.frequencyToSeconds = function (freq) { + return 1 / parseFloat(freq); }; /** - * Ramps to the given value over the duration of the rampTime. - * Automatically selects the best ramp type (exponential or linear) - * depending on the `units` of the signal - * - * @param {number} value - * @param {Time} rampTime the time that it takes the - * value to ramp from it's current value - * @returns {Tone.Signal} this - * @example - * //ramp to the value either linearly or exponentially - * //depending on the "units" value of the signal - * signal.rampTo(0, 10); + * Convert a sample count to seconds. + * @param {number} samples + * @return {number} */ - Tone.Signal.prototype.rampTo = function (value, rampTime) { - rampTime = this.defaultArg(rampTime, 0); - if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) { - this.exponentialRampToValueNow(value, rampTime); - } else { - this.linearRampToValueNow(value, rampTime); - } - return this; + Tone.prototype.samplesToSeconds = function (samples) { + return samples / this.context.sampleRate; }; /** - * dispose and disconnect - * @returns {Tone.Signal} this + * Convert from seconds to samples. + * @param {number} seconds + * @return {number} The number of samples */ - Tone.Signal.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._value = null; - this._scaler = null; - return this; + Tone.prototype.secondsToSamples = function (seconds) { + return seconds * this.context.sampleRate; }; /////////////////////////////////////////////////////////////////////////// - // STATIC + // FROM SECOND CONVERSIONS /////////////////////////////////////////////////////////////////////////// /** - * the constant signal generator - * @static - * @private - * @const - * @type {OscillatorNode} - */ - Tone.Signal._generator = null; - /** - * the signal generator waveshaper. makes the incoming signal - * only output 1 for all inputs. - * @static - * @private - * @const - * @type {Tone.WaveShaper} - */ - Tone.Signal._constant = null; + * 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(':'); + }; /** - * initializer function + * Convert a number in seconds to a frequency. + * @param {number} seconds + * @return {number} */ - Tone._initAudioContext(function (audioContext) { - Tone.Signal._generator = audioContext.createOscillator(); - Tone.Signal._constant = new Tone.WaveShaper([ - 1, - 1 - ]); - Tone.Signal._generator.connect(Tone.Signal._constant); - Tone.Signal._generator.start(0); - Tone.Signal._generator.noGC(); - }); - return Tone.Signal; - }); - Module(function (Tone) { - + Tone.prototype.secondsToFrequency = function (seconds) { + return 1 / seconds; + }; + /////////////////////////////////////////////////////////////////////////// + // GENERALIZED CONVERSIONS + /////////////////////////////////////////////////////////////////////////// /** - * @class Pow applies an exponent to the incoming signal. The incoming signal - * must be AudioRange. + * Convert seconds to the closest transportTime in the form + * measures:quarters:sixteenths * - * @extends {Tone.SignalBase} - * @constructor - * @param {number} exp The exponent to apply to the incoming signal, must be at least 2. - * @example - * var pow = new Tone.Pow(2); - * var sig = new Tone.Signal(0.5).connect(pow); - * //output of pow is 0.25. + * @method toTransportTime + * + * @param {Time} time + * @param {BPM=} bpm + * @param {number=} timeSignature + * @return {TransportTime} + * + * @lends Tone.prototype.toTransportTime */ - Tone.Pow = function (exp) { - /** - * the exponent - * @private - * @type {number} - */ - this._exp = this.defaultArg(exp, 1); - /** - * @type {WaveShaperNode} - * @private - */ - this._expScaler = this.input = this.output = new Tone.WaveShaper(this._expFunc(this._exp), 8192); + Tone.prototype.toTransportTime = function (time, bpm, timeSignature) { + var seconds = this.toSeconds(time); + return this.secondsToTransportTime(seconds, bpm, timeSignature); }; - Tone.extend(Tone.Pow, Tone.SignalBase); /** - * The value of the exponent. - * @memberOf Tone.Pow# - * @type {number} - * @name value + * 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 */ - Object.defineProperty(Tone.Pow.prototype, 'value', { - get: function () { - return this._exp; - }, - set: function (exp) { - this._exp = exp; - this._expScaler.setMap(this._expFunc(this._exp)); + 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; } - }); + }; /** - * the function which maps the waveshaper - * @param {number} exp - * @return {function} - * @private + * Convert the time representation into ticks. + * Now-Relative timing will be relative to the current + * Tone.Transport.ticks. + * @param {Time} time + * @return {Ticks} */ - Tone.Pow.prototype._expFunc = function (exp) { - return function (val) { - return Math.pow(Math.abs(val), exp); - }; + 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); }; /** - * Clean up. - * @returns {Tone.Pow} this + * convert a time into samples + * + * @param {Time} time + * @return {number} */ - Tone.Pow.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._expScaler.dispose(); - this._expScaler = null; - return this; + Tone.prototype.toSamples = function (time) { + var seconds = this.toSeconds(time); + return Math.round(seconds * this.context.sampleRate); }; - return Tone.Pow; - }); - Module(function (Tone) { - /** - * @class Tone.Envelope is an [ADSR](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope) - * envelope generator. Tone.Envelope outputs a signal which - * can be connected to an AudioParam or Tone.Signal. - * + * Convert Time into seconds. + * + * Unlike the method which it overrides, this takes into account + * transporttime and musical notation. * - * @constructor - * @extends {Tone} - * @param {Time} [attack] The amount of time it takes for the envelope to go from - * 0 to it's maximum value. - * @param {Time} [decay] The period of time after the attack that it takes for the envelope - * to fall to the sustain value. - * @param {NormalRange} [sustain] The percent of the maximum value that the envelope rests at until - * the release is triggered. - * @param {Time} [release] The amount of time after the release is triggered it takes to reach 0. - * @example - * //an amplitude envelope - * var gainNode = Tone.context.createGain(); - * var env = new Tone.Envelope({ - * "attack" : 0.1, - * "decay" : 0.2, - * "sustain" : 1, - * "release" : 0.8, - * }); - * env.connect(gainNode.gain); + * 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.Envelope = function () { - //get all of the defaults - var options = this.optionsObject(arguments, [ - 'attack', - 'decay', - 'sustain', - 'release' - ], Tone.Envelope.defaults); - /** - * When triggerAttack is called, the attack time is the amount of - * time it takes for the envelope to reach it's maximum value. - * @type {Time} - */ - this.attack = options.attack; - /** - * After the attack portion of the envelope, the value will fall - * over the duration of the decay time to it's sustain value. - * @type {Time} - */ - this.decay = options.decay; - /** - * The sustain value is the value - * which the envelope rests at after triggerAttack is - * called, but before triggerRelease is invoked. - * @type {NormalRange} - */ - this.sustain = options.sustain; - /** - * After triggerRelease is called, the envelope's - * value will fall to it's miminum value over the - * duration of the release time. - * @type {Time} - */ - this.release = options.release; - /** - * the next time the envelope is attacked - * @type {number} - * @private - */ - this._nextAttack = Infinity; - /** - * the next time the envelope is decayed - * @type {number} - * @private - */ - this._nextDecay = Infinity; - /** - * the next time the envelope is sustain - * @type {number} - * @private - */ - this._nextSustain = Infinity; - /** - * the next time the envelope is released - * @type {number} - * @private - */ - this._nextRelease = Infinity; - /** - * the next time the envelope is at standby - * @type {number} - * @private - */ - this._nextStandby = Infinity; - /** - * the next time the envelope is at standby - * @type {number} - * @private - */ - this._attackCurve = Tone.Envelope.Type.Linear; - /** - * the last recorded velocity value - * @type {number} - * @private - */ - this._peakValue = 1; - /** - * the minimum output value - * @type {number} - * @private - */ - this._minOutput = 0.0001; - /** - * the signal - * @type {Tone.Signal} - * @private - */ - this._sig = this.output = new Tone.Signal(0); - //set the attackCurve initially - this.attackCurve = options.attackCurve; + Tone.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.extend(Tone.Envelope); /** - * the default parameters - * @static - * @const - */ - Tone.Envelope.defaults = { - 'attack': 0.01, - 'decay': 0.1, - 'sustain': 0.5, - 'release': 1, - 'attackCurve': 'linear' + * 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; + } }; /** - * the envelope time multipler - * @type {number} + * Helper method for Tone.toNotation * @private */ - Tone.Envelope.prototype._timeMult = 0.25; - /** - * Read the current value of the envelope. Useful for - * syncronizing visual output to the envelope. - * @memberOf Tone.Envelope# - * @type {Number} - * @name value - * @readOnly - */ - Object.defineProperty(Tone.Envelope.prototype, 'value', { - get: function () { - return this._sig.value; + 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; + } /** - * The slope of the attack. Either "linear" or "exponential". - * @memberOf Tone.Envelope# - * @type {string} - * @name attackCurve - * @example - * env.attackCurve = "linear"; + * 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 */ - 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); + 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; } - }); + }; /** - * Get the phase of the envelope at the specified time. - * @param {number} time - * @return {Tone.Envelope.Phase} - * @private + * Convert a number to the specified units. + * @param {number} val the value to convert + * @return {number} */ - Tone.Envelope.prototype._phaseAtTime = function (time) { - if (this._nextRelease > time) { - if (this._nextAttack <= time && this._nextDecay > time) { - return Tone.Envelope.Phase.Attack; - } else if (this._nextDecay <= time && this._nextSustain > time) { - return Tone.Envelope.Phase.Decay; - } else if (this._nextSustain <= time && this._nextRelease > time) { - return Tone.Envelope.Phase.Sustain; - } else { - return Tone.Envelope.Phase.Standby; + 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 if (this._nextRelease < time && this._nextStandby > time) { - return Tone.Envelope.Phase.Release; } else { - return Tone.Envelope.Phase.Standby; + return val; } }; + /////////////////////////////////////////////////////////////////////////// + // FREQUENCY CONVERSIONS + /////////////////////////////////////////////////////////////////////////// /** - * https://github.com/jsantell/web-audio-automation-timeline - * MIT License, copyright (c) 2014 Jordan Santell - * @private + * Note to scale index + * @type {Object} */ - Tone.Envelope.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { - return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + 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 }; /** - * @private + * scale index to note (sharps) + * @type {Array} */ - Tone.Envelope.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { - return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); - }; + var scaleIndexToNote = [ + 'C', + 'C#', + 'D', + 'D#', + 'E', + 'F', + 'F#', + 'G', + 'G#', + 'A', + 'A#', + 'B' + ]; /** - * @private - */ - Tone.Envelope.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { - return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); - }; - /** - * Get the envelopes value at the given time - * @param {number} time - * @param {number} velocity - * @return {number} - * @private + * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch, + * A4's values in Hertz. + * @type {Frequency} + * @static */ - Tone.Envelope.prototype._valueAtTime = function (time) { - var attack = this.toSeconds(this.attack); - var decay = this.toSeconds(this.decay); - var release = this.toSeconds(this.release); - switch (this._phaseAtTime(time)) { - case Tone.Envelope.Phase.Attack: - if (this._attackCurve === Tone.Envelope.Type.Linear) { - return this._linearInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time); - } else { - return this._exponentialInterpolate(this._nextAttack, this._minOutput, this._nextAttack + attack, this._peakValue, time); - } - break; - case Tone.Envelope.Phase.Decay: - return this._exponentialApproach(this._nextDecay, this._peakValue, this.sustain * this._peakValue, decay * this._timeMult, time); - case Tone.Envelope.Phase.Release: - return this._exponentialApproach(this._nextRelease, this._peakValue, this._minOutput, release * this._timeMult, time); - case Tone.Envelope.Phase.Sustain: - return this.sustain * this._peakValue; - case Tone.Envelope.Phase.Standby: - return this._minOutput; - } - }; + Tone.A4 = 440; /** - * Trigger the attack/decay portion of the ADSR envelope. - * @param {Time} [time=now] When the attack should start. - * @param {NormalRange} [velocity=1] The velocity of the envelope scales the vales. - * number between 0-1 - * @returns {Tone.Envelope} this + * Convert a note name to frequency. + * @param {String} note + * @return {number} * @example - * //trigger the attack 0.5 seconds from now with a velocity of 0.2 - * env.triggerAttack("+0.5", 0.2); + * var freq = tone.noteToFrequency("A4"); //returns 440 */ - Tone.Envelope.prototype.triggerAttack = function (time, velocity) { - //to seconds - time = this.toSeconds(time); - var attack = this.toSeconds(this.attack); - var decay = this.toSeconds(this.decay); - //get the phase and position - var valueAtTime = this._valueAtTime(time); - var attackPast = valueAtTime * attack; - //compute the timing - this._nextAttack = time - attackPast; - this._nextDecay = this._nextAttack + attack; - this._nextSustain = this._nextDecay + decay; - this._nextRelease = Infinity; - //get the values - this._peakValue = this.defaultArg(velocity, 1); - var scaledMax = this._peakValue; - var sustainVal = this.sustain * scaledMax; - //set the curve - this._sig.cancelScheduledValues(time); - this._sig.setValueAtTime(valueAtTime, time); - if (this._attackCurve === Tone.Envelope.Type.Linear) { - this._sig.linearRampToValueAtTime(scaledMax, this._nextDecay); + 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 { - this._sig.exponentialRampToValueAtTime(scaledMax, this._nextDecay); + return 0; } - this._sig.setTargetAtTime(sustainVal, this._nextDecay, decay * this._timeMult); - return this; }; /** - * Triggers the release of the envelope. - * @param {Time} [time=now] When the release portion of the envelope should start. - * @returns {Tone.Envelope} this - * @example - * //trigger release immediately - * env.triggerRelease(); + * Convert a frequency to a note name (i.e. A4, C#5). + * @param {number} freq + * @return {String} */ - Tone.Envelope.prototype.triggerRelease = function (time) { - time = this.toSeconds(time); - var phase = this._phaseAtTime(time); - var release = this.toSeconds(this.release); - //computer the value at the start of the next release - var valueAtTime = this._valueAtTime(time); - this._peakValue = valueAtTime; - this._nextRelease = time; - this._nextStandby = this._nextRelease + release; - //set the values - this._sig.cancelScheduledValues(this._nextRelease); - //if the phase is in the attack still, must reschedule the rest of the attack - if (phase === Tone.Envelope.Phase.Attack) { - this._sig.setCurrentValueNow(); - if (this.attackCurve === Tone.Envelope.Type.Linear) { - this._sig.linearRampToValueAtTime(this._peakValue, this._nextRelease); - } else { - this._sig.exponentialRampToValueAtTime(this._peakValue, this._nextRelease); - } - } else { - this._sig.setValueAtTime(this._peakValue, this._nextRelease); + 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; } - this._sig.setTargetAtTime(this._minOutput, this._nextRelease, release * this._timeMult); - return this; + var noteName = scaleIndexToNote[noteNumber % 12]; + return noteName + octave.toString(); }; /** - * triggerAttackRelease is shorthand for triggerAttack, then waiting - * some duration, then triggerRelease. - * @param {Time} duration The duration of the sustain. - * @param {Time} [time=now] When the attack should be triggered. - * @param {number} [velocity=1] The velocity of the envelope. - * @returns {Tone.Envelope} this + * 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 - * //trigger the attack and then the release after 0.6 seconds. - * env.triggerAttackRelease(0.6); - */ - Tone.Envelope.prototype.triggerAttackRelease = function (duration, time, velocity) { - time = this.toSeconds(time); - this.triggerAttack(time, velocity); - this.triggerRelease(time + this.toSeconds(duration)); - return this; - }; - /** - * Borrows the connect method from Tone.Signal. - * @function - * @private - */ - Tone.Envelope.prototype.connect = Tone.Signal.prototype.connect; - /** - * Disconnect and dispose. - * @returns {Tone.Envelope} this + * tone.intervalToFrequencyRatio(0); // returns 1 + * tone.intervalToFrequencyRatio(12); // returns 2 */ - Tone.Envelope.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._sig.dispose(); - this._sig = null; - return this; + Tone.prototype.intervalToFrequencyRatio = function (interval) { + return Math.pow(2, interval / 12); }; /** - * The phase of the envelope. - * @enum {string} + * 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.Envelope.Phase = { - Attack: 'attack', - Decay: 'decay', - Sustain: 'sustain', - Release: 'release', - Standby: 'standby' + Tone.prototype.midiToNote = function (midiNumber) { + var octave = Math.floor(midiNumber / 12) - 1; + var note = midiNumber % 12; + return scaleIndexToNote[note] + octave; }; /** - * The phase of the envelope. - * @enum {string} + * 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.Envelope.Type = { - Linear: 'linear', - Exponential: 'exponential' + 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 Tone.Envelope; - }); - Module(function (Tone) { - /** - * @class Tone.AmplitudeEnvelope is a Tone.Envelope connected to a gain node. - * Unlike Tone.Envelope, which outputs the envelope's value, Tone.AmplitudeEnvelope accepts - * an audio signal as the input and will apply the envelope to the amplitude - * of the signal. Read more about ADSR Envelopes on [Wikipedia](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope). - * - * @constructor - * @extends {Tone.Envelope} - * @param {Time|Object} [attack] The amount of time it takes for the envelope to go from - * 0 to it's maximum value. - * @param {Time} [decay] The period of time after the attack that it takes for the envelope - * to fall to the sustain value. - * @param {NormalRange} [sustain] The percent of the maximum value that the envelope rests at until - * the release is triggered. - * @param {Time} [release] The amount of time after the release is triggered it takes to reach 0. + * Convert a MIDI note to frequency value. + * + * @param {MIDI} midi The midi number to convert. + * @return {Frequency} the corresponding frequency value * @example - * var ampEnv = new Tone.AmplitudeEnvelope({ - * "attack": 0.1, - * "decay": 0.2, - * "sustain": 1.0, - * "release": 0.8 - * }).toMaster(); - * //create an oscillator and connect it - * var osc = new Tone.Oscillator().connect(ampEnv).start(); - * //trigger the envelopes attack and release "8t" apart - * ampEnv.triggerAttackRelease("8t"); + * tone.midiToFrequency(57); // returns 440 */ - Tone.AmplitudeEnvelope = function () { - Tone.Envelope.apply(this, arguments); - /** - * the input node - * @type {GainNode} - * @private - */ - this.input = this.output = this.context.createGain(); - this._sig.connect(this.output.gain); + Tone.prototype.midiToFrequency = function (midi) { + return Tone.A4 * Math.pow(2, (midi - 69) / 12); }; - Tone.extend(Tone.AmplitudeEnvelope, Tone.Envelope); - return Tone.AmplitudeEnvelope; + return Tone; }); Module(function (Tone) { /** - * @class Tone.Compressor is a thin wrapper around the Web Audio - * [DynamicsCompressorNode](http://webaudio.github.io/web-audio-api/#the-dynamicscompressornode-interface). - * Compression reduces the volume of loud sounds or amplifies quiet sounds - * by narrowing or "compressing" an audio signal's dynamic range. - * Read more on [Wikipedia](https://en.wikipedia.org/wiki/Dynamic_range_compression). - * + * @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} - * @constructor - * @param {Decibels|Object} [threshold] The value above which the compression starts to be applied. - * @param {Positive} [ratio] The gain reduction ratio. - * @example - * var comp = new Tone.Compressor(-30, 3); + * @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.Compressor = function () { + Tone.Param = function () { var options = this.optionsObject(arguments, [ - 'threshold', - 'ratio' - ], Tone.Compressor.defaults); + 'param', + 'units', + 'convert' + ], Tone.Param.defaults); /** - * the compressor node - * @type {DynamicsCompressorNode} + * The native parameter to control + * @type {AudioParam} * @private */ - this._compressor = this.input = this.output = this.context.createDynamicsCompressor(); - /** - * the threshold vaue - * @type {Decibels} - * @signal - */ - this.threshold = this._compressor.threshold; - /** - * The attack parameter - * @type {Time} - * @signal - */ - this.attack = new Tone.Signal(this._compressor.attack, Tone.Type.Time); + this._param = this.input = options.param; /** - * The release parameter - * @type {Time} - * @signal + * The units of the parameter + * @type {Tone.Type} */ - this.release = new Tone.Signal(this._compressor.release, Tone.Type.Time); + this.units = options.units; /** - * The knee parameter - * @type {Decibels} - * @signal + * If the value should be converted or not + * @type {Boolean} */ - this.knee = this._compressor.knee; + this.convert = options.convert; /** - * The ratio value - * @type {Number} - * @signal + * True if the signal value is being overridden by + * a connected signal. + * @readOnly + * @type {boolean} + * @private */ - this.ratio = this._compressor.ratio; - //set the defaults - this._readOnly([ - 'knee', - 'release', - 'attack', - 'ratio', - 'threshold' - ]); - this.set(options); + this.overridden = false; + if (!this.isUndef(options.value)) { + this.value = options.value; + } }; - Tone.extend(Tone.Compressor); + Tone.extend(Tone.Param); /** - * @static + * Defaults + * @type {Object} * @const - * @type {Object} */ - Tone.Compressor.defaults = { - 'ratio': 12, - 'threshold': -24, - 'release': 0.25, - 'attack': 0.003, - 'knee': 30 + Tone.Param.defaults = { + 'units': Tone.Type.Default, + 'convert': true, + 'param': undefined }; /** - * clean up - * @returns {Tone.Compressor} this + * The current value of the parameter. + * @memberOf Tone.Param# + * @type {Number} + * @name value */ - Tone.Compressor.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'knee', - 'release', - 'attack', - 'ratio', - 'threshold' - ]); - this._compressor.disconnect(); - this._compressor = null; - this.attack.dispose(); - this.attack = null; - this.release.dispose(); - this.release = null; - this.threshold = null; - this.ratio = null; - this.knee = null; - return this; - }; - return Tone.Compressor; - }); - Module(function (Tone) { - + Object.defineProperty(Tone.Param.prototype, 'value', { + get: function () { + return this._toUnits(this._param.value); + }, + set: function (value) { + var convertedVal = this._fromUnits(value); + this._param.value = convertedVal; + } + }); /** - * @class Add a signal and a number or two signals. When no value is - * passed into the constructor, Tone.Add will sum input[0] - * and input[1]. If a value is passed into the constructor, - * the it will be added to the input. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} value If no value is provided, Tone.Add will sum the first - * and second inputs. - * @example - * var signal = new Tone.Signal(2); - * var add = new Tone.Add(2); - * signal.connect(add); - * //the output of add equals 4 - * @example - * //if constructed with no arguments - * //it will add the first and second inputs - * var add = new Tone.Add(); - * var sig0 = new Tone.Signal(3).connect(add, 0, 0); - * var sig1 = new Tone.Signal(4).connect(add, 0, 1); - * //the output of add equals 7. + * Convert the given value from the type specified by Tone.Param.units + * into the destination value (such as Gain or Frequency). + * @private + * @param {*} val the value to convert + * @return {number} the number which the value should be set to */ - Tone.Add = function (value) { - Tone.call(this, 2, 0); - /** - * the summing node - * @type {GainNode} - * @private - */ - this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain(); - /** - * @private - * @type {Tone.Signal} - */ - this._value = this.input[1] = new Tone.Signal(value); - this._value.connect(this._sum); + Tone.Param.prototype._fromUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Time: + return this.toSeconds(val); + case Tone.Type.Frequency: + return this.toFrequency(val); + case Tone.Type.Decibels: + return this.dbToGain(val); + case Tone.Type.NormalRange: + return Math.min(Math.max(val, 0), 1); + case Tone.Type.AudioRange: + return Math.min(Math.max(val, -1), 1); + case Tone.Type.Positive: + return Math.max(val, 0); + default: + return val; + } + } else { + return val; + } }; - Tone.extend(Tone.Add, Tone.Signal); /** - * Clean up. - * @returns {Tone.Add} this + * Convert the parameters value into the units specified by Tone.Param.units. + * @private + * @param {number} val the value to convert + * @return {number} */ - Tone.Add.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._sum.disconnect(); - this._sum = null; - this._value.dispose(); - this._value = null; - return this; + Tone.Param.prototype._toUnits = function (val) { + if (this.convert || this.isUndef(this.convert)) { + switch (this.units) { + case Tone.Type.Decibels: + return this.gainToDb(val); + default: + return val; + } + } else { + return val; + } }; - return Tone.Add; - }); - Module(function (Tone) { - /** - * @class Multiply two incoming signals. Or, if a number is given in the constructor, - * multiplies the incoming signal by that value. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} value Constant value to multiple. If no value is provided, - * it will return the product of the first and second inputs - * @example - * var mult = new Tone.Multiply(); - * var sigA = new Tone.Signal(3); - * var sigB = new Tone.Signal(4); - * sigA.connect(mult, 0, 0); - * sigB.connect(mult, 0, 1); - * //output of mult is 12. - * @example - * var mult = new Tone.Multiply(10); - * var sig = new Tone.Signal(2).connect(mult); - * //the output of mult is 20. + * the minimum output value + * @type {Number} + * @private */ - Tone.Multiply = function (value) { - Tone.call(this, 2, 0); - /** - * the input node is the same as the output node - * it is also the GainNode which handles the scaling of incoming signal - * - * @type {GainNode} - * @private - */ - this._mult = this.input[0] = this.output = this.context.createGain(); - /** - * the scaling parameter - * @type {AudioParam} - * @private - */ - this._value = this.input[1] = this.output.gain; - this._value.value = this.defaultArg(value, 0); - }; - Tone.extend(Tone.Multiply, Tone.Signal); + Tone.Param.prototype._minOutput = 0.00001; /** - * clean up - * @returns {Tone.Multiply} this + * Schedules a parameter value change at the given time. + * @param {*} value The value to set the signal. + * @param {Time} time The time when the change should occur. + * @returns {Tone.Param} this + * @example + * //set the frequency to "G4" in exactly 1 second from now. + * freq.setValueAtTime("G4", "+1"); */ - Tone.Multiply.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._mult.disconnect(); - this._mult = null; - this._value = null; + Tone.Param.prototype.setValueAtTime = function (value, time) { + value = this._fromUnits(value); + this._param.setValueAtTime(value, this.toSeconds(time)); return this; }; - return Tone.Multiply; - }); - Module(function (Tone) { - /** - * @class Negate the incoming signal. i.e. an input signal of 10 will output -10 + * Creates a schedule point with the current value at the current time. + * This is useful for creating an automation anchor point in order to + * schedule changes from the current value. * - * @constructor - * @extends {Tone.SignalBase} - * @example - * var neg = new Tone.Negate(); - * var sig = new Tone.Signal(-2).connect(neg); - * //output of neg is positive 2. + * @param {number=} now (Optionally) pass the now value in. + * @returns {Tone.Param} this */ - Tone.Negate = function () { - /** - * negation is done by multiplying by -1 - * @type {Tone.Multiply} - * @private - */ - this._multiply = this.input = this.output = new Tone.Multiply(-1); + Tone.Param.prototype.setRampPoint = function (now) { + now = this.defaultArg(now, this.now()); + var currentVal = this._param.value; + this._param.setValueAtTime(currentVal, now); + return this; }; - Tone.extend(Tone.Negate, Tone.SignalBase); /** - * clean up - * @returns {Tone.Negate} this + * Schedules a linear continuous change in parameter value from the + * previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.Param} this */ - Tone.Negate.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._multiply.dispose(); - this._multiply = null; + Tone.Param.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + this._param.linearRampToValueAtTime(value, this.toSeconds(endTime)); return this; }; - return Tone.Negate; - }); - Module(function (Tone) { - /** - * @class Subtract the signal connected to input[1] from the signal connected - * to input[0]. If an argument is provided in the constructor, the - * signals .value will be subtracted from the incoming signal. - * - * @extends {Tone.Signal} - * @constructor - * @param {number=} value The value to subtract from the incoming signal. If the value - * is omitted, it will subtract the second signal from the first. - * @example - * var sub = new Tone.Subtract(1); - * var sig = new Tone.Signal(4).connect(sub); - * //the output of sub is 3. - * @example - * var sub = new Tone.Subtract(); - * var sigA = new Tone.Signal(10); - * var sigB = new Tone.Signal(2.5); - * sigA.connect(sub, 0, 0); - * sigB.connect(sub, 0, 1); - * //output of sub is 7.5 + * Schedules an exponential continuous change in parameter value from + * the previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.Param} this */ - Tone.Subtract = function (value) { - Tone.call(this, 2, 0); - /** - * the summing node - * @type {GainNode} - * @private - */ - this._sum = this.input[0] = this.output = this.context.createGain(); - /** - * negate the input of the second input before connecting it - * to the summing node. - * @type {Tone.Negate} - * @private - */ - this._neg = new Tone.Negate(); - /** - * the node where the value is set - * @private - * @type {Tone.Signal} - */ - this._value = this.input[1] = new Tone.Signal(value); - this._value.chain(this._neg, this._sum); + Tone.Param.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + this._param.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); + return this; }; - Tone.extend(Tone.Subtract, Tone.Signal); /** - * Clean up. - * @returns {Tone.SignalBase} this + * Schedules an exponential continuous change in parameter value from + * the current time and current value to the given value over the + * duration of the rampTime. + * + * @param {number} value The value to ramp to. + * @param {Time} rampTime the time that it takes the + * value to ramp from it's current value + * @returns {Tone.Param} this + * @example + * //exponentially ramp to the value 2 over 4 seconds. + * signal.exponentialRampToValue(2, 4); */ - Tone.Subtract.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._neg.dispose(); - this._neg = null; - this._sum.disconnect(); - this._sum = null; - this._value.dispose(); - this._value = null; + 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)); return this; }; - return Tone.Subtract; - }); - Module(function (Tone) { - /** - * @class GreaterThanZero outputs 1 when the input is strictly greater than zero + * Schedules an linear continuous change in parameter value from + * the current time and current value to the given value over the + * duration of the rampTime. * - * @constructor - * @extends {Tone.SignalBase} + * @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 + * @returns {Tone.Param} this * @example - * var gt0 = new Tone.GreaterThanZero(); - * var sig = new Tone.Signal(0.01).connect(gt0); - * //the output of gt0 is 1. - * sig.value = 0; - * //the output of gt0 is 0. + * //linearly ramp to the value 4 over 3 seconds. + * signal.linearRampToValue(4, 3); */ - Tone.GreaterThanZero = function () { - /** - * @type {Tone.WaveShaper} - * @private - */ - this._thresh = this.output = new Tone.WaveShaper(function (val) { - if (val <= 0) { - return 0; - } else { - return 1; - } - }); - /** - * scale the first thresholded signal by a large value. - * this will help with values which are very close to 0 - * @type {Tone.Multiply} - * @private - */ - this._scale = this.input = new Tone.Multiply(10000); - //connections - this._scale.connect(this._thresh); + Tone.Param.prototype.linearRampToValue = function (value, rampTime) { + var now = this.now(); + this.setRampPoint(now); + this.linearRampToValueAtTime(value, now + this.toSeconds(rampTime)); + return this; }; - Tone.extend(Tone.GreaterThanZero, Tone.SignalBase); /** - * dispose method - * @returns {Tone.GreaterThanZero} this + * Start exponentially approaching the target value at the given time with + * a rate having the given time constant. + * @param {number} value + * @param {Time} startTime + * @param {number} timeConstant + * @returns {Tone.Param} this */ - Tone.GreaterThanZero.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._scale.dispose(); - this._scale = null; - this._thresh.dispose(); - this._thresh = null; + Tone.Param.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + // The value will never be able to approach without timeConstant > 0. + // http://www.w3.org/TR/webaudio/#dfn-setTargetAtTime, where the equation + // is described. 0 results in a division by 0. + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + this._param.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); return this; }; - return Tone.GreaterThanZero; - }); - Module(function (Tone) { - /** - * @class EqualZero outputs 1 when the input is equal to - * 0 and outputs 0 otherwise. + * Sets an array of arbitrary parameter values starting at the given time + * for the given duration. + * + * @param {Array} values + * @param {Time} startTime + * @param {Time} duration + * @returns {Tone.Param} this + */ + Tone.Param.prototype.setValueCurveAtTime = function (values, startTime, duration) { + for (var i = 0; i < values.length; i++) { + values[i] = this._fromUnits(values[i]); + } + this._param.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); + return this; + }; + /** + * Cancels all scheduled parameter changes with times greater than or + * equal to startTime. * - * @constructor - * @extends {Tone.SignalBase} + * @param {Time} startTime + * @returns {Tone.Param} this + */ + Tone.Param.prototype.cancelScheduledValues = function (startTime) { + this._param.cancelScheduledValues(this.toSeconds(startTime)); + return this; + }; + /** + * Ramps to the given value over the duration of the rampTime. + * Automatically selects the best ramp type (exponential or linear) + * depending on the `units` of the signal + * + * @param {number} value + * @param {Time} rampTime the time that it takes the + * value to ramp from it's current value + * @returns {Tone.Param} this * @example - * var eq0 = new Tone.EqualZero(); - * var sig = new Tone.Signal(0).connect(eq0); - * //the output of eq0 is 1. + * //ramp to the value either linearly or exponentially + * //depending on the "units" value of the signal + * signal.rampTo(0, 10); */ - 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.Param.prototype.rampTo = function (value, rampTime) { + rampTime = this.defaultArg(rampTime, 0); + if (this.units === Tone.Type.Frequency || this.units === Tone.Type.BPM) { + this.exponentialRampToValue(value, rampTime); + } else { + this.linearRampToValue(value, rampTime); + } + return this; }; - Tone.extend(Tone.EqualZero, Tone.SignalBase); /** - * Clean up. - * @returns {Tone.EqualZero} this + * Clean up + * @returns {Tone.Param} this */ - Tone.EqualZero.prototype.dispose = function () { + Tone.Param.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; + this._param = null; return this; }; - return Tone.EqualZero; + return Tone.Param; }); Module(function (Tone) { /** - * @class Output 1 if the signal is equal to the value, otherwise outputs 0. - * Can accept two signals if connected to inputs 0 and 1. - * - * @constructor - * @extends {Tone.SignalBase} - * @param {number=} value The number to compare the incoming signal to - * @example - * var eq = new Tone.Equal(3); - * var sig = new Tone.Signal(3).connect(eq); - * //the output of eq is 1. + * @class A thin wrapper around the Native Web Audio GainNode. + * The GainNode is a basic building block of the Web Audio + * API and is useful for routing audio and adjusting gains. + * @extends {Tone} + * @param {Number=} gain The initial gain of the GainNode + * @param {Tone.Type=} units The units of the gain parameter. */ - Tone.Equal = function (value) { - Tone.call(this, 2, 0); + Tone.Gain = function () { + var options = this.optionsObject(arguments, [ + 'gain', + 'units' + ], Tone.Gain.defaults); /** - * subtract the value from the incoming signal - * - * @type {Tone.Add} + * The GainNode + * @type {GainNode} * @private */ - this._sub = this.input[0] = new Tone.Subtract(value); + this.input = this.output = this._gainNode = this.context.createGain(); /** - * @type {Tone.EqualZero} - * @private + * The gain parameter of the gain node. + * @type {AudioParam} + * @signal */ - this._equals = this.output = new Tone.EqualZero(); - this._sub.connect(this._equals); - this.input[1] = this._sub.input[1]; + this.gain = new Tone.Param({ + 'param': this._gainNode.gain, + 'units': options.units, + 'value': options.gain, + 'convert': options.convert + }); + this._readOnly('gain'); }; - Tone.extend(Tone.Equal, Tone.SignalBase); + Tone.extend(Tone.Gain); /** - * The value to compare to the incoming signal. - * @memberOf Tone.Equal# - * @type {number} - * @name value + * The defaults + * @const + * @type {Object} */ - Object.defineProperty(Tone.Equal.prototype, 'value', { - get: function () { - return this._sub.value; - }, - set: function (value) { - this._sub.value = value; - } - }); + Tone.Gain.defaults = { + 'gain': 1, + 'convert': true + }; /** * Clean up. - * @returns {Tone.Equal} this + * @return {Tone.Gain} 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; + Tone.Gain.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._gainNode.disconnect(); + this._gainNode = null; + this._writable('gain'); + this.gain.dispose(); + this.gain = null; }; - return Tone.Equal; + return Tone.Gain; }); Module(function (Tone) { /** - * @class Select between any number of inputs, sending the one - * selected by the gate signal to the output + * @class A signal is an audio-rate value. Tone.Signal is a core component of the library. + * Unlike a number, Signals can be scheduled with sample-level accuracy. Tone.Signal + * has all of the methods available to native Web Audio + * [AudioParam](http://webaudio.github.io/web-audio-api/#the-audioparam-interface) + * as well as additional conveniences. Read more about working with signals + * [here](https://github.com/Tonejs/Tone.js/wiki/Signals). * * @constructor - * @extends {Tone.SignalBase} - * @param {number} [sourceCount=2] the number of inputs the switch accepts + * @extends {Tone.Param} + * @param {Number|AudioParam} [value] Initial value of the signal. If an AudioParam + * is passed in, that parameter will be wrapped + * and controlled by the Signal. + * @param {string} [units=Number] unit The units the signal is in. * @example - * var 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); + * var signal = new Tone.Signal(10); */ - Tone.Select = function (sourceCount) { - sourceCount = this.defaultArg(sourceCount, 2); - Tone.call(this, sourceCount, 1); + Tone.Signal = function () { + var options = this.optionsObject(arguments, [ + 'value', + 'units' + ], Tone.Signal.defaults); /** - * the control signal - * @type {Number} - * @signal + * The node where the constant signal value is scaled. + * @type {GainNode} + * @private */ - 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.output = this._gain = this.context.createGain(); + options.param = this._gain.gain; + Tone.Param.call(this, options); + /** + * The node where the value is set. + * @type {Tone.Param} + * @private + */ + this.input = this._param = this._gain.gain; + //connect the const output to the node output + Tone.Signal._constant.chain(this._gain); }; - Tone.extend(Tone.Select, Tone.SignalBase); + Tone.extend(Tone.Signal, Tone.Param); /** - * 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"); + * The default values + * @type {Object} + * @static + * @const */ - 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.Signal.defaults = { + 'value': 0, + 'units': Tone.Type.Default, + 'convert': true + }; + /** + * When signals connect to other signals or AudioParams, + * they take over the output value of that signal or AudioParam. + * For all other nodes, the behavior is the same as a default connect. + * + * @override + * @param {AudioParam|AudioNode|Tone.Signal|Tone} node + * @param {number} [outputNumber=0] The output number to connect from. + * @param {number} [inputNumber=0] The input number to connect to. + * @returns {Tone.SignalBase} this + * @method */ - 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); + Tone.Signal.prototype.connect = Tone.SignalBase.prototype.connect; + /** + * dispose and disconnect + * @returns {Tone.Signal} this + */ + Tone.Signal.prototype.dispose = function () { + Tone.Param.prototype.dispose.call(this); + this._param = null; + this._gain.disconnect(); + this._gain = null; return this; }; - ////////////START HELPER//////////// + /////////////////////////////////////////////////////////////////////////// + // STATIC + /////////////////////////////////////////////////////////////////////////// /** - * helper class for Tone.Select representing a single gate - * @constructor - * @extends {Tone} + * Generates a constant output of 1. + * @static * @private + * @const + * @type {AudioBufferSourceNode} */ - 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); + Tone.Signal._constant = null; /** - * clean up - * @private + * initializer function */ - 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; + 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] = 1; + } + Tone.Signal._constant = audioContext.createBufferSource(); + Tone.Signal._constant.channelCount = 1; + Tone.Signal._constant.channelCountMode = 'explicit'; + Tone.Signal._constant.buffer = buffer; + Tone.Signal._constant.loop = true; + Tone.Signal._constant.start(0); + Tone.Signal._constant.noGC(); + }); + return Tone.Signal; }); Module(function (Tone) { /** - * @class IfThenElse has three inputs. When the first input (if) is true (i.e. === 1), - * then it will pass the second input (then) through to the output, otherwise, - * if it's not true (i.e. === 0) then it will pass the third input (else) - * through to the output. - * - * @extends {Tone.SignalBase} - * @constructor - * @example - * var ifThenElse = new Tone.IfThenElse(); - * var ifSignal = new Tone.Signal(1).connect(ifThenElse.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 + * @class A Timeline class for scheduling and maintaining state + * along a timeline. All events must have a "time" property. + * Internally, events are stored in time order for fast + * retrieval. + * @extends {Tone} + * @param {Positive} [memory=Infinity] The number of previous events that are retained. */ - Tone.IfThenElse = function () { - Tone.call(this, 3, 0); + Tone.Timeline = function () { + var options = this.optionsObject(arguments, ['memory'], Tone.Timeline.defaults); /** - * the selector node which is responsible for the routing - * @type {Tone.Select} + * The array of scheduled timeline events + * @type {Array} * @private */ - this._selector = this.output = new Tone.Select(2); - //the input mapping - this.if = this.input[0] = this._selector.gate; - this.then = this.input[1] = this._selector.input[1]; - this.else = this.input[2] = this._selector.input[0]; - }; - Tone.extend(Tone.IfThenElse, Tone.SignalBase); - /** - * clean up - * @returns {Tone.IfThenElse} this - */ - Tone.IfThenElse.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._selector.dispose(); - this._selector = null; - this.if = null; - this.then = null; - this.else = null; - return this; - }; - return Tone.IfThenElse; - }); - Module(function (Tone) { - - /** - * @class [OR](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); + this._timeline = []; /** - * a private summing node - * @type {GainNode} + * An array of items to remove from the list. + * @type {Array} * @private */ - this._sum = this.context.createGain(); + this._toRemove = []; /** - * @type {Tone.Equal} + * Flag if the tieline is mid iteration * @private + * @type {Boolean} */ - 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._iterating = false; + /** + * The memory of the timeline, i.e. + * how many events in the past it will retain + * @type {Positive} + */ + this.memory = options.memory; + }; + Tone.extend(Tone.Timeline); + /** + * the default parameters + * @static + * @const + */ + Tone.Timeline.defaults = { 'memory': Infinity }; + /** + * The number of items in the timeline. + * @type {Number} + * @memberOf Tone.Timeline# + * @name length + * @readOnly + */ + Object.defineProperty(Tone.Timeline.prototype, 'length', { + get: function () { + return this._timeline.length; } - this._sum.connect(this._gtz); + }); + /** + * Insert an event object onto the timeline. Events must have a "time" attribute. + * @param {Object} event The event object to insert into the + * timeline. + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.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'); + } + event.time = this.toSeconds(event.time); + if (this._timeline.length) { + var index = this._search(event.time); + this._timeline.splice(index + 1, 0, event); + } else { + this._timeline.push(event); + } + //if the length is more than the memory, remove the previous ones + if (this.length > this.memory) { + var diff = this.length - this.memory; + this._timeline.splice(0, diff); + } + return this; }; - Tone.extend(Tone.OR, Tone.SignalBase); /** - * clean up - * @returns {Tone.OR} this + * Remove an event from the timeline. + * @param {Object} event The event object to remove from the list. + * @returns {Tone.Timeline} this */ - Tone.OR.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._gtz.dispose(); - this._gtz = null; - this._sum.disconnect(); - this._sum = null; + Tone.Timeline.prototype.removeEvent = function (event) { + if (this._iterating) { + this._toRemove.push(event); + } else { + var index = this._timeline.indexOf(event); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } return this; }; - 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. + * Get the 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. */ - 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.Timeline.prototype.getEvent = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index !== -1) { + return this._timeline[index]; + } else { + return null; } }; - Tone.extend(Tone.AND, Tone.SignalBase); /** - * clean up - * @returns {Tone.AND} this + * Get the event which is scheduled after the given time. + * @param {Number} time The time to query. + * @returns {Object} The event object after the given time */ - Tone.AND.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._equals.dispose(); - this._equals = null; - return this; + Tone.Timeline.prototype.getEventAfter = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index + 1 < this._timeline.length) { + return this._timeline[index + 1]; + } else { + return null; + } }; - 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. + * Get the event before the event at the given time. + * @param {Number} time The time to query. + * @returns {Object} The event object before the given time */ - Tone.NOT = Tone.EqualZero; - return Tone.NOT; - }); - Module(function (Tone) { - + Tone.Timeline.prototype.getEventBefore = function (time) { + time = this.toSeconds(time); + var index = this._search(time); + if (index - 1 >= 0) { + return this._timeline[index - 1]; + } else { + return null; + } + }; /** - * @class Output 1 if the signal is greater than the value, otherwise outputs 0. - * can compare two signals or a signal and a number. - * - * @constructor - * @extends {Tone.Signal} - * @param {number} [value=0] the value to compare to the incoming signal - * @example - * var gt = new Tone.GreaterThan(2); - * var sig = new Tone.Signal(4).connect(gt); - * //output of gt is equal 1. + * Cancel events after the given time + * @param {Time} time The time to query. + * @returns {Tone.Timeline} this */ - Tone.GreaterThan = function (value) { - Tone.call(this, 2, 0); - /** - * subtract the amount from the incoming signal - * @type {Tone.Subtract} - * @private - */ - this._value = this.input[0] = new Tone.Subtract(value); - this.input[1] = this._value.input[1]; - /** - * compare that amount to zero - * @type {Tone.GreaterThanZero} - * @private - */ - this._gtz = this.output = new Tone.GreaterThanZero(); - //connect - this._value.connect(this._gtz); + Tone.Timeline.prototype.cancel = function (after) { + if (this._timeline.length > 1) { + after = this.toSeconds(after); + var index = this._search(after); + if (index >= 0) { + this._timeline = this._timeline.slice(0, index); + } else { + this._timeline = []; + } + } else if (this._timeline.length === 1) { + //the first item's time + if (this._timeline[0].time >= after) { + this._timeline = []; + } + } + return this; }; - Tone.extend(Tone.GreaterThan, Tone.Signal); /** - * dispose method - * @returns {Tone.GreaterThan} this + * Cancel events before or equal to the given time. + * @param {Time} time The time to cancel before. + * @returns {Tone.Timeline} this */ - Tone.GreaterThan.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._value.dispose(); - this._value = null; - this._gtz.dispose(); - this._gtz = null; + Tone.Timeline.prototype.cancelBefore = function (time) { + if (this._timeline.length) { + time = this.toSeconds(time); + var index = this._search(time); + if (index >= 0) { + this._timeline = this._timeline.slice(index + 1); + } + } return this; }; - return Tone.GreaterThan; - }); - Module(function (Tone) { - /** - * @class Output 1 if the signal is less than the value, otherwise outputs 0. - * Can compare two signals or a signal and a number. - * - * @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 + * Does a binary serach on the timeline array and returns the + * event which is after or equal to the time. + * @param {Number} time + * @return {Number} the index in the timeline array + * @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._value = this.input[1] = new Tone.Signal(value); - //connect - this._neg.connect(this._gt); - this._value.connect(this._rhNeg); - this._rhNeg.connect(this._gt, 0, 1); + Tone.Timeline.prototype._search = function (time) { + var beginning = 0; + var len = this._timeline.length; + var end = len; + // continue searching while [imin,imax] is not empty + while (beginning <= end && beginning < len) { + // calculate the midpoint for roughly equal partition + var midPoint = Math.floor(beginning + (end - beginning) / 2); + var event = this._timeline[midPoint]; + if (event.time === time) { + //choose the last one that has the same time + for (var i = midPoint; i < this._timeline.length; i++) { + var testEvent = this._timeline[i]; + if (testEvent.time === time) { + midPoint = i; + } + } + return midPoint; + } else if (event.time > time) { + //search lower + end = midPoint - 1; + } else if (event.time < time) { + //search upper + beginning = midPoint + 1; + } + } + return beginning - 1; }; - Tone.extend(Tone.LessThan, Tone.Signal); /** - * Clean up. - * @returns {Tone.LessThan} this + * Internal iterator. Applies extra safety checks for + * removing items from the array. + * @param {Function} callback + * @param {Number=} lowerBound + * @param {Number=} upperBound + * @private */ - Tone.LessThan.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._neg.dispose(); - this._neg = null; - this._gt.dispose(); - this._gt = null; - this._rhNeg.dispose(); - this._rhNeg = null; - this._value.dispose(); - this._value = null; + Tone.Timeline.prototype._iterate = function (callback, lowerBound, upperBound) { + this._iterating = true; + lowerBound = this.defaultArg(lowerBound, 0); + upperBound = this.defaultArg(upperBound, this._timeline.length - 1); + for (var i = lowerBound; i <= upperBound; i++) { + callback(this._timeline[i]); + } + this._iterating = false; + if (this._toRemove.length > 0) { + for (var j = 0; j < this._toRemove.length; j++) { + var index = this._timeline.indexOf(this._toRemove[j]); + if (index !== -1) { + this._timeline.splice(index, 1); + } + } + this._toRemove = []; + } + }; + /** + * Iterate over everything in the array + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this + */ + Tone.Timeline.prototype.forEach = function (callback) { + this._iterate(callback); return this; }; - return Tone.LessThan; - }); - Module(function (Tone) { - /** - * @class Return the absolute value of an incoming signal. - * - * @constructor - * @extends {Tone.SignalBase} - * @example - * var signal = new Tone.Signal(-1); - * var abs = new Tone.Abs(); - * signal.connect(abs); - * //the output of abs is 1. + * Iterate over everything in the array at or before the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this */ - Tone.Abs = function () { - Tone.call(this, 1, 0); - /** - * @type {Tone.LessThan} - * @private - */ - this._ltz = new Tone.LessThan(0); - /** - * @type {Tone.Select} - * @private - */ - this._switch = this.output = new Tone.Select(2); - /** - * @type {Tone.Negate} - * @private - */ - this._negate = new Tone.Negate(); - //two signal paths, positive and negative - this.input.connect(this._switch, 0, 0); - this.input.connect(this._negate); - this._negate.connect(this._switch, 0, 1); - //the control signal - this.input.chain(this._ltz, this._switch.gate); + Tone.Timeline.prototype.forEachBefore = function (time, callback) { + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(callback, 0, upperBound); + } + return this; }; - Tone.extend(Tone.Abs, Tone.SignalBase); /** - * dispose method - * @returns {Tone.Abs} this + * Iterate over everything in the array after the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this */ - Tone.Abs.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._switch.dispose(); - this._switch = null; - this._ltz.dispose(); - this._ltz = null; - this._negate.dispose(); - this._negate = null; + Tone.Timeline.prototype.forEachAfter = function (time, callback) { + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var lowerBound = this._search(time); + this._iterate(callback, lowerBound + 1); return this; }; - return Tone.Abs; - }); - Module(function (Tone) { - /** - * @class Outputs the greater of two signals. If a number is provided in the constructor - * it will use that instead of the signal. - * - * @constructor - * @extends {Tone.Signal} - * @param {number=} max Max value if provided. if not provided, it will use the - * signal value from input 1. - * @example - * var max = new Tone.Max(2); - * var sig = new Tone.Signal(3).connect(max); - * //max outputs 3 - * sig.value = 1; - * //max outputs 2 - * @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. + * Iterate over everything in the array at or after the given time. Similar to + * forEachAfter, but includes the item(s) at the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this */ - Tone.Max = function (max) { - Tone.call(this, 2, 0); - this.input[0] = this.context.createGain(); - /** - * the max signal - * @type {Tone.Signal} - * @private - */ - this._value = this.input[1] = new Tone.Signal(max); - /** - * @type {Tone.Select} - * @private - */ - this._ifThenElse = this.output = new Tone.IfThenElse(); - /** - * @type {Tone.Select} - * @private - */ - this._gt = new Tone.GreaterThan(); - //connections - this.input[0].chain(this._gt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._value.connect(this._ifThenElse.else); - this._value.connect(this._gt, 0, 1); + Tone.Timeline.prototype.forEachFrom = function (time, callback) { + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var lowerBound = this._search(time); + //work backwards until the event time is less than time + while (lowerBound >= 0 && this._timeline[lowerBound].time >= time) { + lowerBound--; + } + this._iterate(callback, lowerBound + 1); + return this; }; - Tone.extend(Tone.Max, Tone.Signal); /** - * Clean up. - * @returns {Tone.Max} this + * Iterate over everything in the array at the given time + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.Timeline} this */ - Tone.Max.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._value.dispose(); - this._ifThenElse.dispose(); - this._gt.dispose(); - this._value = null; - this._ifThenElse = null; - this._gt = null; + Tone.Timeline.prototype.forEachAtTime = function (time, callback) { + //iterate over the items in reverse so that removing an item doesn't break things + time = this.toSeconds(time); + var upperBound = this._search(time); + if (upperBound !== -1) { + this._iterate(function (event) { + if (event.time === time) { + callback(event); + } + }, 0, upperBound); + } return this; }; - return Tone.Max; + /** + * Clean up. + * @return {Tone.Timeline} this + */ + Tone.Timeline.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._timeline = null; + this._toRemove = null; + }; + return Tone.Timeline; }); 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. + * @class A signal which adds the method getValueAtTime. + * Code and inspiration from https://github.com/jsantell/web-audio-automation-timeline + * @extends {Tone.Param} + * @param {Number=} value The initial value of the signal + * @param {String=} units The conversion units of the signal. */ - 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(); + 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); /** - * @type {Tone.Select} + * The scheduled events + * @type {Tone.Timeline} * @private */ - this._lt = new Tone.LessThan(); + this._events = new Tone.Timeline(10); /** - * the min signal - * @type {Tone.Signal} + * The initial scheduled value + * @type {Number} * @private */ - this._value = this.input[1] = new Tone.Signal(min); - //connections - this.input[0].chain(this._lt, this._ifThenElse.if); - this.input[0].connect(this._ifThenElse.then); - this._value.connect(this._ifThenElse.else); - this._value.connect(this._lt, 0, 1); + this._initial = this._fromUnits(this._param.value); }; - Tone.extend(Tone.Min, Tone.Signal); + Tone.extend(Tone.TimelineSignal, Tone.Param); /** - * clean up - * @returns {Tone.Min} this + * The event types of a schedulable signal. + * @enum {String} */ - Tone.Min.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._value.dispose(); - this._ifThenElse.dispose(); - this._lt.dispose(); - this._value = null; - this._ifThenElse = null; - this._lt = null; + Tone.TimelineSignal.Type = { + Linear: 'linear', + Exponential: 'exponential', + Target: 'target', + Set: 'set' + }; + /** + * The current value of the signal. + * @memberOf Tone.TimelineSignal# + * @type {Number} + * @name value + */ + Object.defineProperty(Tone.TimelineSignal.prototype, 'value', { + get: function () { + return this._toUnits(this._param.value); + }, + set: function (value) { + var convertedVal = this._fromUnits(value); + this._initial = convertedVal; + this._param.value = convertedVal; + } + }); + /////////////////////////////////////////////////////////////////////////// + // SCHEDULING + /////////////////////////////////////////////////////////////////////////// + /** + * Schedules a parameter value change at the given time. + * @param {*} value The value to set the signal. + * @param {Time} time The time when the change should occur. + * @returns {Tone.TimelineSignal} this + * @example + * //set the frequency to "G4" in exactly 1 second from now. + * freq.setValueAtTime("G4", "+1"); + */ + Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) { + value = this._fromUnits(value); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Set, + 'value': value, + 'time': startTime + }); + //invoke the original event + this._param.setValueAtTime(value, startTime); return this; }; - return Tone.Min; + /** + * Schedules a linear continuous change in parameter value from the + * previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Linear, + 'value': value, + 'time': endTime + }); + this._param.linearRampToValueAtTime(value, endTime); + return this; + }; + /** + * Schedules an exponential continuous change in parameter value from + * the previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Time} endTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + endTime = this.toSeconds(endTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Exponential, + 'value': value, + 'time': endTime + }); + this._param.exponentialRampToValueAtTime(value, endTime); + return this; + }; + /** + * Start exponentially approaching the target value at the given time with + * a rate having the given time constant. + * @param {number} value + * @param {Time} startTime + * @param {number} timeConstant + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) { + value = this._fromUnits(value); + value = Math.max(this._minOutput, value); + timeConstant = Math.max(this._minOutput, timeConstant); + startTime = this.toSeconds(startTime); + this._events.addEvent({ + 'type': Tone.TimelineSignal.Type.Target, + 'value': value, + 'time': startTime, + 'constant': timeConstant + }); + this._param.setTargetAtTime(value, startTime, timeConstant); + return this; + }; + /** + * Cancels all scheduled parameter changes with times greater than or + * equal to startTime. + * + * @param {Time} startTime + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) { + this._events.cancel(after); + this._param.cancelScheduledValues(this.toSeconds(after)); + return this; + }; + /** + * Sets the computed value at the given time. This provides + * a point from which a linear or exponential curve + * can be scheduled after. Will cancel events after + * the given time and shorten the currently scheduled + * linear or exponential ramp so that it ends at `time` . + * This is to avoid discontinuities and clicks in envelopes. + * @param {Time} time When to set the ramp point + * @returns {Tone.TimelineSignal} this + */ + 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) + 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; + }; + /** + * Do a linear ramp to the given value between the start and finish times. + * @param {Number} value The value to ramp to. + * @param {Time} start The beginning anchor point to do the linear ramp + * @param {Time} finish The ending anchor point by which the value of + * the signal will equal the given value. + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.linearRampToValueAtTime(value, finish); + return this; + }; + /** + * Do a exponential ramp to the given value between the start and finish times. + * @param {Number} value The value to ramp to. + * @param {Time} start The beginning anchor point to do the exponential ramp + * @param {Time} finish The ending anchor point by which the value of + * the signal will equal the given value. + * @returns {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) { + this.setRampPoint(start); + this.exponentialRampToValueAtTime(value, finish); + return this; + }; + /////////////////////////////////////////////////////////////////////////// + // GETTING SCHEDULED VALUES + /////////////////////////////////////////////////////////////////////////// + /** + * Returns the value before or equal to the given time + * @param {Number} time The time to query + * @return {Object} The event at or before the given time. + * @private + */ + Tone.TimelineSignal.prototype._searchBefore = function (time) { + return this._events.getEvent(time); + }; + /** + * The event after the given time + * @param {Number} time The time to query. + * @return {Object} The next event after the given time + * @private + */ + Tone.TimelineSignal.prototype._searchAfter = function (time) { + return this._events.getEventAfter(time); + }; + /** + * 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.TimelineSignal.prototype.getValueAtTime = function (time) { + var after = this._searchAfter(time); + var before = this._searchBefore(time); + var value = this._initial; + //if it was set by + if (before === null) { + value = this._initial; + } else if (before.type === Tone.TimelineSignal.Type.Target) { + var previous = this._events.getEventBefore(before.time); + var previouVal; + if (previous === null) { + previouVal = this._initial; + } else { + previouVal = previous.value; + } + value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time); + } else if (after === null) { + value = before.value; + } else if (after.type === Tone.TimelineSignal.Type.Linear) { + value = this._linearInterpolate(before.time, before.value, after.time, after.value, time); + } else if (after.type === Tone.TimelineSignal.Type.Exponential) { + value = this._exponentialInterpolate(before.time, before.value, after.time, after.value, time); + } else { + value = before.value; + } + return value; + }; + /** + * When signals connect to other signals or AudioParams, + * they take over the output value of that signal or AudioParam. + * For all other nodes, the behavior is the same as a default connect. + * + * @override + * @param {AudioParam|AudioNode|Tone.Signal|Tone} node + * @param {number} [outputNumber=0] The output number to connect from. + * @param {number} [inputNumber=0] The input number to connect to. + * @returns {Tone.TimelineSignal} this + * @method + */ + Tone.TimelineSignal.prototype.connect = Tone.SignalBase.prototype.connect; + /////////////////////////////////////////////////////////////////////////// + // AUTOMATION CURVE CALCULATIONS + // MIT License, copyright (c) 2014 Jordan Santell + /////////////////////////////////////////////////////////////////////////// + /** + * Calculates the the value along the curve produced by setTargetAtTime + * @private + */ + Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) { + return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant); + }; + /** + * Calculates the the value along the curve produced by linearRampToValueAtTime + * @private + */ + Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) { + return v0 + (v1 - v0) * ((t - t0) / (t1 - t0)); + }; + /** + * Calculates the the value along the curve produced by exponentialRampToValueAtTime + * @private + */ + Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) { + v0 = Math.max(this._minOutput, v0); + return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0)); + }; + /** + * Clean up. + * @return {Tone.TimelineSignal} this + */ + Tone.TimelineSignal.prototype.dispose = function () { + Tone.Signal.prototype.dispose.call(this); + Tone.Param.prototype.dispose.call(this); + this._events.dispose(); + this._events = null; + }; + return Tone.TimelineSignal; }); Module(function (Tone) { /** - * @class Signal-rate modulo operator. Only works in AudioRange [-1, 1] and for modulus - * values in the NormalRange. + * @class Pow applies an exponent to the incoming signal. The incoming signal + * must be AudioRange. * - * @constructor * @extends {Tone.SignalBase} - * @param {NormalRange} modulus The modulus to apply. + * @constructor + * @param {Positive} exp The exponent to apply to the incoming signal, must be at least 2. * @example - * var mod = new Tone.Modulo(0.2) - * var sig = new Tone.Signal(0.5).connect(mod); - * //mod outputs 0.1 + * var pow = new Tone.Pow(2); + * var sig = new Tone.Signal(0.5).connect(pow); + * //output of pow is 0.25. */ - Tone.Modulo = function (modulus) { - Tone.call(this, 1, 1); - /** - * A waveshaper gets the integer multiple of - * the input signal and the modulus. - * @private - * @type {Tone.WaveShaper} - */ - this._shaper = new Tone.WaveShaper(Math.pow(2, 16)); - /** - * the integer multiple is multiplied by the modulus - * @type {Tone.Multiply} - * @private - */ - this._multiply = new Tone.Multiply(); + Tone.Pow = function (exp) { /** - * and subtracted from the input signal - * @type {Tone.Subtract} - * @private + * the exponent + * @private + * @type {number} */ - this._subtract = this.output = new Tone.Subtract(); + this._exp = this.defaultArg(exp, 1); /** - * the modulus signal - * @type {Tone.Signal} + * @type {WaveShaperNode} * @private */ - this._modSignal = new Tone.Signal(modulus); - //connections - this.input.fan(this._shaper, this._subtract); - this._modSignal.connect(this._multiply, 0, 0); - this._shaper.connect(this._multiply, 0, 1); - this._multiply.connect(this._subtract, 0, 1); - this._setWaveShaper(modulus); - }; - Tone.extend(Tone.Modulo, Tone.SignalBase); - /** - * @param {number} mod the modulus to apply - * @private - */ - Tone.Modulo.prototype._setWaveShaper = function (mod) { - this._shaper.setMap(function (val) { - var multiple = Math.floor((val + 0.0001) / mod); - return multiple; - }); + this._expScaler = this.input = this.output = new Tone.WaveShaper(this._expFunc(this._exp), 8192); }; + Tone.extend(Tone.Pow, Tone.SignalBase); /** - * The modulus value. - * @memberOf Tone.Modulo# - * @type {NormalRange} + * The value of the exponent. + * @memberOf Tone.Pow# + * @type {number} * @name value */ - Object.defineProperty(Tone.Modulo.prototype, 'value', { + Object.defineProperty(Tone.Pow.prototype, 'value', { get: function () { - return this._modSignal.value; + return this._exp; }, - set: function (mod) { - this._modSignal.value = mod; - this._setWaveShaper(mod); + set: function (exp) { + this._exp = exp; + this._expScaler.setMap(this._expFunc(this._exp)); } }); /** - * clean up - * @returns {Tone.Modulo} this + * the function which maps the waveshaper + * @param {number} exp + * @return {function} + * @private */ - Tone.Modulo.prototype.dispose = function () { + Tone.Pow.prototype._expFunc = function (exp) { + return function (val) { + return Math.pow(Math.abs(val), exp); + }; + }; + /** + * Clean up. + * @returns {Tone.Pow} this + */ + Tone.Pow.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; - this._multiply.dispose(); - this._multiply = null; - this._subtract.dispose(); - this._subtract = null; - this._modSignal.dispose(); - this._modSignal = null; + this._expScaler.dispose(); + this._expScaler = null; return this; }; - return Tone.Modulo; + return Tone.Pow; }); 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.Envelope is an [ADSR](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope) + * envelope generator. Tone.Envelope outputs a signal which + * can be connected to an AudioParam or Tone.Signal. + * * - * @extends {Tone.SignalBase} * @constructor - * @param {string} expr the expression to generate + * @extends {Tone} + * @param {Time} [attack] The amount of time it takes for the envelope to go from + * 0 to it's maximum value. + * @param {Time} [decay] The period of time after the attack that it takes for the envelope + * to fall to the sustain value. + * @param {NormalRange} [sustain] The percent of the maximum value that the envelope rests at until + * the release is triggered. + * @param {Time} [release] The amount of time after the release is triggered it takes to reach 0. * @example - * //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); + * //an amplitude envelope + * var gainNode = Tone.context.createGain(); + * var env = new Tone.Envelope({ + * "attack" : 0.1, + * "decay" : 0.2, + * "sustain" : 1, + * "release" : 0.8, + * }); + * env.connect(gainNode.gain); + */ + Tone.Envelope = function () { + //get all of the defaults + var options = this.optionsObject(arguments, [ + 'attack', + 'decay', + 'sustain', + 'release' + ], Tone.Envelope.defaults); + /** + * When triggerAttack is called, the attack time is the amount of + * time it takes for the envelope to reach it's maximum value. + * @type {Time} + */ + this.attack = options.attack; /** - * hold onto all of the nodes for disposal - * @type {Array} + * After the attack portion of the envelope, the value will fall + * over the duration of the decay time to it's sustain value. + * @type {Time} + */ + this.decay = options.decay; + /** + * The sustain value is the value + * which the envelope rests at after triggerAttack is + * called, but before triggerRelease is invoked. + * @type {NormalRange} + */ + this.sustain = options.sustain; + /** + * After triggerRelease is called, the envelope's + * value will fall to it's miminum value over the + * duration of the release time. + * @type {Time} + */ + this.release = options.release; + /** + * the next time the envelope is at standby + * @type {number} * @private */ - this._nodes = []; + this._attackCurve = Tone.Envelope.Type.Linear; /** - * The inputs. The length is determined by the expression. - * @type {Array} + * the next time the envelope is at standby + * @type {number} + * @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); - } + this._releaseCurve = Tone.Envelope.Type.Exponential; /** - * The output node is the result of the expression - * @type {Tone} + * the minimum output value + * @type {number} + * @private */ - this.output = result; + this._minOutput = 0.00001; + /** + * the signal + * @type {Tone.TimelineSignal} + * @private + */ + this._sig = this.output = new Tone.TimelineSignal(); + this._sig.setValueAtTime(this._minOutput, 0); + //set the attackCurve initially + this.attackCurve = options.attackCurve; + this.releaseCurve = options.releaseCurve; }; - 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} + Tone.extend(Tone.Envelope); + /** + * the default parameters + * @static + * @const + */ + Tone.Envelope.defaults = { + 'attack': 0.01, + 'decay': 0.1, + 'sustain': 0.5, + 'release': 1, + 'attackCurve': 'linear', + 'releaseCurve': 'exponential' + }; + /** + * the envelope time multipler + * @type {number} * @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: /^,/ } + Tone.Envelope.prototype._timeMult = 0.25; + /** + * Read the current value of the envelope. Useful for + * syncronizing visual output to the envelope. + * @memberOf Tone.Envelope# + * @type {Number} + * @name value + * @readOnly + */ + Object.defineProperty(Tone.Envelope.prototype, 'value', { + get: function () { + return this._sig.value; + } + }); + /** + * The slope of the attack. Either "linear" or "exponential". + * @memberOf Tone.Envelope# + * @type {string} + * @name attackCurve + * @example + * env.attackCurve = "linear"; + */ + Object.defineProperty(Tone.Envelope.prototype, 'attackCurve', { + get: function () { + return this._attackCurve; }, - //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; - } + 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); } + } + }); + /** + * The slope of the Release. Either "linear" or "exponential". + * @memberOf Tone.Envelope# + * @type {string} + * @name releaseCurve + * @example + * env.releaseCurve = "linear"; + */ + Object.defineProperty(Tone.Envelope.prototype, 'releaseCurve', { + get: function () { + return this._releaseCurve; }, - //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: /^input[0] + * and input[1]. If a value is passed into the constructor, + * the it will be added to the input. + * + * @constructor + * @extends {Tone.Signal} + * @param {number=} value If no value is provided, Tone.Add will sum the first + * and second inputs. + * @example + * var signal = new Tone.Signal(2); + * var add = new Tone.Add(2); + * signal.connect(add); + * //the output of add equals 4 + * @example + * //if constructed with no arguments + * //it will add the first and second inputs + * var add = new Tone.Add(); + * var sig0 = new Tone.Signal(3).connect(add, 0, 0); + * var sig1 = new Tone.Signal(4).connect(add, 0, 1); + * //the output of add equals 7. + */ + Tone.Add = function (value) { + Tone.call(this, 2, 0); + /** + * the summing node + * @type {GainNode} + * @private + */ + this._sum = this.input[0] = this.input[1] = this.output = this.context.createGain(); + /** + * @private + * @type {Tone.Signal} + */ + this._param = this.input[1] = new Tone.Signal(value); + this._param.connect(this._sum); + }; + Tone.extend(Tone.Add, Tone.Signal); + /** + * Clean up. + * @returns {Tone.Add} this + */ + Tone.Add.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._sum.disconnect(); + this._sum = null; + this._param.dispose(); + this._param = null; + return this; + }; + return Tone.Add; + }); + Module(function (Tone) { + + /** + * @class Multiply two incoming signals. Or, if a number is given in the constructor, + * multiplies the incoming signal by that value. + * + * @constructor + * @extends {Tone.Signal} + * @param {number=} value Constant value to multiple. If no value is provided, + * it will return the product of the first and second inputs + * @example + * var mult = new Tone.Multiply(); + * var sigA = new Tone.Signal(3); + * var sigB = new Tone.Signal(4); + * sigA.connect(mult, 0, 0); + * sigB.connect(mult, 0, 1); + * //output of mult is 12. + * @example + * var mult = new Tone.Multiply(10); + * var sig = new Tone.Signal(2).connect(mult); + * //the output of mult is 20. + */ + Tone.Multiply = function (value) { + Tone.call(this, 2, 0); + /** + * the input node is the same as the output node + * it is also the GainNode which handles the scaling of incoming signal + * + * @type {GainNode} + * @private + */ + this._mult = this.input[0] = this.output = this.context.createGain(); + /** + * the scaling parameter + * @type {AudioParam} + * @private + */ + this._param = this.input[1] = this.output.gain; + this._param.value = this.defaultArg(value, 0); + }; + Tone.extend(Tone.Multiply, Tone.Signal); + /** + * clean up + * @returns {Tone.Multiply} this + */ + Tone.Multiply.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._mult.disconnect(); + this._mult = null; + this._param = null; + return this; + }; + return Tone.Multiply; + }); + Module(function (Tone) { + + /** + * @class Negate the incoming signal. i.e. an input signal of 10 will output -10 + * + * @constructor + * @extends {Tone.SignalBase} + * @example + * var neg = new Tone.Negate(); + * var sig = new Tone.Signal(-2).connect(neg); + * //output of neg is positive 2. + */ + Tone.Negate = function () { + /** + * negation is done by multiplying by -1 + * @type {Tone.Multiply} + * @private + */ + this._multiply = this.input = this.output = new Tone.Multiply(-1); + }; + Tone.extend(Tone.Negate, Tone.SignalBase); + /** + * clean up + * @returns {Tone.Negate} this + */ + Tone.Negate.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._multiply.dispose(); + this._multiply = null; + return this; + }; + return Tone.Negate; + }); + Module(function (Tone) { + + /** + * @class Subtract the signal connected to input[1] from the signal connected + * to input[0]. If an argument is provided in the constructor, the + * signals .value will be subtracted from the incoming signal. + * + * @extends {Tone.Signal} + * @constructor + * @param {number=} value The value to subtract from the incoming signal. If the value + * is omitted, it will subtract the second signal from the first. + * @example + * var sub = new Tone.Subtract(1); + * var sig = new Tone.Signal(4).connect(sub); + * //the output of sub is 3. + * @example + * var sub = new Tone.Subtract(); + * var sigA = new Tone.Signal(10); + * var sigB = new Tone.Signal(2.5); + * sigA.connect(sub, 0, 0); + * sigB.connect(sub, 0, 1); + * //output of sub is 7.5 + */ + Tone.Subtract = function (value) { + Tone.call(this, 2, 0); + /** + * the summing node + * @type {GainNode} + * @private + */ + this._sum = this.input[0] = this.output = this.context.createGain(); + /** + * negate the input of the second input before connecting it + * to the summing node. + * @type {Tone.Negate} + * @private + */ + this._neg = new Tone.Negate(); + /** + * the node where the value is set + * @private + * @type {Tone.Signal} + */ + this._param = this.input[1] = new Tone.Signal(value); + this._param.chain(this._neg, this._sum); + }; + Tone.extend(Tone.Subtract, Tone.Signal); + /** + * Clean up. + * @returns {Tone.SignalBase} this + */ + Tone.Subtract.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._neg.dispose(); + this._neg = null; + this._sum.disconnect(); + this._sum = null; + this._param.dispose(); + this._param = null; + return this; + }; + return Tone.Subtract; + }); + Module(function (Tone) { + + /** + * @class GreaterThanZero outputs 1 when the input is strictly greater than zero + * + * @constructor + * @extends {Tone.SignalBase} + * @example + * var gt0 = new Tone.GreaterThanZero(); + * var sig = new Tone.Signal(0.01).connect(gt0); + * //the output of gt0 is 1. + * sig.value = 0; + * //the output of gt0 is 0. + */ + Tone.GreaterThanZero = function () { + /** + * @type {Tone.WaveShaper} + * @private + */ + this._thresh = this.output = new Tone.WaveShaper(function (val) { + if (val <= 0) { + return 0; + } else { + return 1; + } + }); + /** + * scale the first thresholded signal by a large value. + * this will help with values which are very close to 0 + * @type {Tone.Multiply} + * @private + */ + this._scale = this.input = new Tone.Multiply(10000); + //connections + this._scale.connect(this._thresh); + }; + Tone.extend(Tone.GreaterThanZero, Tone.SignalBase); + /** + * dispose method + * @returns {Tone.GreaterThanZero} this + */ + Tone.GreaterThanZero.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._scale.dispose(); + this._scale = null; + this._thresh.dispose(); + this._thresh = null; + return this; + }; + return Tone.GreaterThanZero; + }); + Module(function (Tone) { + + /** + * @class EqualZero outputs 1 when the input is 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; + }); + Module(function (Tone) { + + /** + * @class Output 1 if the signal is equal to the value, otherwise outputs 0. + * Can accept two signals if connected to inputs 0 and 1. + * + * @constructor + * @extends {Tone.SignalBase} + * @param {number=} value The number to compare the incoming signal to + * @example + * var eq = new Tone.Equal(3); + * var sig = new Tone.Signal(3).connect(eq); + * //the output of eq is 1. + */ + Tone.Equal = function (value) { + Tone.call(this, 2, 0); + /** + * subtract the value from the incoming signal + * + * @type {Tone.Add} + * @private + */ + this._sub = this.input[0] = new Tone.Subtract(value); + /** + * @type {Tone.EqualZero} + * @private + */ + this._equals = this.output = new Tone.EqualZero(); + this._sub.connect(this._equals); + this.input[1] = this._sub.input[1]; + }; + Tone.extend(Tone.Equal, Tone.SignalBase); + /** + * The value to compare to the incoming signal. + * @memberOf Tone.Equal# + * @type {number} + * @name value + */ + Object.defineProperty(Tone.Equal.prototype, 'value', { + get: function () { + return this._sub.value; + }, + set: function (value) { + this._sub.value = value; + } + }); + /** + * 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; + }); + Module(function (Tone) { + + /** + * @class Select between any number of inputs, sending the one + * selected by the gate signal to the output + * + * @constructor + * @extends {Tone.SignalBase} + * @param {number} [sourceCount=2] the number of inputs the switch accepts + * @example + * var sel = new Tone.Select(2); + * var sigA = new Tone.Signal(10).connect(sel, 0, 0); + * var sigB = new Tone.Signal(20).connect(sel, 0, 1); + * sel.gate.value = 0; + * //sel outputs 10 (the value of sigA); + * sel.gate.value = 1; + * //sel outputs 20 (the value of sigB); + */ + Tone.Select = function (sourceCount) { + sourceCount = this.defaultArg(sourceCount, 2); + Tone.call(this, sourceCount, 1); + /** + * the control signal + * @type {Number} + * @signal + */ + this.gate = new Tone.Signal(0); + this._readOnly('gate'); + //make all the inputs and connect them + for (var i = 0; i < sourceCount; i++) { + var switchGate = new SelectGate(i); + this.input[i] = switchGate; + this.gate.connect(switchGate.selecter); + switchGate.connect(this.output); + } + }; + Tone.extend(Tone.Select, Tone.SignalBase); + /** + * Open 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; + }); + Module(function (Tone) { + + /** + * @class IfThenElse has three inputs. When the first input (if) is true (i.e. === 1), + * then it will pass the second input (then) through to the output, otherwise, + * if it's not true (i.e. === 0) then it will pass the third input (else) + * through to the output. + * + * @extends {Tone.SignalBase} + * @constructor + * @example + * var ifThenElse = new Tone.IfThenElse(); + * var ifSignal = new Tone.Signal(1).connect(ifThenElse.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 + */ + Tone.IfThenElse = function () { + Tone.call(this, 3, 0); + /** + * the selector node which is responsible for the routing + * @type {Tone.Select} + * @private + */ + this._selector = this.output = new Tone.Select(2); + //the input mapping + this.if = this.input[0] = this._selector.gate; + this.then = this.input[1] = this._selector.input[1]; + this.else = this.input[2] = this._selector.input[0]; + }; + Tone.extend(Tone.IfThenElse, Tone.SignalBase); + /** + * clean up + * @returns {Tone.IfThenElse} this + */ + Tone.IfThenElse.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._selector.dispose(); + this._selector = null; + this.if = null; + this.then = null; + this.else = null; + return this; + }; + return Tone.IfThenElse; + }); + Module(function (Tone) { + + /** + * @class [OR](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; + }); + 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. + */ + Tone.AND = function (inputCount) { + inputCount = this.defaultArg(inputCount, 2); + Tone.call(this, inputCount, 0); + /** + * @type {Tone.Equal} + * @private + */ + this._equals = this.output = new Tone.Equal(inputCount); + //make each of the inputs an alias + for (var i = 0; i < inputCount; i++) { + this.input[i] = this._equals; + } + }; + Tone.extend(Tone.AND, Tone.SignalBase); + /** + * clean up + * @returns {Tone.AND} this + */ + Tone.AND.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._equals.dispose(); + this._equals = null; + return this; + }; + return Tone.AND; + }); + Module(function (Tone) { + + /** + * @class Just an alias for 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; + }); + Module(function (Tone) { + + /** + * @class Output 1 if the signal is greater than the value, otherwise outputs 0. + * can compare two signals or a signal and a number. + * + * @constructor + * @extends {Tone.Signal} + * @param {number} [value=0] the value to compare to the incoming signal + * @example + * var gt = new Tone.GreaterThan(2); + * var sig = new Tone.Signal(4).connect(gt); + * //output of gt is equal 1. + */ + Tone.GreaterThan = function (value) { + Tone.call(this, 2, 0); + /** + * subtract the amount from the incoming signal + * @type {Tone.Subtract} + * @private + */ + this._param = this.input[0] = new Tone.Subtract(value); + this.input[1] = this._param.input[1]; + /** + * compare that amount to zero + * @type {Tone.GreaterThanZero} + * @private + */ + this._gtz = this.output = new Tone.GreaterThanZero(); + //connect + this._param.connect(this._gtz); + }; + Tone.extend(Tone.GreaterThan, Tone.Signal); + /** + * dispose method + * @returns {Tone.GreaterThan} this + */ + Tone.GreaterThan.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._param.dispose(); + this._param = null; + this._gtz.dispose(); + this._gtz = null; + return this; + }; + return Tone.GreaterThan; + }); + Module(function (Tone) { + + /** + * @class Output 1 if the signal is less than the value, otherwise outputs 0. + * Can compare two signals or a signal and a number. + * + * @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 + */ + 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; + }); + Module(function (Tone) { + + /** + * @class Return the absolute value of an incoming signal. + * + * @constructor + * @extends {Tone.SignalBase} + * @example + * var signal = new Tone.Signal(-1); + * var abs = new Tone.Abs(); + * signal.connect(abs); + * //the output of abs is 1. + */ + Tone.Abs = function () { + Tone.call(this, 1, 0); + /** + * @type {Tone.LessThan} + * @private + */ + this._ltz = new Tone.LessThan(0); + /** + * @type {Tone.Select} + * @private + */ + this._switch = this.output = new Tone.Select(2); + /** + * @type {Tone.Negate} + * @private + */ + this._negate = new Tone.Negate(); + //two signal paths, positive and negative + this.input.connect(this._switch, 0, 0); + this.input.connect(this._negate); + this._negate.connect(this._switch, 0, 1); + //the control signal + this.input.chain(this._ltz, this._switch.gate); + }; + Tone.extend(Tone.Abs, Tone.SignalBase); + /** + * dispose method + * @returns {Tone.Abs} this + */ + Tone.Abs.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._switch.dispose(); + this._switch = null; + this._ltz.dispose(); + this._ltz = null; + this._negate.dispose(); + this._negate = null; + return this; + }; + return Tone.Abs; + }); + Module(function (Tone) { + + /** + * @class Outputs the greater of two signals. If a number is provided in the constructor + * it will use that instead of the signal. + * + * @constructor + * @extends {Tone.Signal} + * @param {number=} max Max value if provided. if not provided, it will use the + * signal value from input 1. + * @example + * var max = new Tone.Max(2); + * var sig = new Tone.Signal(3).connect(max); + * //max outputs 3 + * sig.value = 1; + * //max outputs 2 + * @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; + }); + 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(); + /** + * @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; + }); + Module(function (Tone) { + + /** + * @class Signal-rate modulo operator. Only works in AudioRange [-1, 1] and for modulus + * values in the NormalRange. + * + * @constructor + * @extends {Tone.SignalBase} + * @param {NormalRange} modulus The modulus to apply. + * @example + * var mod = new Tone.Modulo(0.2) + * var sig = new Tone.Signal(0.5).connect(mod); + * //mod outputs 0.1 + */ + Tone.Modulo = function (modulus) { + Tone.call(this, 1, 1); + /** + * A waveshaper gets the integer multiple of + * the input signal and the modulus. + * @private + * @type {Tone.WaveShaper} + */ + this._shaper = new Tone.WaveShaper(Math.pow(2, 16)); + /** + * the integer multiple is multiplied by the modulus + * @type {Tone.Multiply} + * @private + */ + this._multiply = new Tone.Multiply(); + /** + * and subtracted from the input signal + * @type {Tone.Subtract} + * @private + */ + this._subtract = this.output = new Tone.Subtract(); + /** + * the modulus signal + * @type {Tone.Signal} + * @private + */ + this._modSignal = new Tone.Signal(modulus); + //connections + this.input.fan(this._shaper, this._subtract); + this._modSignal.connect(this._multiply, 0, 0); + this._shaper.connect(this._multiply, 0, 1); + this._multiply.connect(this._subtract, 0, 1); + this._setWaveShaper(modulus); + }; + Tone.extend(Tone.Modulo, Tone.SignalBase); + /** + * @param {number} mod the modulus to apply + * @private + */ + Tone.Modulo.prototype._setWaveShaper = function (mod) { + this._shaper.setMap(function (val) { + var multiple = Math.floor((val + 0.0001) / mod); + return multiple; + }); + }; + /** + * The modulus value. + * @memberOf Tone.Modulo# + * @type {NormalRange} + * @name value + */ + Object.defineProperty(Tone.Modulo.prototype, 'value', { + get: function () { + return this._modSignal.value; + }, + set: function (mod) { + this._modSignal.value = mod; + this._setWaveShaper(mod); + } + }); + /** + * clean up + * @returns {Tone.Modulo} this + */ + Tone.Modulo.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; + this._multiply.dispose(); + this._multiply = null; + this._subtract.dispose(); + this._subtract = null; + this._modSignal.dispose(); + this._modSignal = null; + return this; + }; + return Tone.Modulo; + }); + Module(function (Tone) { + + /** + * @class AudioToGain converts an input in AudioRange [-1,1] to NormalRange [0,1]. + * See Tone.GainToAudio. + * + * @extends {Tone.SignalBase} + * @constructor + * @example + * var a2g = new Tone.AudioToGain(); + */ + Tone.AudioToGain = function () { + /** + * @type {WaveShaperNode} + * @private + */ + this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { + return (x + 1) / 2; + }); + }; + Tone.extend(Tone.AudioToGain, Tone.SignalBase); + /** + * clean up + * @returns {Tone.AudioToGain} this + */ + Tone.AudioToGain.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._norm.dispose(); + this._norm = null; + return this; + }; + return Tone.AudioToGain; + }); + Module(function (Tone) { + + /** + * @class Evaluate an expression at audio rate.

+ * Parsing code modified from https://code.google.com/p/tapdigit/ + * Copyright 2011 2012 Ariya Hidayat, New BSD License + * + * @extends {Tone.SignalBase} + * @constructor + * @param {string} expr the expression to generate + * @example + * //adds the signals from input[0] and input[1]. + * var expr = new Tone.Expr("$0 + $1"); + */ + Tone.Expr = function () { + var expr = this._replacements(Array.prototype.slice.call(arguments)); + var inputCount = this._parseInputs(expr); + /** + * hold onto all of the nodes for disposal + * @type {Array} + * @private + */ + this._nodes = []; + /** + * The inputs. The length is determined by the expression. + * @type {Array} + */ + this.input = new Array(inputCount); + //create a gain for each input + for (var i = 0; i < inputCount; i++) { + this.input[i] = this.context.createGain(); + } + //parse the syntax tree + var tree = this._parseTree(expr); + //evaluate the results + var result; + try { + result = this._eval(tree); + } catch (e) { + this._disposeNodes(); + throw new Error('Could evaluate expression: ' + expr); + } + /** + * The output node is the result of the expression + * @type {Tone} + */ + this.output = result; + }; + Tone.extend(Tone.Expr, Tone.SignalBase); + //some helpers to cut down the amount of code + function applyBinary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + self._eval(args[1]).connect(op, 0, 1); + return op; + } + function applyUnary(Constructor, args, self) { + var op = new Constructor(); + self._eval(args[0]).connect(op, 0, 0); + return op; + } + function getNumber(arg) { + return arg ? parseFloat(arg) : undefined; + } + function literalNumber(arg) { + return arg && arg.args ? parseFloat(arg.args) : undefined; + } + /* + * the Expressions that Tone.Expr can parse. + * + * each expression belongs to a group and contains a regexp + * for selecting the operator as well as that operators method + * + * @type {Object} + * @private + */ + Tone.Expr._Expressions = { + //values + 'value': { + 'signal': { + regexp: /^\d+\.\d+|^\d+/, + method: function (arg) { + var sig = new Tone.Signal(getNumber(arg)); + return sig; + } + }, + 'input': { + regexp: /^\$\d/, + method: function (arg, self) { + return self.input[getNumber(arg.substr(1))]; + } + } + }, + //syntactic glue + 'glue': { + '(': { regexp: /^\(/ }, + ')': { regexp: /^\)/ }, + ',': { regexp: /^,/ } + }, + //functions + 'func': { + 'abs': { + regexp: /^abs/, + method: applyUnary.bind(this, Tone.Abs) + }, + 'min': { + regexp: /^min/, + method: applyBinary.bind(this, Tone.Min) + }, + 'max': { + regexp: /^max/, + method: applyBinary.bind(this, Tone.Max) + }, + 'if': { + regexp: /^if/, + method: function (args, self) { + var op = new Tone.IfThenElse(); + self._eval(args[0]).connect(op.if); + self._eval(args[1]).connect(op.then); + self._eval(args[2]).connect(op.else); + return op; + } + }, + 'gt0': { + regexp: /^gt0/, + method: applyUnary.bind(this, Tone.GreaterThanZero) + }, + 'eq0': { + regexp: /^eq0/, + method: applyUnary.bind(this, Tone.EqualZero) + }, + 'mod': { + regexp: /^mod/, + method: function (args, self) { + var modulus = literalNumber(args[1]); + var op = new Tone.Modulo(modulus); + self._eval(args[0]).connect(op); + return op; + } + }, + 'pow': { + regexp: /^pow/, + method: function (args, self) { + var exp = literalNumber(args[1]); + var op = new Tone.Pow(exp); + self._eval(args[0]).connect(op); + return op; + } + }, + '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 + }); + /** + * The gain for the lower signals + * @type {Tone.Gain} + * @private + */ + this._lowGain = new Tone.Gain(options.low, Tone.Type.Decibels); + /** + * The gain for the mid signals + * @type {Tone.Gain} + * @private + */ + this._midGain = new Tone.Gain(options.mid, Tone.Type.Decibels); + /** + * The gain in decibels of the high part + * @type {Tone.Gain} + * @private + */ + this._highGain = new Tone.Gain(options.high, Tone.Type.Decibels); + /** + * The gain in decibels of the low part + * @type {Decibels} + * @signal + */ + this.low = this._lowGain.gain; + /** + * The gain in decibels of the mid part + * @type {Decibels} + * @signal + */ + this.mid = this._midGain.gain; + /** + * The gain in decibels of the high part + * @type {Decibels} + * @signal + */ + this.high = this._highGain.gain; + /** + * The Q value for all of the filters. + * @type {Positive} + * @signal + */ + this.Q = this._multibandSplit.Q; + /** + * The low/mid crossover frequency. + * @type {Frequency} + * @signal + */ + this.lowFrequency = this._multibandSplit.lowFrequency; + /** + * The mid/high crossover frequency. + * @type {Frequency} + * @signal + */ + this.highFrequency = this._multibandSplit.highFrequency; + //the frequency bands + this._multibandSplit.low.chain(this._lowGain, this.output); + this._multibandSplit.mid.chain(this._midGain, this.output); + this._multibandSplit.high.chain(this._highGain, this.output); + this._readOnly([ + 'low', + 'mid', + 'high', + 'lowFrequency', + 'highFrequency' + ]); + }; + Tone.extend(Tone.EQ3); + /** + * the default values + */ + Tone.EQ3.defaults = { + 'low': 0, + 'mid': 0, + 'high': 0, + 'lowFrequency': 400, + 'highFrequency': 2500 + }; + /** + * clean up + * @returns {Tone.EQ3} this + */ + Tone.EQ3.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable([ + 'low', + 'mid', + 'high', + 'lowFrequency', + 'highFrequency' + ]); + this._multibandSplit.dispose(); + this._multibandSplit = null; + this.lowFrequency = null; + this.highFrequency = null; + this._lowGain.dispose(); + this._lowGain = null; + this._midGain.dispose(); + this._midGain = null; + this._highGain.dispose(); + this._highGain = null; + this.low = null; + this.mid = null; + this.high = null; + this.Q = null; + return this; + }; + return Tone.EQ3; + }); + Module(function (Tone) { + + /** + * @class Performs a linear scaling on an input signal. + * Scales a NormalRange input to between + * outputMin and outputMax. + * + * @constructor + * @extends {Tone.SignalBase} + * @param {number} [outputMin=0] The output value when the input is 0. + * @param {number} [outputMax=1] The output value when the input is 1. + * @example + * var scale = new Tone.Scale(50, 100); + * var signal = new Tone.Signal(0.5).connect(scale); + * //the output of scale equals 75 + */ + Tone.Scale = function (outputMin, outputMax) { + /** + * @private + * @type {number} + */ + this._outputMin = this.defaultArg(outputMin, 0); + /** + * @private + * @type {number} + */ + this._outputMax = this.defaultArg(outputMax, 1); + /** + * @private + * @type {Tone.Multiply} + * @private + */ + this._scale = this.input = new Tone.Multiply(1); + /** + * @private + * @type {Tone.Add} + * @private + */ + this._add = this.output = new Tone.Add(0); + this._scale.connect(this._add); + this._setRange(); + }; + Tone.extend(Tone.Scale, Tone.SignalBase); + /** + * The minimum output value. This number is output when + * the value input value is 0. + * @memberOf Tone.Scale# + * @type {number} + * @name min + */ + Object.defineProperty(Tone.Scale.prototype, 'min', { + get: function () { + return this._outputMin; + }, + set: function (min) { + this._outputMin = min; + this._setRange(); + } + }); + /** + * The maximum output value. This number is output when + * the value input value is 1. + * @memberOf Tone.Scale# + * @type {number} + * @name max + */ + Object.defineProperty(Tone.Scale.prototype, 'max', { + get: function () { + return this._outputMax; + }, + set: function (max) { + this._outputMax = max; + this._setRange(); + } + }); + /** + * set the values + * @private + */ + Tone.Scale.prototype._setRange = function () { + this._add.value = this._outputMin; + this._scale.value = this._outputMax - this._outputMin; + }; + /** + * Clean up. + * @returns {Tone.Scale} this + */ + Tone.Scale.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._add.dispose(); + this._add = null; + this._scale.dispose(); + this._scale = null; + return this; + }; + return Tone.Scale; + }); + Module(function (Tone) { + /** + * @class Performs an exponential scaling on an input signal. + * Scales a NormalRange value [0,1] exponentially + * to the output range of outputMin to outputMax. + * + * @constructor + * @extends {Tone.SignalBase} + * @param {number} [outputMin=0] The output value when the input is 0. + * @param {number} [outputMax=1] The output value when the input is 1. + * @param {number} [exponent=2] The exponent which scales the incoming signal. + * @example + * var scaleExp = new Tone.ScaleExp(0, 100, 2); + * var signal = new Tone.Signal(0.5).connect(scaleExp); + */ + Tone.ScaleExp = function (outputMin, outputMax, exponent) { + /** + * scale the input to the output range + * @type {Tone.Scale} + * @private + */ + this._scale = this.output = new Tone.Scale(outputMin, outputMax); + /** + * @private + * @type {Tone.Pow} + * @private + */ + this._exp = this.input = new Tone.Pow(this.defaultArg(exponent, 2)); + this._exp.connect(this._scale); + }; + Tone.extend(Tone.ScaleExp, Tone.SignalBase); + /** + * Instead of interpolating linearly between the min and + * max values, setting the exponent will interpolate between + * the two values with an exponential curve. + * @memberOf Tone.ScaleExp# + * @type {number} + * @name exponent + */ + Object.defineProperty(Tone.ScaleExp.prototype, 'exponent', { + get: function () { + return this._exp.value; + }, + set: function (exp) { + this._exp.value = exp; + } + }); + /** + * The minimum output value. This number is output when + * the value input value is 0. + * @memberOf Tone.ScaleExp# + * @type {number} + * @name min + */ + Object.defineProperty(Tone.ScaleExp.prototype, 'min', { + get: function () { + return this._scale.min; + }, + set: function (min) { + this._scale.min = min; + } + }); + /** + * The maximum output value. This number is output when + * the value input value is 1. + * @memberOf Tone.ScaleExp# + * @type {number} + * @name max + */ + Object.defineProperty(Tone.ScaleExp.prototype, 'max', { + get: function () { + return this._scale.max; + }, + set: function (max) { + this._scale.max = max; + } + }); + /** + * Clean up. + * @returns {Tone.ScaleExp} this + */ + Tone.ScaleExp.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._scale.dispose(); + this._scale = null; + this._exp.dispose(); + this._exp = null; + return this; + }; + return Tone.ScaleExp; + }); + Module(function (Tone) { + + /** + * @class Comb filters are basic building blocks for physical modeling. Read more + * about comb filters on [CCRMA's website](https://ccrma.stanford.edu/~jos/pasp/Feedback_Comb_Filters.html). + * + * @extends {Tone} + * @constructor + * @param {Time|Object} [delayTime] The delay time of the filter. + * @param {NormalRange=} resonance The amount of feedback the filter has. + */ + Tone.FeedbackCombFilter = function () { + Tone.call(this); + var options = this.optionsObject(arguments, [ + 'delayTime', + 'resonance' + ], Tone.FeedbackCombFilter.defaults); + /** + * the delay node + * @type {DelayNode} + * @private + */ + this._delay = this.input = this.output = this.context.createDelay(1); + /** + * The amount of delay of the comb filter. + * @type {Time} + * @signal + */ + this.delayTime = new Tone.Param({ + 'param': this._delay.delayTime, + 'value': options.delayTime, + 'units': Tone.Type.Time + }); + /** + * the feedback node + * @type {GainNode} + * @private + */ + this._feedback = this.context.createGain(); + /** + * The amount of feedback of the delayed signal. + * @type {NormalRange} + * @signal + */ + this.resonance = new Tone.Param({ + 'param': this._feedback.gain, + 'value': options.resonance, + 'units': Tone.Type.NormalRange + }); + this._delay.chain(this._feedback, this._delay); + this._readOnly([ + 'resonance', + 'delayTime' + ]); + }; + Tone.extend(Tone.FeedbackCombFilter); + /** + * the default parameters + * @static + * @const + * @type {Object} + */ + Tone.FeedbackCombFilter.defaults = { + 'delayTime': 0.1, + 'resonance': 0.5 + }; + /** + * clean up + * @returns {Tone.FeedbackCombFilter} this + */ + Tone.FeedbackCombFilter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable([ + 'resonance', + 'delayTime' + ]); + this._delay.disconnect(); + this._delay = null; + this.delayTime.dispose(); + this.delayTime = null; + this.resonance.dispose(); + this.resonance = null; + this._feedback.disconnect(); + this._feedback = null; + return this; + }; + return Tone.FeedbackCombFilter; + }); + Module(function (Tone) { + + /** + * @class Tone.Follower is a crude envelope follower which will follow + * the amplitude of an incoming signal. + * Take care with small (< 0.02) attack or decay values + * as follower has some ripple which is exaggerated + * at these values. Read more about envelope followers (also known + * as envelope detectors) on [Wikipedia](https://en.wikipedia.org/wiki/Envelope_detector). + * + * @constructor + * @extends {Tone} + * @param {Time|Object} [attack] The rate at which the follower rises. + * @param {Time=} release The rate at which the folower falls. + * @example + * var follower = new Tone.Follower(0.2, 0.4); + */ + Tone.Follower = function () { + Tone.call(this); + var options = this.optionsObject(arguments, [ + 'attack', + 'release' + ], Tone.Follower.defaults); + /** + * @type {Tone.Abs} + * @private + */ + this._abs = new Tone.Abs(); + /** + * the lowpass filter which smooths the input + * @type {BiquadFilterNode} + * @private + */ + this._filter = this.context.createBiquadFilter(); + this._filter.type = 'lowpass'; + this._filter.frequency.value = 0; + this._filter.Q.value = -100; + /** + * @type {WaveShaperNode} + * @private + */ + this._frequencyValues = new Tone.WaveShaper(); + /** + * @type {Tone.Subtract} + * @private + */ + this._sub = new Tone.Subtract(); + /** + * @type {DelayNode} + * @private + */ + this._delay = this.context.createDelay(); + this._delay.delayTime.value = this.blockTime; + /** + * this keeps it far from 0, even for very small differences + * @type {Tone.Multiply} + * @private + */ + this._mult = new Tone.Multiply(10000); + /** + * @private + * @type {number} + */ + this._attack = options.attack; + /** + * @private + * @type {number} + */ + this._release = options.release; + //the smoothed signal to get the values + this.input.chain(this._abs, this._filter, this.output); + //the difference path + this._abs.connect(this._sub, 0, 1); + this._filter.chain(this._delay, this._sub); + //threshold the difference and use the thresh to set the frequency + this._sub.chain(this._mult, this._frequencyValues, this._filter.frequency); + //set the attack and release values in the table + this._setAttackRelease(this._attack, this._release); + }; + Tone.extend(Tone.Follower); + /** + * @static + * @type {Object} + */ + Tone.Follower.defaults = { + 'attack': 0.05, + 'release': 0.5 + }; + /** + * sets the attack and release times in the wave shaper + * @param {Time} attack + * @param {Time} release + * @private + */ + Tone.Follower.prototype._setAttackRelease = function (attack, release) { + var minTime = this.blockTime; + attack = this.secondsToFrequency(this.toSeconds(attack)); + release = this.secondsToFrequency(this.toSeconds(release)); + attack = Math.max(attack, minTime); + release = Math.max(release, minTime); + this._frequencyValues.setMap(function (val) { + if (val <= 0) { + return attack; + } else { + return release; + } + }); + }; + /** + * The attack time. + * @memberOf Tone.Follower# + * @type {Time} + * @name attack + */ + Object.defineProperty(Tone.Follower.prototype, 'attack', { + get: function () { + return this._attack; + }, + set: function (attack) { + this._attack = attack; + this._setAttackRelease(this._attack, this._release); + } + }); + /** + * The release time. + * @memberOf Tone.Follower# + * @type {Time} + * @name release + */ + Object.defineProperty(Tone.Follower.prototype, 'release', { + get: function () { + return this._release; + }, + set: function (release) { + this._release = release; + this._setAttackRelease(this._attack, this._release); + } + }); + /** + * Borrows the connect method from Signal so that the output can be used + * as a Tone.Signal control signal. + * @function + */ + Tone.Follower.prototype.connect = Tone.Signal.prototype.connect; + /** + * dispose + * @returns {Tone.Follower} this + */ + Tone.Follower.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._filter.disconnect(); + this._filter = null; + this._frequencyValues.disconnect(); + this._frequencyValues = null; + this._delay.disconnect(); + this._delay = null; + this._sub.disconnect(); + this._sub = null; + this._abs.dispose(); + this._abs = null; + this._mult.dispose(); + this._mult = null; + this._curve = null; + return this; + }; + return Tone.Follower; + }); + Module(function (Tone) { + + /** + * @class Tone.ScaledEnvelop is an envelope which can be scaled + * to any range. It's useful for applying an envelope + * to a frequency or any other non-NormalRange signal + * parameter. + * + * @extends {Tone.Envelope} + * @constructor + * @param {Time|Object} [attack] the attack time in seconds + * @param {Time} [decay] the decay time in seconds + * @param {number} [sustain] a percentage (0-1) of the full amplitude + * @param {Time} [release] the release time in seconds + * @example + * var scaledEnv = new Tone.ScaledEnvelope({ + * "attack" : 0.2, + * "min" : 200, + * "max" : 2000 + * }); + * scaledEnv.connect(oscillator.frequency); + */ + Tone.ScaledEnvelope = function () { + //get all of the defaults + var options = this.optionsObject(arguments, [ + 'attack', + 'decay', + 'sustain', + 'release' + ], Tone.Envelope.defaults); + Tone.Envelope.call(this, options); + options = this.defaultArg(options, Tone.ScaledEnvelope.defaults); + /** + * scale the incoming signal by an exponent + * @type {Tone.Pow} + * @private + */ + this._exp = this.output = new Tone.Pow(options.exponent); + /** + * scale the signal to the desired range + * @type {Tone.Multiply} + * @private + */ + this._scale = this.output = new Tone.Scale(options.min, options.max); + this._sig.chain(this._exp, this._scale); + }; + Tone.extend(Tone.ScaledEnvelope, Tone.Envelope); + /** + * the default parameters + * @static + */ + Tone.ScaledEnvelope.defaults = { + 'min': 0, + 'max': 1, + 'exponent': 1 + }; + /** + * The envelope's min output value. This is the value which it + * starts at. + * @memberOf Tone.ScaledEnvelope# + * @type {number} + * @name min + */ + Object.defineProperty(Tone.ScaledEnvelope.prototype, 'min', { + get: function () { + return this._scale.min; + }, + set: function (min) { + this._scale.min = min; + } + }); + /** + * The envelope's max output value. In other words, the value + * at the peak of the attack portion of the envelope. + * @memberOf Tone.ScaledEnvelope# + * @type {number} + * @name max + */ + Object.defineProperty(Tone.ScaledEnvelope.prototype, 'max', { + get: function () { + return this._scale.max; + }, + set: function (max) { + this._scale.max = max; + } + }); + /** + * The envelope's exponent value. + * @memberOf Tone.ScaledEnvelope# + * @type {number} + * @name exponent + */ + Object.defineProperty(Tone.ScaledEnvelope.prototype, 'exponent', { + get: function () { + return this._exp.value; + }, + set: function (exp) { + this._exp.value = exp; + } + }); + /** + * clean up + * @returns {Tone.ScaledEnvelope} this + */ + Tone.ScaledEnvelope.prototype.dispose = function () { + Tone.Envelope.prototype.dispose.call(this); + this._scale.dispose(); + this._scale = null; + this._exp.dispose(); + this._exp = null; + return this; + }; + return Tone.ScaledEnvelope; + }); + Module(function (Tone) { + + /** + * @class Tone.FrequencyEnvelope is a Tone.ScaledEnvelope, but instead of `min` and `max` + * it's got a `baseFrequency` and `octaves` parameter. + * + * @extends {Tone.Envelope} + * @constructor + * @param {Time|Object} [attack] the attack time in seconds + * @param {Time} [decay] the decay time in seconds + * @param {number} [sustain] a percentage (0-1) of the full amplitude + * @param {Time} [release] the release time in seconds + * @example + * var env = new Tone.FrequencyEnvelope({ + * "attack" : 0.2, + * "baseFrequency" : "C2", + * "octaves" : 4 + * }); + * scaledEnv.connect(oscillator.frequency); + */ + Tone.FrequencyEnvelope = function () { + var options = this.optionsObject(arguments, [ + 'attack', + 'decay', + 'sustain', + 'release' + ], Tone.Envelope.defaults); + Tone.ScaledEnvelope.call(this, options); + options = this.defaultArg(options, Tone.FrequencyEnvelope.defaults); + /** + * Stores the octave value + * @type {Positive} + * @private + */ + this._octaves = options.octaves; + //setup + this.baseFrequency = options.baseFrequency; + this.octaves = options.octaves; + }; + Tone.extend(Tone.FrequencyEnvelope, Tone.Envelope); + /** + * the default parameters + * @static + */ + Tone.FrequencyEnvelope.defaults = { + 'baseFrequency': 200, + 'octaves': 4, + 'exponent': 2 + }; + /** + * The envelope's mininum output value. This is the value which it + * starts at. + * @memberOf Tone.FrequencyEnvelope# + * @type {Frequency} + * @name baseFrequency + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'baseFrequency', { + get: function () { + return this._scale.min; + }, + set: function (min) { + this._scale.min = this.toFrequency(min); + } + }); + /** + * The number of octaves above the baseFrequency that the + * envelope will scale to. + * @memberOf Tone.FrequencyEnvelope# + * @type {Positive} + * @name octaves + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'octaves', { + get: function () { + return this._octaves; + }, + set: function (octaves) { + this._octaves = octaves; + this._scale.max = this.baseFrequency * Math.pow(2, octaves); + } + }); + /** + * The envelope's exponent value. + * @memberOf Tone.FrequencyEnvelope# + * @type {number} + * @name exponent + */ + Object.defineProperty(Tone.FrequencyEnvelope.prototype, 'exponent', { + get: function () { + return this._exp.value; + }, + set: function (exp) { + this._exp.value = exp; + } + }); + /** + * clean up + * @returns {Tone.FrequencyEnvelope} this + */ + Tone.FrequencyEnvelope.prototype.dispose = function () { + Tone.ScaledEnvelope.prototype.dispose.call(this); + return this; + }; + return Tone.FrequencyEnvelope; + }); + Module(function (Tone) { + + /** + * @class Tone.Gate only passes a signal through when the incoming + * signal exceeds a specified threshold. To do this, Gate uses + * a Tone.Follower to follow the amplitude of the incoming signal. + * A common implementation of this class is a [Noise Gate](https://en.wikipedia.org/wiki/Noise_gate). + * + * @constructor + * @extends {Tone} + * @param {Decibels|Object} [threshold] The threshold above which the gate will open. + * @param {Time=} attack The follower's attack time + * @param {Time=} release The follower's release time + * @example + * var gate = new Tone.Gate(-30, 0.2, 0.3).toMaster(); + * var mic = new Tone.Microphone().connect(gate); + * //the gate will only pass through the incoming + * //signal when it's louder than -30db + */ + Tone.Gate = function () { + Tone.call(this); + var options = this.optionsObject(arguments, [ + 'threshold', + 'attack', + 'release' + ], Tone.Gate.defaults); + /** + * @type {Tone.Follower} + * @private + */ + this._follower = new Tone.Follower(options.attack, options.release); + /** + * @type {Tone.GreaterThan} + * @private + */ + this._gt = new Tone.GreaterThan(this.dbToGain(options.threshold)); + //the connections + this.input.connect(this.output); + //the control signal + this.input.chain(this._gt, this._follower, this.output.gain); + }; + Tone.extend(Tone.Gate); + /** + * @const + * @static + * @type {Object} + */ + Tone.Gate.defaults = { + 'attack': 0.1, + 'release': 0.1, + 'threshold': -40 + }; + /** + * The threshold of the gate in decibels + * @memberOf Tone.Gate# + * @type {Decibels} + * @name threshold + */ + Object.defineProperty(Tone.Gate.prototype, 'threshold', { + get: function () { + return this.gainToDb(this._gt.value); + }, + set: function (thresh) { + this._gt.value = this.dbToGain(thresh); + } + }); + /** + * The attack speed of the gate + * @memberOf Tone.Gate# + * @type {Time} + * @name attack + */ + Object.defineProperty(Tone.Gate.prototype, 'attack', { + get: function () { + return this._follower.attack; + }, + set: function (attackTime) { + this._follower.attack = attackTime; + } + }); + /** + * The release speed of the gate + * @memberOf Tone.Gate# + * @type {Time} + * @name release + */ + Object.defineProperty(Tone.Gate.prototype, 'release', { + get: function () { + return this._follower.release; + }, + set: function (releaseTime) { + this._follower.release = releaseTime; + } + }); + /** + * Clean up. + * @returns {Tone.Gate} this + */ + Tone.Gate.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._follower.dispose(); + this._gt.dispose(); + this._follower = null; + this._gt = null; + return this; + }; + return Tone.Gate; + }); + Module(function (Tone) { + + /** + * @class A Timeline State. Provides the methods: setStateAtTime("state", time) + * and getStateAtTime(time). + * + * @extends {Tone.Timeline} + * @param {String} initial The initial state of the TimelineState. + * Defaults to undefined + */ + Tone.TimelineState = function (initial) { + Tone.Timeline.call(this); + /** + * The initial state + * @private + * @type {String} + */ + this._initial = initial; + }; + Tone.extend(Tone.TimelineState, Tone.Timeline); + /** + * Returns the scheduled state scheduled before or at + * the given time. + * @param {Time} time The time to query. + * @return {String} The name of the state input in setStateAtTime. + */ + Tone.TimelineState.prototype.getStateAtTime = function (time) { + var event = this.getEvent(time); + if (event !== null) { + return event.state; + } else { + return this._initial; + } + }; + /** + * Returns the scheduled state scheduled before or at + * the given time. + * @param {String} state The name of the state to set. + * @param {Time} time The time to query. + */ + Tone.TimelineState.prototype.setStateAtTime = function (state, time) { + this.addEvent({ + 'state': state, + 'time': this.toSeconds(time) + }); + }; + return Tone.TimelineState; + }); + Module(function (Tone) { + + /** + * @class A sample accurate clock which provides a callback at the given rate. + * While the callback is not sample-accurate (it is still susceptible to + * loose JS timing), the time passed in as the argument to the callback + * is precise. For most applications, it is better to use Tone.Transport + * instead of the Clock by itself since you can synchronize multiple callbacks. + * + * @constructor + * @extends {Tone} + * @param {function} callback The callback to be invoked with the time of the audio event + * @param {Frequency} frequency The rate of the callback + * @example + * //the callback will be invoked approximately once a second + * //and will print the time exactly once a second apart. + * var clock = new Tone.Clock(function(time){ + * console.log(time); + * }, 1); + */ + Tone.Clock = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'frequency' + ], Tone.Clock.defaults); + /** + * The callback function to invoke at the scheduled tick. + * @type {Function} + */ + this.callback = options.callback; + /** + * The time which the clock will schedule events in advance + * of the current time. Scheduling notes in advance improves + * performance and decreases the chance for clicks caused + * by scheduling events in the past. If set to "auto", + * this value will be automatically computed based on the + * rate of requestAnimationFrame (0.016 seconds). Larger values + * will yeild better performance, but at the cost of latency. + * Values less than 0.016 are not recommended. + * @type {Number|String} + */ + this._lookAhead = 'auto'; + /** + * The lookahead value which was automatically + * computed using a time-based averaging. + * @type {Number} + * @private + */ + this._computedLookAhead = 1 / 60; + /** + * The value afterwhich events are thrown out + * @type {Number} + * @private + */ + this._threshold = 0.5; + /** + * The next time the callback is scheduled. + * @type {Number} + * @private + */ + this._nextTick = -1; + /** + * The last time the callback was invoked + * @type {Number} + * @private + */ + this._lastUpdate = 0; + /** + * The id of the requestAnimationFrame + * @type {Number} + * @private + */ + this._loopID = -1; + /** + * The rate the callback function should be invoked. + * @type {BPM} + * @signal + */ + this.frequency = new Tone.TimelineSignal(options.frequency, Tone.Type.Frequency); + /** + * The number of times the callback was invoked. Starts counting at 0 + * and increments after the callback was invoked. + * @type {Ticks} + * @readOnly + */ + this.ticks = 0; + /** + * The state timeline + * @type {Tone.TimelineState} + * @private + */ + 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} + * @private + */ + this._boundLoop = this._loop.bind(this); + this._readOnly('frequency'); + //start the loop + this._loop(); + }; + Tone.extend(Tone.Clock); + /** + * The defaults + * @const + * @type {Object} + */ + Tone.Clock.defaults = { + 'callback': Tone.noOp, + 'frequency': 1, + 'lookAhead': 'auto' + }; + /** + * Returns the playback state of the source, either "started", "stopped" or "paused". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.Clock# + * @name state + */ + Object.defineProperty(Tone.Clock.prototype, 'state', { + get: function () { + return this._state.getStateAtTime(this.now()); + } + }); + /** + * The time which the clock will schedule events in advance + * of the current time. Scheduling notes in advance improves + * performance and decreases the chance for clicks caused + * by scheduling events in the past. If set to "auto", + * this value will be automatically computed based on the + * rate of requestAnimationFrame (0.016 seconds). Larger values + * will yeild better performance, but at the cost of latency. + * Values less than 0.016 are not recommended. + * @type {Number|String} + * @memberOf Tone.Clock# + * @name lookAhead + */ + Object.defineProperty(Tone.Clock.prototype, 'lookAhead', { + get: function () { + return this._lookAhead; }, - //unary expressions - 'unary': { - '-': { - regexp: /^\-/, - method: applyUnary.bind(this, Tone.Negate) - }, - '!': { - regexp: /^\!/, - method: applyUnary.bind(this, Tone.NOT) + set: function (val) { + if (val === 'auto') { + this._lookAhead = 'auto'; + } else { + this._lookAhead = this.toSeconds(val); } } + }); + /** + * Start the clock at the given time. Optionally pass in an offset + * of where to start the tick counter from. + * @param {Time} time The time the clock should start + * @param {Ticks=} offset Where the tick counter starts counting from. + * @return {Tone.Clock} this + */ + Tone.Clock.prototype.start = function (time, offset) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Started) { + this._state.addEvent({ + 'state': Tone.State.Started, + 'time': time, + 'offset': offset + }); + } + return this; }; /** - * @param {string} expr the expression string - * @return {number} the input count - * @private + * Stop the clock. Stopping the clock resets the tick counter to 0. + * @param {Time} [time=now] The time when the clock should stop. + * @returns {Tone.Clock} this + * @example + * clock.stop(); */ - 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); - } + 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); } - return inputMax; + return this; }; /** - * @param {Array} args an array of arguments - * @return {string} the results of the replacements being replaced - * @private + * Pause the clock. Pausing does not reset the tick counter. + * @param {Time} [time=now] The time when the clock should stop. + * @returns {Tone.Clock} 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]); + Tone.Clock.prototype.pause = function (time) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) === Tone.State.Started) { + this._state.setStateAtTime(Tone.State.Paused, time); } - return expr; + return this; }; /** - * tokenize the expression based on the Expressions object - * @param {string} expr - * @return {Object} returns two methods on the tokenized list, next and peek + * The scheduling loop. + * @param {Number} time The current page time starting from 0 + * when the page was loaded. * @private */ - Tone.Expr.prototype._tokenize = function (expr) { - var position = -1; - var tokens = []; - while (expr.length > 0) { - expr = expr.trim(); - var token = getNextToken(expr); - tokens.push(token); - expr = expr.substr(token.value.length); + Tone.Clock.prototype._loop = function (time) { + this._loopID = requestAnimationFrame(this._boundLoop); + //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; + } + } + } else { + this._computedLookAhead = this._lookAhead; } - 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 - }; - } + //get the frequency value to compute the value of the next loop + var now = this.now(); + //if it's started + var lookAhead = this._computedLookAhead * 2; + var event = this._state.getEvent(now + lookAhead); + var state = Tone.State.Stopped; + if (event) { + state = event.state; + //if it was stopped and now started + if (this._nextTick === -1 && state === Tone.State.Started) { + this._nextTick = event.time; + if (!this.isUndef(event.offset)) { + this.ticks = event.offset; } } - throw new SyntaxError('Unexpected token ' + expr); } - return { - next: function () { - return tokens[++position]; - }, - peek: function () { - return tokens[position + 1]; + 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); + this.ticks++; } - }; + } else if (state === Tone.State.Stopped) { + this._nextTick = -1; + this.ticks = 0; + } }; /** - * recursively parse the string expression into a syntax tree - * - * @param {string} expr - * @return {Object} - * @private + * Returns the scheduled state at the given time. + * @param {Time} time The time to query. + * @return {String} The name of the state input in setStateAtTime. + * @example + * clock.start("+0.1"); + * clock.getStateAtTime("+0.1"); //returns "started" */ - 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; + Tone.Clock.prototype.getStateAtTime = function (time) { + return this._state.getStateAtTime(time); + }; + /** + * Clean up + * @returns {Tone.Clock} this + */ + Tone.Clock.prototype.dispose = function () { + cancelAnimationFrame(this._loopID); + Tone.TimelineState.prototype.dispose.call(this); + this._writable('frequency'); + this.frequency.dispose(); + this.frequency = null; + this._boundLoop = Tone.noOp; + this._nextTick = Infinity; + this.callback = null; + this._state.dispose(); + this._state = null; + }; + return Tone.Clock; + }); + Module(function (Tone) { + + /** + * @class Tone.Emitter gives classes which extend it + * the ability to listen for and trigger events. + * Inspiration and reference from Jerome Etienne's [MicroEvent](https://github.com/jeromeetienne/microevent.js). + * MIT (c) 2011 Jerome Etienne. + * + * @extends {Tone} + */ + Tone.Emitter = function () { + /** + * Contains all of the events. + * @private + * @type {Object} + */ + this._events = {}; + }; + Tone.extend(Tone.Emitter); + /** + * Bind a callback to a specific event. + * @param {String} event The name of the event to listen for. + * @param {Function} callback The callback to invoke when the + * event is triggered + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.on = function (event, callback) { + //split the event + var events = event.split(/\W+/); + for (var i = 0; i < events.length; i++) { + var eventName = events[i]; + if (!this._events.hasOwnProperty(eventName)) { + this._events[eventName] = []; + } + this._events[eventName].push(callback); } - 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 this; + }; + /** + * Remove the event listener. + * @param {String} event The event to stop listening to. + * @param {Function=} callback The callback which was bound to + * the event with Tone.Emitter.on. + * If no callback is given, all callbacks + * events are removed. + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.off = function (event, callback) { + var events = event.split(/\W+/); + for (var ev = 0; ev < events.length; ev++) { + event = events[ev]; + if (this._events.hasOwnProperty(event)) { + if (Tone.prototype.isUndef(callback)) { + this._events[event] = []; + } else { + var eventList = this._events[event]; + for (var i = 0; i < eventList.length; i++) { + if (eventList[i] === callback) { + eventList.splice(i, 1); } } } } - return ret; } - function parseExpression(precedence) { - if (isUndef(precedence)) { - precedence = 5; + return this; + }; + /** + * Invoke all of the callbacks bound to the event + * with any arguments passed in. + * @param {String} event The name of the event. + * @param {*...} args The arguments to pass to the functions listening. + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.trigger = function (event) { + if (this._events) { + var args = Array.prototype.slice.call(arguments, 1); + if (this._events.hasOwnProperty(event)) { + var eventList = this._events[event]; + for (var i = 0, len = eventList.length; i < len; i++) { + eventList[i].apply(this, args); + } } - var expr; - if (precedence < 0) { - expr = parseUnary(); + } + return this; + }; + /** + * Add Emitter functions (on/off/trigger) to the object + * @param {Object|Function} object The object or class to extend. + */ + Tone.Emitter.mixin = function (object) { + var functions = [ + 'on', + 'off', + 'trigger' + ]; + object._events = {}; + for (var i = 0; i < functions.length; i++) { + var func = functions[i]; + var emitterFunc = Tone.Emitter.prototype[func]; + object[func] = emitterFunc; + } + }; + /** + * Clean up + * @return {Tone.Emitter} this + */ + Tone.Emitter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._events = null; + return this; + }; + return Tone.Emitter; + }); + Module(function (Tone) { + + /** + * @class Similar to Tone.Timeline, but all events represent + * intervals with both "time" and "duration" times. The + * events are placed in a tree structure optimized + * for querying an intersection point with the timeline + * events. Internally uses an [Interval Tree](https://en.wikipedia.org/wiki/Interval_tree) + * to represent the data. + * @extends {Tone} + */ + Tone.IntervalTimeline = function () { + /** + * The root node of the inteval tree + * @type {IntervalNode} + * @private + */ + this._root = null; + /** + * Keep track of the length of the timeline. + * @type {Number} + * @private + */ + this._length = 0; + }; + Tone.extend(Tone.IntervalTimeline); + /** + * The event to add to the timeline. All events must + * have a time and duration value + * @param {Object} event The event to add to the timeline + * @return {Tone.IntervalTimeline} this + */ + 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'); + } + var node = new IntervalNode(event.time, event.time + event.duration, event); + if (this._root === null) { + this._root = node; + } else { + this._root.insert(node); + } + this._length++; + // Restructure tree to be balanced + while (node !== null) { + node.updateHeight(); + node.updateMax(); + this._rebalance(node); + node = node.parent; + } + return this; + }; + /** + * Remove an event from the timeline. + * @param {Object} event The event to remove from the timeline + * @return {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.removeEvent = function (event) { + if (this._root !== null) { + var results = []; + this._root.search(event.time, results); + for (var i = 0; i < results.length; i++) { + var node = results[i]; + if (node.event === event) { + this._removeNode(node); + this._length--; + break; + } + } + } + return this; + }; + /** + * The number of items in the timeline. + * @type {Number} + * @memberOf Tone.IntervalTimeline# + * @name length + * @readOnly + */ + Object.defineProperty(Tone.IntervalTimeline.prototype, 'length', { + get: function () { + return this._length; + } + }); + /** + * Remove events whose time time is after the given time + * @param {Time} time The time to query. + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.cancel = function (after) { + after = this.toSeconds(after); + this.forEachAfter(after, function (event) { + this.removeEvent(event); + }.bind(this)); + return this; + }; + /** + * Set the root node as the given node + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._setRoot = function (node) { + this._root = node; + if (this._root !== null) { + this._root.parent = null; + } + }; + /** + * Replace the references to the node in the node's parent + * with the replacement node. + * @param {IntervalNode} node + * @param {IntervalNode} replacement + * @private + */ + Tone.IntervalTimeline.prototype._replaceNodeInParent = function (node, replacement) { + if (node.parent !== null) { + if (node.isLeftChild()) { + node.parent.left = replacement; } else { - expr = parseExpression(precedence - 1); + node.parent.right = replacement; } - 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(); + this._rebalance(node.parent); + } else { + this._setRoot(replacement); + } + }; + /** + * Remove the node from the tree and replace it with + * a successor which follows the schema. + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._removeNode = function (node) { + if (node.left === null && node.right === null) { + this._replaceNodeInParent(node, null); + } else if (node.right === null) { + this._replaceNodeInParent(node, node.left); + } else if (node.left === null) { + this._replaceNodeInParent(node, node.right); + } else { + var balance = node.getBalance(); + var replacement, temp; + if (balance > 0) { + if (node.left.right === null) { + replacement = node.left; + replacement.right = node.right; + temp = replacement; + } else { + replacement = node.left.right; + while (replacement.right !== null) { + replacement = replacement.right; + } + replacement.parent.right = replacement.left; + temp = replacement.parent; + replacement.left = node.left; + replacement.right = node.right; + } + } else { + if (node.right.left === null) { + replacement = node.right; + replacement.left = node.left; + temp = replacement; + } else { + replacement = node.right.left; + while (replacement.left !== null) { + replacement = replacement.left; + } + replacement.parent = replacement.parent; + replacement.parent.left = replacement.right; + temp = replacement.parent; + replacement.left = node.left; + replacement.right = node.right; + } + } + if (node.parent !== null) { + if (node.isLeftChild()) { + node.parent.left = replacement; + } else { + node.parent.right = replacement; + } + } else { + this._setRoot(replacement); } - return expr; + // this._replaceNodeInParent(node, replacement); + this._rebalance(temp); } - 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] - }; + node.dispose(); + }; + /** + * Rotate the tree to the left + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rotateLeft = function (node) { + var parent = node.parent; + var isLeftChild = node.isLeftChild(); + // Make node.right the new root of this sub tree (instead of node) + var pivotNode = node.right; + node.right = pivotNode.left; + pivotNode.left = node; + if (parent !== null) { + if (isLeftChild) { + parent.left = pivotNode; + } else { + parent.right = pivotNode; } - return parsePrimary(); + } else { + this._setRoot(pivotNode); } - function parsePrimary() { - var token, expr; - token = lexer.peek(); - if (isUndef(token)) { - throw new SyntaxError('Unexpected termination of expression'); + }; + /** + * Rotate the tree to the right + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rotateRight = function (node) { + var parent = node.parent; + var isLeftChild = node.isLeftChild(); + // Make node.left the new root of this sub tree (instead of node) + var pivotNode = node.left; + node.left = pivotNode.right; + pivotNode.right = node; + if (parent !== null) { + if (isLeftChild) { + parent.left = pivotNode; + } else { + parent.right = pivotNode; } - if (token.type === 'func') { - token = lexer.next(); - return parseFunctionCall(token); + } else { + this._setRoot(pivotNode); + } + }; + /** + * Balance the BST + * @param {IntervalNode} node + * @private + */ + Tone.IntervalTimeline.prototype._rebalance = function (node) { + var balance = node.getBalance(); + if (balance > 1) { + if (node.left.getBalance() < 0) { + this._rotateLeft(node.left); + } else { + this._rotateRight(node); } - if (token.type === 'value') { - token = lexer.next(); - return { - method: token.method, - args: token.value - }; + } else if (balance < -1) { + if (node.right.getBalance() > 0) { + this._rotateRight(node.right); + } else { + this._rotateLeft(node); } - if (matchSyntax(token, '(')) { - lexer.next(); - expr = parseExpression(); - token = lexer.next(); - if (!matchSyntax(token, ')')) { - throw new SyntaxError('Expected )'); + } + }; + /** + * Get an event whose time and duration span the give time. Will + * return the match whose "time" value is closest to the given time. + * @param {Object} event The event to add to the timeline + * @return {Object} The event which spans the desired time + */ + Tone.IntervalTimeline.prototype.getEvent = function (time) { + if (this._root !== null) { + var results = []; + this._root.search(time, results); + if (results.length > 0) { + var max = results[0]; + for (var i = 1; i < results.length; i++) { + if (results[i].low > max.low) { + max = results[i]; + } } - return expr; + return max.event; } - 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(); + return null; + }; + /** + * Iterate over everything in the timeline. + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEach = function (callback) { + if (this._root !== null) { + var allNodes = []; + if (this._root !== null) { + this._root.traverse(function (node) { + allNodes.push(node); + }); } - token = lexer.next(); - if (!matchSyntax(token, ')')) { - throw new SyntaxError('Expected ) in a function call "' + func.value + '"'); + for (var i = 0; i < allNodes.length; i++) { + var ev = allNodes[i].event; + if (ev) { + callback(ev); + } } - 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; + return this; + }; + /** + * Iterate over everything in the array in which the given time + * overlaps with the time and duration time of the event. + * @param {Time} time The time to check if items are overlapping + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEachOverlap = function (time, callback) { + time = this.toSeconds(time); + if (this._root !== null) { + var results = []; + this._root.search(time, results); + for (var i = results.length - 1; i >= 0; i--) { + var ev = results[i].event; + if (ev) { + callback(ev); } - args.push(expr); - token = lexer.peek(); - if (!matchSyntax(token, ',')) { - break; + } + } + return this; + }; + /** + * Iterate over everything in the array in which the time is greater + * than the given time. + * @param {Time} time The time to check if items are before + * @param {Function} callback The callback to invoke with every item + * @returns {Tone.IntervalTimeline} this + */ + Tone.IntervalTimeline.prototype.forEachAfter = function (time, callback) { + time = this.toSeconds(time); + if (this._root !== null) { + var results = []; + this._root.searchAfter(time, results); + for (var i = results.length - 1; i >= 0; i--) { + var ev = results[i].event; + if (ev) { + callback(ev); } - lexer.next(); } - return args; } - return parseExpression(); + return this; }; /** - * recursively evaluate the expression tree - * @param {Object} tree - * @return {AudioNode} the resulting audio node from the expression - * @private + * Clean up + * @return {Tone.IntervalTimeline} this */ - Tone.Expr.prototype._eval = function (tree) { - if (!this.isUndef(tree)) { - var node = tree.method(tree.args, this); - this._nodes.push(node); - return node; + Tone.IntervalTimeline.prototype.dispose = function () { + var allNodes = []; + if (this._root !== null) { + this._root.traverse(function (node) { + allNodes.push(node); + }); + } + for (var i = 0; i < allNodes.length; i++) { + allNodes[i].dispose(); } + allNodes = null; + this._root = null; + return this; }; + /////////////////////////////////////////////////////////////////////////// + // INTERVAL NODE HELPER + /////////////////////////////////////////////////////////////////////////// /** - * dispose all the nodes + * Represents a node in the binary search tree, with the addition + * of a "high" value which keeps track of the highest value of + * its children. + * References: + * https://brooknovak.wordpress.com/2013/12/07/augmented-interval-tree-in-c/ + * http://www.mif.vu.lt/~valdas/ALGORITMAI/LITERATURA/Cormen/Cormen.pdf + * @param {Number} low + * @param {Number} high * @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(); + var IntervalNode = function (low, high, event) { + //the event container + this.event = event; + //the low value + this.low = low; + //the high value + this.high = high; + //the high value for this and all child nodes + this.max = this.high; + //the nodes to the left + this._left = null; + //the nodes to the right + this._right = null; + //the parent node + this.parent = null; + //the number of child nodes + this.height = 0; + }; + /** + * Insert a node into the correct spot in the tree + * @param {IntervalNode} node + */ + IntervalNode.prototype.insert = function (node) { + if (node.low <= this.low) { + if (this.left === null) { + this.left = node; + } else { + this.left.insert(node); + } + } else { + if (this.right === null) { + this.right = node; + } else { + this.right.insert(node); } - node = null; - this._nodes[i] = null; } - this._nodes = null; }; /** - * clean up + * Search the tree for nodes which overlap + * with the given point + * @param {Number} point The point to query + * @param {Array} results The array to put the results */ - Tone.Expr.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._disposeNodes(); + IntervalNode.prototype.search = function (point, results) { + // If p is to the right of the rightmost point of any interval + // in this node and all children, there won't be any matches. + if (point > this.max) { + return; + } + // Search left children + if (this.left !== null) { + this.left.search(point, results); + } + // Check this node + if (this.low <= point && this.high >= point) { + results.push(this); + } + // If p is to the left of the time of this interval, + // then it can't be in any child to the right. + if (this.low > point) { + return; + } + // Search right children + if (this.right !== null) { + this.right.search(point, results); + } }; - 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(); + * Search the tree for nodes which are less + * than the given point + * @param {Number} point The point to query + * @param {Array} results The array to put the results */ - 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); + IntervalNode.prototype.searchAfter = function (point, results) { + // Check this node + if (this.low >= point) { + results.push(this); + if (this.left !== null) { + this.left.searchAfter(point, results); } - }.bind(this), 4096); + } + // search the right side + if (this.right !== null) { + this.right.searchAfter(point, results); + } }; - Tone.extend(Tone.EqualPowerGain, Tone.SignalBase); /** - * clean up - * @returns {Tone.EqualPowerGain} this + * Invoke the callback on this element and both it's branches + * @param {Function} callback */ - Tone.EqualPowerGain.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._eqPower.dispose(); - this._eqPower = null; - return this; + IntervalNode.prototype.traverse = function (callback) { + callback(this); + if (this.left !== null) { + this.left.traverse(callback); + } + if (this.right !== null) { + this.right.traverse(callback); + } }; - return Tone.EqualPowerGain; + /** + * Update the height of the node + */ + IntervalNode.prototype.updateHeight = function () { + if (this.left !== null && this.right !== null) { + this.height = Math.max(this.left.height, this.right.height) + 1; + } else if (this.right !== null) { + this.height = this.right.height + 1; + } else if (this.left !== null) { + this.height = this.left.height + 1; + } else { + this.height = 0; + } + }; + /** + * Update the height of the node + */ + IntervalNode.prototype.updateMax = function () { + this.max = this.high; + if (this.left !== null) { + this.max = Math.max(this.max, this.left.max); + } + if (this.right !== null) { + this.max = Math.max(this.max, this.right.max); + } + }; + /** + * The balance is how the leafs are distributed on the node + * @return {Number} Negative numbers are balanced to the right + */ + IntervalNode.prototype.getBalance = function () { + var balance = 0; + if (this.left !== null && this.right !== null) { + balance = this.left.height - this.right.height; + } else if (this.left !== null) { + balance = this.left.height + 1; + } else if (this.right !== null) { + balance = -(this.right.height + 1); + } + return balance; + }; + /** + * @returns {Boolean} true if this node is the left child + * of its parent + */ + IntervalNode.prototype.isLeftChild = function () { + return this.parent !== null && this.parent.left === this; + }; + /** + * get/set the left node + * @type {IntervalNode} + */ + Object.defineProperty(IntervalNode.prototype, 'left', { + get: function () { + return this._left; + }, + set: function (node) { + this._left = node; + if (node !== null) { + node.parent = this; + } + this.updateHeight(); + this.updateMax(); + } + }); + /** + * get/set the right node + * @type {IntervalNode} + */ + Object.defineProperty(IntervalNode.prototype, 'right', { + get: function () { + return this._right; + }, + set: function (node) { + this._right = node; + if (node !== null) { + node.parent = this; + } + this.updateHeight(); + this.updateMax(); + } + }); + /** + * null out references. + */ + IntervalNode.prototype.dispose = function () { + this.parent = null; + this._left = null; + this._right = null; + this.event = null; + }; + /////////////////////////////////////////////////////////////////////////// + // END INTERVAL NODE HELPER + /////////////////////////////////////////////////////////////////////////// + return Tone.IntervalTimeline; }); 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). + * @class Transport for timing musical events. + * Supports tempo curves and time changes. Unlike browser-based timing (setInterval, requestAnimationFrame) + * Tone.Transport timing events pass in the exact time of the scheduled event + * in the argument of the callback function. Pass that time value to the object + * you're scheduling.

+ * A single transport is created for you when the library is initialized. + *

+ * The transport emits the events: "start", "stop", "pause", and "loop" which are + * called with the time of that event as the argument. * - * @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. + * @extends {Tone.Emitter} + * @singleton + * @example + * //repeated event every 8th note + * Tone.Transport.setInterval(function(time){ + * //do something with the time + * }, "8n"); + * @example + * //one time event 1 second in the future + * Tone.Transport.setTimeout(function(time){ + * //do something with the time + * }, 1); + * @example + * //event fixed to the Transports timeline. + * Tone.Transport.setTimeline(function(time){ + * //do something with the time + * }, "16:0:0"); */ - Tone.CrossFade = function (initialFade) { - Tone.call(this, 2, 1); + Tone.Transport = function () { + Tone.Emitter.call(this); + /////////////////////////////////////////////////////////////////////// + // LOOPING + ////////////////////////////////////////////////////////////////////// + /** + * If the transport loops or not. + * @type {boolean} + */ + this.loop = false; + /** + * The loop start position in ticks + * @type {Ticks} + * @private + */ + this._loopStart = 0; + /** + * The loop end position in ticks + * @type {Ticks} + * @private + */ + this._loopEnd = 0; + /////////////////////////////////////////////////////////////////////// + // CLOCK/TEMPO + ////////////////////////////////////////////////////////////////////// /** - * Alias for input[0]. - * @type {GainNode} + * Pulses per quarter is the number of ticks per quarter note. + * @private + * @type {Number} */ - this.a = this.input[0] = this.context.createGain(); + this._ppq = TransportConstructor.defaults.PPQ; /** - * Alias for input[1]. - * @type {GainNode} + * watches the main oscillator for timing ticks + * initially starts at 120bpm + * @private + * @type {Tone.Clock} */ - this.b = this.input[1] = this.context.createGain(); + this._clock = new Tone.Clock({ + 'callback': this._processTick.bind(this), + 'frequency': 0 + }); /** - * 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} + * The Beats Per Minute of the Transport. + * @type {BPM} * @signal + * @example + * Tone.Transport.bpm.value = 80; + * //ramp the bpm to 120 over 10 seconds + * Tone.Transport.bpm.rampTo(120, 10); */ - this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange); + this.bpm = this._clock.frequency; + this.bpm._toUnits = this._toUnits.bind(this); + this.bpm._fromUnits = this._fromUnits.bind(this); + this.bpm.units = Tone.Type.BPM; + this.bpm.value = TransportConstructor.defaults.bpm; + this._readOnly('bpm'); /** - * equal power gain cross fade + * The time signature, or more accurately the numerator + * of the time signature over a denominator of 4. + * @type {Number} * @private - * @type {Tone.EqualPowerGain} */ - this._equalPowerA = new Tone.EqualPowerGain(); + this._timeSignature = TransportConstructor.defaults.timeSignature; + /////////////////////////////////////////////////////////////////////// + // TIMELINE EVENTS + ////////////////////////////////////////////////////////////////////// /** - * equal power gain cross fade + * All the events in an object to keep track by ID + * @type {Object} * @private - * @type {Tone.EqualPowerGain} */ - this._equalPowerB = new Tone.EqualPowerGain(); + this._scheduledEvents = {}; /** - * invert the incoming signal + * The event ID counter + * @type {Number} * @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); + this._eventID = 0; /** - * the filter(s) - * @type {Array} + * The scheduled events. + * @type {Tone.Timeline} * @private */ - this._filters = []; - /** - * The cutoff frequency of the filter. - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + this._timeline = new Tone.Timeline(); /** - * The detune parameter - * @type {Cents} - * @signal + * Repeated events + * @type {Array} + * @private */ - this.detune = new Tone.Signal(0, Tone.Type.Cents); + this._repeatedEvents = new Tone.IntervalTimeline(); /** - * The gain of the filter, only used in certain filter types - * @type {Gain} - * @signal + * Events that occur once + * @type {Array} + * @private */ - this.gain = new Tone.Signal({ - 'value': options.gain, - 'units': Tone.Type.Decibels, - 'convert': false - }); - /** - * The Q or Quality of the filter - * @type {Positive} - * @signal + this._onceEvents = new Tone.Timeline(); + /** + * All of the synced Signals + * @private + * @type {Array} */ - this.Q = new Tone.Signal(options.Q); + this._syncedSignals = []; + /////////////////////////////////////////////////////////////////////// + // SWING + ////////////////////////////////////////////////////////////////////// + var swingSeconds = this.notationToSeconds(TransportConstructor.defaults.swingSubdivision, TransportConstructor.defaults.bpm, TransportConstructor.defaults.timeSignature); /** - * the type of the filter - * @type {string} + * The subdivision of the swing + * @type {Ticks} * @private */ - this._type = options.type; + this._swingTicks = swingSeconds / (60 / TransportConstructor.defaults.bpm) * this._ppq; /** - * the rolloff value of the filter - * @type {number} + * The swing amount + * @type {NormalRange} * @private */ - this._rolloff = options.rolloff; - //set the rolloff; - this.rolloff = options.rolloff; - this._readOnly([ - 'detune', - 'frequency', - 'gain', - 'Q' - ]); + this._swingAmount = 0; + }; + Tone.extend(Tone.Transport, Tone.Emitter); + /** + * the defaults + * @type {Object} + * @const + * @static + */ + Tone.Transport.defaults = { + 'bpm': 120, + 'swing': 0, + 'swingSubdivision': '16n', + 'timeSignature': 4, + 'loopStart': 0, + 'loopEnd': '4m', + 'PPQ': 48 + }; + /////////////////////////////////////////////////////////////////////////////// + // TICKS + /////////////////////////////////////////////////////////////////////////////// + /** + * called on every tick + * @param {number} tickTime clock relative tick time + * @private + */ + Tone.Transport.prototype._processTick = function (tickTime) { + //handle swing + if (this._swingAmount > 0 && this._clock.ticks % this._ppq !== 0 && //not on a downbeat + this._clock.ticks % this._swingTicks === 0) { + //add some swing + tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount; + } + //do the loop test + if (this.loop) { + if (this._clock.ticks === this._loopEnd) { + this.ticks = this._loopStart; + this.trigger('loop', tickTime); + } + } + var ticks = this._clock.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) { + 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 + /////////////////////////////////////////////////////////////////////////////// + /** + * 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. + * @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 + * Tone.Transport.schedule(function(time){ + * envelope.triggerAttack(time); + * }, "128i"); + */ + Tone.Transport.prototype.schedule = function (callback, time) { + var event = { + 'time': this.toTicks(time), + 'callback': callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + 'event': event, + 'timeline': this._timeline + }; + this._timeline.addEvent(event); + return id; + }; + /** + * Schedule a repeated event along the timeline. The event will fire + * at the `interval` starting at the `startTime` and for the specified + * `duration`. + * @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 + * 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 + * the event. + * @example + * //a callback invoked every eighth note after the first measure + * Tone.Transport.scheduleRepeat(callback, "8n", "1m"); + */ + Tone.Transport.prototype.scheduleRepeat = function (callback, interval, startTime, duration) { + if (interval <= 0) { + throw new Error('repeat events must have an interval larger than 0'); + } + var event = { + 'time': this.toTicks(startTime), + 'duration': this.toTicks(this.defaultArg(duration, Infinity)), + 'interval': this.toTicks(interval), + 'callback': callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + 'event': event, + 'timeline': this._repeatedEvents + }; + this._repeatedEvents.addEvent(event); + return id; + }; + /** + * Schedule an event that will be removed after it is invoked. + * 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. + * @returns {Number} The ID of the scheduled event. + */ + Tone.Transport.prototype.scheduleOnce = function (callback, time) { + var event = { + 'time': this.toTicks(time), + 'callback': callback + }; + var id = this._eventID++; + this._scheduledEvents[id.toString()] = { + 'event': event, + 'timeline': this._onceEvents + }; + this._onceEvents.addEvent(event); + return id; + }; + /** + * Clear the passed in event id from the timeline + * @param {Number} eventId The id of the event. + * @returns {Tone.Transport} this + */ + Tone.Transport.prototype.clear = function (eventId) { + if (this._scheduledEvents.hasOwnProperty(eventId)) { + var item = this._scheduledEvents[eventId.toString()]; + item.timeline.removeEvent(item.event); + delete this._scheduledEvents[eventId.toString()]; + } + return this; + }; + /** + * 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 + * this time. + * @returns {Tone.Transport} this + */ + Tone.Transport.prototype.cancel = function (after) { + after = this.defaultArg(after, 0); + after = this.toTicks(after); + this._timeline.cancel(after); + this._onceEvents.cancel(after); + this._repeatedEvents.cancel(after); + 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 + /////////////////////////////////////////////////////////////////////////////// + /** + * Returns the playback state of the source, either "started", "stopped", or "paused" + * @type {Tone.State} + * @readOnly + * @memberOf Tone.Transport# + * @name state + */ + Object.defineProperty(Tone.Transport.prototype, 'state', { + get: function () { + return this._clock.getStateAtTime(this.now()); + } + }); + /** + * 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. + * @returns {Tone.Transport} this + * @example + * //start the transport in one second starting at beginning of the 5th measure. + * Tone.Transport.start("+1", "4:0:0"); + */ + Tone.Transport.prototype.start = function (time, offset) { + time = this.toSeconds(time); + if (!this.isUndef(offset)) { + offset = this.toTicks(offset); + } else { + offset = this.defaultArg(offset, this._clock.ticks); + } + //start the clock + this._clock.start(time, offset); + this.trigger('start', time, this.ticksToSeconds(offset)); + return this; }; - Tone.extend(Tone.Filter); /** - * the default parameters - * - * @static - * @type {Object} + * Stop the transport and all sources synced to the transport. + * @param {Time} [time=now] The time when the transport should stop. + * @returns {Tone.Transport} this + * @example + * Tone.Transport.stop(); */ - Tone.Filter.defaults = { - 'type': 'lowpass', - 'frequency': 350, - 'rolloff': -12, - 'Q': 1, - 'gain': 0 + Tone.Transport.prototype.stop = function (time) { + time = this.toSeconds(time); + this._clock.stop(time); + this.trigger('stop', time); + return this; }; /** - * The type of the filter. Types: "lowpass", "highpass", - * "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking". - * @memberOf Tone.Filter# - * @type {string} - * @name type + * Pause the transport and all sources synced to the transport. + * @param {Time} [time=now] + * @returns {Tone.Transport} this */ - Object.defineProperty(Tone.Filter.prototype, 'type', { + Tone.Transport.prototype.pause = function (time) { + time = this.toSeconds(time); + this._clock.pause(time); + this.trigger('pause', time); + return this; + }; + /////////////////////////////////////////////////////////////////////////////// + // SETTERS/GETTERS + /////////////////////////////////////////////////////////////////////////////// + /** + * The time signature as just the numerator over 4. + * For example 4/4 would be just 4 and 6/8 would be 3. + * @memberOf Tone.Transport# + * @type {Number|Array} + * @name timeSignature + * @example + * //common time + * Tone.Transport.timeSignature = 4; + * // 7/8 + * Tone.Transport.timeSignature = [7, 8]; + * //this will be reduced to a single number + * Tone.Transport.timeSignature; //returns 3.5 + */ + Object.defineProperty(Tone.Transport.prototype, 'timeSignature', { get: function () { - return this._type; + return this._timeSignature; }, - set: function (type) { - var types = [ - 'lowpass', - 'highpass', - 'bandpass', - 'lowshelf', - 'highshelf', - 'notch', - 'allpass', - 'peaking' - ]; - if (types.indexOf(type) === -1) { - throw new TypeError('Tone.Filter does not have filter type ' + type); - } - this._type = type; - for (var i = 0; i < this._filters.length; i++) { - this._filters[i].type = type; + set: function (timeSig) { + if (this.isArray(timeSig)) { + timeSig = timeSig[0] / timeSig[1] * 4; } + this._timeSignature = timeSig; } }); /** - * The rolloff of the filter which is the drop in db - * per octave. Implemented internally by cascading filters. - * Only accepts the values -12, -24, and -48. - * @memberOf Tone.Filter# - * @type {number} - * @name rolloff + * When the Tone.Transport.loop = true, this is the starting position of the loop. + * @memberOf Tone.Transport# + * @type {Time} + * @name loopStart */ - Object.defineProperty(Tone.Filter.prototype, 'rolloff', { + Object.defineProperty(Tone.Transport.prototype, 'loopStart', { get: function () { - return this._rolloff; + return this.ticksToSeconds(this._loopStart); }, - set: function (rolloff) { - var possibilities = [ - -12, - -24, - -48 - ]; - var cascadingCount = possibilities.indexOf(rolloff); - //check the rolloff is valid - if (cascadingCount === -1) { - throw new RangeError('Filter rolloff can only be -12, -24, or -48'); - } - cascadingCount++; - this._rolloff = rolloff; - //first disconnect the filters and throw them away - this.input.disconnect(); - for (var i = 0; i < this._filters.length; i++) { - this._filters[i].disconnect(); - this._filters[i] = null; + set: function (startPosition) { + this._loopStart = this.toTicks(startPosition); + } + }); + /** + * When the Tone.Transport.loop = true, this is the ending position of the loop. + * @memberOf Tone.Transport# + * @type {Time} + * @name loopEnd + */ + Object.defineProperty(Tone.Transport.prototype, 'loopEnd', { + get: function () { + return this.ticksToSeconds(this._loopEnd); + }, + set: function (endPosition) { + this._loopEnd = this.toTicks(endPosition); + } + }); + /** + * Set the loop start and stop at the same time. + * @param {Time} startPosition + * @param {Time} endPosition + * @returns {Tone.Transport} this + * @example + * //loop over the first measure + * Tone.Transport.setLoopPoints(0, "1m"); + * Tone.Transport.loop = true; + */ + Tone.Transport.prototype.setLoopPoints = function (startPosition, endPosition) { + this.loopStart = startPosition; + this.loopEnd = endPosition; + return this; + }; + /** + * The swing value. Between 0-1 where 1 equal to + * the note + half the subdivision. + * @memberOf Tone.Transport# + * @type {NormalRange} + * @name swing + */ + Object.defineProperty(Tone.Transport.prototype, 'swing', { + get: function () { + return this._swingAmount * 2; + }, + set: function (amount) { + //scale the values to a normal range + this._swingAmount = amount * 0.5; + } + }); + /** + * Set the subdivision which the swing will be applied to. + * The default values is a 16th note. Value must be less + * than a quarter note. + * + * @memberOf Tone.Transport# + * @type {Time} + * @name swingSubdivision + */ + Object.defineProperty(Tone.Transport.prototype, 'swingSubdivision', { + get: function () { + return this.toNotation(this._swingTicks + 'i'); + }, + set: function (subdivision) { + this._swingTicks = this.toTicks(subdivision); + } + }); + /** + * The Transport's position in MEASURES:BEATS:SIXTEENTHS. + * Setting the value will jump to that position right away. + * + * @memberOf Tone.Transport# + * @type {TransportTime} + * @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); } - 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; + quarters = Math.floor(quarters) % this._timeSignature; + var progress = [ + measures, + quarters, + sixteenths + ]; + return progress.join(':'); + }, + set: function (progress) { + var ticks = this.toTicks(progress); + this.ticks = ticks; + } + }); + /** + * The Transport's loop position as a normalized value. Always + * returns 0 if the transport if loop is not true. + * @memberOf Tone.Transport# + * @name progress + * @type {NormalRange} + */ + Object.defineProperty(Tone.Transport.prototype, 'progress', { + get: function () { + if (this.loop) { + return (this.ticks - this._loopStart) / (this._loopEnd - this._loopStart); + } else { + return 0; } - //connect them up - var connectionChain = [this.input].concat(this._filters).concat([this.output]); - this.connectSeries.apply(this, connectionChain); } }); /** - * Clean up. - * @return {Tone.Filter} this + * The transports current tick position. + * + * @memberOf Tone.Transport# + * @type {Ticks} + * @name ticks */ - 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; + Object.defineProperty(Tone.Transport.prototype, 'ticks', { + get: function () { + return this._clock.ticks; + }, + set: function (t) { + this._clock.ticks = t; } - 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; + }); + /** + * Pulses Per Quarter note. This is the smallest resolution + * the Transport timing supports. This should be set once + * on initialization and not set again. Changing this value + * after other objects have been created can cause problems. + * + * @memberOf Tone.Transport# + * @type {Number} + * @name PPQ + */ + Object.defineProperty(Tone.Transport.prototype, 'PPQ', { + get: function () { + return this._ppq; + }, + set: function (ppq) { + this._ppq = ppq; + this.bpm.value = this.bpm.value; + } + }); + /** + * Convert from BPM to frequency (factoring in PPQ) + * @param {BPM} bpm The BPM value to convert to frequency + * @return {Frequency} The BPM as a frequency with PPQ factored in. + * @private + */ + Tone.Transport.prototype._fromUnits = function (bpm) { + return 1 / (60 / bpm / this.PPQ); + }; + /** + * Convert from frequency (with PPQ) into BPM + * @param {Frequency} freq The clocks frequency to convert to BPM + * @return {BPM} The frequency value as BPM. + * @private + */ + Tone.Transport.prototype._toUnits = function (freq) { + return freq / this.PPQ * 60; + }; + /////////////////////////////////////////////////////////////////////////////// + // SYNCING + /////////////////////////////////////////////////////////////////////////////// + /** + * Attaches the signal to the tempo control signal so that + * any changes in the tempo will change the signal in the same + * ratio. + * + * @param {Tone.Signal} signal + * @param {number=} ratio Optionally pass in the ratio between + * the two signals. Otherwise it will be computed + * based on their current values. + * @returns {Tone.Transport} this + */ + Tone.Transport.prototype.syncSignal = function (signal, ratio) { + if (!ratio) { + //get the sync ratio + if (signal._param.value !== 0) { + ratio = signal._param.value / this.bpm._param.value; + } else { + ratio = 0; + } + } + var ratioSignal = new Tone.Gain(ratio); + this.bpm.chain(ratioSignal, signal._param); + this._syncedSignals.push({ + 'ratio': ratioSignal, + 'signal': signal, + 'initial': signal._param.value + }); + signal._param.value = 0; 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 + * Unsyncs a previously synced signal from the transport's control. + * See Tone.Transport.syncSignal. + * @param {Tone.Signal} signal + * @returns {Tone.Transport} this */ - 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.Transport.prototype.unsyncSignal = function (signal) { + for (var i = this._syncedSignals.length - 1; i >= 0; i--) { + var syncedSignal = this._syncedSignals[i]; + if (syncedSignal.signal === signal) { + syncedSignal.ratio.dispose(); + syncedSignal.signal._param.value = syncedSignal.initial; + this._syncedSignals.splice(i, 1); + } + } + return this; }; - Tone.extend(Tone.MultibandSplit); /** + * Clean up. + * @returns {Tone.Transport} this * @private - * @static - * @type {Object} */ - Tone.MultibandSplit.defaults = { - 'lowFrequency': 400, - 'highFrequency': 2500, - 'Q': 1 + Tone.Transport.prototype.dispose = function () { + Tone.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; + return this; }; + /////////////////////////////////////////////////////////////////////////////// + // DEPRECATED FUNCTIONS + // (will be removed in r7) + /////////////////////////////////////////////////////////////////////////////// /** - * Clean up. - * @returns {Tone.MultibandSplit} this + * @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.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; + Tone.Transport.prototype.setInterval = function (callback, interval) { + console.warn('This method is deprecated. Use Tone.Transport.scheduleRepeat instead.'); + return Tone.Transport.scheduleRepeat(callback, interval); }; - 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. + * @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. * - * @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. + * @param {function} callback + * @param {Time} time The time (from now) that the callback will be invoked. + * @return {number} The id of the timeout. * @example - * var eq = new Tone.EQ3(-10, 3, -20); - */ - Tone.EQ3 = function () { - var options = this.optionsObject(arguments, [ - 'low', - 'mid', - 'high' - ], Tone.EQ3.defaults); - /** - * the output node - * @type {GainNode} - * @private - */ - this.output = this.context.createGain(); - /** - * the multiband split - * @type {Tone.MultibandSplit} - * @private - */ - this._multibandSplit = this.input = new Tone.MultibandSplit({ - 'lowFrequency': options.lowFrequency, - 'highFrequency': options.highFrequency - }); - /** - * the low gain - * @type {GainNode} - * @private - */ - this._lowGain = this.context.createGain(); - /** - * the mid gain - * @type {GainNode} - * @private - */ - this._midGain = this.context.createGain(); - /** - * the high gain - * @type {GainNode} - * @private - */ - this._highGain = this.context.createGain(); - /** - * The gain in decibels of the low part - * @type {Decibels} - * @signal - */ - this.low = new Tone.Signal(this._lowGain.gain, Tone.Type.Decibels); - /** - * The gain in decibels of the mid part - * @type {Decibels} - * @signal - */ - this.mid = new Tone.Signal(this._midGain.gain, Tone.Type.Decibels); - /** - * The gain in decibels of the high part - * @type {Decibels} - * @signal - */ - this.high = new Tone.Signal(this._highGain.gain, Tone.Type.Decibels); - /** - * The Q value for all of the filters. - * @type {Positive} - * @signal - */ - this.Q = this._multibandSplit.Q; - /** - * The low/mid crossover frequency. - * @type {Frequency} - * @signal - */ - this.lowFrequency = this._multibandSplit.lowFrequency; - /** - * The mid/high crossover frequency. - * @type {Frequency} - * @signal - */ - this.highFrequency = this._multibandSplit.highFrequency; - //the frequency bands - this._multibandSplit.low.chain(this._lowGain, this.output); - this._multibandSplit.mid.chain(this._midGain, this.output); - this._multibandSplit.high.chain(this._highGain, this.output); - //set the gains - this.low.value = options.low; - this.mid.value = options.mid; - this.high.value = options.high; - this._readOnly([ - 'low', - 'mid', - 'high', - 'lowFrequency', - 'highFrequency' - ]); + * //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); }; - Tone.extend(Tone.EQ3); /** - * the default values + * @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.EQ3.defaults = { - 'low': 0, - 'mid': 0, - 'high': 0, - 'lowFrequency': 400, - 'highFrequency': 2500 + Tone.Transport.prototype.clearTimeout = function (id) { + console.warn('This method is deprecated. Use Tone.Transport.clear instead.'); + return Tone.Transport.clear(id); }; /** - * clean up - * @returns {Tone.EQ3} this + * @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.EQ3.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'low', - 'mid', - 'high', - 'lowFrequency', - 'highFrequency' - ]); - this._multibandSplit.dispose(); - this._multibandSplit = null; - this.lowFrequency = null; - this.highFrequency = null; - this._lowGain.disconnect(); - this._lowGain = null; - this._midGain.disconnect(); - this._midGain = null; - this._highGain.disconnect(); - this._highGain = null; - this.low.dispose(); - this.low = null; - this.mid.dispose(); - this.mid = null; - this.high.dispose(); - this.high = null; - this.Q = null; - return this; + Tone.Transport.prototype.setTimeline = function (callback, time) { + console.warn('This method is deprecated. Use Tone.Transport.schedule instead.'); + return Tone.Transport.schedule(callback, time); }; - return Tone.EQ3; + /** + * @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 + /////////////////////////////////////////////////////////////////////////////// + var TransportConstructor = Tone.Transport; + Tone._initAudioContext(function () { + if (typeof Tone.Transport === 'function') { + //a single transport object + Tone.Transport = new Tone.Transport(); + } else { + //stop the clock + Tone.Transport.stop(); + //get the previous values + var prevSettings = Tone.Transport.get(); + //destory the old transport + Tone.Transport.dispose(); + //make new Transport insides + TransportConstructor.call(Tone.Transport); + //set the previous config + Tone.Transport.set(prevSettings); + } + }); + return Tone.Transport; }); Module(function (Tone) { /** - * @class Performs a linear scaling on an input signal. - * Scales a NormalRange input to between - * outputMin and outputMax. + * @class Tone.Volume is a simple volume node, useful for creating a volume fader. * + * @extends {Tone} * @constructor - * @extends {Tone.SignalBase} - * @param {number} [outputMin=0] The output value when the input is 0. - * @param {number} [outputMax=1] The output value when the input is 1. + * @param {Decibels} [volume=0] the initial volume * @example - * var scale = new Tone.Scale(50, 100); - * var signal = new Tone.Signal(0.5).connect(scale); - * //the output of scale equals 75 + * var vol = new Tone.Volume(-12); + * instrument.chain(vol, Tone.Master); */ - Tone.Scale = function (outputMin, outputMax) { - /** - * @private - * @type {number} - */ - this._outputMin = this.defaultArg(outputMin, 0); - /** - * @private - * @type {number} - */ - this._outputMax = this.defaultArg(outputMax, 1); - /** - * @private - * @type {Tone.Multiply} - * @private + Tone.Volume = function () { + var options = this.optionsObject(arguments, ['volume'], Tone.Volume.defaults); + /** + * the output node + * @type {GainNode} + * @private */ - this._scale = this.input = new Tone.Multiply(1); - /** - * @private - * @type {Tone.Add} - * @private + this.output = this.input = new Tone.Gain(options.volume, Tone.Type.Decibels); + /** + * The volume control in decibels. + * @type {Decibels} + * @signal */ - this._add = this.output = new Tone.Add(0); - this._scale.connect(this._add); - this._setRange(); + this.volume = this.output.gain; + this._readOnly('volume'); }; - Tone.extend(Tone.Scale, Tone.SignalBase); - /** - * The minimum output value. This number is output when - * the value input value is 0. - * @memberOf Tone.Scale# - * @type {number} - * @name min - */ - Object.defineProperty(Tone.Scale.prototype, 'min', { - get: function () { - return this._outputMin; - }, - set: function (min) { - this._outputMin = min; - this._setRange(); - } - }); - /** - * The maximum output value. This number is output when - * the value input value is 1. - * @memberOf Tone.Scale# - * @type {number} - * @name max - */ - Object.defineProperty(Tone.Scale.prototype, 'max', { - get: function () { - return this._outputMax; - }, - set: function (max) { - this._outputMax = max; - this._setRange(); - } - }); + Tone.extend(Tone.Volume); /** - * set the values - * @private + * Defaults + * @type {Object} + * @const + * @static */ - Tone.Scale.prototype._setRange = function () { - this._add.value = this._outputMin; - this._scale.value = this._outputMax - this._outputMin; - }; + Tone.Volume.defaults = { 'volume': 0 }; /** - * Clean up. - * @returns {Tone.Scale} this + * clean up + * @returns {Tone.Volume} this */ - Tone.Scale.prototype.dispose = function () { + Tone.Volume.prototype.dispose = function () { + this.input.dispose(); Tone.prototype.dispose.call(this); - this._add.dispose(); - this._add = null; - this._scale.dispose(); - this._scale = null; + this._writable('volume'); + this.volume.dispose(); + this.volume = null; return this; }; - return Tone.Scale; + return Tone.Volume; }); Module(function (Tone) { + /** - * @class Performs an exponential scaling on an input signal. - * Scales a NormalRange value [0,1] exponentially - * to the output range of outputMin to outputMax. + * @class Base class for sources. Sources have start/stop methods + * and the ability to be synced to the + * start/stop of Tone.Transport. * * @constructor - * @extends {Tone.SignalBase} - * @param {number} [outputMin=0] The output value when the input is 0. - * @param {number} [outputMax=1] The output value when the input is 1. - * @param {number} [exponent=2] The exponent which scales the incoming signal. + * @extends {Tone} * @example - * var scaleExp = new Tone.ScaleExp(0, 100, 2); - * var signal = new Tone.Signal(0.5).connect(scaleExp); + * //Multiple state change events can be chained together, + * //but must be set in the correct order and with ascending times + * + * // OK + * state.start().stop("+0.2"); + * // AND + * state.start().stop("+0.2").start("+0.4").stop("+0.7") + * + * // BAD + * state.stop("+0.2").start(); + * // OR + * state.start("+0.3").stop("+0.2"); + * */ - Tone.ScaleExp = function (outputMin, outputMax, exponent) { + Tone.Source = function (options) { + //Sources only have an output and no input + Tone.call(this); + options = this.defaultArg(options, Tone.Source.defaults); /** - * scale the input to the output range - * @type {Tone.Scale} + * The output volume node + * @type {Tone.Volume} * @private */ - this._scale = this.output = new Tone.Scale(outputMin, outputMax); + 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'); /** + * Keep track of the scheduled state. + * @type {Tone.TimelineState} * @private - * @type {Tone.Pow} + */ + this._state = new Tone.TimelineState(Tone.State.Stopped); + /** + * The synced `start` callback function from the transport + * @type {Function} * @private */ - this._exp = this.input = new Tone.Pow(this.defaultArg(exponent, 2)); - this._exp.connect(this._scale); + this._syncStart = function (time, offset) { + time = this.toSeconds(time); + time += this.toSeconds(this._startDelay); + this.start(time, offset); + }.bind(this); + /** + * The synced `stop` callback function from the transport + * @type {Function} + * @private + */ + this._syncStop = this.stop.bind(this); + /** + * The offset from the start of the Transport `start` + * @type {Time} + * @private + */ + this._startDelay = 0; + //make the output explicitly stereo + this._volume.output.output.channelCount = 2; + this._volume.output.output.channelCountMode = 'explicit'; }; - Tone.extend(Tone.ScaleExp, Tone.SignalBase); + Tone.extend(Tone.Source); /** - * Instead of interpolating linearly between the min and - * max values, setting the exponent will interpolate between - * the two values with an exponential curve. - * @memberOf Tone.ScaleExp# - * @type {number} - * @name exponent + * The default parameters + * @static + * @const + * @type {Object} */ - Object.defineProperty(Tone.ScaleExp.prototype, 'exponent', { - get: function () { - return this._exp.value; - }, - set: function (exp) { - this._exp.value = exp; - } - }); + Tone.Source.defaults = { 'volume': 0 }; /** - * The minimum output value. This number is output when - * the value input value is 0. - * @memberOf Tone.ScaleExp# - * @type {number} - * @name min + * Returns the playback state of the source, either "started" or "stopped". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.Source# + * @name state */ - Object.defineProperty(Tone.ScaleExp.prototype, 'min', { + Object.defineProperty(Tone.Source.prototype, 'state', { get: function () { - return this._scale.min; - }, - set: function (min) { - this._scale.min = min; + return this._state.getStateAtTime(this.now()); } }); /** - * The maximum output value. This number is output when - * the value input value is 1. - * @memberOf Tone.ScaleExp# - * @type {number} - * @name max + * Start the source at the specified time. If no time is given, + * start the source now. + * @param {Time} [time=now] When the source should be started. + * @returns {Tone.Source} this + * @example + * source.start("+0.5"); //starts the source 0.5 seconds from now */ - Object.defineProperty(Tone.ScaleExp.prototype, 'max', { - get: function () { - return this._scale.max; - }, - set: function (max) { - this._scale.max = max; + Tone.Source.prototype.start = function (time) { + time = this.toSeconds(time); + if (this._state.getStateAtTime(time) !== Tone.State.Started || this.retrigger) { + this._state.setStateAtTime(Tone.State.Started, time); + if (this._start) { + this._start.apply(this, arguments); + } } - }); + return this; + }; /** - * Clean up. - * @returns {Tone.ScaleExp} this + * Stop the source at the specified time. If no time is given, + * stop the source now. + * @param {Time} [time=now] When the source should be stopped. + * @returns {Tone.Source} this + * @example + * source.stop(); // stops the source immediately */ - Tone.ScaleExp.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._scale.dispose(); - this._scale = null; - this._exp.dispose(); - this._exp = null; + 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); + } + } return this; }; - return Tone.ScaleExp; + /** + * Sync the source to the Transport so that when the transport + * is started, this source is started and when the transport is stopped + * or paused, so is the source. + * + * @param {Time} [delay=0] Delay time before starting the source after the + * Transport has started. + * @returns {Tone.Source} this + * @example + * //sync the source to start 1 measure after the transport starts + * source.sync("1m"); + * //start the transport. the source will start 1 measure later. + * Tone.Transport.start(); + */ + Tone.Source.prototype.sync = function (delay) { + this._startDelay = this.defaultArg(delay, 0); + Tone.Transport.on('start', this._syncStart); + Tone.Transport.on('stop pause', this._syncStop); + return this; + }; + /** + * Unsync the source to the Transport. See Tone.Source.sync + * @returns {Tone.Source} this + */ + Tone.Source.prototype.unsync = function () { + this._startDelay = 0; + Tone.Transport.off('start', this._syncStart); + Tone.Transport.off('stop pause', this._syncStop); + return this; + }; + /** + * Clean up. + * @return {Tone.Source} this + */ + Tone.Source.prototype.dispose = function () { + this.stop(); + Tone.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; + }; + return Tone.Source; }); Module(function (Tone) { /** - * @class Comb filters are basic building blocks for physical modeling. Read more - * about comb filters on [CCRMA's website](https://ccrma.stanford.edu/~jos/pasp/Feedback_Comb_Filters.html). + * @class Tone.Oscillator supports a number of features including + * phase rotation, multiple oscillator types (see Tone.Oscillator.type), + * and Transport syncing (see Tone.Oscillator.syncFrequency). * - * @extends {Tone} * @constructor - * @param {Time|Object} [delayTime] The delay time of the filter. - * @param {NormalRange=} resonance The amount of feedback the filter has. + * @extends {Tone.Source} + * @param {Frequency} [frequency] Starting frequency + * @param {string} [type] The oscillator type. Read more about type below. + * @example + * //make and start a 440hz sine tone + * var osc = new Tone.Oscillator(440, "sine").toMaster().start(); */ - Tone.FeedbackCombFilter = function () { - Tone.call(this); + Tone.Oscillator = function () { var options = this.optionsObject(arguments, [ - 'delayTime', - 'resonance' - ], Tone.FeedbackCombFilter.defaults); + 'frequency', + 'type' + ], Tone.Oscillator.defaults); + Tone.Source.call(this, options); /** - * The amount of feedback of the delayed signal. - * @type {NormalRange} + * the main oscillator + * @type {OscillatorNode} + * @private + */ + this._oscillator = null; + /** + * The frequency control. + * @type {Frequency} * @signal */ - this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange); + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * the delay node - * @type {DelayNode} + * The detune control signal. + * @type {Cents} + * @signal + */ + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + /** + * the periodic wave + * @type {PeriodicWave} * @private */ - this._delay = this.input = this.output = this.context.createDelay(1); + this._wave = null; /** - * The amount of delay of the comb filter. - * @type {Time} - * @signal + * The partials of the oscillator + * @type {Array} + * @private */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + this._partials = this.defaultArg(options.partials, [1]); /** - * the feedback node - * @type {GainNode} + * the phase of the oscillator + * between 0 - 360 + * @type {number} * @private */ - this._feedback = this.context.createGain(); - this._delay.chain(this._feedback, this._delay); - this.resonance.connect(this._feedback.gain); - this.delayTime.connect(this._delay.delayTime); + this._phase = options.phase; + /** + * the type of the oscillator + * @type {string} + * @private + */ + this._type = null; + //setup + this.type = options.type; + this.phase = this._phase; this._readOnly([ - 'resonance', - 'delayTime' + 'frequency', + 'detune' ]); }; - Tone.extend(Tone.FeedbackCombFilter); + Tone.extend(Tone.Oscillator, Tone.Source); + /** + * the default parameters + * @type {Object} + */ + Tone.Oscillator.defaults = { + 'type': 'sine', + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'partials': [] + }; + /** + * The Oscillator types + * @enum {String} + */ + Tone.Oscillator.Type = { + Sine: 'sine', + Triangle: 'triangle', + Sawtooth: 'sawtooth', + Square: 'square', + Custom: 'custom' + }; + /** + * start the oscillator + * @param {Time} [time=now] + * @private + */ + Tone.Oscillator.prototype._start = function (time) { + //new oscillator with previous values + this._oscillator = this.context.createOscillator(); + this._oscillator.setPeriodicWave(this._wave); + //connect the control signal to the oscillator frequency & detune + this._oscillator.connect(this.output); + this.frequency.connect(this._oscillator.frequency); + this.detune.connect(this._oscillator.detune); + //start the oscillator + this._oscillator.start(this.toSeconds(time)); + }; + /** + * stop the oscillator + * @private + * @param {Time} [time=now] (optional) timing parameter + * @returns {Tone.Oscillator} this + */ + Tone.Oscillator.prototype._stop = function (time) { + if (this._oscillator) { + this._oscillator.stop(this.toSeconds(time)); + this._oscillator = null; + } + return this; + }; + /** + * Sync the signal to the Transport's bpm. Any changes to the transports bpm, + * will also affect the oscillators frequency. + * @returns {Tone.Oscillator} this + * @example + * Tone.Transport.bpm.value = 120; + * osc.frequency.value = 440; + * //the ration between the bpm and the frequency will be maintained + * osc.syncFrequency(); + * Tone.Transport.bpm.value = 240; + * // the frequency of the oscillator is doubled to 880 + */ + Tone.Oscillator.prototype.syncFrequency = function () { + Tone.Transport.syncSignal(this.frequency); + return this; + }; + /** + * Unsync the oscillator's frequency from the Transport. + * See Tone.Oscillator.syncFrequency + * @returns {Tone.Oscillator} this + */ + Tone.Oscillator.prototype.unsyncFrequency = function () { + Tone.Transport.unsyncSignal(this.frequency); + return this; + }; + /** + * The type of the oscillator: either sine, square, triangle, or sawtooth. Also capable of + * setting the first x number of partials of the oscillator. For example: "sine4" would + * set be the first 4 partials of the sine wave and "triangle8" would set the first + * 8 partials of the triangle wave. + *

+ * Uses PeriodicWave internally even for native types so that it can set the phase. + * PeriodicWave equations are from the + * [Webkit Web Audio implementation](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/webaudio/PeriodicWave.cpp&sq=package:chromium). + * + * @memberOf Tone.Oscillator# + * @type {string} + * @name type + * @example + * //set it to a square wave + * osc.type = "square"; + * @example + * //set the first 6 partials of a sawtooth wave + * osc.type = "sawtooth6"; + */ + Object.defineProperty(Tone.Oscillator.prototype, 'type', { + get: function () { + return this._type; + }, + set: function (type) { + var coefs = this._getRealImaginary(type, this._phase); + var periodicWave = this.context.createPeriodicWave(coefs[0], coefs[1]); + this._wave = periodicWave; + if (this._oscillator !== null) { + this._oscillator.setPeriodicWave(this._wave); + } + this._type = type; + } + }); + /** + * Returns the real and imaginary components based + * on the oscillator type. + * @returns {Array} [real, imaginary] + * @private + */ + Tone.Oscillator.prototype._getRealImaginary = function (type, phase) { + var fftSize = 4096; + var periodicWaveSize = fftSize / 2; + var real = new Float32Array(periodicWaveSize); + var imag = new Float32Array(periodicWaveSize); + var partialCount = 1; + if (type === Tone.Oscillator.Type.Custom) { + partialCount = this._partials.length + 1; + periodicWaveSize = partialCount; + } else { + var partial = /^(sine|triangle|square|sawtooth)(\d+)$/.exec(type); + if (partial) { + partialCount = parseInt(partial[2]) + 1; + type = partial[1]; + partialCount = Math.max(partialCount, 2); + periodicWaveSize = partialCount; + } + } + for (var n = 1; n < periodicWaveSize; ++n) { + var piFactor = 2 / (n * Math.PI); + var b; + switch (type) { + case Tone.Oscillator.Type.Sine: + b = n <= partialCount ? 1 : 0; + break; + case Tone.Oscillator.Type.Square: + b = n & 1 ? 2 * piFactor : 0; + break; + case Tone.Oscillator.Type.Sawtooth: + b = piFactor * (n & 1 ? 1 : -1); + break; + case Tone.Oscillator.Type.Triangle: + if (n & 1) { + b = 2 * (piFactor * piFactor) * (n - 1 >> 1 & 1 ? -1 : 1); + } else { + b = 0; + } + break; + case Tone.Oscillator.Type.Custom: + b = this._partials[n - 1]; + break; + default: + throw new Error('invalid oscillator type: ' + type); + } + if (b !== 0) { + real[n] = -b * Math.sin(phase * n); + imag[n] = b * Math.cos(phase * n); + } else { + real[n] = 0; + imag[n] = 0; + } + } + return [ + real, + imag + ]; + }; /** - * the default parameters - * @static - * @const - * @type {Object} + * Compute the inverse FFT for a given phase. + * @param {Float32Array} real + * @param {Float32Array} imag + * @param {NormalRange} phase + * @return {AudioRange} + * @private */ - Tone.FeedbackCombFilter.defaults = { - 'delayTime': 0.1, - 'resonance': 0.5 + Tone.Oscillator.prototype._inverseFFT = function (real, imag, phase) { + var sum = 0; + var len = real.length; + for (var i = 0; i < len; i++) { + sum += real[i] * Math.cos(i * phase) + imag[i] * Math.sin(i * phase); + } + return sum; }; /** - * clean up - * @returns {Tone.FeedbackCombFilter} this + * Returns the initial value of the oscillator. + * @return {AudioRange} + * @private */ - Tone.FeedbackCombFilter.prototype.dispose = function () { - Tone.prototype.dispose.call(this); + Tone.Oscillator.prototype._getInitialValue = function () { + var coefs = this._getRealImaginary(this._type, 0); + var real = coefs[0]; + var imag = coefs[1]; + var maxValue = 0; + var twoPi = Math.PI * 2; + //check for peaks in 8 places + for (var i = 0; i < 8; i++) { + maxValue = Math.max(this._inverseFFT(real, imag, i / 8 * twoPi), maxValue); + } + return -this._inverseFFT(real, imag, this._phase) / maxValue; + }; + /** + * The partials of the 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.Oscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; + */ + Object.defineProperty(Tone.Oscillator.prototype, 'partials', { + get: function () { + if (this._type !== Tone.Oscillator.Type.Custom) { + return []; + } else { + return this._partials; + } + }, + set: function (partials) { + this._partials = partials; + this.type = Tone.Oscillator.Type.Custom; + } + }); + /** + * The phase of the oscillator in degrees. + * @memberOf Tone.Oscillator# + * @type {Degrees} + * @name phase + * @example + * osc.phase = 180; //flips the phase of the oscillator + */ + Object.defineProperty(Tone.Oscillator.prototype, 'phase', { + get: function () { + return this._phase * (180 / Math.PI); + }, + set: function (phase) { + this._phase = phase * Math.PI / 180; + //reset the type + this.type = this._type; + } + }); + /** + * Dispose and disconnect. + * @return {Tone.Oscillator} this + */ + Tone.Oscillator.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + if (this._oscillator !== null) { + this._oscillator.disconnect(); + this._oscillator = null; + } + this._wave = null; this._writable([ - 'resonance', - 'delayTime' + 'frequency', + 'detune' ]); - this._delay.disconnect(); - this._delay = null; - this.delayTime.dispose(); - this.delayTime = null; - this.resonance.dispose(); - this.resonance = null; - this._feedback.disconnect(); - this._feedback = null; + this.frequency.dispose(); + this.frequency = null; + this.detune.dispose(); + this.detune = null; + this._partials = null; return this; }; - return Tone.FeedbackCombFilter; + return Tone.Oscillator; }); Module(function (Tone) { /** - * @class Tone.Follower is a crude envelope follower which will follow - * the amplitude of an incoming signal. - * Take care with small (< 0.02) attack or decay values - * as follower has some ripple which is exaggerated - * at these values. Read more about envelope followers (also known - * as envelope detectors) on [Wikipedia](https://en.wikipedia.org/wiki/Envelope_detector). - * + * @class LFO stands for low frequency oscillator. Tone.LFO produces an output signal + * which can be attached to an AudioParam or Tone.Signal + * in order to modulate that parameter with an oscillator. The LFO can + * also be synced to the transport to start/stop and change when the tempo changes. + * * @constructor - * @extends {Tone} - * @param {Time|Object} [attack] The rate at which the follower rises. - * @param {Time=} release The rate at which the folower falls. + * @extends {Tone.Oscillator} + * @param {Frequency|Object} [frequency] The frequency of the oscillation. Typically, LFOs will be + * in the frequency range of 0.1 to 10 hertz. + * @param {number=} min The minimum output value of the LFO. + * @param {number=} max The maximum value of the LFO. * @example - * var follower = new Tone.Follower(0.2, 0.4); + * var lfo = new Tone.LFO("4n", 400, 4000); + * lfo.connect(filter.frequency); */ - Tone.Follower = function () { - Tone.call(this); + Tone.LFO = function () { var options = this.optionsObject(arguments, [ - 'attack', - 'release' - ], Tone.Follower.defaults); - /** - * @type {Tone.Abs} + 'frequency', + 'min', + 'max' + ], Tone.LFO.defaults); + /** + * The oscillator. + * @type {Tone.Oscillator} * @private */ - this._abs = new Tone.Abs(); + this._oscillator = new Tone.Oscillator({ + 'frequency': options.frequency, + 'type': options.type + }); /** - * the lowpass filter which smooths the input - * @type {BiquadFilterNode} - * @private + * the lfo's frequency + * @type {Frequency} + * @signal */ - this._filter = this.context.createBiquadFilter(); - this._filter.type = 'lowpass'; - this._filter.frequency.value = 0; - this._filter.Q.value = -100; + this.frequency = this._oscillator.frequency; /** - * @type {WaveShaperNode} - * @private + * The amplitude of the LFO, which controls the output range between + * the min and max output. For example if the min is -10 and the max + * is 10, setting the amplitude to 0.5 would make the LFO modulate + * between -5 and 5. + * @type {Number} + * @signal */ - this._frequencyValues = new Tone.WaveShaper(); + this.amplitude = this._oscillator.volume; + this.amplitude.units = Tone.Type.NormalRange; + this.amplitude.value = options.amplitude; /** - * @type {Tone.Subtract} + * The signal which is output when the LFO is stopped + * @type {Tone.Signal} * @private */ - this._sub = new Tone.Subtract(); + this._stoppedSignal = new Tone.Signal(0, Tone.Type.AudioRange); /** - * @type {DelayNode} + * The value that the LFO outputs when it's stopped + * @type {AudioRange} * @private */ - this._delay = this.context.createDelay(); - this._delay.delayTime.value = this.bufferTime; + this._stoppedValue = 0; /** - * this keeps it far from 0, even for very small differences - * @type {Tone.Multiply} + * @type {Tone.AudioToGain} * @private */ - this._mult = new Tone.Multiply(10000); + this._a2g = new Tone.AudioToGain(); /** + * @type {Tone.Scale} * @private - * @type {number} */ - this._attack = options.attack; + this._scaler = this.output = new Tone.Scale(options.min, options.max); /** + * the units of the LFO (used for converting) + * @type {Tone.Type} * @private - * @type {number} */ - this._release = options.release; - //the smoothed signal to get the values - this.input.chain(this._abs, this._filter, this.output); - //the difference path - this._abs.connect(this._sub, 0, 1); - this._filter.chain(this._delay, this._sub); - //threshold the difference and use the thresh to set the frequency - this._sub.chain(this._mult, this._frequencyValues, this._filter.frequency); - //set the attack and release values in the table - this._setAttackRelease(this._attack, this._release); + this._units = Tone.Type.Default; + this.units = options.units; + //connect it up + this._oscillator.chain(this._a2g, this._scaler); + this._stoppedSignal.connect(this._a2g); + this._readOnly([ + 'amplitude', + 'frequency' + ]); + this.phase = options.phase; }; - Tone.extend(Tone.Follower); + Tone.extend(Tone.LFO, Tone.Oscillator); /** + * the default parameters + * * @static + * @const * @type {Object} */ - Tone.Follower.defaults = { - 'attack': 0.05, - 'release': 0.5 - }; - /** - * sets the attack and release times in the wave shaper - * @param {Time} attack - * @param {Time} release - * @private - */ - Tone.Follower.prototype._setAttackRelease = function (attack, release) { - var minTime = this.bufferTime; - attack = this.secondsToFrequency(this.toSeconds(attack)); - release = this.secondsToFrequency(this.toSeconds(release)); - attack = Math.max(attack, minTime); - release = Math.max(release, minTime); - this._frequencyValues.setMap(function (val) { - if (val <= 0) { - return attack; - } else { - return release; - } - }); - }; - /** - * The attack time. - * @memberOf Tone.Follower# - * @type {Time} - * @name attack - */ - Object.defineProperty(Tone.Follower.prototype, 'attack', { - get: function () { - return this._attack; - }, - set: function (attack) { - this._attack = attack; - this._setAttackRelease(this._attack, this._release); - } - }); - /** - * The release time. - * @memberOf Tone.Follower# - * @type {Time} - * @name release - */ - Object.defineProperty(Tone.Follower.prototype, 'release', { - get: function () { - return this._release; - }, - set: function (release) { - this._release = release; - this._setAttackRelease(this._attack, this._release); - } - }); + Tone.LFO.defaults = { + 'type': 'sine', + 'min': 0, + 'max': 1, + 'phase': 0, + 'frequency': '4n', + 'amplitude': 1, + 'units': Tone.Type.Default + }; /** - * Borrows the connect method from Signal so that the output can be used - * as a Tone.Signal control signal. - * @function + * Start the LFO. + * @param {Time} [time=now] the time the LFO will start + * @returns {Tone.LFO} this */ - Tone.Follower.prototype.connect = Tone.Signal.prototype.connect; + Tone.LFO.prototype.start = function (time) { + time = this.toSeconds(time); + this._stoppedSignal.setValueAtTime(0, time); + this._oscillator.start(time); + return this; + }; /** - * dispose - * @returns {Tone.Follower} this + * Stop the LFO. + * @param {Time} [time=now] the time the LFO will stop + * @returns {Tone.LFO} this */ - Tone.Follower.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._filter.disconnect(); - this._filter = null; - this._frequencyValues.disconnect(); - this._frequencyValues = null; - this._delay.disconnect(); - this._delay = null; - this._sub.disconnect(); - this._sub = null; - this._abs.dispose(); - this._abs = null; - this._mult.dispose(); - this._mult = null; - this._curve = null; + Tone.LFO.prototype.stop = function (time) { + time = this.toSeconds(time); + this._stoppedSignal.setValueAtTime(this._stoppedValue, time); + this._oscillator.stop(time); return this; }; - return Tone.Follower; - }); - Module(function (Tone) { - /** - * @class Tone.Gate only passes a signal through when the incoming - * signal exceeds a specified threshold. To do this, Gate uses - * a Tone.Follower to follow the amplitude of the incoming signal. - * A common implementation of this class is a [Noise Gate](https://en.wikipedia.org/wiki/Noise_gate). - * - * @constructor - * @extends {Tone} - * @param {Decibels|Object} [threshold] The threshold above which the gate will open. - * @param {Time=} attack The follower's attack time - * @param {Time=} release The follower's release time + * Sync the start/stop/pause to the transport + * and the frequency to the bpm of the transport + * + * @param {Time} [delay=0] the time to delay the start of the + * LFO from the start of the transport + * @returns {Tone.LFO} this * @example - * var gate = new Tone.Gate(-30, 0.2, 0.3).toMaster(); - * var mic = new Tone.Microphone().connect(gate); - * //the gate will only pass through the incoming - * //signal when it's louder than -30db + * lfo.frequency.value = "8n"; + * lfo.sync(); + * //the rate of the LFO will always be an eighth note, + * //even as the tempo changes */ - Tone.Gate = function () { - Tone.call(this); - var options = this.optionsObject(arguments, [ - 'threshold', - 'attack', - 'release' - ], Tone.Gate.defaults); - /** - * @type {Tone.Follower} - * @private - */ - this._follower = new Tone.Follower(options.attack, options.release); - /** - * @type {Tone.GreaterThan} - * @private - */ - this._gt = new Tone.GreaterThan(this.dbToGain(options.threshold)); - //the connections - this.input.connect(this.output); - //the control signal - this.input.chain(this._gt, this._follower, this.output.gain); + Tone.LFO.prototype.sync = function (delay) { + this._oscillator.sync(delay); + this._oscillator.syncFrequency(); + return this; }; - Tone.extend(Tone.Gate); /** - * @const - * @static - * @type {Object} + * unsync the LFO from transport control + * @returns {Tone.LFO} this */ - Tone.Gate.defaults = { - 'attack': 0.1, - 'release': 0.1, - 'threshold': -40 + Tone.LFO.prototype.unsync = function () { + this._oscillator.unsync(); + this._oscillator.unsyncFrequency(); + return this; }; /** - * The threshold of the gate in decibels - * @memberOf Tone.Gate# - * @type {Decibels} - * @name threshold + * The miniumum output of the LFO. + * @memberOf Tone.LFO# + * @type {number} + * @name min */ - Object.defineProperty(Tone.Gate.prototype, 'threshold', { + Object.defineProperty(Tone.LFO.prototype, 'min', { get: function () { - return this.gainToDb(this._gt.value); + return this._toUnits(this._scaler.min); }, - set: function (thresh) { - this._gt.value = this.dbToGain(thresh); + set: function (min) { + min = this._fromUnits(min); + this._scaler.min = min; } }); /** - * The attack speed of the gate - * @memberOf Tone.Gate# - * @type {Time} - * @name attack + * The maximum output of the LFO. + * @memberOf Tone.LFO# + * @type {number} + * @name max */ - Object.defineProperty(Tone.Gate.prototype, 'attack', { + Object.defineProperty(Tone.LFO.prototype, 'max', { get: function () { - return this._follower.attack; + return this._toUnits(this._scaler.max); }, - set: function (attackTime) { - this._follower.attack = attackTime; + set: function (max) { + max = this._fromUnits(max); + this._scaler.max = max; } }); /** - * The release speed of the gate - * @memberOf Tone.Gate# - * @type {Time} - * @name release + * The type of the oscillator: sine, square, sawtooth, triangle. + * @memberOf Tone.LFO# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Gate.prototype, 'release', { + Object.defineProperty(Tone.LFO.prototype, 'type', { get: function () { - return this._follower.release; + return this._oscillator.type; }, - set: function (releaseTime) { - this._follower.release = releaseTime; + set: function (type) { + this._oscillator.type = type; + this._stoppedValue = this._oscillator._getInitialValue(); + this._stoppedSignal.value = this._stoppedValue; } }); /** - * Clean up. - * @returns {Tone.Gate} this + * The phase of the LFO. + * @memberOf Tone.LFO# + * @type {number} + * @name phase */ - Tone.Gate.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._follower.dispose(); - this._gt.dispose(); - this._follower = null; - this._gt = null; - return this; - }; - return Tone.Gate; - }); - Module(function (Tone) { - + Object.defineProperty(Tone.LFO.prototype, 'phase', { + get: function () { + return this._oscillator.phase; + }, + set: function (phase) { + this._oscillator.phase = phase; + this._stoppedValue = this._oscillator._getInitialValue(); + this._stoppedSignal.value = this._stoppedValue; + } + }); /** - * @class A sample accurate clock which provides a callback at the given rate. - * While the callback is not sample-accurate (it is still susceptible to - * loose JS timing), the time passed in as the argument to the callback - * is precise. For most applications, it is better to use Tone.Transport - * instead of the clock. - * - * @constructor - * @extends {Tone} - * @param {Frequency} frequency The rate of the callback - * @param {function} callback The callback to be invoked with the time of the audio event - * @example - * //the callback will be invoked approximately once a second - * //and will print the time exactly once a second apart. - * var clock = new Tone.Clock(1, function(time){ - * console.log(time); - * }); + * The output units of the LFO. + * @memberOf Tone.LFO# + * @type {Tone.Type} + * @name units */ - Tone.Clock = function (frequency, callback) { - /** - * the oscillator - * @type {OscillatorNode} - * @private - */ - this._oscillator = null; - /** - * the script processor which listens to the oscillator - * @type {ScriptProcessorNode} - * @private - */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); - this._jsNode.onaudioprocess = this._processBuffer.bind(this); - /** - * The frequency in which the callback will be invoked. - * @type {Frequency} - * @signal - */ - this.frequency = new Tone.Signal(frequency, Tone.Type.Frequency); - /** - * whether the tick is on the up or down - * @type {boolean} - * @private - */ - this._upTick = false; - /** - * The callback which is invoked on every tick - * with the time of that tick as the argument - * @type {function(number)} - */ - this.tick = callback; - /** - * Callback is invoked when the clock is stopped. - * @type {function} - * @example - * clock.onended = function(){ - * console.log("the clock is stopped"); - * } - */ - this.onended = Tone.noOp; - //setup - this._jsNode.noGC(); - }; - Tone.extend(Tone.Clock); + Object.defineProperty(Tone.LFO.prototype, 'units', { + get: function () { + return this._units; + }, + set: function (val) { + var currentMin = this.min; + var currentMax = this.max; + //convert the min and the max + this._units = val; + this.min = currentMin; + this.max = currentMax; + } + }); /** - * Start the clock. - * @param {Time} [time=now] the time when the clock should start - * @returns {Tone.Clock} this - * @example - * clock.start(); + * Returns the playback state of the source, either "started" or "stopped". + * @type {Tone.State} + * @readOnly + * @memberOf Tone.LFO# + * @name state */ - Tone.Clock.prototype.start = function (time) { - if (!this._oscillator) { - this._oscillator = this.context.createOscillator(); - this._oscillator.type = 'square'; - this._oscillator.connect(this._jsNode); - //connect it up - this.frequency.connect(this._oscillator.frequency); - this._upTick = false; - var startTime = this.toSeconds(time); - this._oscillator.start(startTime); + Object.defineProperty(Tone.LFO.prototype, 'state', { + get: function () { + return this._oscillator.state; } - return this; - }; + }); /** - * Stop the clock. - * @param {Time} [time=now] The time when the clock should stop. - * @returns {Tone.Clock} this - * @example - * clock.stop(); + * Connect the output of the LFO to an AudioParam, AudioNode, or Tone Node. + * Tone.LFO will automatically convert to the destination units of the + * will get the units from the connected node. + * @param {Tone | AudioParam | AudioNode} node + * @param {number} [outputNum=0] optionally which output to connect from + * @param {number} [inputNum=0] optionally which input to connect to + * @returns {Tone.LFO} this + * @private */ - Tone.Clock.prototype.stop = function (time) { - if (this._oscillator) { - var now = this.now(); - var stopTime = this.toSeconds(time, now); - this._oscillator.stop(stopTime); - this._oscillator = null; - if (time) { - //set a timeout for when it stops - setTimeout(this.onended, (stopTime - now) * 1000); - } else { - this.onended(); - } + Tone.LFO.prototype.connect = function (node) { + if (node.constructor === Tone.Signal || node.constructor === Tone.Param || node.constructor === Tone.TimelineSignal) { + this.convert = node.convert; + this.units = node.units; } + Tone.Signal.prototype.connect.apply(this, arguments); return this; }; /** + * private method borrowed from Param converts + * units from their destination value + * @function * @private - * @param {AudioProcessingEvent} event */ - Tone.Clock.prototype._processBuffer = function (event) { - var now = this.defaultArg(event.playbackTime, this.now()); - var bufferSize = this._jsNode.bufferSize; - var incomingBuffer = event.inputBuffer.getChannelData(0); - var upTick = this._upTick; - var self = this; - for (var i = 0; i < bufferSize; i++) { - var sample = incomingBuffer[i]; - if (sample > 0 && !upTick) { - upTick = true; - //get the callback out of audio thread - setTimeout(function () { - //to account for the double buffering - var tickTime = now + self.samplesToSeconds(i + bufferSize * 2); - return function () { - if (self.tick) { - self.tick(tickTime); - } - }; - }(), 0); // jshint ignore:line - } else if (sample < 0 && upTick) { - upTick = false; - } - } - this._upTick = upTick; - }; + Tone.LFO.prototype._fromUnits = Tone.Param.prototype._fromUnits; /** - * Clean up. - * @returns {Tone.Clock} this + * private method borrowed from Param converts + * units to their destination value + * @function + * @private */ - Tone.Clock.prototype.dispose = function () { - this._jsNode.disconnect(); - this.frequency.dispose(); + Tone.LFO.prototype._toUnits = Tone.Param.prototype._toUnits; + /** + * disconnect and dispose + * @returns {Tone.LFO} this + */ + Tone.LFO.prototype.dispose = function () { + Tone.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; - if (this._oscillator) { - this._oscillator.disconnect(); - this._oscillator = null; - } - this._jsNode.onaudioprocess = Tone.noOp; - this._jsNode = null; - this.tick = null; - this.onended = Tone.noOp; + this.amplitude = null; return this; }; - return Tone.Clock; + return Tone.LFO; }); Module(function (Tone) { /** - * @class Oscillator-based transport allows for timing musical events. - * Supports tempo curves and time changes. A single transport is created - * on initialization. Unlike browser-based timing (setInterval, requestAnimationFrame) - * Tone.Transport timing events pass in the exact time of the scheduled event - * in the argument of the callback function. Pass that time value to the object - * you're scheduling.

- * A single transport is created for you when the library is initialized. + * @class Tone.Limiter will limit the loudness of an incoming signal. + * It is composed of a Tone.Compressor with a fast attack + * and release. Limiters are commonly used to safeguard against + * signal clipping. Unlike a compressor, limiters do not provide + * smooth gain reduction and almost completely prevent + * additional gain above the threshold. * * @extends {Tone} - * @singleton - * @example - * //repeated event every 8th note - * Tone.Transport.setInterval(function(time){ - * //do something with the time - * }, "8n"); - * @example - * //one time event 1 second in the future - * Tone.Transport.setTimeout(function(time){ - * //do something with the time - * }, 1); + * @constructor + * @param {number} threshold The theshold above which the limiting is applied. * @example - * //event fixed to the Transports timeline. - * Tone.Transport.setTimeline(function(time){ - * //do something with the time - * }, "16:0:0"); + * var limiter = new Tone.Limiter(-6); */ - Tone.Transport = function () { - /** - * watches the main oscillator for timing ticks - * initially starts at 120bpm - * - * @private - * @type {Tone.Clock} - */ - this._clock = new Tone.Clock(0, this._processTick.bind(this)); - this._clock.onended = this._onended.bind(this); - /** - * If the transport loops or not. - * @type {boolean} - */ - this.loop = false; - /** - * The Beats Per Minute of the Transport. - * @type {BPM} - * @signal - * @example - * Tone.Transport.bpm.value = 80; - * //ramp the bpm to 120 over 10 seconds - * Tone.Transport.bpm.rampTo(120, 10); - */ - this.bpm = new Tone.Signal(120, Tone.Type.BPM); + Tone.Limiter = function () { + var options = this.optionsObject(arguments, ['threshold'], Tone.Limiter.defaults); /** - * the signal scalar - * @type {Tone.Multiply} + * the compressor * @private + * @type {Tone.Compressor} */ - this._bpmMult = new Tone.Multiply(1 / 60 * tatum); + this._compressor = this.input = this.output = new Tone.Compressor({ + 'attack': 0.001, + 'decay': 0.001, + 'threshold': options.threshold + }); /** - * The state of the transport. READ ONLY. - * @type {Tone.State} + * The threshold of of the limiter + * @type {Decibel} + * @signal */ - this.state = Tone.State.Stopped; - //connect it all up - this.bpm.chain(this._bpmMult, this._clock.frequency); + this.threshold = this._compressor.threshold; + this._readOnly('threshold'); }; - Tone.extend(Tone.Transport); + Tone.extend(Tone.Limiter); /** - * the defaults + * The default value * @type {Object} * @const * @static */ - Tone.Transport.defaults = { - 'bpm': 120, - 'swing': 0, - 'swingSubdivision': '16n', - 'timeSignature': 4, - 'loopStart': 0, - 'loopEnd': '4m' - }; - /** - * @private - * @type {number} - */ - var tatum = 12; - /** - * @private - * @type {number} - */ - var timelineTicks = 0; - /** - * @private - * @type {number} - */ - var transportTicks = 0; - /** - * Which subdivision the swing is applied to. - * defaults to an 16th note - * @private - * @type {number} - */ - var swingSubdivision = '16n'; - /** - * controls which beat the swing is applied to - * defaults to an 16th note - * @private - * @type {number} - */ - var swingTatum = 3; - /** - * controls which beat the swing is applied to - * @private - * @type {number} - */ - var swingAmount = 0; - /** - * @private - * @type {number} - */ - var transportTimeSignature = 4; - /** - * @private - * @type {number} - */ - var loopStart = 0; - /** - * @private - * @type {number} - */ - var loopEnd = tatum * 4; - /** - * @private - * @type {Array} - */ - var intervals = []; - /** - * @private - * @type {Array} - */ - var timeouts = []; - /** - * @private - * @type {Array} - */ - var transportTimeline = []; - /** - * @private - * @type {number} - */ - var timelineProgress = 0; - /** - * All of the synced components - * @private - * @type {Array} - */ - var SyncedSources = []; - /** - * All of the synced Signals - * @private - * @type {Array} - */ - var SyncedSignals = []; - /////////////////////////////////////////////////////////////////////////////// - // TICKS - /////////////////////////////////////////////////////////////////////////////// - /** - * called on every tick - * @param {number} tickTime clock relative tick time - * @private - */ - Tone.Transport.prototype._processTick = function (tickTime) { - if (this.state === Tone.State.Started) { - if (swingAmount > 0 && timelineTicks % tatum !== 0 && //not on a downbeat - timelineTicks % swingTatum === 0) { - //add some swing - tickTime += this._ticksToSeconds(swingTatum) * swingAmount; - } - processIntervals(tickTime); - processTimeouts(tickTime); - processTimeline(tickTime); - transportTicks += 1; - timelineTicks += 1; - if (this.loop) { - if (timelineTicks === loopEnd) { - this._setTicks(loopStart); - } - } - } - }; - /** - * jump to a specific tick in the timeline - * updates the timeline callbacks - * - * @param {number} ticks the tick to jump to - * @private - */ - Tone.Transport.prototype._setTicks = function (ticks) { - timelineTicks = ticks; - for (var i = 0; i < transportTimeline.length; i++) { - var timeout = transportTimeline[i]; - if (timeout.callbackTick() >= ticks) { - timelineProgress = i; - break; - } - } - }; - /////////////////////////////////////////////////////////////////////////////// - // EVENT PROCESSING - /////////////////////////////////////////////////////////////////////////////// - /** - * process the intervals - * @param {number} time - */ - var processIntervals = function (time) { - for (var i = 0, len = intervals.length; i < len; i++) { - var interval = intervals[i]; - if (interval.testInterval(transportTicks)) { - interval.doCallback(time); - } - } - }; - /** - * process the timeouts - * @param {number} time - */ - var processTimeouts = function (time) { - var removeTimeouts = 0; - for (var i = 0, len = timeouts.length; i < len; i++) { - var timeout = timeouts[i]; - var callbackTick = timeout.callbackTick(); - if (callbackTick <= transportTicks) { - timeout.doCallback(time); - removeTimeouts++; - } else if (callbackTick > transportTicks) { - break; - } - } - //remove the timeouts off the front of the array after they've been called - timeouts.splice(0, removeTimeouts); - }; + Tone.Limiter.defaults = { 'threshold': -12 }; /** - * process the transportTimeline events - * @param {number} time + * Clean up. + * @returns {Tone.Limiter} this */ - var processTimeline = function (time) { - for (var i = timelineProgress, len = transportTimeline.length; i < len; i++) { - var evnt = transportTimeline[i]; - var callbackTick = evnt.callbackTick(); - if (callbackTick === timelineTicks) { - timelineProgress = i; - evnt.doCallback(time); - } else if (callbackTick > timelineTicks) { - break; - } - } + Tone.Limiter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._compressor.dispose(); + this._compressor = null; + this._writable('threshold'); + this.threshold = null; + return this; }; - /////////////////////////////////////////////////////////////////////////////// - // INTERVAL - /////////////////////////////////////////////////////////////////////////////// + return Tone.Limiter; + }); + Module(function (Tone) { + /** - * 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"); + * @class Tone.Lowpass is a lowpass feedback comb filter. It is similar to + * Tone.FeedbackCombFilter, but includes a lowpass filter. + * + * @extends {Tone} + * @constructor + * @param {Time|Object} [delayTime] The delay time of the comb filter + * @param {NormalRange=} resonance The resonance (feedback) of the comb filter + * @param {Frequency=} dampening The cutoff of the lowpass filter dampens the + * signal as it is fedback. */ - Tone.Transport.prototype.setInterval = function (callback, interval, ctx) { - var tickTime = this._toTicks(interval); - var timeout = new TimelineEvent(callback, ctx, tickTime, transportTicks); - intervals.push(timeout); - return timeout.id; + Tone.LowpassCombFilter = function () { + Tone.call(this); + var options = this.optionsObject(arguments, [ + 'delayTime', + 'resonance', + 'dampening' + ], Tone.LowpassCombFilter.defaults); + /** + * the delay node + * @type {DelayNode} + * @private + */ + this._delay = this.input = this.context.createDelay(1); + /** + * The delayTime of the comb filter. + * @type {Time} + * @signal + */ + this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + /** + * the lowpass filter + * @type {BiquadFilterNode} + * @private + */ + this._lowpass = this.output = this.context.createBiquadFilter(); + this._lowpass.Q.value = 0; + this._lowpass.type = 'lowpass'; + /** + * The dampening control of the feedback + * @type {Frequency} + * @signal + */ + this.dampening = new Tone.Param({ + 'param': this._lowpass.frequency, + 'units': Tone.Type.Frequency, + 'value': options.dampening + }); + /** + * the feedback gain + * @type {GainNode} + * @private + */ + this._feedback = this.context.createGain(); + /** + * The amount of feedback of the delayed signal. + * @type {NormalRange} + * @signal + */ + this.resonance = new Tone.Param({ + 'param': this._feedback.gain, + 'units': Tone.Type.NormalRange, + 'value': options.resonance + }); + //connections + this._delay.chain(this._lowpass, this._feedback, this._delay); + this.delayTime.connect(this._delay.delayTime); + this._readOnly([ + 'dampening', + 'resonance', + 'delayTime' + ]); }; + Tone.extend(Tone.LowpassCombFilter); /** - * 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 + * the default parameters + * @static + * @const + * @type {Object} */ - Tone.Transport.prototype.clearInterval = function (rmInterval) { - for (var i = 0; i < intervals.length; i++) { - var interval = intervals[i]; - if (interval.id === rmInterval) { - intervals.splice(i, 1); - return true; - } - } - return false; + Tone.LowpassCombFilter.defaults = { + 'delayTime': 0.1, + 'resonance': 0.5, + 'dampening': 3000 }; /** - * Removes all of the intervals that are currently set. - * @return {boolean} true if the event was removed + * Clean up. + * @returns {Tone.LowpassCombFilter} this */ - Tone.Transport.prototype.clearIntervals = function () { - var willRemove = intervals.length > 0; - intervals = []; - return willRemove; + Tone.LowpassCombFilter.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable([ + 'dampening', + 'resonance', + 'delayTime' + ]); + this.dampening.dispose(); + this.dampening = null; + this.resonance.dispose(); + this.resonance = null; + this._delay.disconnect(); + this._delay = null; + this._lowpass.disconnect(); + this._lowpass = null; + this._feedback.disconnect(); + this._feedback = null; + this.delayTime.dispose(); + this.delayTime = null; + return this; }; - /////////////////////////////////////////////////////////////////////////////// - // TIMEOUT - /////////////////////////////////////////////////////////////////////////////// + return Tone.LowpassCombFilter; + }); + Module(function (Tone) { + /** - * 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. + * @class Tone.Merge brings two signals into the left and right + * channels of a single stereo channel. * - * @param {function} callback - * @param {Time} time The time (from now) that the callback will be invoked. - * @return {number} The id of the timeout. + * @constructor + * @extends {Tone} * @example - * //trigger an event to happen 1 second from now - * Tone.Transport.setTimeout(function(time){ - * player.start(time); - * }, 1) - */ - Tone.Transport.prototype.setTimeout = function (callback, time, ctx) { - var ticks = this._toTicks(time); - var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0); - //put it in the right spot - for (var i = 0, len = timeouts.length; i < len; i++) { - var testEvnt = timeouts[i]; - if (testEvnt.callbackTick() > timeout.callbackTick()) { - timeouts.splice(i, 0, timeout); - return timeout.id; - } - } - //otherwise push it on the end - timeouts.push(timeout); - return timeout.id; - }; - /** - * Clear 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 + * var merge = new Tone.Merge().toMaster(); + * //routing a sine tone in the left channel + * //and noise in the right channel + * var osc = new Tone.Oscillator().connect(merge.left); + * var noise = new Tone.Noise().connect(merge.right); + * //starting our oscillators + * noise.start(); + * osc.start(); */ - Tone.Transport.prototype.clearTimeout = function (timeoutID) { - for (var i = 0; i < timeouts.length; i++) { - var testTimeout = timeouts[i]; - if (testTimeout.id === timeoutID) { - timeouts.splice(i, 1); - return true; - } - } - return false; + Tone.Merge = function () { + Tone.call(this, 2, 0); + /** + * The left input channel. + * Alias for input[0] + * @type {GainNode} + */ + this.left = this.input[0] = this.context.createGain(); + /** + * The right input channel. + * Alias for input[1]. + * @type {GainNode} + */ + this.right = this.input[1] = this.context.createGain(); + /** + * the merger node for the two channels + * @type {ChannelMergerNode} + * @private + */ + this._merger = this.output = this.context.createChannelMerger(2); + //connections + this.left.connect(this._merger, 0, 0); + this.right.connect(this._merger, 0, 1); + this.left.channelCount = 1; + this.right.channelCount = 1; + this.left.channelCountMode = 'explicit'; + this.right.channelCountMode = 'explicit'; }; + Tone.extend(Tone.Merge); /** - * Removes all of the timeouts that are currently set. - * @return {boolean} true if the event was removed + * Clean up. + * @returns {Tone.Merge} this */ - Tone.Transport.prototype.clearTimeouts = function () { - var willRemove = timeouts.length > 0; - timeouts = []; - return willRemove; + Tone.Merge.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this.left.disconnect(); + this.left = null; + this.right.disconnect(); + this.right = null; + this._merger.disconnect(); + this._merger = null; + return this; }; - /////////////////////////////////////////////////////////////////////////////// - // TIMELINE - /////////////////////////////////////////////////////////////////////////////// + return Tone.Merge; + }); + Module(function (Tone) { + /** - * 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. + * @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). * - * @param {function} callback - * @param {Tome.Time} timeout - * @return {number} the id for clearing the transportTimeline event + * @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 * @example - * //trigger the start of a part on the 16th measure - * Tone.Transport.setTimeline(function(time){ - * part.start(time); - * }, "16m"); + * 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(); */ - Tone.Transport.prototype.setTimeline = function (callback, timeout, ctx) { - var ticks = this._toTicks(timeout); - var timelineEvnt = new TimelineEvent(callback, ctx, ticks, 0); - //put it in the right spot - for (var i = timelineProgress, len = transportTimeline.length; i < len; i++) { - var testEvnt = transportTimeline[i]; - if (testEvnt.callbackTick() > timelineEvnt.callbackTick()) { - transportTimeline.splice(i, 0, timelineEvnt); - return timelineEvnt.id; - } + 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; + /** + * 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} + */ + this._volume = new Array(this._channels); + /** + * the raw values for each of the channels + * @private + * @type {Array} + */ + this._values = new Array(this._channels); + //zero out the volume array + for (var i = 0; i < this._channels; i++) { + this._volume[i] = 0; + this._values[i] = 0; } - //otherwise push it on the end - transportTimeline.push(timelineEvnt); - return timelineEvnt.id; - }; - /** - * Clear the timeline event. - * @param {number} timelineID - * @return {boolean} true if it was removed - */ - Tone.Transport.prototype.clearTimeline = function (timelineID) { - for (var i = 0; i < transportTimeline.length; i++) { - var testTimeline = transportTimeline[i]; - if (testTimeline.id === timelineID) { - transportTimeline.splice(i, 1); - return true; - } + /** + * last time the values clipped + * @private + * @type {Array} + */ + this._lastClip = new Array(this._channels); + //zero out the clip array + for (var j = 0; j < this._lastClip.length; j++) { + this._lastClip[j] = 0; } - return false; - }; - /** - * Remove all events from the timeline. - * @returns {boolean} true if the events were removed - */ - Tone.Transport.prototype.clearTimelines = function () { - timelineProgress = 0; - var willRemove = transportTimeline.length > 0; - transportTimeline = []; - return willRemove; - }; - /////////////////////////////////////////////////////////////////////////////// - // TIME CONVERSIONS - /////////////////////////////////////////////////////////////////////////////// - /** - * turns the time into - * @param {Time} time - * @return {number} - * @private - */ - Tone.Transport.prototype._toTicks = function (time) { - //get the seconds - var seconds = this.toSeconds(time); - var quarter = this.notationToSeconds('4n'); - var quarters = seconds / quarter; - var tickNum = quarters * tatum; - //quantize to tick value - return Math.round(tickNum); - }; - /** - * convert ticks into seconds - * - * @param {number} ticks - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * @private - */ - Tone.Transport.prototype._ticksToSeconds = function (ticks, bpm, timeSignature) { - ticks = Math.floor(ticks); - var quater = this.notationToSeconds('4n', bpm, timeSignature); - return quater * ticks / tatum; + /** + * @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); }; + Tone.extend(Tone.Meter); /** - * Returns the time of the next beat. - * @param {string} [subdivision="4n"] - * @return {number} the time in seconds of the next subdivision + * The defaults + * @type {Object} + * @static + * @const */ - Tone.Transport.prototype.nextBeat = function (subdivision) { - subdivision = this.defaultArg(subdivision, '4n'); - var tickNum = this._toTicks(subdivision); - var remainingTicks = transportTicks % tickNum; - var nextTick = remainingTicks; - if (remainingTicks > 0) { - nextTick = tickNum - remainingTicks; - } - return this._ticksToSeconds(nextTick); + Tone.Meter.defaults = { + 'smoothing': 0.8, + 'bufferSize': 1024, + 'clipMemory': 0.5, + 'clipLevel': 0.9, + 'channels': 1 }; - /////////////////////////////////////////////////////////////////////////////// - // START/STOP/PAUSE - /////////////////////////////////////////////////////////////////////////////// /** - * 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. - * @returns {Tone.Transport} this - * @example - * //start the transport in one second starting at beginning of the 5th measure. - * Tone.Transport.start("+1", "4:0:0"); + * called on each processing frame + * @private + * @param {AudioProcessingEvent} event */ - Tone.Transport.prototype.start = function (time, offset) { - if (this.state === Tone.State.Stopped || this.state === Tone.State.Paused) { - if (!this.isUndef(offset)) { - this._setTicks(this._toTicks(offset)); + 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; } - this.state = Tone.State.Started; - var startTime = this.toSeconds(time); - this._clock.start(startTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++) { - var source = SyncedSources[i].source; - var delay = SyncedSources[i].delay; - source.start(startTime + delay); + 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; } - return this; }; /** - * Stop the transport and all sources synced to the transport. - * @param {Time} [time=now] The time when the transport should stop. - * @returns {Tone.Transport} this - * @example - * Tone.Transport.stop(); + * Get the rms of the signal. + * @param {number} [channel=0] which channel + * @return {number} the value */ - Tone.Transport.prototype.stop = function (time) { - if (this.state === Tone.State.Started || this.state === Tone.State.Paused) { - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++) { - var source = SyncedSources[i].source; - source.stop(stopTime); - } + Tone.Meter.prototype.getLevel = function (channel) { + channel = this.defaultArg(channel, 0); + var vol = this._volume[channel]; + if (vol < 0.00001) { + return 0; } else { - this._onended(); + return vol; } - return this; }; /** - * invoked when the transport is stopped - * @private + * Get the raw value of the signal. + * @param {number=} channel + * @return {number} */ - Tone.Transport.prototype._onended = function () { - transportTicks = 0; - this._setTicks(0); - this.clearTimeouts(); - this.state = Tone.State.Stopped; + Tone.Meter.prototype.getValue = function (channel) { + channel = this.defaultArg(channel, 0); + return this._values[channel]; }; /** - * Pause the transport and all sources synced to the transport. - * @param {Time} [time=now] - * @returns {Tone.Transport} this + * Get the volume of the signal in dB + * @param {number=} channel + * @return {Decibels} */ - Tone.Transport.prototype.pause = function (time) { - if (this.state === Tone.State.Started) { - this.state = Tone.State.Paused; - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call pause on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++) { - var source = SyncedSources[i].source; - source.pause(stopTime); - } - } - return this; + Tone.Meter.prototype.getDb = function (channel) { + return this.gainToDb(this.getLevel(channel)); }; - /////////////////////////////////////////////////////////////////////////////// - // SETTERS/GETTERS - /////////////////////////////////////////////////////////////////////////////// - /** - * The time signature as just the numerator over 4. - * For example 4/4 would be just 4 and 6/8 would be 3. - * @memberOf Tone.Transport# - * @type {number} - * @name timeSignature - * @example - * //common time - * Tone.Transport.timeSignature = 4; - * // 7/8 - * Tone.Transport.timeSignature = 3.5; - */ - Object.defineProperty(Tone.Transport.prototype, 'timeSignature', { - get: function () { - return transportTimeSignature; - }, - set: function (numerator) { - transportTimeSignature = numerator; - } - }); - /** - * When the Tone.Transport.loop = true, this is the starting position of the loop. - * @memberOf Tone.Transport# - * @type {Time} - * @name loopStart - */ - Object.defineProperty(Tone.Transport.prototype, 'loopStart', { - get: function () { - return this._ticksToSeconds(loopStart); - }, - set: function (startPosition) { - loopStart = this._toTicks(startPosition); - } - }); /** - * When the Tone.Transport.loop = true, this is the ending position of the loop. - * @memberOf Tone.Transport# - * @type {Time} - * @name loopEnd + * @returns {boolean} if the audio has clipped. The value resets + * based on the clipMemory defined. */ - Object.defineProperty(Tone.Transport.prototype, 'loopEnd', { - get: function () { - return this._ticksToSeconds(loopEnd); - }, - set: function (endPosition) { - loopEnd = this._toTicks(endPosition); - } - }); + Tone.Meter.prototype.isClipped = function (channel) { + channel = this.defaultArg(channel, 0); + return Date.now() - this._lastClip[channel] < this._clipMemory * 1000; + }; /** - * Set the loop start and stop at the same time. - * @param {Time} startPosition - * @param {Time} endPosition - * @returns {Tone.Transport} this - * @example - * //loop over the first measure - * Tone.Transport.setLoopPoints(0, "1m"); - * Tone.Transport.loop = true; + * Clean up. + * @returns {Tone.Meter} this */ - Tone.Transport.prototype.setLoopPoints = function (startPosition, endPosition) { - this.loopStart = startPosition; - this.loopEnd = endPosition; + 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; return this; }; + return Tone.Meter; + }); + Module(function (Tone) { + /** - * The swing value. Between 0-1 where 1 equal to - * the note + half the subdivision. - * @memberOf Tone.Transport# - * @type {NormalRange} - * @name swing - */ - Object.defineProperty(Tone.Transport.prototype, 'swing', { - get: function () { - return swingAmount * 2; - }, - set: function (amount) { - //scale the values to a normal range - swingAmount = amount * 0.5; - } - }); - /** - * Set the subdivision which the swing will be applied to. - * The default values is a 16th note. Value must be less - * than a quarter note. - * - * @memberOf Tone.Transport# - * @type {Time} - * @name swingSubdivision + * @class Tone.Split splits an incoming signal into left and right channels. + * + * @constructor + * @extends {Tone} + * @example + * var split = new Tone.Split(); + * stereoSignal.connect(split); */ - Object.defineProperty(Tone.Transport.prototype, 'swingSubdivision', { - get: function () { - return swingSubdivision; - }, - set: function (subdivision) { - //scale the values to a normal range - swingSubdivision = subdivision; - swingTatum = this._toTicks(subdivision); - } - }); + Tone.Split = function () { + Tone.call(this, 0, 2); + /** + * @type {ChannelSplitterNode} + * @private + */ + this._splitter = this.input = this.context.createChannelSplitter(2); + /** + * Left channel output. + * Alias for output[0] + * @type {GainNode} + */ + this.left = this.output[0] = this.context.createGain(); + /** + * Right channel output. + * Alias for output[1] + * @type {GainNode} + */ + this.right = this.output[1] = this.context.createGain(); + //connections + this._splitter.connect(this.left, 0, 0); + this._splitter.connect(this.right, 1, 0); + }; + Tone.extend(Tone.Split); /** - * The Transport's position in MEASURES:BEATS:SIXTEENTHS. - * Setting the value will jump to that position right away. - * - * @memberOf Tone.Transport# - * @type {TransportTime} - * @name position + * Clean up. + * @returns {Tone.Split} this */ - Object.defineProperty(Tone.Transport.prototype, 'position', { - get: function () { - var quarters = timelineTicks / tatum; - var measures = Math.floor(quarters / transportTimeSignature); - var sixteenths = Math.floor(quarters % 1 * 4); - quarters = Math.floor(quarters) % transportTimeSignature; - var progress = [ - measures, - quarters, - sixteenths - ]; - return progress.join(':'); - }, - set: function (progress) { - var ticks = this._toTicks(progress); - this._setTicks(ticks); - } - }); - /////////////////////////////////////////////////////////////////////////////// - // SYNCING - /////////////////////////////////////////////////////////////////////////////// + Tone.Split.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._splitter.disconnect(); + this.left.disconnect(); + this.right.disconnect(); + this.left = null; + this.right = null; + this._splitter = null; + return this; + }; + return Tone.Split; + }); + Module(function (Tone) { + /** - * Sync a source to the transport so that - * @param {Tone.Source} source the source to sync to the transport - * @param {Time} delay (optionally) start the source with a delay from the transport - * @returns {Tone.Transport} this - * @example - * Tone.Transport.syncSource(player, "1m"); - * Tone.Transport.start(); - * //the player will start 1 measure after the transport starts + * @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).

+ * + * Mid = (Left+Right)/sqrt(2); // obtain mid-signal from left and right
+ * Side = (Left-Right)/sqrt(2); // obtain side-signal from left and righ
+ *
+ * + * @extends {Tone} + * @constructor */ - Tone.Transport.prototype.syncSource = function (source, startDelay) { - SyncedSources.push({ - source: source, - delay: this.toSeconds(this.defaultArg(startDelay, 0)) - }); - return this; + Tone.MidSideSplit = function () { + Tone.call(this, 0, 2); + /** + * split the incoming signal into left and right channels + * @type {Tone.Split} + * @private + */ + this._split = this.input = new Tone.Split(); + /** + * The mid send. Connect to mid processing. Alias for + * output[0] + * @type {Tone.Expr} + */ + this.mid = this.output[0] = new Tone.Expr('($0 + $1) * $2'); + /** + * The side output. Connect to side processing. Alias for + * output[1] + * @type {Tone.Expr} + */ + this.side = this.output[1] = new Tone.Expr('($0 - $1) * $2'); + this._split.connect(this.mid, 0, 0); + this._split.connect(this.mid, 1, 1); + this._split.connect(this.side, 0, 0); + this._split.connect(this.side, 1, 1); + sqrtTwo.connect(this.mid, 0, 2); + sqrtTwo.connect(this.side, 0, 2); }; + Tone.extend(Tone.MidSideSplit); /** - * Unsync the source from the transport. See Tone.Transport.syncSource. - * - * @param {Tone.Source} source [description] - * @returns {Tone.Transport} this + * a constant signal equal to 1 / sqrt(2) + * @type {Number} + * @signal + * @private + * @static */ - Tone.Transport.prototype.unsyncSource = function (source) { - for (var i = 0; i < SyncedSources.length; i++) { - if (SyncedSources[i].source === source) { - SyncedSources.splice(i, 1); - } - } - return this; - }; + var sqrtTwo = null; + Tone._initAudioContext(function () { + sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + }); /** - * Attaches the signal to the tempo control signal so that - * any changes in the tempo will change the signal in the same - * ratio. - * - * @param {Tone.Signal} signal - * @param {number=} ratio Optionally pass in the ratio between - * the two signals. Otherwise it will be computed - * based on their current values. - * @returns {Tone.Transport} this + * clean up + * @returns {Tone.MidSideSplit} this */ - Tone.Transport.prototype.syncSignal = function (signal, ratio) { - if (!ratio) { - //get the sync ratio - if (signal._value.value !== 0) { - ratio = signal._value.value / this.bpm.value; - } else { - ratio = 0; - } - } - var ratioSignal = this.context.createGain(); - ratioSignal.gain.value = ratio; - this.bpm.chain(ratioSignal, signal._value); - SyncedSignals.push({ - 'ratio': ratioSignal, - 'signal': signal, - 'initial': signal._value.value - }); - signal._value.value = 0; + Tone.MidSideSplit.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this.mid.dispose(); + this.mid = null; + this.side.dispose(); + this.side = null; + this._split.dispose(); + this._split = null; return this; }; + return Tone.MidSideSplit; + }); + Module(function (Tone) { + /** - * Unsyncs a previously synced signal from the transport's control. - * See Tone.Transport.syncSignal. - * @param {Tone.Signal} signal - * @returns {Tone.Transport} this + * @class Mid/Side processing separates the the 'mid' signal + * (which comes out of both the left and the right channel) + * and the 'side' (which only comes out of the the side channels). + * MidSideMerge merges the mid and side signal after they've been seperated + * by Tone.MidSideSplit.

+ * + * Left = (Mid+Side)/sqrt(2); // obtain left signal from mid and side
+ * Right = (Mid-Side)/sqrt(2); // obtain right signal from mid and side
+ *
+ * + * @extends {Tone.StereoEffect} + * @constructor */ - Tone.Transport.prototype.unsyncSignal = function (signal) { - for (var i = 0; i < SyncedSignals.length; i++) { - var syncedSignal = SyncedSignals[i]; - if (syncedSignal.signal === signal) { - syncedSignal.ratio.disconnect(); - syncedSignal.signal._value.value = syncedSignal.initial; - SyncedSignals.splice(i, 1); - } - } - return this; + Tone.MidSideMerge = function () { + Tone.call(this, 2, 0); + /** + * The mid signal input. Alias for + * input[0] + * @type {GainNode} + */ + this.mid = this.input[0] = this.context.createGain(); + /** + * recombine the mid/side into Left + * @type {Tone.Expr} + * @private + */ + this._left = new Tone.Expr('($0 + $1) * $2'); + /** + * The side signal input. Alias for + * input[1] + * @type {GainNode} + */ + this.side = this.input[1] = this.context.createGain(); + /** + * recombine the mid/side into Right + * @type {Tone.Expr} + * @private + */ + this._right = new Tone.Expr('($0 - $1) * $2'); + /** + * Merge the left/right signal back into a stereo signal. + * @type {Tone.Merge} + * @private + */ + this._merge = this.output = new Tone.Merge(); + this.mid.connect(this._left, 0, 0); + this.side.connect(this._left, 0, 1); + this.mid.connect(this._right, 0, 0); + this.side.connect(this._right, 0, 1); + this._left.connect(this._merge, 0, 0); + this._right.connect(this._merge, 0, 1); + sqrtTwo.connect(this._left, 0, 2); + sqrtTwo.connect(this._right, 0, 2); }; + Tone.extend(Tone.MidSideMerge); /** - * Clean up. - * @returns {Tone.Transport} this + * A constant signal equal to 1 / sqrt(2). + * @type {Number} + * @signal * @private + * @static */ - Tone.Transport.prototype.dispose = function () { - this._clock.dispose(); - this._clock = null; - this.bpm.dispose(); - this.bpm = null; - this._bpmMult.dispose(); - this._bpmMult = null; - return this; - }; - /////////////////////////////////////////////////////////////////////////////// - // TIMELINE EVENT - /////////////////////////////////////////////////////////////////////////////// + var sqrtTwo = null; + Tone._initAudioContext(function () { + sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + }); /** - * @static - * @type {number} + * clean up + * @returns {Tone.MidSideMerge} this */ - var TimelineEventIDCounter = 0; + Tone.MidSideMerge.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this.mid.disconnect(); + this.mid = null; + this.side.disconnect(); + this.side = null; + this._left.dispose(); + this._left = null; + this._right.dispose(); + this._right = null; + this._merge.dispose(); + this._merge = null; + return this; + }; + return Tone.MidSideMerge; + }); + Module(function (Tone) { + /** - * A Timeline event + * @class Tone.MidSideCompressor applies two different compressors to the mid + * and side signal components. See Tone.MidSideSplit. * + * @extends {Tone} + * @param {Object} options The options that are passed to the mid and side + * compressors. * @constructor - * @private - * @param {function(number)} callback - * @param {Object} context - * @param {number} tickTime - * @param {number} startTicks */ - var TimelineEvent = function (callback, context, tickTime, startTicks) { - this.startTicks = startTicks; - this.tickTime = tickTime; - this.callback = callback; - this.context = context; - this.id = TimelineEventIDCounter++; + Tone.MidSideCompressor = function (options) { + options = this.defaultArg(options, Tone.MidSideCompressor.defaults); + /** + * the mid/side split + * @type {Tone.MidSideSplit} + * @private + */ + this._midSideSplit = this.input = new Tone.MidSideSplit(); + /** + * the mid/side recombination + * @type {Tone.MidSideMerge} + * @private + */ + this._midSideMerge = this.output = new Tone.MidSideMerge(); + /** + * The compressor applied to the mid signal + * @type {Tone.Compressor} + */ + this.mid = new Tone.Compressor(options.mid); + /** + * The compressor applied to the side signal + * @type {Tone.Compressor} + */ + this.side = new Tone.Compressor(options.side); + this._midSideSplit.mid.chain(this.mid, this._midSideMerge.mid); + this._midSideSplit.side.chain(this.side, this._midSideMerge.side); + this._readOnly([ + 'mid', + 'side' + ]); }; + Tone.extend(Tone.MidSideCompressor); /** - * invoke the callback in the correct context - * passes in the playback time - * - * @param {number} playbackTime + * @const + * @static + * @type {Object} */ - TimelineEvent.prototype.doCallback = function (playbackTime) { - this.callback.call(this.context, playbackTime); + Tone.MidSideCompressor.defaults = { + 'mid': { + 'ratio': 3, + 'threshold': -24, + 'release': 0.03, + 'attack': 0.02, + 'knee': 16 + }, + 'side': { + 'ratio': 6, + 'threshold': -30, + 'release': 0.25, + 'attack': 0.03, + 'knee': 10 + } }; /** - * get the tick which the callback is supposed to occur on - * - * @return {number} + * Clean up. + * @returns {Tone.MidSideCompressor} this */ - TimelineEvent.prototype.callbackTick = function () { - return this.startTicks + this.tickTime; + Tone.MidSideCompressor.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable([ + 'mid', + 'side' + ]); + this.mid.dispose(); + this.mid = null; + this.side.dispose(); + this.side = null; + this._midSideSplit.dispose(); + this._midSideSplit = null; + this._midSideMerge.dispose(); + this._midSideMerge = null; + return this; }; + return Tone.MidSideCompressor; + }); + Module(function (Tone) { + /** - * test if the tick occurs on the interval - * - * @param {number} tick - * @return {boolean} + * @class Tone.Mono coerces the incoming mono or stereo signal into a mono signal + * where both left and right channels have the same value. This can be useful + * for [stereo imaging](https://en.wikipedia.org/wiki/Stereo_imaging). + * + * @extends {Tone} + * @constructor */ - TimelineEvent.prototype.testInterval = function (tick) { - return (tick - this.startTicks) % this.tickTime === 0; + Tone.Mono = function () { + Tone.call(this, 1, 0); + /** + * merge the signal + * @type {Tone.Merge} + * @private + */ + this._merge = this.output = new Tone.Merge(); + this.input.connect(this._merge, 0, 0); + this.input.connect(this._merge, 0, 1); + this.input.gain.value = this.dbToGain(-10); }; - /////////////////////////////////////////////////////////////////////////////// - // AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING - /////////////////////////////////////////////////////////////////////////////// + Tone.extend(Tone.Mono); /** - * tests if a string is musical notation - * i.e.: - * 4n = quarter note - * 2m = two measures - * 8t = eighth-note triplet - * - * @return {boolean} - * @method isNotation - * @lends Tone.prototype.isNotation + * clean up + * @returns {Tone.Mono} this */ - Tone.prototype.isNotation = function () { - var notationFormat = new RegExp(/[0-9]+[mnt]$/i); - return function (note) { - return notationFormat.test(note); - }; - }(); + Tone.Mono.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._merge.dispose(); + this._merge = null; + return this; + }; + return Tone.Mono; + }); + Module(function (Tone) { + /** - * tests if a string is transportTime - * i.e. : - * 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes - * - * @return {boolean} + * @class A compressor with seperate controls over low/mid/high dynamics * - * @method isTransportTime - * @lends Tone.prototype.isTransportTime + * @extends {Tone} + * @constructor + * @param {Object} options The low/mid/high compressor settings. + * @example + * var multiband = new Tone.MultibandCompressor({ + * "lowFrequency" : 200, + * "highFrequency" : 1300 + * "low" : { + * "threshold" : -12 + * } + * }) */ - Tone.prototype.isTransportTime = function () { - var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/i); - return function (transportTime) { - return transportTimeFormat.test(transportTime); - }; - }(); + Tone.MultibandCompressor = function (options) { + options = this.defaultArg(arguments, Tone.MultibandCompressor.defaults); + /** + * split the incoming signal into high/mid/low + * @type {Tone.MultibandSplit} + * @private + */ + this._splitter = this.input = new Tone.MultibandSplit({ + 'lowFrequency': options.lowFrequency, + 'highFrequency': options.highFrequency + }); + /** + * low/mid crossover frequency. + * @type {Frequency} + * @signal + */ + this.lowFrequency = this._splitter.lowFrequency; + /** + * mid/high crossover frequency. + * @type {Frequency} + * @signal + */ + this.highFrequency = this._splitter.highFrequency; + /** + * the output + * @type {GainNode} + * @private + */ + this.output = this.context.createGain(); + /** + * The compressor applied to the low frequencies. + * @type {Tone.Compressor} + */ + this.low = new Tone.Compressor(options.low); + /** + * The compressor applied to the mid frequencies. + * @type {Tone.Compressor} + */ + this.mid = new Tone.Compressor(options.mid); + /** + * The compressor applied to the high frequencies. + * @type {Tone.Compressor} + */ + this.high = new Tone.Compressor(options.high); + //connect the compressor + this._splitter.low.chain(this.low, this.output); + this._splitter.mid.chain(this.mid, this.output); + this._splitter.high.chain(this.high, this.output); + this._readOnly([ + 'high', + 'mid', + 'low', + 'highFrequency', + 'lowFrequency' + ]); + }; + Tone.extend(Tone.MultibandCompressor); /** - * - * convert notation format strings to seconds - * - * @param {string} notation - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} - * + * @const + * @static + * @type {Object} */ - Tone.prototype.notationToSeconds = function (notation, bpm, timeSignature) { - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var beatTime = 60 / bpm; - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0) { - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === 't') { - beats = 4 / subdivision * 2 / 3; - } else if (lastLetter === 'n') { - beats = 4 / subdivision; - } else if (lastLetter === 'm') { - beats = subdivision * timeSignature; - } else { - beats = 0; - } - return beatTime * beats; + Tone.MultibandCompressor.defaults = { + 'low': Tone.Compressor.defaults, + 'mid': Tone.Compressor.defaults, + 'high': Tone.Compressor.defaults, + 'lowFrequency': 250, + 'highFrequency': 2000 }; /** - * convert transportTime into seconds. - * - * ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - * - * @param {string} transportTime - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * - * @lends Tone.prototype.transportTimeToSeconds + * clean up + * @returns {Tone.MultibandCompressor} this */ - Tone.prototype.transportTimeToSeconds = function (transportTime, bpm, timeSignature) { - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(':'); - if (split.length === 2) { - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1) { - quarters = parseFloat(split[0]); - } else if (split.length === 3) { - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = measures * timeSignature + quarters + sixteenths / 4; - return beats * this.notationToSeconds('4n'); + Tone.MultibandCompressor.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._splitter.dispose(); + this._writable([ + 'high', + 'mid', + 'low', + 'highFrequency', + 'lowFrequency' + ]); + this.low.dispose(); + this.mid.dispose(); + this.high.dispose(); + this._splitter = null; + this.low = null; + this.mid = null; + this.high = null; + this.lowFrequency = null; + this.highFrequency = null; + return this; }; + return Tone.MultibandCompressor; + }); + Module(function (Tone) { + /** - * Convert seconds to the closest transportTime in the form - * measures:quarters:sixteenths + * @class Maps a NormalRange [0, 1] to an AudioRange [-1, 1]. + * See also Tone.AudioToGain. * - * @method toTransportTime - * - * @param {Time} seconds - * @param {number=} bpm - * @param {number=} timeSignature - * @return {string} - * - * @lends Tone.prototype.toTransportTime + * @extends {Tone.SignalBase} + * @constructor + * @example + * var g2a = new Tone.GainToAudio(); */ - Tone.prototype.toTransportTime = function (time, bpm, timeSignature) { - var seconds = this.toSeconds(time, bpm, timeSignature); - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var quarterTime = this.notationToSeconds('4n'); - var quarters = seconds / quarterTime; - var measures = Math.floor(quarters / timeSignature); - var sixteenths = Math.floor(quarters % 1 * 4); - quarters = Math.floor(quarters) % timeSignature; - var progress = [ - measures, - quarters, - sixteenths - ]; - return progress.join(':'); + 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); /** - * 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 + * clean up + * @returns {Tone.GainToAudio} this */ - Tone.prototype.toFrequency = function (freq, now) { - if (this.isFrequency(freq)) { - return parseFloat(freq); - } else if (this.isNotation(freq) || this.isTransportTime(freq)) { - return this.secondsToFrequency(this.toSeconds(freq, now)); - } else { - return freq; - } + Tone.GainToAudio.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._norm.dispose(); + this._norm = null; + return this; }; + return Tone.GainToAudio; + }); + Module(function (Tone) { + /** - * Convert Time into seconds. + * @class Tone.Panner is an equal power Left/Right Panner and does not + * support 3D. Panner uses the StereoPannerNode when available. * - * Unlike the method which it overrides, this takes into account - * transporttime and musical notation. - * - * Time : 1.40 - * Notation: 4n|1m|2t - * TransportTime: 2:4:1 (measure:quarters:sixteens) - * Now Relative: +3n - * Math: 3n+16n or even very complicated expressions ((3n*2)/6 + 1) - * - * @override - * @param {Time} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} + * @constructor + * @extends {Tone} + * @param {NormalRange} [initialPan=0.5] The initail panner value (defaults to 0.5 = center) + * @example + * //pan the input signal hard right. + * var panner = new Tone.Panner(1); */ - Tone.prototype.toSeconds = function (time, now) { - now = this.defaultArg(now, this.now()); - if (typeof time === 'number') { - return time; //assuming that it's seconds - } else if (typeof time === 'string') { - var plusTime = 0; - if (time.charAt(0) === '+') { - plusTime = now; - time = time.slice(1); - } - var components = time.split(/[\(\)\-\+\/\*]/); - if (components.length > 1) { - var originalTime = time; - for (var i = 0; i < components.length; i++) { - var symb = components[i].trim(); - if (symb !== '') { - var val = this.toSeconds(symb); - time = time.replace(symb, val); - } - } - try { - //i know eval is evil, but i think it's safe here - time = eval(time); // jshint ignore:line - } catch (e) { - throw new EvalError('problem evaluating Tone.Type.Time: ' + originalTime); - } - } else if (this.isNotation(time)) { - time = this.notationToSeconds(time); - } else if (this.isTransportTime(time)) { - time = this.transportTimeToSeconds(time); - } else if (this.isFrequency(time)) { - time = this.frequencyToSeconds(time); - } else { - time = parseFloat(time); - } - return time + plusTime; + Tone.Panner = function (initialPan) { + Tone.call(this); + /** + * indicates if the panner is using the new StereoPannerNode internally + * @type {boolean} + * @private + */ + this._hasStereoPanner = this.isFunction(this.context.createStereoPanner); + if (this._hasStereoPanner) { + /** + * the panner node + * @type {StereoPannerNode} + * @private + */ + this._panner = this.input = this.output = this.context.createStereoPanner(); + /** + * The pan control. 0 = 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); } else { - return now; + /** + * the dry/wet knob + * @type {Tone.CrossFade} + * @private + */ + this._crossFade = new Tone.CrossFade(); + /** + * @type {Tone.Merge} + * @private + */ + this._merger = this.output = new Tone.Merge(); + /** + * @type {Tone.Split} + * @private + */ + this._splitter = this.input = new Tone.Split(); + /** + * The pan control. 0 = hard left, 1 = hard right. + * @type {NormalRange} + * @signal + */ + this.pan = this._crossFade.fade; + //CONNECTIONS: + //left channel is a, right channel is b + this._splitter.connect(this._crossFade, 0, 0); + this._splitter.connect(this._crossFade, 1, 1); + //merge it back together + this._crossFade.a.connect(this._merger, 0, 0); + this._crossFade.b.connect(this._merger, 0, 1); } + //initial value + this.pan.value = this.defaultArg(initialPan, 0.5); + this._readOnly('pan'); }; - var TransportConstructor = Tone.Transport; - Tone._initAudioContext(function () { - if (typeof Tone.Transport === 'function') { - //a single transport object - Tone.Transport = new Tone.Transport(); + Tone.extend(Tone.Panner); + /** + * Clean up. + * @returns {Tone.Panner} this + */ + Tone.Panner.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._writable('pan'); + if (this._hasStereoPanner) { + this._panner.disconnect(); + this._panner = null; + this.pan.dispose(); + this.pan = null; + this._scalePan.dispose(); + this._scalePan = null; } else { - //stop the clock - Tone.Transport.stop(); - //get the previous bpm - var bpm = Tone.Transport.bpm.value; - //destory the old clock - Tone.Transport._clock.dispose(); - //make new Transport insides - TransportConstructor.call(Tone.Transport); - //set the bpm - Tone.Transport.bpm.value = bpm; + this._crossFade.dispose(); + this._crossFade = null; + this._splitter.dispose(); + this._splitter = null; + this._merger.dispose(); + this._merger = null; + this.pan = null; } - }); - return Tone.Transport; + return this; + }; + return Tone.Panner; }); 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.PanVol is a Tone.Panner and Tone.Volume in one. * - * @constructor * @extends {Tone} - * @singleton + * @constructor + * @param {NormalRange} pan the initial pan + * @param {number} volume The output volume. * @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. + * //pan the incoming signal left and drop the volume + * var panVol = new Tone.PanVol(0.25, -12); */ - Tone.Master = function () { - Tone.call(this); + Tone.PanVol = function () { + var options = this.optionsObject(arguments, [ + 'pan', + 'volume' + ], Tone.PanVol.defaults); /** - * the unmuted volume - * @type {number} - * @private + * The panning node + * @type {Tone.Panner} + * @private */ - this._unmutedVolume = 1; + this._panner = this.input = new Tone.Panner(options.pan); /** - * if the master is muted - * @type {boolean} - * @private + * The L/R panning control. + * @type {NormalRange} + * @signal */ - this._muted = false; + this.pan = this._panner.pan; /** - * The volume of the master output. - * @type {Decibels} - * @signal + * The volume node + * @type {Tone.Volume} + */ + this._volume = this.output = new Tone.Volume(options.volume); + /** + * The volume control in decibels. + * @type {Decibels} + * @signal */ - this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels); + this.volume = this._volume.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._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; - } - }); - /** - * 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); - }; - /////////////////////////////////////////////////////////////////////////// - // AUGMENT TONE's PROTOTYPE - /////////////////////////////////////////////////////////////////////////// + this._panner.connect(this._volume); + this._readOnly([ + 'pan', + 'volume' + ]); + }; + Tone.extend(Tone.PanVol); /** - * 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(); + * The defaults + * @type {Object} + * @const + * @static */ - Tone.prototype.toMaster = function () { - this.connect(Tone.Master); - return this; + Tone.PanVol.defaults = { + 'pan': 0.5, + 'volume': 0 }; /** - * Also augment AudioNode's prototype to include toMaster - * as a convenience - * @returns {AudioNode} this + * clean up + * @returns {Tone.PanVol} this */ - AudioNode.prototype.toMaster = function () { - this.connect(Tone.Master); + Tone.PanVol.prototype.dispose = function () { + Tone.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; 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.PanVol; }); Module(function (Tone) { /** - * @class Base class for sources. Sources have start/stop methods - * and the ability to be synced to the - * start/stop of Tone.Transport. + * @class Tone.CtrlInterpolate will interpolate between given values based + * on the "index" property. Passing in an array or object literal + * will interpolate each of the parameters. Note (i.e. "C3") + * and Time (i.e. "4n + 2") can be interpolated. All other values are + * assumed to be numbers. + * @example + * var interp = new Tone.CtrlInterpolate([0, 2, 9, 4]); + * interp.index = 0.75; + * interp.value; //returns 1.5 * - * @constructor - * @extends {Tone} + * @example + * var interp = new Tone.CtrlInterpolate([ + * ["C3", "G4", "E5"], + * ["D4", "F#4", "E5"], + * ]); + * @param {Array} values The array of values to interpolate over + * @param {Positive} index The initial interpolation index. + * @extends {Tone} */ - Tone.Source = function (options) { - //unlike most ToneNodes, Sources only have an output and no input - Tone.call(this, 0, 1); - options = this.defaultArg(options, Tone.Source.defaults); - /** - * Callback is invoked when the source is done playing. - * @type {function} - * @example - * source.onended = function(){ - * console.log("the source is done playing"); - * } - */ - this.onended = options.onended; - /** - * the next time the source is started - * @type {number} - * @private - */ - this._nextStart = Infinity; - /** - * the next time the source is stopped - * @type {number} - * @private - */ - this._nextStop = Infinity; + Tone.CtrlInterpolate = function () { + var options = this.optionsObject(arguments, [ + 'values', + 'index' + ], Tone.CtrlInterpolate.defaults); /** - * The volume of the output in decibels. - * @type {Decibels} - * @signal - * @example - * source.volume.value = -6; + * The values to interpolate between + * @type {Array} */ - this.volume = new Tone.Signal({ - 'param': this.output.gain, - 'value': options.volume, - 'units': Tone.Type.Decibels - }); - this._readOnly('volume'); + this.values = options.values; /** - * keeps track of the timeout for chaning the state - * and calling the onended - * @type {number} - * @private + * The interpolated index between values. For example: a value of 1.5 + * would interpolate equally between the value at index 1 + * and the value at index 2. + * @example + * interp.index = 0; + * interp.value; //returns the value at 0 + * interp.index = 0.5; + * interp.value; //returns the value between indices 0 and 1. + * @type {Positive} */ - this._timeout = -1; - //make the output explicitly stereo - this.output.channelCount = 2; - this.output.channelCountMode = 'explicit'; + this.index = options.index; }; - Tone.extend(Tone.Source); + Tone.extend(Tone.CtrlInterpolate); /** - * The default parameters - * @static + * The defaults * @const - * @type {Object} + * @type {Object} */ - Tone.Source.defaults = { - 'onended': Tone.noOp, - 'volume': 0 + Tone.CtrlInterpolate.defaults = { + 'index': 0, + 'values': [] }; /** - * Returns the playback state of the source, either "started" or "stopped". - * @type {Tone.State} + * The current interpolated value based on the index * @readOnly - * @memberOf Tone.Source# - * @name state + * @memberOf Tone.CtrlInterpolate# + * @type {*} + * @name value */ - Object.defineProperty(Tone.Source.prototype, 'state', { + Object.defineProperty(Tone.CtrlInterpolate.prototype, 'value', { get: function () { - return this._stateAtTime(this.now()); + var index = this.index; + index = Math.min(index, this.values.length - 1); + var lowerPosition = Math.floor(index); + var lower = this.values[lowerPosition]; + var upper = this.values[Math.ceil(index)]; + return this._interpolate(index - lowerPosition, lower, upper); } }); /** - * Get the state of the source at the specified time. - * @param {Time} time - * @return {Tone.State} + * Internal interpolation routine + * @param {NormalRange} index The index between the lower and upper + * @param {*} lower + * @param {*} upper + * @return {*} The interpolated value * @private */ - Tone.Source.prototype._stateAtTime = function (time) { - time = this.toSeconds(time); - if (this._nextStart <= time && this._nextStop > time) { - return Tone.State.Started; - } else if (this._nextStop <= time) { - return Tone.State.Stopped; + Tone.CtrlInterpolate.prototype._interpolate = function (index, lower, upper) { + if (this.isArray(lower)) { + var retArray = []; + for (var i = 0; i < lower.length; i++) { + retArray[i] = this._interpolate(index, lower[i], upper[i]); + } + return retArray; + } else if (this.isObject(lower)) { + var retObj = {}; + for (var attr in lower) { + retObj[attr] = this._interpolate(index, lower[attr], upper[attr]); + } + return retObj; } else { - return Tone.State.Stopped; - } - }; - /** - * Start the source at the specified time. If no time is given, - * start the source now. - * @param {Time} [time=now] When the source should be started. - * @returns {Tone.Source} this - * @example - * source.start("+0.5"); //starts the source 0.5 seconds from now - */ - Tone.Source.prototype.start = function (time) { - time = this.toSeconds(time); - if (this._stateAtTime(time) !== Tone.State.Started || this.retrigger) { - this._nextStart = time; - this._nextStop = Infinity; - this._start.apply(this, arguments); + lower = this._toNumber(lower); + upper = this._toNumber(upper); + return (1 - index) * lower + index * upper; } - return this; }; /** - * Stop the source at the specified time. If no time is given, - * stop the source now. - * @param {Time} [time=now] When the source should be stopped. - * @returns {Tone.Source} this - * @example - * source.stop(); // stops the source immediately + * Convert from the given type into a number + * @param {Number|String} value + * @return {Number} + * @private */ - Tone.Source.prototype.stop = function (time) { - var now = this.now(); - time = this.toSeconds(time, now); - if (this._stateAtTime(time) === Tone.State.Started) { - this._nextStop = this.toSeconds(time); - clearTimeout(this._timeout); - var diff = time - now; - if (diff > 0) { - //add a small buffer before invoking the callback - this._timeout = setTimeout(this.onended, diff * 1000 + 20); - } else { - this.onended(); - } - this._stop.apply(this, arguments); + 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); } - return this; - }; - /** - * Not ready yet. - * @private - * @abstract - * @param {Time} time - * @returns {Tone.Source} this - */ - Tone.Source.prototype.pause = function (time) { - //if there is no pause, just stop it - this.stop(time); - return this; - }; - /** - * Sync the source to the Transport so that when the transport - * is started, this source is started and when the transport is stopped - * or paused, so is the source. - * - * @param {Time} [delay=0] Delay time before starting the source after the - * Transport has started. - * @returns {Tone.Source} this - * @example - * //sync the source to start 1 measure after the transport starts - * source.sync("1m"); - * //start the transport. the source will start 1 measure later. - * Tone.Transport.start(); - */ - Tone.Source.prototype.sync = function (delay) { - Tone.Transport.syncSource(this, delay); - return this; - }; - /** - * Unsync the source to the Transport. See Tone.Source.sync - * @returns {Tone.Source} this - */ - Tone.Source.prototype.unsync = function () { - Tone.Transport.unsyncSource(this); - return this; }; /** - * Clean up. - * @return {Tone.Source} this + * Clean up + * @return {Tone.CtrlInterpolate} this */ - Tone.Source.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this.stop(); - clearTimeout(this._timeout); - this.onended = Tone.noOp; - this._writable('volume'); - this.volume.dispose(); - this.volume = null; + Tone.CtrlInterpolate.prototype.dispose = function () { + this.values = null; }; - return Tone.Source; + return Tone.CtrlInterpolate; }); Module(function (Tone) { /** - * @class Tone.Oscillator supports a number of features including - * phase rotation, multiple oscillator types (see Tone.Oscillator.type), - * and Transport syncing (see Tone.Oscillator.syncFrequency). + * @class Tone.CtrlMarkov represents a Markov Chain where each call + * to Tone.CtrlMarkov.next will move to the next state. If the next + * state choice is an array, the next state is chosen randomly with + * even probability for all of the choices. For a weighted probability + * of the next choices, pass in an object with "state" and "probability" attributes. + * The probabilities will be normalized and then chosen. If no next options + * are given for the current state, the state will stay there. + * @extends {Tone} + * @example + * var chain = new Tone.CtrlMarkov({ + * "beginning" : ["end", "middle"], + * "middle" : "end" + * }); + * chain.value = "beginning"; + * chain.next(); //returns "end" or "middle" with 50% probability * - * @constructor - * @extends {Tone.Source} - * @param {Frequency} [frequency] Starting frequency - * @param {string} [type] The oscillator type. Read more about type below. * @example - * //make and start a 440hz sine tone - * var osc = new Tone.Oscillator(440, "sine").toMaster().start(); + * var chain = new Tone.CtrlMarkov({ + * "beginning" : [{"value" : "end", "probability" : 0.8}, + * {"value" : "middle", "probability" : 0.2}], + * "middle" : "end" + * }); + * chain.value = "beginning"; + * chain.next(); //returns "end" with 80% probability or "middle" with 20%. + * @param {Object} values An object with the state names as the keys + * and the next state(s) as the values. */ - Tone.Oscillator = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'type' - ], Tone.Oscillator.defaults); - Tone.Source.call(this, options); - /** - * the main oscillator - * @type {OscillatorNode} - * @private - */ - this._oscillator = null; - /** - * The frequency control. - * @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 periodic wave - * @type {PeriodicWave} - * @private - */ - this._wave = null; - /** - * the phase of the oscillator - * between 0 - 360 - * @type {number} - * @private - */ - this._phase = options.phase; + Tone.CtrlMarkov = function (values, initial) { /** - * the type of the oscillator - * @type {string} - * @private + * The Markov values with states as the keys + * and next state(s) as the values. + * @type {Object} */ - this._type = null; - //setup - this.type = options.type; - this.phase = this._phase; - this._readOnly([ - 'frequency', - 'detune' - ]); - }; - Tone.extend(Tone.Oscillator, Tone.Source); - /** - * the default parameters - * @type {Object} - */ - Tone.Oscillator.defaults = { - 'type': 'sine', - 'frequency': 440, - 'detune': 0, - 'phase': 0 + this.values = this.defaultArg(values, {}); + /** + * The current state of the Markov values. The next + * state will be evaluated and returned when Tone.CtrlMarkov.next + * is invoked. + * @type {String} + */ + this.value = this.defaultArg(initial, Object.keys(this.values)[0]); + }; + Tone.extend(Tone.CtrlMarkov); + /** + * Returns the next state of the Markov values. + * @return {String} + */ + Tone.CtrlMarkov.prototype.next = function () { + if (this.values.hasOwnProperty(this.value)) { + var next = this.values[this.value]; + if (this.isArray(next)) { + var distribution = this._getProbDistribution(next); + var rand = Math.random(); + var total = 0; + for (var i = 0; i < distribution.length; i++) { + var dist = distribution[i]; + if (rand > total && rand < total + dist) { + var chosen = next[i]; + if (this.isObject(chosen)) { + this.value = chosen.value; + } else { + this.value = chosen; + } + } + total += dist; + } + } else { + this.value = next; + } + } + return this.value; }; /** - * start the oscillator - * @param {Time} [time=now] + * Choose randomly from an array weighted options in the form + * {"state" : string, "probability" : number} or an array of values + * @param {Array} options + * @return {Array} The randomly selected choice * @private */ - Tone.Oscillator.prototype._start = function (time) { - //new oscillator with previous values - this._oscillator = this.context.createOscillator(); - this._oscillator.setPeriodicWave(this._wave); - //connect the control signal to the oscillator frequency & detune - this._oscillator.connect(this.output); - this.frequency.connect(this._oscillator.frequency); - this.detune.connect(this._oscillator.detune); - //start the oscillator - this._oscillator.start(this.toSeconds(time)); + Tone.CtrlMarkov.prototype._getProbDistribution = function (options) { + var distribution = []; + var total = 0; + var needsNormalizing = false; + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (this.isObject(option)) { + needsNormalizing = true; + distribution[i] = option.probability; + } else { + distribution[i] = 1 / options.length; + } + total += distribution[i]; + } + if (needsNormalizing) { + //normalize the values + for (var j = 0; j < distribution.length; j++) { + distribution[j] = distribution[j] / total; + } + } + return distribution; }; /** - * stop the oscillator - * @private - * @param {Time} [time=now] (optional) timing parameter - * @returns {Tone.Oscillator} this + * Clean up + * @return {Tone.CtrlMarkov} this */ - Tone.Oscillator.prototype._stop = function (time) { - if (this._oscillator) { - this._oscillator.stop(this.toSeconds(time)); - this._oscillator = null; - } - return this; + Tone.CtrlMarkov.prototype.dispose = function () { + this.values = null; }; + return Tone.CtrlMarkov; + }); + Module(function (Tone) { + /** - * Sync the signal to the Transport's bpm. Any changes to the transports bpm, - * will also affect the oscillators frequency. - * @returns {Tone.Oscillator} this - * @example - * Tone.Transport.bpm.value = 120; - * osc.frequency.value = 440; - * //the ration between the bpm and the frequency will be maintained - * osc.syncFrequency(); - * Tone.Transport.bpm.value = 240; - * // the frequency of the oscillator is doubled to 880 + * @class Generate patterns from an array of values. + * Has a number of arpeggiation and randomized + * selection patterns. + *
    + *
  • "up" - cycles upward
  • + *
  • "down" - cycles downward
  • + *
  • "upDown" - up then and down
  • + *
  • "downUp" - cycles down then and up
  • + *
  • "alternateUp" - jump up two and down one
  • + *
  • "alternateDown" - jump down two and up one
  • + *
  • "random" - randomly select an index
  • + *
  • "randomWalk" - randomly moves one index away from the current position
  • + *
  • "randomOnce" - randomly select an index without repeating until all values have been chosen.
  • + *
+ * @param {Array} values An array of options to choose from. + * @param {Tone.CtrlPattern.Type=} type The name of the pattern. + * @extends {Tone} */ - Tone.Oscillator.prototype.syncFrequency = function () { - Tone.Transport.syncSignal(this.frequency); - return this; + Tone.CtrlPattern = function () { + var options = this.optionsObject(arguments, [ + 'values', + 'type' + ], Tone.CtrlPattern.defaults); + /** + * The array of values to arpeggiate over + * @type {Array} + */ + this.values = options.values; + /** + * The current position in the values array + * @type {Number} + */ + this.index = 0; + /** + * The type placeholder + * @type {Tone.CtrlPattern.Type} + * @private + */ + this._type = null; + /** + * Shuffled values for the RandomOnce type + * @type {Array} + * @private + */ + this._shuffled = null; + /** + * The direction of the movement + * @type {String} + * @private + */ + this._direction = null; + this.type = options.type; }; + Tone.extend(Tone.CtrlPattern); /** - * Unsync the oscillator's frequency from the Transport. - * See Tone.Oscillator.syncFrequency - * @returns {Tone.Oscillator} this + * The Control Patterns + * @type {Object} + * @static */ - Tone.Oscillator.prototype.unsyncFrequency = function () { - Tone.Transport.unsyncSignal(this.frequency); - return this; + Tone.CtrlPattern.Type = { + Up: 'up', + Down: 'down', + UpDown: 'upDown', + DownUp: 'downUp', + AlternateUp: 'alternateUp', + AlternateDown: 'alternateDown', + Random: 'random', + RandomWalk: 'randomWalk', + RandomOnce: 'randomOnce' }; /** - * The type of the oscillator: either sine, square, triangle, or sawtooth. Also capable of - * setting the first x number of partials of the oscillator. For example: "sine4" would - * set be the first 4 partials of the sine wave and "triangle8" would set the first - * 8 partials of the triangle wave. - *

- * Uses PeriodicWave internally even for native types so that it can set the phase. - * PeriodicWave equations are from the - * [Webkit Web Audio implementation](https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/modules/webaudio/PeriodicWave.cpp&sq=package:chromium). - * - * @memberOf Tone.Oscillator# - * @type {string} - * @name type - * @example - * //set it to a square wave - * osc.type = "square"; - * @example - * //set the first 6 partials of a sawtooth wave - * osc.type = "sawtooth6"; + * The default values. + * @type {Object} */ - Object.defineProperty(Tone.Oscillator.prototype, 'type', { + Tone.CtrlPattern.defaults = { + 'type': Tone.CtrlPattern.Type.Up, + 'values': [] + }; + /** + * The value at the current index of the pattern. + * @readOnly + * @memberOf Tone.CtrlPattern# + * @type {*} + * @name value + */ + Object.defineProperty(Tone.CtrlPattern.prototype, 'value', { get: function () { - return this._type; - }, - set: function (type) { - var originalType = type; - var fftSize = 4096; - var periodicWaveSize = fftSize / 2; - var real = new Float32Array(periodicWaveSize); - var imag = new Float32Array(periodicWaveSize); - var partialCount = 1; - var partial = /(sine|triangle|square|sawtooth)(\d+)$/.exec(type); - if (partial) { - partialCount = parseInt(partial[2]); - type = partial[1]; - partialCount = Math.max(partialCount, 2); - periodicWaveSize = partialCount; + //some safeguards + if (this.values.length === 0) { + return; + } else if (this.values.length === 1) { + return this.values[0]; } - var shift = this._phase; - for (var n = 1; n < periodicWaveSize; ++n) { - var piFactor = 2 / (n * Math.PI); - var b; - switch (type) { - case 'sine': - b = n <= partialCount ? 1 : 0; - break; - case 'square': - b = n & 1 ? 2 * piFactor : 0; - break; - case 'sawtooth': - b = piFactor * (n & 1 ? 1 : -1); - break; - case 'triangle': - if (n & 1) { - b = 2 * (piFactor * piFactor) * (n - 1 >> 1 & 1 ? -1 : 1); - } else { - b = 0; - } - break; - default: - throw new TypeError('invalid oscillator type: ' + type); + this.index = Math.min(this.index, this.values.length - 1); + var val = this.values[this.index]; + if (this.type === Tone.CtrlPattern.Type.RandomOnce) { + if (this.values.length !== this._shuffled.length) { + this._shuffleValues(); } - if (b !== 0) { - real[n] = -b * Math.sin(shift * n); - imag[n] = b * Math.cos(shift * n); - } else { - real[n] = 0; - imag[n] = 0; - } - } - var periodicWave = this.context.createPeriodicWave(real, imag); - this._wave = periodicWave; - if (this._oscillator !== null) { - this._oscillator.setPeriodicWave(this._wave); + val = this.values[this._shuffled[this.index]]; } - this._type = originalType; + return val; } }); /** - * The phase of the oscillator in degrees. - * @memberOf Tone.Oscillator# - * @type {Degrees} - * @name phase - * @example - * osc.phase = 180; //flips the phase of the oscillator + * The pattern used to select the next + * item from the values array + * @memberOf Tone.CtrlPattern# + * @type {Tone.CtrlPattern.Type} + * @name type */ - Object.defineProperty(Tone.Oscillator.prototype, 'phase', { + Object.defineProperty(Tone.CtrlPattern.prototype, 'type', { get: function () { - return this._phase * (180 / Math.PI); + return this._type; }, - set: function (phase) { - this._phase = phase * Math.PI / 180; - //reset the type - this.type = this._type; + set: function (type) { + this._type = type; + this._shuffled = null; + //the first index + if (this._type === Tone.CtrlPattern.Type.Up || this._type === Tone.CtrlPattern.Type.UpDown || this._type === Tone.CtrlPattern.Type.RandomOnce || this._type === Tone.CtrlPattern.Type.AlternateUp) { + this.index = 0; + } else if (this._type === Tone.CtrlPattern.Type.Down || this._type === Tone.CtrlPattern.Type.DownUp || this._type === Tone.CtrlPattern.Type.AlternateDown) { + this.index = this.values.length - 1; + } + //the direction + if (this._type === Tone.CtrlPattern.Type.UpDown || this._type === Tone.CtrlPattern.Type.AlternateUp) { + this._direction = Tone.CtrlPattern.Type.Up; + } else if (this._type === Tone.CtrlPattern.Type.DownUp || this._type === Tone.CtrlPattern.Type.AlternateDown) { + this._direction = Tone.CtrlPattern.Type.Down; + } + //randoms + if (this._type === Tone.CtrlPattern.Type.RandomOnce) { + this._shuffleValues(); + } else if (this._type === Tone.CtrlPattern.Random) { + this.index = Math.floor(Math.random() * this.values.length); + } } }); /** - * Dispose and disconnect. - * @return {Tone.Oscillator} this + * Return the next value given the current position + * and pattern. + * @return {*} The next value + */ + Tone.CtrlPattern.prototype.next = function () { + var type = this.type; + //choose the next index + if (type === Tone.CtrlPattern.Type.Up) { + this.index++; + if (this.index >= this.values.length) { + this.index = 0; + } + } else if (type === Tone.CtrlPattern.Type.Down) { + this.index--; + if (this.index < 0) { + this.index = this.values.length - 1; + } + } else if (type === Tone.CtrlPattern.Type.UpDown || type === Tone.CtrlPattern.Type.DownUp) { + if (this._direction === Tone.CtrlPattern.Type.Up) { + this.index++; + } else { + this.index--; + } + if (this.index < 0) { + this.index = 1; + this._direction = Tone.CtrlPattern.Type.Up; + } else if (this.index >= this.values.length) { + this.index = this.values.length - 2; + this._direction = Tone.CtrlPattern.Type.Down; + } + } else if (type === Tone.CtrlPattern.Type.Random) { + this.index = Math.floor(Math.random() * this.values.length); + } else if (type === Tone.CtrlPattern.Type.RandomWalk) { + if (Math.random() < 0.5) { + this.index--; + this.index = Math.max(this.index, 0); + } else { + this.index++; + this.index = Math.min(this.index, this.values.length - 1); + } + } else if (type === Tone.CtrlPattern.Type.RandomOnce) { + this.index++; + if (this.index >= this.values.length) { + this.index = 0; + //reshuffle the values for next time + this._shuffleValues(); + } + } else if (type === Tone.CtrlPattern.Type.AlternateUp) { + if (this._direction === Tone.CtrlPattern.Type.Up) { + this.index += 2; + this._direction = Tone.CtrlPattern.Type.Down; + } else { + this.index -= 1; + this._direction = Tone.CtrlPattern.Type.Up; + } + if (this.index >= this.values.length) { + this.index = 0; + this._direction = Tone.CtrlPattern.Type.Up; + } + } else if (type === Tone.CtrlPattern.Type.AlternateDown) { + if (this._direction === Tone.CtrlPattern.Type.Up) { + this.index += 1; + this._direction = Tone.CtrlPattern.Type.Down; + } else { + this.index -= 2; + this._direction = Tone.CtrlPattern.Type.Up; + } + if (this.index < 0) { + this.index = this.values.length - 1; + this._direction = Tone.CtrlPattern.Type.Down; + } + } + return this.value; + }; + /** + * Shuffles the values and places the results into the _shuffled + * @private */ - Tone.Oscillator.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); - if (this._oscillator !== null) { - this._oscillator.disconnect(); - this._oscillator = null; + Tone.CtrlPattern.prototype._shuffleValues = function () { + var copy = []; + this._shuffled = []; + for (var i = 0; i < this.values.length; i++) { + copy[i] = i; + } + while (copy.length > 0) { + var randVal = copy.splice(Math.floor(copy.length * Math.random()), 1); + this._shuffled.push(randVal[0]); } - this._wave = null; - this._writable([ - 'frequency', - 'detune' - ]); - this.frequency.dispose(); - this.frequency = null; - this.detune.dispose(); - this.detune = null; - return this; }; - return Tone.Oscillator; + /** + * Clean up + * @returns {Tone.CtrlPattern} this + */ + Tone.CtrlPattern.prototype.dispose = function () { + this._shuffled = null; + this.values = null; + }; + return Tone.CtrlPattern; }); Module(function (Tone) { /** - * @class AudioToGain converts an input in AudioRange [-1,1] to NormalRange [0,1]. - * See Tone.GainToAudio. - * - * @extends {Tone.SignalBase} - * @constructor + * @class Choose a random value. + * @extends {Tone} * @example - * var a2g = new Tone.AudioToGain(); + * var randomWalk = new Tone.CtrlRandom({ + * "min" : 0, + * "max" : 10, + * "integer" : true + * }); + * randomWalk.eval(); + * + * @param {Number|Time=} min The minimum return value. + * @param {Number|Time=} max The maximum return value. */ - Tone.AudioToGain = function () { + Tone.CtrlRandom = function () { + var options = this.optionsObject(arguments, [ + 'min', + 'max' + ], Tone.CtrlRandom.defaults); /** - * @type {WaveShaperNode} - * @private + * The minimum return value + * @type {Number|Time} */ - this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { - return (x + 1) / 2; - }); + this.min = options.min; + /** + * The maximum return value + * @type {Number|Time} + */ + this.max = options.max; + /** + * If the return value should be an integer + * @type {Boolean} + */ + this.integer = options.integer; }; - Tone.extend(Tone.AudioToGain, Tone.SignalBase); + Tone.extend(Tone.CtrlRandom); /** - * clean up - * @returns {Tone.AudioToGain} this + * The defaults + * @const + * @type {Object} */ - Tone.AudioToGain.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._norm.dispose(); - this._norm = null; - return this; + Tone.CtrlRandom.defaults = { + 'min': 0, + 'max': 1, + 'integer': false }; - return Tone.AudioToGain; + /** + * Return a random value between min and max. + * @readOnly + * @memberOf Tone.CtrlRandom# + * @type {*} + * @name value + */ + Object.defineProperty(Tone.CtrlRandom.prototype, 'value', { + get: function () { + var min = this.toSeconds(this.min); + var max = this.toSeconds(this.max); + var rand = Math.random(); + var val = rand * min + (1 - rand) * max; + if (this.integer) { + val = Math.floor(val); + } + return val; + } + }); + return Tone.CtrlRandom; }); Module(function (Tone) { /** - * @class LFO stands for low frequency oscillator. Tone.LFO produces an output signal - * which can be attached to an AudioParam or Tone.Signal - * in order to modulate that parameter with an oscillator. The LFO can - * also be synced to the transport to start/stop and change when the tempo changes. + * @class Buffer loading and storage. Tone.Buffer is used internally by all + * classes that make requests for audio files such as Tone.Player, + * Tone.Sampler and Tone.Convolver. + *

+ * Aside from load callbacks from individual buffers, Tone.Buffer + * provides static methods which keep track of the loading progress + * of all of the buffers. These methods are Tone.Buffer.onload, Tone.Buffer.onprogress, + * and Tone.Buffer.onerror. * - * @constructor - * @extends {Tone.Oscillator} - * @param {Frequency|Object} [frequency] The frequency of the oscillation. Typically, LFOs will be - * in the frequency range of 0.1 to 10 hertz. - * @param {number=} min The minimum output value of the LFO. The LFO starts - * at it's minimum value. - * @param {number=} max The maximum value of the LFO. + * @constructor + * @extends {Tone} + * @param {AudioBuffer|string} url The url to load, or the audio buffer to set. + * @param {function=} onload A callback which is invoked after the buffer is loaded. + * It's recommended to use Tone.Buffer.onload instead + * since it will give you a callback when ALL buffers are loaded. * @example - * var lfo = new Tone.LFO("4n", 400, 4000); - * lfo.connect(filter.frequency); + * var buffer = new Tone.Buffer("path/to/sound.mp3", function(){ + * //the buffer is now available. + * var buff = buffer.get(); + * }); */ - Tone.LFO = function () { + Tone.Buffer = function () { var options = this.optionsObject(arguments, [ - 'frequency', - 'min', - 'max' - ], Tone.LFO.defaults); - /** - * The oscillator. - * @type {Tone.Oscillator} - * @private - */ - this.oscillator = new Tone.Oscillator({ - 'frequency': options.frequency, - 'type': options.type, - 'phase': options.phase + 90 - }); + 'url', + 'onload' + ], Tone.Buffer.defaults); /** - * the lfo's frequency - * @type {Frequency} - * @signal + * stores the loaded AudioBuffer + * @type {AudioBuffer} + * @private */ - this.frequency = this.oscillator.frequency; + this._buffer = null; /** - * The amplitude of the LFO, which controls the output range between - * the min and max output. For example if the min is -10 and the max - * is 10, setting the amplitude to 0.5 would make the LFO modulate - * between -5 and 5. - * @type {Number} - * @signal + * indicates if the buffer should be reversed or not + * @type {boolean} + * @private */ - this.amplitude = this.oscillator.volume; - this.amplitude.units = Tone.Type.NormalRange; - this.amplitude.value = options.amplitude; + this._reversed = options.reverse; /** - * @type {Tone.AudioToGain} - * @private + * The url of the buffer. undefined if it was + * constructed with a buffer + * @type {string} + * @readOnly */ - this._a2g = new Tone.AudioToGain(); + this.url = undefined; /** - * @type {Tone.Scale} - * @private + * Indicates if the buffer is loaded or not. + * @type {boolean} + * @readOnly */ - this._scaler = this.output = new Tone.Scale(options.min, options.max); + this.loaded = false; /** - * the units of the LFO (used for converting) - * @type {string} - * @private + * The callback to invoke when everything is loaded. + * @type {function} */ - this._units = Tone.Type.Default; - //connect it up - this.oscillator.chain(this._a2g, this._scaler); - this._readOnly([ - 'amplitude', - 'frequency', - 'oscillator' - ]); + this.onload = options.onload.bind(this, this); + if (options.url instanceof AudioBuffer || options.url instanceof Tone.Buffer) { + this.set(options.url); + this.onload(this); + } else if (this.isString(options.url)) { + this.url = options.url; + Tone.Buffer._addToQueue(options.url, this); + } }; - Tone.extend(Tone.LFO, Tone.Oscillator); + Tone.extend(Tone.Buffer); /** * the default parameters - * - * @static - * @const * @type {Object} */ - Tone.LFO.defaults = { - 'type': 'sine', - 'min': 0, - 'max': 1, - 'phase': 0, - 'frequency': '4n', - 'amplitude': 1 + Tone.Buffer.defaults = { + 'url': undefined, + 'onload': Tone.noOp, + 'reverse': false }; /** - * Start the LFO. - * @param {Time} [time=now] the time the LFO will start - * @returns {Tone.LFO} this + * Pass in an AudioBuffer or Tone.Buffer to set the value + * of this buffer. + * @param {AudioBuffer|Tone.Buffer} buffer the buffer + * @returns {Tone.Buffer} this */ - Tone.LFO.prototype.start = function (time) { - this.oscillator.start(time); + Tone.Buffer.prototype.set = function (buffer) { + if (buffer instanceof Tone.Buffer) { + this._buffer = buffer.get(); + } else { + this._buffer = buffer; + } + this.loaded = true; return this; }; /** - * Stop the LFO. - * @param {Time} [time=now] the time the LFO will stop - * @returns {Tone.LFO} this + * @return {AudioBuffer} The audio buffer stored in the object. */ - Tone.LFO.prototype.stop = function (time) { - this.oscillator.stop(time); - return this; + Tone.Buffer.prototype.get = function () { + return this._buffer; }; /** - * Sync the start/stop/pause to the transport - * and the frequency to the bpm of the transport - * - * @param {Time} [delay=0] the time to delay the start of the - * LFO from the start of the transport - * @returns {Tone.LFO} this - * @example - * lfo.frequency.value = "8n"; - * lfo.sync(); - * //the rate of the LFO will always be an eighth note, - * //even as the tempo changes + * Load url into the buffer. + * @param {String} url The url to load + * @param {Function=} callback The callback to invoke on load. + * don't need to set if `onload` is + * already set. + * @returns {Tone.Buffer} this */ - Tone.LFO.prototype.sync = function (delay) { - this.oscillator.sync(delay); - this.oscillator.syncFrequency(); + Tone.Buffer.prototype.load = function (url, callback) { + this.url = url; + this.onload = this.defaultArg(callback, this.onload); + Tone.Buffer._addToQueue(url, this); return this; }; /** - * unsync the LFO from transport control - * @returns {Tone.LFO} this + * dispose and disconnect + * @returns {Tone.Buffer} this */ - Tone.LFO.prototype.unsync = function () { - this.oscillator.unsync(); - this.oscillator.unsyncFrequency(); + Tone.Buffer.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + Tone.Buffer._removeFromQueue(this); + this._buffer = null; + this.onload = Tone.Buffer.defaults.onload; return this; }; /** - * The miniumum output of the LFO. - * @memberOf Tone.LFO# + * The duration of the buffer. + * @memberOf Tone.Buffer# * @type {number} - * @name min + * @name duration + * @readOnly */ - Object.defineProperty(Tone.LFO.prototype, 'min', { + Object.defineProperty(Tone.Buffer.prototype, 'duration', { get: function () { - return this._toUnits(this._scaler.min); - }, - set: function (min) { - min = this._fromUnits(min); - this._scaler.min = min; + if (this._buffer) { + return this._buffer.duration; + } else { + return 0; + } } }); /** - * The maximum output of the LFO. - * @memberOf Tone.LFO# - * @type {number} - * @name max + * Reverse the buffer. + * @private + * @return {Tone.Buffer} this */ - Object.defineProperty(Tone.LFO.prototype, 'max', { - get: function () { - return this._toUnits(this._scaler.max); - }, - set: function (max) { - max = this._fromUnits(max); - this._scaler.max = max; + Tone.Buffer.prototype._reverse = function () { + if (this.loaded) { + for (var i = 0; i < this._buffer.numberOfChannels; i++) { + Array.prototype.reverse.call(this._buffer.getChannelData(i)); + } } - }); + return this; + }; /** - * The type of the oscillator: sine, square, sawtooth, triangle. - * @memberOf Tone.LFO# - * @type {string} - * @name type + * Reverse the buffer. + * @memberOf Tone.Buffer# + * @type {boolean} + * @name reverse */ - Object.defineProperty(Tone.LFO.prototype, 'type', { + Object.defineProperty(Tone.Buffer.prototype, 'reverse', { get: function () { - return this.oscillator.type; + return this._reversed; }, - set: function (type) { - this.oscillator.type = type; + set: function (rev) { + if (this._reversed !== rev) { + this._reversed = rev; + this._reverse(); + } } }); + /////////////////////////////////////////////////////////////////////////// + // STATIC METHODS + /////////////////////////////////////////////////////////////////////////// + //statically inherits Emitter methods + Tone.Emitter.mixin(Tone.Buffer); /** - * The phase of the LFO. - * @memberOf Tone.LFO# - * @type {number} - * @name phase + * the static queue for all of the xhr requests + * @type {Array} + * @private */ - Object.defineProperty(Tone.LFO.prototype, 'phase', { - get: function () { - return this.oscillator.phase - 90; - }, - set: function (phase) { - this.oscillator.phase = phase + 90; - } - }); + Tone.Buffer._queue = []; /** - * The output units of the LFO. - * @memberOf Tone.LFO# - * @type {Tone.Type} - * @name units + * the array of current downloads + * @type {Array} + * @private */ - Object.defineProperty(Tone.LFO.prototype, 'units', { - get: function () { - return this._units; - }, - set: function (val) { - var currentMin = this.min; - var currentMax = this.max; - //convert the min and the max - this._units = val; - this.min = currentMin; - this.max = currentMax; + Tone.Buffer._currentDownloads = []; + /** + * the total number of downloads + * @type {number} + * @private + */ + Tone.Buffer._totalDownloads = 0; + /** + * the maximum number of simultaneous downloads + * @static + * @type {number} + */ + Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS = 6; + /** + * Adds a file to be loaded to the loading queue + * @param {string} url the url to load + * @param {function} callback the callback to invoke once it's loaded + * @private + */ + Tone.Buffer._addToQueue = function (url, buffer) { + Tone.Buffer._queue.push({ + url: url, + Buffer: buffer, + progress: 0, + xhr: null + }); + this._totalDownloads++; + Tone.Buffer._next(); + }; + /** + * Remove an object from the queue's (if it's still there) + * Abort the XHR if it's in progress + * @param {Tone.Buffer} buffer the buffer to remove + * @private + */ + Tone.Buffer._removeFromQueue = function (buffer) { + var i; + for (i = 0; i < Tone.Buffer._queue.length; i++) { + var q = Tone.Buffer._queue[i]; + if (q.Buffer === buffer) { + Tone.Buffer._queue.splice(i, 1); + } + } + for (i = 0; i < Tone.Buffer._currentDownloads.length; i++) { + var dl = Tone.Buffer._currentDownloads[i]; + if (dl.Buffer === buffer) { + Tone.Buffer._currentDownloads.splice(i, 1); + dl.xhr.abort(); + dl.xhr.onprogress = null; + dl.xhr.onload = null; + dl.xhr.onerror = null; + } } - }); + }; /** - * Connect the output of a ToneNode to an AudioParam, AudioNode, or Tone Node. - * will get the units from the connected node. - * @param {Tone | AudioParam | AudioNode} node - * @param {number} [outputNum=0] optionally which output to connect from - * @param {number} [inputNum=0] optionally which input to connect to - * @returns {Tone.LFO} this + * load the next buffer in the queue * @private */ - Tone.LFO.prototype.connect = function (node) { - if (node.constructor === Tone.Signal) { - this.convert = node.convert; - this.units = node.units; + Tone.Buffer._next = function () { + if (Tone.Buffer._queue.length > 0) { + if (Tone.Buffer._currentDownloads.length < Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS) { + var next = Tone.Buffer._queue.shift(); + Tone.Buffer._currentDownloads.push(next); + next.xhr = Tone.Buffer.load(next.url, function (buffer) { + //remove this one from the queue + var index = Tone.Buffer._currentDownloads.indexOf(next); + Tone.Buffer._currentDownloads.splice(index, 1); + next.Buffer.set(buffer); + if (next.Buffer._reversed) { + next.Buffer._reverse(); + } + next.Buffer.onload(next.Buffer); + Tone.Buffer._onprogress(); + Tone.Buffer._next(); + }); + next.xhr.onprogress = function (event) { + next.progress = event.loaded / event.total; + Tone.Buffer._onprogress(); + }; + next.xhr.onerror = function (e) { + Tone.Buffer.trigger('error', e); + }; + } + } else if (Tone.Buffer._currentDownloads.length === 0) { + Tone.Buffer.trigger('load'); + //reset the downloads + Tone.Buffer._totalDownloads = 0; } - Tone.Signal.prototype.connect.apply(this, arguments); - return this; }; /** - * private method borroed from Signal converts - * units from their destination value - * @function + * internal progress event handler * @private */ - Tone.LFO.prototype._fromUnits = Tone.Signal.prototype._fromUnits; + Tone.Buffer._onprogress = function () { + var curretDownloadsProgress = 0; + var currentDLLen = Tone.Buffer._currentDownloads.length; + var inprogress = 0; + if (currentDLLen > 0) { + for (var i = 0; i < currentDLLen; i++) { + var dl = Tone.Buffer._currentDownloads[i]; + curretDownloadsProgress += dl.progress; + } + inprogress = curretDownloadsProgress; + } + var currentDownloadProgress = currentDLLen - inprogress; + var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress; + Tone.Buffer.trigger('progress', completed / Tone.Buffer._totalDownloads); + }; /** - * private method borroed from Signal converts - * units to their destination value - * @function - * @private + * Makes an xhr reqest for the selected url then decodes + * the file as an audio buffer. Invokes + * the callback once the audio buffer loads. + * @param {string} url The url of the buffer to load. + * filetype support depends on the + * browser. + * @param {function} callback The function to invoke when the url is loaded. + * @returns {XMLHttpRequest} returns the XHR */ - Tone.LFO.prototype._toUnits = Tone.Signal.prototype._toUnits; + Tone.Buffer.load = function (url, callback) { + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + // decode asynchronously + request.onload = function () { + Tone.context.decodeAudioData(request.response, function (buff) { + if (!buff) { + throw new Error('could not decode audio data:' + url); + } + callback(buff); + }); + }; + //send the request + request.send(); + return request; + }; /** - * disconnect and dispose - * @returns {Tone.LFO} this + * @deprecated us on([event]) instead */ - Tone.LFO.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'amplitude', - 'frequency', - 'oscillator' - ]); - this.oscillator.dispose(); - this.oscillator = null; - this._scaler.dispose(); - this._scaler = null; - this._a2g.dispose(); - this._a2g = null; - this.frequency = null; - this.amplitude = null; - return this; - }; - return Tone.LFO; + 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); + } + }); + return Tone.Buffer; }); Module(function (Tone) { /** - * @class Tone.Limiter will limit the loudness of an incoming signal. - * It is composed of a Tone.Compressor with a fast attack - * and release. Limiters are commonly used to safeguard against - * signal clipping. Unlike a compressor, limiters do not provide - * smooth gain reduction and almost completely prevent - * additional gain above the threshold. + * buses are another way of routing audio * - * @extends {Tone} - * @constructor - * @param {number} threshold The theshold above which the limiting is applied. + * augments Tone.prototype to include send and recieve + */ + /** + * All of the routes + * + * @type {Object} + * @static + * @private + */ + var Buses = {}; + /** + * Send this signal to the channel name. + * @param {string} channelName A named channel to send the signal to. + * @param {Decibels} amount The amount of the source to send to the bus. + * @return {GainNode} The gain node which connects this node to the desired channel. + * Can be used to adjust the levels of the send. * @example - * var limiter = new Tone.Limiter(-6); + * source.send("reverb", -12); */ - Tone.Limiter = function (threshold) { - /** - * the compressor - * @private - * @type {Tone.Compressor} - */ - this._compressor = this.input = this.output = new Tone.Compressor({ - 'attack': 0.001, - 'decay': 0.001, - 'threshold': threshold - }); - /** - * The threshold of of the limiter - * @type {Decibel} - * @signal - */ - this.threshold = this._compressor.threshold; - this._readOnly('threshold'); + Tone.prototype.send = function (channelName, amount) { + if (!Buses.hasOwnProperty(channelName)) { + Buses[channelName] = this.context.createGain(); + } + var sendKnob = this.context.createGain(); + sendKnob.gain.value = this.dbToGain(this.defaultArg(amount, 1)); + this.output.chain(sendKnob, Buses[channelName]); + return sendKnob; }; - Tone.extend(Tone.Limiter); /** - * Clean up. - * @returns {Tone.Limiter} this + * Recieve the input from the desired channelName to the input + * + * @param {string} channelName A named channel to send the signal to. + * @param {AudioNode} [input] If no input is selected, the + * input of the current node is + * chosen. + * @returns {Tone} this + * @example + * reverbEffect.receive("reverb"); */ - Tone.Limiter.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._compressor.dispose(); - this._compressor = null; - this._writable('threshold'); - this.threshold = null; + Tone.prototype.receive = function (channelName, input) { + if (!Buses.hasOwnProperty(channelName)) { + Buses[channelName] = this.context.createGain(); + } + if (this.isUndef(input)) { + input = this.input; + } + Buses[channelName].connect(input); return this; }; - return Tone.Limiter; + return Tone; }); Module(function (Tone) { /** - * @class Tone.Lowpass is a lowpass feedback comb filter. It is similar to - * Tone.FeedbackCombFilter, but includes a lowpass filter. - * + * @class Wrapper around Web Audio's native [DelayNode](http://webaudio.github.io/web-audio-api/#the-delaynode-interface). * @extends {Tone} - * @constructor - * @param {Time|Object} [delayTime] The delay time of the comb filter - * @param {NormalRange=} resonance The resonance (feedback) of the comb filter - * @param {Frequency=} dampening The cutoff of the lowpass filter dampens the - * signal as it is fedback. + * @param {Time=} delayTime The delay applied to the incoming signal. + * @param {Time=} maxDelay The maximum delay time. */ - Tone.LowpassCombFilter = function () { - Tone.call(this); + Tone.Delay = function () { var options = this.optionsObject(arguments, [ 'delayTime', - 'resonance', - 'dampening' - ], Tone.LowpassCombFilter.defaults); + 'maxDelay' + ], Tone.Delay.defaults); /** - * the delay node + * The native delay node * @type {DelayNode} * @private */ - this._delay = this.input = this.context.createDelay(1); - /** - * The delayTime of the comb filter. - * @type {Time} - * @signal - */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); - /** - * the lowpass filter - * @type {BiquadFilterNode} - * @private - */ - this._lowpass = this.output = this.context.createBiquadFilter(); - this._lowpass.Q.value = 0; - this._lowpass.type = 'lowpass'; - /** - * The dampening control of the feedback - * @type {Frequency} - * @signal - */ - this.dampening = new Tone.Signal(this._lowpass.frequency, Tone.Type.Frequency); - this.dampening.value = options.dampening; - /** - * the feedback gain - * @type {GainNode} - * @private - */ - this._feedback = this.context.createGain(); + this._delayNode = this.input = this.output = this.context.createDelay(this.toSeconds(options.maxDelay)); /** - * The amount of feedback of the delayed signal. - * @type {NormalRange} + * The amount of time the incoming signal is + * delayed. + * @type {Tone.Param} * @signal */ - this.resonance = new Tone.Signal(options.resonance, Tone.Type.NormalRange); - //connections - this._delay.chain(this._lowpass, this._feedback, this._delay); - this.delayTime.connect(this._delay.delayTime); - this.resonance.connect(this._feedback.gain); - this.dampening.connect(this._lowpass.frequency); - this._readOnly([ - 'dampening', - 'resonance', - 'delayTime' - ]); + this.delayTime = new Tone.Param({ + 'param': this._delayNode.delayTime, + 'units': Tone.Type.Time, + 'value': options.delayTime + }); + this._readOnly('delayTime'); }; - Tone.extend(Tone.LowpassCombFilter); + Tone.extend(Tone.Delay); /** - * the default parameters - * @static + * The defaults * @const - * @type {Object} + * @type {Object} */ - Tone.LowpassCombFilter.defaults = { - 'delayTime': 0.1, - 'resonance': 0.5, - 'dampening': 3000 + Tone.Delay.defaults = { + 'maxDelay': 1, + 'delayTime': 0 }; /** - * Clean up. - * @returns {Tone.LowpassCombFilter} this + * Clean up. + * @return {Tone.Delay} this */ - Tone.LowpassCombFilter.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'dampening', - 'resonance', - 'delayTime' - ]); - this.dampening.dispose(); - this.dampening = null; - this.resonance.dispose(); - this.resonance = null; - this._delay.disconnect(); - this._delay = null; - this._lowpass.disconnect(); - this._lowpass = null; - this._feedback.disconnect(); - this._feedback = null; - this.delayTime.dispose(); + 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.LowpassCombFilter; + return Tone.Delay; }); Module(function (Tone) { /** - * @class Tone.Merge brings two signals into the left and right - * channels of a single stereo channel. + * @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 - * var merge = new Tone.Merge().toMaster(); - * //routing a sine tone in the left channel - * //and noise in the right channel - * var osc = new Tone.Oscillator().connect(merge.left); - * var noise = new Tone.Noise().connect(merge.right); - * //starting our oscillators - * noise.start(); - * osc.start(); + * //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.Merge = function () { - Tone.call(this, 2, 0); + Tone.Master = function () { + Tone.call(this); /** - * The left input channel. - * Alias for input[0] - * @type {GainNode} + * the unmuted volume + * @type {number} + * @private */ - this.left = this.input[0] = this.context.createGain(); + this._unmutedVolume = 1; /** - * The right input channel. - * Alias for input[1]. - * @type {GainNode} + * if the master is muted + * @type {boolean} + * @private */ - this.right = this.input[1] = this.context.createGain(); + this._muted = false; /** - * the merger node for the two channels - * @type {ChannelMergerNode} + * The private volume node + * @type {Tone.Volume} * @private */ - this._merger = this.output = this.context.createChannelMerger(2); + 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.left.connect(this._merger, 0, 0); - this.right.connect(this._merger, 0, 1); + this.input.chain(this.output, this.context.destination); }; - Tone.extend(Tone.Merge); + Tone.extend(Tone.Master); /** - * Clean up. - * @returns {Tone.Merge} this + * @type {Object} + * @const */ - Tone.Merge.prototype.dispose = function () { + 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._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; + } + }); + /** + * 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.left.disconnect(); - this.left = null; - this.right.disconnect(); - this.right = null; - this._merger.disconnect(); - this._merger = null; + 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; }; - return Tone.Merge; + /** + * Also augment AudioNode's prototype to include toMaster + * as a convenience + * @returns {AudioNode} this + */ + AudioNode.prototype.toMaster = function () { + this.connect(Tone.Master); + return this; + }; + var MasterConstructor = Tone.Master; + /** + * initialize the module and listen for new audio contexts + */ + Tone._initAudioContext(function () { + //a single master output + if (!Tone.prototype.isUndef(Tone.Master)) { + Tone.Master = new MasterConstructor(); + } else { + MasterConstructor.prototype.dispose.call(Tone.Master); + MasterConstructor.call(Tone.Master); + } + }); + return Tone.Master; }); Module(function (Tone) { /** - * @class 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). + * @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 - * @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 - * @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(); + * @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.Meter = function (channels, smoothing, clipMemory) { - //extends Unit - Tone.call(this); - /** - * The channel count - * @type {number} - * @private - */ - this._channels = this.defaultArg(channels, 1); - /** - * the smoothing value - * @type {number} - * @private - */ - this._smoothing = this.defaultArg(smoothing, 0.8); - /** - * the amount of time a clip is remember for. - * @type {number} - * @private - */ - this._clipMemory = this.defaultArg(clipMemory, 0.5) * 1000; - /** - * the rms for each of the channels - * @private - * @type {Array} - */ - this._volume = new Array(this._channels); - /** - * the raw values for each of the channels - * @private - * @type {Array} + 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._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 + this.value = value; + /** + * the channel name or number + * + * @type {string|number} * @private - * @type {number} */ - this._lastClip = 0; - /** + this._channel = channel; + /** + * an internal reference to the id of the timeline + * callback which is set. + * + * @type {number} * @private - * @type {ScriptProcessorNode} */ - this._jsNode = this.context.createScriptProcessor(this.bufferSize, this._channels, 1); - this._jsNode.onaudioprocess = this._onprocess.bind(this); - //so it doesn't get garbage collected - this._jsNode.noGC(); - //signal just passes - this.input.connect(this.output); - this.input.connect(this._jsNode); + this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time); }; - Tone.extend(Tone.Meter); /** - * called on each processing frame + * invoked by the timeline * @private - * @param {AudioProcessingEvent} event + * @param {number} time the time at which the note should play */ - Tone.Meter.prototype._onprocess = function (event) { - var bufferSize = this._jsNode.bufferSize; - var smoothing = this._smoothing; - for (var channel = 0; channel < this._channels; channel++) { - var input = event.inputBuffer.getChannelData(channel); - var sum = 0; - var total = 0; - var x; - var clipped = false; - for (var i = 0; i < bufferSize; i++) { - x = input[i]; - if (!clipped && x > 0.95) { - clipped = true; - this._lastClip = Date.now(); - } - total += x; - sum += x * x; - } - var average = total / bufferSize; - var rms = Math.sqrt(sum / bufferSize); - this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing); - this._values[channel] = average; - } + Tone.Note.prototype._trigger = function (time) { + //invoke the callback + channelCallbacks(this._channel, time, this.value); }; /** - * Get the rms of the signal. - * @param {number} [channel=0] which channel - * @return {number} the value + * clean up + * @returns {Tone.Note} this */ - 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; - } + Tone.Note.prototype.dispose = function () { + Tone.Transport.clearTimeline(this._timelineID); + this.value = null; + return this; }; /** - * Get the raw value of the signal. - * @param {number=} channel - * @return {number} + * @private + * @static + * @type {Object} */ - Tone.Meter.prototype.getValue = function (channel) { - channel = this.defaultArg(channel, 0); - return this._values[channel]; - }; + var NoteChannels = {}; /** - * Get the volume of the signal in dB - * @param {number=} channel - * @return {Decibels} + * invoke all of the callbacks on a specific channel + * @private */ - Tone.Meter.prototype.getDb = function (channel) { - return this.gainToDb(this.getLevel(channel)); - }; + 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); + } + } + } + } /** - * @returns {boolean} if the audio has clipped. The value resets - * based on the clipMemory defined. + * 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.Meter.prototype.isClipped = function () { - return Date.now() - this._lastClip < this._clipMemory; + Tone.Note.route = function (channel, callback) { + if (NoteChannels.hasOwnProperty(channel)) { + NoteChannels[channel].push(callback); + } else { + NoteChannels[channel] = [callback]; + } }; /** - * Clean up. - * @returns {Tone.Meter} this + * Remove a previously routed callback from a channel. + * @static + * @param {string|number} channel The channel to unroute note events from + * @param {function(*)} callback Callback which was registered to the channel. */ - Tone.Meter.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._jsNode.disconnect(); - this._jsNode.onaudioprocess = null; - this._volume = null; - this._values = null; - return this; + Tone.Note.unroute = function (channel, callback) { + if (NoteChannels.hasOwnProperty(channel)) { + var channelCallback = NoteChannels[channel]; + var index = channelCallback.indexOf(callback); + if (index !== -1) { + NoteChannels[channel].splice(index, 1); + } + } }; - return Tone.Meter; - }); - Module(function (Tone) { - /** - * @class Tone.Split splits an incoming signal into left and right channels. - * - * @constructor - * @extends {Tone} + * Parses a score and registers all of the notes along the timeline. + *

+ * Scores are a JSON object with instruments at the top level + * and an array of time and values. The value of a note can be 0 or more + * parameters. + *

+ * The only requirement for the score format is that the time is the first (or only) + * value in the array. All other values are optional and will be passed into the callback + * function registered using `Note.route(channelName, callback)`. + *

+ * To convert MIDI files to score notation, take a look at utils/MidiToScore.js + * * @example - * var split = new Tone.Split(); - * stereoSignal.connect(split); - */ - Tone.Split = function () { - Tone.call(this, 0, 2); - /** - * @type {ChannelSplitterNode} - * @private - */ - this._splitter = this.input = this.context.createChannelSplitter(2); - /** - * Left channel output. - * Alias for output[0] - * @type {GainNode} - */ - this.left = this.output[0] = this.context.createGain(); - /** - * Right channel output. - * Alias for output[1] - * @type {GainNode} - */ - this.right = this.output[1] = this.context.createGain(); - //connections - this._splitter.connect(this.left, 0, 0); - this._splitter.connect(this.right, 1, 0); - }; - Tone.extend(Tone.Split); - /** - * Clean up. - * @returns {Tone.Split} this + * //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.Split.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._splitter.disconnect(); - this.left.disconnect(); - this.right.disconnect(); - this.left = null; - this.right = null; - this._splitter = null; - return this; + 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.Split; + return Tone.Note; }); 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).

- * - * Mid = (Left+Right)/sqrt(2); // obtain mid-signal from left and right
- * Side = (Left-Right)/sqrt(2); // obtain side-signal from left and righ
- *
+ * @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. * - * @extends {Tone} * @constructor + * @extends {Tone} + * @param {NormalRange|Object} [wet] The starting wet value. */ - Tone.MidSideSplit = function () { - Tone.call(this, 0, 2); + Tone.Effect = function () { + Tone.call(this); + //get all of the defaults + var options = this.optionsObject(arguments, ['wet'], Tone.Effect.defaults); /** - * split the incoming signal into left and right channels - * @type {Tone.Split} + * the drywet knob to control the amount of effect + * @type {Tone.CrossFade} * @private */ - this._split = this.input = new Tone.Split(); + 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; /** - * The mid send. Connect to mid processing. Alias for - * output[0] - * @type {Tone.Expr} + * connect the effectSend to the input of hte effect + * @type {GainNode} + * @private */ - this.mid = this.output[0] = new Tone.Expr('($0 + $1) * $2'); + this.effectSend = this.context.createGain(); /** - * The side output. Connect to side processing. Alias for - * output[1] - * @type {Tone.Expr} + * connect the output of the effect to the effectReturn + * @type {GainNode} + * @private */ - this.side = this.output[1] = new Tone.Expr('($0 - $1) * $2'); - this._split.connect(this.mid, 0, 0); - this._split.connect(this.mid, 1, 1); - this._split.connect(this.side, 0, 0); - this._split.connect(this.side, 1, 1); - sqrtTwo.connect(this.mid, 0, 2); - sqrtTwo.connect(this.side, 0, 2); + 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.MidSideSplit); + Tone.extend(Tone.Effect); /** - * a constant signal equal to 1 / sqrt(2) - * @type {Number} - * @signal - * @private * @static + * @type {Object} */ - var sqrtTwo = null; - Tone._initAudioContext(function () { - sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); - }); + Tone.Effect.defaults = { 'wet': 1 }; /** - * clean up - * @returns {Tone.MidSideSplit} this + * chains the effect in between the effectSend and effectReturn + * @param {Tone} effect + * @private + * @returns {Tone.Effect} this */ - Tone.MidSideSplit.prototype.dispose = function () { + Tone.Effect.prototype.connectEffect = function (effect) { + this.effectSend.chain(effect, this.effectReturn); + return this; + }; + /** + * Clean up. + * @returns {Tone.Effect} this + */ + Tone.Effect.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this.mid.dispose(); - this.mid = null; - this.side.dispose(); - this.side = null; - this._split.dispose(); - this._split = null; + 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.MidSideSplit; + return Tone.Effect; }); Module(function (Tone) { /** - * @class Mid/Side processing separates the the 'mid' signal - * (which comes out of both the left and the right channel) - * and the 'side' (which only comes out of the the side channels). - * MidSideMerge merges the mid and side signal after they've been seperated - * by Tone.MidSideSplit.

- * - * Left = (Mid+Side)/sqrt(2); // obtain left signal from mid and side
- * Right = (Mid-Side)/sqrt(2); // obtain right signal from mid and side
- *
+ * @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. * - * @extends {Tone.StereoEffect} * @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 + * @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(); */ - Tone.MidSideMerge = function () { - Tone.call(this, 2, 0); + Tone.AutoFilter = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'baseFrequency', + 'octaves' + ], Tone.AutoFilter.defaults); + Tone.Effect.call(this, options); /** - * The mid signal input. Alias for - * input[0] - * @type {GainNode} + * the lfo which drives the filter cutoff + * @type {Tone.LFO} + * @private */ - this.mid = this.input[0] = this.context.createGain(); + this._lfo = new Tone.LFO({ + 'frequency': options.frequency, + 'amplitude': options.depth + }); /** - * recombine the mid/side into Left - * @type {Tone.Expr} - * @private + * The range of the filter modulating between the min and max frequency. + * 0 = no modulation. 1 = full modulation. + * @type {NormalRange} + * @signal */ - this._left = new Tone.Expr('($0 + $1) * $2'); + this.depth = this._lfo.amplitude; /** - * The side signal input. Alias for - * input[1] - * @type {GainNode} + * How fast the filter modulates between min and max. + * @type {Frequency} + * @signal */ - this.side = this.input[1] = this.context.createGain(); + this.frequency = this._lfo.frequency; /** - * recombine the mid/side into Right - * @type {Tone.Expr} - * @private + * The filter node + * @type {Tone.Filter} */ - this._right = new Tone.Expr('($0 - $1) * $2'); + this.filter = new Tone.Filter(options.filter); /** - * Merge the left/right signal back into a stereo signal. - * @type {Tone.Merge} + * The octaves placeholder + * @type {Positive} * @private */ - this._merge = this.output = new Tone.Merge(); - this.mid.connect(this._left, 0, 0); - this.side.connect(this._left, 0, 1); - this.mid.connect(this._right, 0, 0); - this.side.connect(this._right, 0, 1); - this._left.connect(this._merge, 0, 0); - this._right.connect(this._merge, 0, 1); - sqrtTwo.connect(this._left, 0, 2); - sqrtTwo.connect(this._right, 0, 2); + 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; }; - Tone.extend(Tone.MidSideMerge); + //extend Effect + Tone.extend(Tone.AutoFilter, Tone.Effect); /** - * A constant signal equal to 1 / sqrt(2). - * @type {Number} - * @signal - * @private + * defaults * @static + * @type {Object} */ - var sqrtTwo = null; - Tone._initAudioContext(function () { - sqrtTwo = new Tone.Signal(1 / Math.sqrt(2)); + Tone.AutoFilter.defaults = { + 'frequency': 1, + 'type': 'sine', + 'depth': 1, + 'baseFrequency': 200, + 'octaves': 2.6, + 'filter': { + 'type': 'lowpass', + 'rolloff': -12, + 'Q': 1 + } + }; + /** + * Start the effect. + * @param {Time} [time=now] When the LFO will start. + * @returns {Tone.AutoFilter} this + */ + Tone.AutoFilter.prototype.start = function (time) { + this._lfo.start(time); + return this; + }; + /** + * Stop the effect. + * @param {Time} [time=now] When the LFO will stop. + * @returns {Tone.AutoFilter} this + */ + Tone.AutoFilter.prototype.stop = function (time) { + this._lfo.stop(time); + return this; + }; + /** + * Sync the filter to the transport. + * @param {Time} [delay=0] Delay time before starting the effect after the + * Transport has started. + * @returns {Tone.AutoFilter} this + */ + Tone.AutoFilter.prototype.sync = function (delay) { + this._lfo.sync(delay); + return this; + }; + /** + * Unsync the filter from the transport. + * @returns {Tone.AutoFilter} this + */ + Tone.AutoFilter.prototype.unsync = function () { + this._lfo.unsync(); + return this; + }; + /** + * Type of oscillator attached to the AutoFilter. + * 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; + } }); /** - * clean up - * @returns {Tone.MidSideMerge} this + * 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 */ - Tone.MidSideMerge.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this.mid.disconnect(); - this.mid = null; - this.side.disconnect(); - this.side = null; - this._left.dispose(); - this._left = null; - this._right.dispose(); - this._right = null; - this._merge.dispose(); - this._merge = null; + Tone.AutoFilter.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._lfo.dispose(); + this._lfo = null; + this.filter.dispose(); + this.filter = null; + this._writable([ + 'frequency', + 'depth' + ]); + this.frequency = null; + this.depth = null; return this; }; - return Tone.MidSideMerge; + return Tone.AutoFilter; }); Module(function (Tone) { /** - * @class Tone.MidSideCompressor applies two different compressors to the mid - * and side signal components. See Tone.MidSideSplit. + * @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/). * - * @extends {Tone} - * @param {Object} options The options that are passed to the mid and side - * compressors. * @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(); */ - Tone.MidSideCompressor = function (options) { - options = this.defaultArg(options, Tone.MidSideCompressor.defaults); + Tone.AutoPanner = function () { + var options = this.optionsObject(arguments, ['frequency'], Tone.AutoPanner.defaults); + Tone.Effect.call(this, options); /** - * the mid/side split - * @type {Tone.MidSideSplit} + * the lfo which drives the panning + * @type {Tone.LFO} * @private */ - this._midSideSplit = this.input = new Tone.MidSideSplit(); + this._lfo = new Tone.LFO({ + 'frequency': options.frequency, + 'amplitude': options.depth, + 'min': 0, + 'max': 1 + }); /** - * the mid/side recombination - * @type {Tone.MidSideMerge} - * @private + * The amount of panning between left and right. + * 0 = always center. 1 = full range between left and right. + * @type {NormalRange} + * @signal */ - this._midSideMerge = this.output = new Tone.MidSideMerge(); + this.depth = this._lfo.amplitude; /** - * The compressor applied to the mid signal - * @type {Tone.Compressor} + * the panner node which does the panning + * @type {Tone.Panner} + * @private */ - this.mid = new Tone.Compressor(options.mid); + this._panner = new Tone.Panner(); /** - * The compressor applied to the side signal - * @type {Tone.Compressor} + * How fast the panner modulates between left and right. + * @type {Frequency} + * @signal */ - this.side = new Tone.Compressor(options.side); - this._midSideSplit.mid.chain(this.mid, this._midSideMerge.mid); - this._midSideSplit.side.chain(this.side, this._midSideMerge.side); + this.frequency = this._lfo.frequency; + //connections + this.connectEffect(this._panner); + this._lfo.connect(this._panner.pan); + this.type = options.type; this._readOnly([ - 'mid', - 'side' + 'depth', + 'frequency' ]); }; - Tone.extend(Tone.MidSideCompressor); + //extend Effect + Tone.extend(Tone.AutoPanner, Tone.Effect); /** - * @const + * defaults * @static * @type {Object} */ - Tone.MidSideCompressor.defaults = { - 'mid': { - 'ratio': 3, - 'threshold': -24, - 'release': 0.03, - 'attack': 0.02, - 'knee': 16 - }, - 'side': { - 'ratio': 6, - 'threshold': -30, - 'release': 0.25, - 'attack': 0.03, - 'knee': 10 - } + Tone.AutoPanner.defaults = { + 'frequency': 1, + 'type': 'sine', + 'depth': 1 }; /** - * Clean up. - * @returns {Tone.MidSideCompressor} this + * Start the effect. + * @param {Time} [time=now] When the LFO will start. + * @returns {Tone.AutoPanner} this */ - Tone.MidSideCompressor.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'mid', - 'side' - ]); - this.mid.dispose(); - this.mid = null; - this.side.dispose(); - this.side = null; - this._midSideSplit.dispose(); - this._midSideSplit = null; - this._midSideMerge.dispose(); - this._midSideMerge = null; + Tone.AutoPanner.prototype.start = function (time) { + this._lfo.start(time); return this; }; - return Tone.MidSideCompressor; - }); - Module(function (Tone) { - /** - * @class Tone.Mono coerces the incoming mono or stereo signal into a mono signal - * where both left and right channels have the same value. This can be useful - * for [stereo imaging](https://en.wikipedia.org/wiki/Stereo_imaging). - * - * @extends {Tone} - * @constructor + * Stop the effect. + * @param {Time} [time=now] When the LFO will stop. + * @returns {Tone.AutoPanner} this */ - Tone.Mono = function () { - Tone.call(this, 1, 0); - /** - * merge the signal - * @type {Tone.Merge} - * @private - */ - this._merge = this.output = new Tone.Merge(); - this.input.connect(this._merge, 0, 0); - this.input.connect(this._merge, 0, 1); - this.input.gain.value = this.dbToGain(-10); + Tone.AutoPanner.prototype.stop = function (time) { + this._lfo.stop(time); + return this; }; - Tone.extend(Tone.Mono); + /** + * 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 + */ + Tone.AutoPanner.prototype.sync = function (delay) { + this._lfo.sync(delay); + return this; + }; + /** + * Unsync the panner from the transport + * @returns {Tone.AutoPanner} this + */ + Tone.AutoPanner.prototype.unsync = function () { + this._lfo.unsync(); + return this; + }; + /** + * Type of oscillator attached to the 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; + } + }); /** * clean up - * @returns {Tone.Mono} this + * @returns {Tone.AutoPanner} this */ - Tone.Mono.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._merge.dispose(); - this._merge = null; + 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.Mono; + return Tone.AutoPanner; }); Module(function (Tone) { /** - * @class A compressor with seperate controls over low/mid/high dynamics + * @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). * - * @extends {Tone} * @constructor - * @param {Object} options The low/mid/high compressor settings. + * @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 multiband = new Tone.MultibandCompressor({ - * "lowFrequency" : 200, - * "highFrequency" : 1300 - * "low" : { - * "threshold" : -12 - * } - * }) + * 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") */ - Tone.MultibandCompressor = function (options) { - options = this.defaultArg(arguments, Tone.MultibandCompressor.defaults); + Tone.AutoWah = 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); /** - * split the incoming signal into high/mid/low - * @type {Tone.MultibandSplit} + * scales the follower value to the frequency domain + * @type {Tone} * @private */ - this._splitter = this.input = new Tone.MultibandSplit({ - 'lowFrequency': options.lowFrequency, - 'highFrequency': options.highFrequency - }); + this._sweepRange = new Tone.ScaleExp(0, 1, 0.5); /** - * low/mid crossover frequency. - * @type {Frequency} - * @signal + * @type {number} + * @private */ - this.lowFrequency = this._splitter.lowFrequency; + this._baseFrequency = options.baseFrequency; /** - * mid/high crossover frequency. - * @type {Frequency} - * @signal + * @type {number} + * @private */ - this.highFrequency = this._splitter.highFrequency; + this._octaves = options.octaves; /** - * the output + * the input gain to adjust the sensitivity * @type {GainNode} * @private */ - this.output = this.context.createGain(); + this._inputBoost = this.context.createGain(); /** - * The compressor applied to the low frequencies. - * @type {Tone.Compressor} + * @type {BiquadFilterNode} + * @private */ - this.low = new Tone.Compressor(options.low); + this._bandpass = new Tone.Filter({ + 'rolloff': -48, + 'frequency': 0, + 'Q': options.Q + }); /** - * The compressor applied to the mid frequencies. - * @type {Tone.Compressor} + * @type {Tone.Filter} + * @private */ - this.mid = new Tone.Compressor(options.mid); + this._peaking = new Tone.Filter(0, 'peaking'); + this._peaking.gain.value = options.gain; /** - * The compressor applied to the high frequencies. - * @type {Tone.Compressor} + * The gain of the filter. + * @type {Number} + * @signal */ - this.high = new Tone.Compressor(options.high); - //connect the compressor - this._splitter.low.chain(this.low, this.output); - this._splitter.mid.chain(this.mid, this.output); - this._splitter.high.chain(this.high, this.output); + this.gain = this._peaking.gain; + /** + * The quality of the filter. + * @type {Positive} + * @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([ - 'high', - 'mid', - 'low', - 'highFrequency', - 'lowFrequency' + 'gain', + 'Q' ]); }; - Tone.extend(Tone.MultibandCompressor); + Tone.extend(Tone.AutoWah, Tone.Effect); /** - * @const * @static * @type {Object} */ - Tone.MultibandCompressor.defaults = { - 'low': Tone.Compressor.defaults, - 'mid': Tone.Compressor.defaults, - 'high': Tone.Compressor.defaults, - 'lowFrequency': 250, - 'highFrequency': 2000 + Tone.AutoWah.defaults = { + 'baseFrequency': 100, + 'octaves': 6, + 'sensitivity': 0, + 'Q': 2, + 'gain': 2, + 'follower': { + 'attack': 0.3, + 'release': 0.5 + } }; /** - * clean up - * @returns {Tone.MultibandCompressor} this + * The number of octaves that the filter will sweep above the + * baseFrequency. + * @memberOf Tone.AutoWah# + * @type {Number} + * @name octaves */ - Tone.MultibandCompressor.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._splitter.dispose(); + 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 + */ + Tone.AutoWah.prototype._setSweepRange = function () { + this._sweepRange.min = this._baseFrequency; + this._sweepRange.max = Math.min(this._baseFrequency * Math.pow(2, this._octaves), this.context.sampleRate / 2); + }; + /** + * Clean up. + * @returns {Tone.AutoWah} this + */ + Tone.AutoWah.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this.follower.dispose(); + this.follower = null; + this._sweepRange.dispose(); + this._sweepRange = null; + this._bandpass.dispose(); + this._bandpass = null; + this._peaking.dispose(); + this._peaking = null; + this._inputBoost.disconnect(); + this._inputBoost = null; this._writable([ - 'high', - 'mid', - 'low', - 'highFrequency', - 'lowFrequency' + 'gain', + 'Q' ]); - 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.gain = null; + this.Q = null; return this; }; - return Tone.MultibandCompressor; + return Tone.AutoWah; }); Module(function (Tone) { /** - * @class Maps a NormalRange [0, 1] to an AudioRange [-1, 1]. - * See also Tone.AudioToGain. + * @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). * - * @extends {Tone.SignalBase} * @constructor + * @extends {Tone.Effect} + * @param {Number} bits The number of bits to downsample the signal. Nominal range + * of 1 to 8. * @example - * var g2a = new Tone.GainToAudio(); + * //initialize crusher and route a synth through it + * var crusher = new Tone.BitCrusher(4).toMaster(); + * var synth = new Tone.MonoSynth().connect(crusher); */ - Tone.GainToAudio = function () { + 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); /** - * @type {WaveShaperNode} + * Subtract the input signal and the modulus of the input signal + * @type {Tone.Subtract} * @private */ - this._norm = this.input = this.output = new Tone.WaveShaper(function (x) { - return Math.abs(x) * 2 - 1; - }); + this._subtract = new Tone.Subtract(); + /** + * The mod function + * @type {Tone.Modulo} + * @private + */ + this._modulo = new Tone.Modulo(invStepSize); + /** + * keeps track of the bits + * @type {number} + * @private + */ + this._bits = options.bits; + //connect it up + this.effectSend.fan(this._subtract, this._modulo); + this._modulo.connect(this._subtract, 0, 1); + this._subtract.connect(this.effectReturn); }; - Tone.extend(Tone.GainToAudio, Tone.SignalBase); + Tone.extend(Tone.BitCrusher, Tone.Effect); + /** + * the default values + * @static + * @type {Object} + */ + Tone.BitCrusher.defaults = { 'bits': 4 }; + /** + * The bit depth of the effect. Nominal range of 1-8. + * @memberOf Tone.BitCrusher# + * @type {number} + * @name bits + */ + Object.defineProperty(Tone.BitCrusher.prototype, 'bits', { + get: function () { + return this._bits; + }, + set: function (bits) { + this._bits = bits; + var invStepSize = 1 / Math.pow(2, bits - 1); + this._modulo.value = invStepSize; + } + }); /** - * clean up - * @returns {Tone.GainToAudio} this + * Clean up. + * @returns {Tone.BitCrusher} this */ - Tone.GainToAudio.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._norm.dispose(); - this._norm = null; + 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.GainToAudio; + return Tone.BitCrusher; }); Module(function (Tone) { /** - * @class Tone.Panner is an equal power Left/Right Panner and does not - * support 3D. Panner uses the StereoPannerNode when available. - * + * @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 - * @extends {Tone} - * @param {NormalRange} [initialPan=0.5] The initail panner value (defaults to 0.5 = center) + * @param {Positive|Object} [order] The order of the chebyshev polynomial. Normal range between 1-100. * @example - * //pan the input signal hard right. - * var panner = new Tone.Panner(1); + * //create a new cheby + * var cheby = new Tone.Chebyshev(50); + * //create a monosynth connected to our cheby + * synth = new Tone.MonoSynth().connect(cheby); */ - Tone.Panner = function (initialPan) { - Tone.call(this); + Tone.Chebyshev = function () { + var options = this.optionsObject(arguments, ['order'], Tone.Chebyshev.defaults); + Tone.Effect.call(this, options); /** - * indicates if the panner is using the new StereoPannerNode internally - * @type {boolean} + * @type {WaveShaperNode} * @private */ - this._hasStereoPanner = this.isFunction(this.context.createStereoPanner); - if (this._hasStereoPanner) { - /** - * the panner node - * @type {StereoPannerNode} - * @private - */ - this._panner = this.input = this.output = this.context.createStereoPanner(); - /** - * The pan control. 0 = 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._shaper = new Tone.WaveShaper(4096); + /** + * holds onto the order of the filter + * @type {number} + * @private + */ + this._order = options.order; + this.connectEffect(this._shaper); + this.order = options.order; + this.oversample = options.oversample; + }; + Tone.extend(Tone.Chebyshev, Tone.Effect); + /** + * @static + * @const + * @type {Object} + */ + Tone.Chebyshev.defaults = { + 'order': 1, + 'oversample': 'none' + }; + /** + * get the coefficient for that degree + * @param {number} x the x value + * @param {number} degree + * @param {Object} memo memoize the computed value. + * this speeds up computation greatly. + * @return {number} the coefficient + * @private + */ + Tone.Chebyshev.prototype._getCoefficient = function (x, degree, memo) { + if (memo.hasOwnProperty(degree)) { + return memo[degree]; + } else if (degree === 0) { + memo[degree] = 0; + } else if (degree === 1) { + memo[degree] = x; } else { - /** - * the dry/wet knob - * @type {Tone.CrossFade} - * @private - */ - this._crossFade = new Tone.CrossFade(); - /** - * @type {Tone.Merge} - * @private - */ - this._merger = this.output = new Tone.Merge(); - /** - * @type {Tone.Split} - * @private - */ - this._splitter = this.input = new Tone.Split(); - /** - * The pan control. 0 = hard left, 1 = hard right. - * @type {NormalRange} - * @signal - */ - this.pan = this._crossFade.fade; - //CONNECTIONS: - //left channel is a, right channel is b - this._splitter.connect(this._crossFade, 0, 0); - this._splitter.connect(this._crossFade, 1, 1); - //merge it back together - this._crossFade.a.connect(this._merger, 0, 0); - this._crossFade.b.connect(this._merger, 0, 1); + memo[degree] = 2 * x * this._getCoefficient(x, degree - 1, memo) - this._getCoefficient(x, degree - 2, memo); } - //initial value - this.pan.value = this.defaultArg(initialPan, 0.5); - this._readOnly('pan'); + return memo[degree]; }; - Tone.extend(Tone.Panner); /** - * Clean up. - * @returns {Tone.Panner} this + * 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 */ - Tone.Panner.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable('pan'); - if (this._hasStereoPanner) { - this._panner.disconnect(); - this._panner = null; - this.pan.dispose(); - this.pan = null; - this._scalePan.dispose(); - this._scalePan = null; - } else { - this._crossFade.dispose(); - this._crossFade = null; - this._splitter.dispose(); - this._splitter = null; - this._merger.dispose(); - this._merger = null; - this.pan = null; + Object.defineProperty(Tone.Chebyshev.prototype, 'order', { + get: function () { + return this._order; + }, + set: function (order) { + this._order = order; + var curve = new Array(4096); + var len = curve.length; + for (var i = 0; i < len; ++i) { + var x = i * 2 / len - 1; + if (x === 0) { + //should output 0 when input is 0 + curve[i] = 0; + } else { + curve[i] = this._getCoefficient(x, order, {}); + } + } + this._shaper.curve = curve; + } + }); + /** + * The oversampling of the effect. Can either be "none", "2x" or "4x". + * @memberOf Tone.Chebyshev# + * @type {string} + * @name oversample + */ + Object.defineProperty(Tone.Chebyshev.prototype, 'oversample', { + get: function () { + return this._shaper.oversample; + }, + set: function (oversampling) { + this._shaper.oversample = oversampling; } + }); + /** + * Clean up. + * @returns {Tone.Chebyshev} this + */ + Tone.Chebyshev.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; return this; }; - return Tone.Panner; + return Tone.Chebyshev; }); Module(function (Tone) { /** - * @class Tone.Volume is a simple volume node, useful for creating a volume fader. + * @class Base class for Stereo effects. Provides effectSendL/R and effectReturnL/R. * - * @extends {Tone} - * @constructor - * @param {Decibels} [volume=0] the initial volume - * @example - * var vol = new Tone.Volume(-12); - * instrument.chain(vol, Tone.Master); + * @constructor + * @extends {Tone.Effect} */ - Tone.Volume = function (volume) { + 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 output node - * @type {GainNode} - * @private + * the effect return LEFT + * @type {GainNode} + * @private */ - this.output = this.input = this.context.createGain(); + this.effectReturnL = this._merge.left; /** - * The volume control in decibels. - * @type {Decibels} - * @signal + * the effect return RIGHT + * @type {GainNode} + * @private */ - this.volume = new Tone.Signal(this.output.gain, Tone.Type.Decibels); - this.volume.value = this.defaultArg(volume, 0); - this._readOnly('volume'); + 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.Volume); + Tone.extend(Tone.StereoEffect, Tone.Effect); /** - * clean up - * @returns {Tone.Volume} this + * Clean up. + * @returns {Tone.StereoEffect} this */ - Tone.Volume.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose = function () { Tone.prototype.dispose.call(this); - this._writable('volume'); - this.volume.dispose(); - this.volume = null; + 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.Volume; + return Tone.StereoEffect; }); Module(function (Tone) { /** - * @class Tone.PanVol is a Tone.Panner and Tone.Volume in one. + * @class Tone.FeedbackEffect provides a loop between an + * audio source and its own output. This is a base-class + * for feedback effects. * - * @extends {Tone} * @constructor - * @param {NormalRange} pan the initial pan - * @param {number} volume The output volume. - * @example - * //pan the incoming signal left and drop the volume - * var panVol = new Tone.PanVol(0.25, -12); + * @extends {Tone.Effect} + * @param {NormalRange|Object} [feedback] The initial feedback value. */ - Tone.PanVol = function (pan, volume) { - /** - * The panning node - * @type {Tone.Panner} - * @private - */ - this._panner = this.input = new Tone.Panner(pan); + Tone.FeedbackEffect = function () { + var options = this.optionsObject(arguments, ['feedback']); + options = this.defaultArg(options, Tone.FeedbackEffect.defaults); + Tone.Effect.call(this, options); /** - * The L/R panning control. + * The amount of signal which is fed back into the effect input. * @type {NormalRange} * @signal */ - this.pan = this._panner.pan; - /** - * The volume object. - * @type {Tone.Volume} - * @signal - * @private - */ - this._volume = this.output = new Tone.Volume(volume); + this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); /** - * The volume control in decibels. - * @type {Decibels} - * @signal + * the gain which controls the feedback + * @type {GainNode} + * @private */ - this.volume = this._volume.volume; - //connections - this._panner.connect(this._volume); - this._readOnly([ - 'pan', - 'volume' - ]); + this._feedbackGain = this.context.createGain(); + //the feedback loop + this.effectReturn.chain(this._feedbackGain, this.effectSend); + this.feedback.connect(this._feedbackGain.gain); + this._readOnly(['feedback']); }; - Tone.extend(Tone.PanVol); + Tone.extend(Tone.FeedbackEffect, Tone.Effect); /** - * clean up - * @returns {Tone.PanVol} this + * @static + * @type {Object} */ - Tone.PanVol.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable([ - 'pan', - 'volume' - ]); - this._panner.dispose(); - this._panner = null; - this._volume.dispose(); - this._volume = null; - this.pan = null; - this.volume = null; + Tone.FeedbackEffect.defaults = { 'feedback': 0.125 }; + /** + * Clean up. + * @returns {Tone.FeedbackEffect} this + */ + Tone.FeedbackEffect.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._writable(['feedback']); + this.feedback.dispose(); + this.feedback = null; + this._feedbackGain.disconnect(); + this._feedbackGain = null; return this; }; - return Tone.PanVol; + return Tone.FeedbackEffect; }); Module(function (Tone) { /** - * @class Tone.ScaledEnvelop is an envelope which can be scaled - * to any range. It's useful for applying an envelope - * to a frequency or any other non-NormalRange signal - * parameter. + * @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. * - * @extends {Tone.Envelope} - * @constructor - * @param {Time|Object} [attack] the attack time in seconds - * @param {Time} [decay] the decay time in seconds - * @param {number} [sustain] a percentage (0-1) of the full amplitude - * @param {Time} [release] the release time in seconds - * @example - * var scaledEnv = new Tone.ScaledEnvelope({ - * "attack" : 0.2, - * "min" : 200, - * "max" : 2000 - * }); - * scaledEnv.connect(oscillator.frequency); + * @constructor + * @extends {Tone.FeedbackEffect} */ - Tone.ScaledEnvelope = function () { - //get all of the defaults - var options = this.optionsObject(arguments, [ - 'attack', - 'decay', - 'sustain', - 'release' - ], Tone.Envelope.defaults); - Tone.Envelope.call(this, options); - options = this.defaultArg(options, Tone.ScaledEnvelope.defaults); - /** - * scale the incoming signal by an exponent - * @type {Tone.Pow} + 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._exp = this.output = new Tone.Pow(options.exponent); + this._feedbackLR = this.context.createGain(); /** - * scale the signal to the desired range - * @type {Tone.Multiply} + * the right side feeback + * @type {GainNode} * @private */ - this._scale = this.output = new Tone.Scale(options.min, options.max); - this._sig.chain(this._exp, this._scale); - }; - Tone.extend(Tone.ScaledEnvelope, Tone.Envelope); - /** - * the default parameters - * @static - */ - Tone.ScaledEnvelope.defaults = { - 'min': 0, - 'max': 1, - 'exponent': 1 + 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); /** - * The envelope's min output value. This is the value which it - * starts at. - * @memberOf Tone.ScaledEnvelope# - * @type {number} - * @name min - */ - Object.defineProperty(Tone.ScaledEnvelope.prototype, 'min', { - get: function () { - return this._scale.min; - }, - set: function (min) { - this._scale.min = min; - } - }); - /** - * The envelope's max output value. In other words, the value - * at the peak of the attack portion of the envelope. - * @memberOf Tone.ScaledEnvelope# - * @type {number} - * @name max - */ - Object.defineProperty(Tone.ScaledEnvelope.prototype, 'max', { - get: function () { - return this._scale.max; - }, - set: function (max) { - this._scale.max = max; - } - }); - /** - * The envelope's exponent value. - * @memberOf Tone.ScaledEnvelope# - * @type {number} - * @name exponent - */ - Object.defineProperty(Tone.ScaledEnvelope.prototype, 'exponent', { - get: function () { - return this._exp.value; - }, - set: function (exp) { - this._exp.value = exp; - } - }); - /** - * clean up - * @returns {Tone.ScaledEnvelope} this - */ - Tone.ScaledEnvelope.prototype.dispose = function () { - Tone.Envelope.prototype.dispose.call(this); - this._scale.dispose(); - this._scale = null; - this._exp.dispose(); - this._exp = null; + * clean up + * @returns {Tone.StereoXFeedbackEffect} this + */ + Tone.StereoXFeedbackEffect.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); + this._writable(['feedback']); + this.feedback.dispose(); + this.feedback = null; + this._feedbackLR.disconnect(); + this._feedbackLR = null; + this._feedbackRL.disconnect(); + this._feedbackRL = null; return this; }; - return Tone.ScaledEnvelope; + return Tone.StereoXFeedbackEffect; }); Module(function (Tone) { /** - * @class Buffer loading and storage. Tone.Buffer is used internally by all - * classes that make requests for audio files such as Tone.Player, - * Tone.Sampler and Tone.Convolver. - *

- * Aside from load callbacks from individual buffers, Tone.Buffer - * provides static methods which keep track of the loading progress - * of all of the buffers. These methods are Tone.Buffer.onload, Tone.Buffer.onprogress, - * and Tone.Buffer.onerror. + * @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} - * @param {AudioBuffer|string} url The url to load, or the audio buffer to set. - * @param {function=} onload A callback which is invoked after the buffer is loaded. - * It's recommended to use Tone.Buffer.onload instead - * since it will give you a callback when ALL buffers are loaded. - * @example - * var buffer = new Tone.Buffer("path/to/sound.mp3", function(){ - * //the buffer is now available. - * var buff = buffer.get(); - * }); + * @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.Buffer = function () { + Tone.Chorus = function () { var options = this.optionsObject(arguments, [ - 'url', - 'onload' - ], Tone.Buffer.defaults); + 'frequency', + 'delayTime', + 'depth' + ], Tone.Chorus.defaults); + Tone.StereoXFeedbackEffect.call(this, options); /** - * stores the loaded AudioBuffer - * @type {AudioBuffer} + * the depth of the chorus + * @type {number} * @private */ - this._buffer = null; + this._depth = options.depth; /** - * indicates if the buffer should be reversed or not - * @type {boolean} + * the delayTime + * @type {number} * @private */ - this._reversed = options.reverse; + this._delayTime = options.delayTime / 1000; /** - * The url of the buffer. undefined if it was - * constructed with a buffer - * @type {string} - * @readOnly + * the lfo which controls the delayTime + * @type {Tone.LFO} + * @private */ - this.url = undefined; + this._lfoL = new Tone.LFO({ + 'frequency': options.frequency, + 'min': 0, + 'max': 1 + }); /** - * Indicates if the buffer is loaded or not. - * @type {boolean} - * @readOnly + * another LFO for the right side with a 180 degree phase diff + * @type {Tone.LFO} + * @private */ - this.loaded = false; + this._lfoR = new Tone.LFO({ + 'frequency': options.frequency, + 'min': 0, + 'max': 1, + 'phase': 180 + }); /** - * The callback to invoke when everything is loaded. - * @type {function} + * delay for left + * @type {DelayNode} + * @private */ - this.onload = options.onload.bind(this, this); - if (options.url instanceof AudioBuffer) { - this._buffer.set(options.url); - this.onload(this); - } else if (typeof options.url === 'string') { - this.url = options.url; - Tone.Buffer._addToQueue(options.url, this); - } - }; - Tone.extend(Tone.Buffer); - /** - * the default parameters - * @type {Object} - */ - Tone.Buffer.defaults = { - 'url': undefined, - 'onload': Tone.noOp, - 'reverse': false - }; - /** - * Pass in an AudioBuffer or Tone.Buffer to set the value - * of this buffer. - * @param {AudioBuffer|Tone.Buffer} buffer the buffer - * @returns {Tone.Buffer} this - */ - Tone.Buffer.prototype.set = function (buffer) { - if (buffer instanceof Tone.Buffer) { - this._buffer = buffer.get(); - } else { - this._buffer = buffer; - } - this.loaded = true; - return this; - }; - /** - * @return {AudioBuffer} The audio buffer stored in the object. - */ - Tone.Buffer.prototype.get = function () { - return this._buffer; - }; - /** - * Load url into the buffer. - * @param {String} url The url to load - * @param {Function=} callback The callback to invoke on load. - * don't need to set if `onload` is - * already set. - * @returns {Tone.Buffer} this - */ - Tone.Buffer.prototype.load = function (url, callback) { - this.url = url; - this.onload = this.defaultArg(callback, this.onload); - Tone.Buffer._addToQueue(url, this); - return this; - }; - /** - * dispose and disconnect - * @returns {Tone.Buffer} this - */ - Tone.Buffer.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - Tone.Buffer._removeFromQueue(this); - this._buffer = null; - this.onload = Tone.Buffer.defaults.onload; - return this; - }; - /** - * The duration of the buffer. - * @memberOf Tone.Buffer# - * @type {number} - * @name duration - * @readOnly - */ - Object.defineProperty(Tone.Buffer.prototype, 'duration', { - get: function () { - if (this._buffer) { - return this._buffer.duration; - } else { - return 0; - } - } - }); - /** - * Reverse the buffer. - * @private - * @return {Tone.Buffer} this - */ - Tone.Buffer.prototype._reverse = function () { - if (this.loaded) { - for (var i = 0; i < this._buffer.numberOfChannels; i++) { - Array.prototype.reverse.call(this._buffer.getChannelData(i)); - } - } - return this; + this._delayNodeL = this.context.createDelay(); + /** + * delay for right + * @type {DelayNode} + * @private + */ + this._delayNodeR = this.context.createDelay(); + /** + * The frequency of the LFO which modulates the delayTime. + * @type {Frequency} + * @signal + */ + 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.Chorus, Tone.StereoXFeedbackEffect); /** - * Reverse the buffer. - * @memberOf Tone.Buffer# - * @type {boolean} - * @name reverse - */ - Object.defineProperty(Tone.Buffer.prototype, 'reverse', { - get: function () { - return this._reversed; - }, - set: function (rev) { - if (this._reversed !== rev) { - this._reversed = rev; - this._reverse(); - } - } - }); - /////////////////////////////////////////////////////////////////////////// - // STATIC METHODS - /////////////////////////////////////////////////////////////////////////// - /** - * the static queue for all of the xhr requests - * @type {Array} - * @private - */ - Tone.Buffer._queue = []; - /** - * the array of current downloads - * @type {Array} - * @private - */ - Tone.Buffer._currentDownloads = []; - /** - * the total number of downloads - * @type {number} - * @private - */ - Tone.Buffer._totalDownloads = 0; - /** - * the maximum number of simultaneous downloads - * @static - * @type {number} - */ - Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS = 6; - /** - * Adds a file to be loaded to the loading queue - * @param {string} url the url to load - * @param {function} callback the callback to invoke once it's loaded - * @private + * @static + * @type {Object} */ - Tone.Buffer._addToQueue = function (url, buffer) { - Tone.Buffer._queue.push({ - url: url, - Buffer: buffer, - progress: 0, - xhr: null - }); - this._totalDownloads++; - Tone.Buffer._next(); + Tone.Chorus.defaults = { + 'frequency': 1.5, + 'delayTime': 3.5, + 'depth': 0.7, + 'feedback': 0.1, + 'type': 'sine', + 'spread': 180 }; /** - * Remove an object from the queue's (if it's still there) - * Abort the XHR if it's in progress - * @param {Tone.Buffer} buffer the buffer to remove - * @private + * The 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 */ - Tone.Buffer._removeFromQueue = function (buffer) { - var i; - for (i = 0; i < Tone.Buffer._queue.length; i++) { - var q = Tone.Buffer._queue[i]; - if (q.Buffer === buffer) { - Tone.Buffer._queue.splice(i, 1); - } - } - for (i = 0; i < Tone.Buffer._currentDownloads.length; i++) { - var dl = Tone.Buffer._currentDownloads[i]; - if (dl.Buffer === buffer) { - Tone.Buffer._currentDownloads.splice(i, 1); - dl.xhr.abort(); - dl.xhr.onprogress = null; - dl.xhr.onload = null; - dl.xhr.onerror = null; - } + Object.defineProperty(Tone.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; } - }; + }); /** - * load the next buffer in the queue - * @private + * 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 */ - Tone.Buffer._next = function () { - if (Tone.Buffer._queue.length > 0) { - if (Tone.Buffer._currentDownloads.length < Tone.Buffer.MAX_SIMULTANEOUS_DOWNLOADS) { - var next = Tone.Buffer._queue.shift(); - Tone.Buffer._currentDownloads.push(next); - next.xhr = Tone.Buffer.load(next.url, function (buffer) { - //remove this one from the queue - var index = Tone.Buffer._currentDownloads.indexOf(next); - Tone.Buffer._currentDownloads.splice(index, 1); - next.Buffer.set(buffer); - if (next.Buffer._reversed) { - next.Buffer._reverse(); - } - next.Buffer.onload(next.Buffer); - Tone.Buffer._onprogress(); - Tone.Buffer._next(); - }); - next.xhr.onprogress = function (event) { - next.progress = event.loaded / event.total; - Tone.Buffer._onprogress(); - }; - next.xhr.onerror = Tone.Buffer.onerror; - } - } else if (Tone.Buffer._currentDownloads.length === 0) { - Tone.Buffer.onload(); - //reset the downloads - Tone.Buffer._totalDownloads = 0; + Object.defineProperty(Tone.Chorus.prototype, 'delayTime', { + get: function () { + return this._delayTime * 1000; + }, + set: function (delayTime) { + this._delayTime = delayTime / 1000; + this.depth = this._depth; } - }; + }); /** - * internal progress event handler - * @private + * The oscillator type of the LFO. + * @memberOf Tone.Chorus# + * @type {string} + * @name type */ - Tone.Buffer._onprogress = function () { - var curretDownloadsProgress = 0; - var currentDLLen = Tone.Buffer._currentDownloads.length; - var inprogress = 0; - if (currentDLLen > 0) { - for (var i = 0; i < currentDLLen; i++) { - var dl = Tone.Buffer._currentDownloads[i]; - curretDownloadsProgress += dl.progress; - } - inprogress = curretDownloadsProgress; + Object.defineProperty(Tone.Chorus.prototype, 'type', { + get: function () { + return this._lfoL.type; + }, + set: function (type) { + this._lfoL.type = type; + this._lfoR.type = type; } - var currentDownloadProgress = currentDLLen - inprogress; - var completed = Tone.Buffer._totalDownloads - Tone.Buffer._queue.length - currentDownloadProgress; - Tone.Buffer.onprogress(completed / Tone.Buffer._totalDownloads); - }; + }); + /** + * 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 + */ + 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; + } + }); /** - * Makes an xhr reqest for the selected url then decodes - * the file as an audio buffer. Invokes - * the callback once the audio buffer loads. - * @param {string} url The url of the buffer to load. - * filetype support depends on the - * browser. - * @param {function} callback The function to invoke when the url is loaded. - * @returns {XMLHttpRequest} returns the XHR + * Clean up. + * @returns {Tone.Chorus} this */ - Tone.Buffer.load = function (url, callback) { - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.responseType = 'arraybuffer'; - // decode asynchronously - request.onload = function () { - Tone.context.decodeAudioData(request.response, function (buff) { - if (!buff) { - throw new Error('could not decode audio data:' + url); - } - callback(buff); - }); - }; - //send the request - request.send(); - return request; + Tone.Chorus.prototype.dispose = function () { + Tone.StereoXFeedbackEffect.prototype.dispose.call(this); + this._lfoL.dispose(); + this._lfoL = null; + this._lfoR.dispose(); + this._lfoR = null; + this._delayNodeL.disconnect(); + this._delayNodeL = null; + this._delayNodeR.disconnect(); + this._delayNodeR = null; + this._writable('frequency'); + this.frequency = null; + return this; }; + return Tone.Chorus; + }); + Module(function (Tone) { + /** - * Callback when all of the buffers in the queue have loaded - * @static - * @function + * @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 {string|Tone.Buffer|Object} [url] The URL of the impulse response or the Tone.Buffer + * contianing the impulse response. * @example - * //invoked when all of the queued samples are done loading + * //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(){ - * console.log("everything is loaded"); - * }; - */ - Tone.Buffer.onload = Tone.noOp; - /** - * Callback function is invoked with the progress of all of the loads in the queue. - * The value passed to the callback is between 0-1. - * @static - * @param {Number} percent The progress between 0 and 1. - * @function - * @example - * Tone.Buffer.onprogress = function(percent){ - * console.log("progress:" + (percent * 100).toFixed(1) + "%"); + * //testing out convolution with a noise burst + * var burst = new Tone.NoiseSynth().connect(convolver); + * burst.triggerAttackRelease("16n"); * }; */ - Tone.Buffer.onprogress = Tone.noOp; + Tone.Convolver = function () { + var options = this.optionsObject(arguments, ['url'], Tone.Convolver.defaults); + Tone.Effect.call(this, options); + /** + * convolver node + * @type {ConvolverNode} + * @private + */ + this._convolver = this.context.createConvolver(); + /** + * the convolution buffer + * @type {Tone.Buffer} + * @private + */ + this._buffer = new Tone.Buffer(options.url, function (buffer) { + this.buffer = buffer; + options.onload(); + }.bind(this)); + this.connectEffect(this._convolver); + }; + Tone.extend(Tone.Convolver, Tone.Effect); /** - * Callback if one of the buffers in the queue encounters an error. The error - * is passed in as the argument. * @static - * @param {Error} err - * @function - * @example - * Tone.Buffer.onerror = function(e){ - * console.log("there was an error while loading the buffers: "+e); - * } - */ - Tone.Buffer.onerror = Tone.noOp; - return Tone.Buffer; - }); - Module(function (Tone) { - - /** - * buses are another way of routing audio - * - * augments Tone.prototype to include send and recieve - */ - /** - * All of the routes - * - * @type {Object} - * @static - * @private - */ - var Buses = {}; - /** - * Send this signal to the channel name. - * @param {string} channelName A named channel to send the signal to. - * @param {Decibels} amount The amount of the source to send to the bus. - * @return {GainNode} The gain node which connects this node to the desired channel. - * Can be used to adjust the levels of the send. - * @example - * source.send("reverb", -12); + * @const + * @type {Object} */ - Tone.prototype.send = function (channelName, amount) { - if (!Buses.hasOwnProperty(channelName)) { - Buses[channelName] = this.context.createGain(); - } - var sendKnob = this.context.createGain(); - sendKnob.gain.value = this.dbToGain(this.defaultArg(amount, 1)); - this.output.chain(sendKnob, Buses[channelName]); - return sendKnob; + Tone.Convolver.defaults = { + 'url': '', + 'onload': Tone.noOp }; /** - * Recieve the input from the desired channelName to the input - * - * @param {string} channelName A named channel to send the signal to. - * @param {AudioNode} [input] If no input is selected, the - * input of the current node is - * chosen. - * @returns {Tone} this - * @example - * reverbEffect.receive("reverb"); + * The convolver's buffer + * @memberOf Tone.Convolver# + * @type {AudioBuffer} + * @name buffer */ - Tone.prototype.receive = function (channelName, input) { - if (!Buses.hasOwnProperty(channelName)) { - Buses[channelName] = this.context.createGain(); - } - if (this.isUndef(input)) { - input = this.input; + Object.defineProperty(Tone.Convolver.prototype, 'buffer', { + get: function () { + return this._buffer.get(); + }, + set: function (buffer) { + this._buffer.set(buffer); + this._convolver.buffer = this._buffer.get(); } - Buses[channelName].connect(input); + }); + /** + * 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; }; - return Tone; + /** + * Clean up. + * @returns {Tone.Convolver} this + */ + Tone.Convolver.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._convolver.disconnect(); + this._convolver = null; + this._buffer.dispose(); + this._buffer = null; + return this; + }; + return Tone.Convolver; }); Module(function (Tone) { /** - * @class A 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.Distortion is a simple distortion effect using Tone.WaveShaper. + * Algorithm from [a stackoverflow answer](http://stackoverflow.com/a/22313408). * + * @extends {Tone.Effect} * @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 + * @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"); */ - 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; + Tone.Distortion = function () { + var options = this.optionsObject(arguments, ['distortion'], Tone.Distortion.defaults); + Tone.Effect.call(this, options); /** - * the channel name or number - * - * @type {string|number} + * @type {Tone.WaveShaper} * @private */ - this._channel = channel; + this._shaper = new Tone.WaveShaper(4096); /** - * an internal reference to the id of the timeline - * callback which is set. - * - * @type {number} - * @private + * holds the distortion amount + * @type {number} + * @private */ - this._timelineID = Tone.Transport.setTimeline(this._trigger.bind(this), time); - }; - /** - * invoked by the timeline - * @private - * @param {number} time the time at which the note should play - */ - Tone.Note.prototype._trigger = function (time) { - //invoke the callback - channelCallbacks(this._channel, time, this.value); - }; - /** - * clean up - * @returns {Tone.Note} this - */ - Tone.Note.prototype.dispose = function () { - Tone.Tranport.clearTimeline(this._timelineID); - this.value = null; - return this; + this._distortion = options.distortion; + this.connectEffect(this._shaper); + this.distortion = options.distortion; + this.oversample = options.oversample; }; + Tone.extend(Tone.Distortion, Tone.Effect); /** - * @private * @static + * @const * @type {Object} */ - var NoteChannels = {}; + Tone.Distortion.defaults = { + 'distortion': 0.4, + 'oversample': 'none' + }; /** - * invoke all of the callbacks on a specific channel - * @private + * The amount of distortion. + * @memberOf Tone.Distortion# + * @type {NormalRange} + * @name distortion */ - 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)); + Object.defineProperty(Tone.Distortion.prototype, 'distortion', { + get: function () { + return this._distortion; + }, + set: function (amount) { + this._distortion = amount; + var k = amount * 100; + var deg = Math.PI / 180; + this._shaper.setMap(function (x) { + if (Math.abs(x) < 0.001) { + //should output 0 when input is 0 + return 0; } else { - callback(time, value); + return (3 + k) * x * 20 * deg / (Math.PI + k * Math.abs(x)); } - } - } - } - /** - * 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 + * The oversampling of the effect. Can either be "none", "2x" or "4x". + * @memberOf Tone.Distortion# + * @type {string} + * @name oversample */ - Tone.Note.parseScore = function (score) { - var notes = []; - for (var inst in score) { - var part = score[inst]; - if (inst === 'tempo') { - Tone.Transport.bpm.value = part; - } else if (inst === 'timeSignature') { - Tone.Transport.timeSignature = part[0] / (part[1] / 4); - } else if (Array.isArray(part)) { - for (var i = 0; i < part.length; i++) { - var noteDescription = part[i]; - var note; - if (Array.isArray(noteDescription)) { - var time = noteDescription[0]; - var value = noteDescription.slice(1); - note = new Tone.Note(inst, time, value); - } else { - note = new Tone.Note(inst, noteDescription); - } - notes.push(note); - } - } else { - throw new TypeError('score parts must be Arrays'); - } + Object.defineProperty(Tone.Distortion.prototype, 'oversample', { + get: function () { + return this._shaper.oversample; + }, + set: function (oversampling) { + this._shaper.oversample = oversampling; } - return notes; - }; - /////////////////////////////////////////////////////////////////////////// - // MUSIC NOTES - // - // Augments Tone.prototype to include note methods - /////////////////////////////////////////////////////////////////////////// - var noteToIndex = { - 'c': 0, - 'c#': 1, - 'db': 1, - 'd': 2, - 'd#': 3, - 'eb': 3, - 'e': 4, - 'f': 5, - 'f#': 6, - 'gb': 6, - 'g': 7, - 'g#': 8, - 'ab': 8, - 'a': 9, - 'a#': 10, - 'bb': 10, - 'b': 11 + }); + /** + * Clean up. + * @returns {Tone.Distortion} this + */ + Tone.Distortion.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._shaper.dispose(); + this._shaper = null; + return this; }; - var noteIndexToNote = [ - 'C', - 'C#', - 'D', - 'D#', - 'E', - 'F', - 'F#', - 'G', - 'G#', - 'A', - 'A#', - 'B' - ]; - var middleC = 261.6255653005986; + return Tone.Distortion; + }); + Module(function (Tone) { + /** - * Convert a note name to frequency. - * @param {string} note - * @return {number} + * @class Tone.FeedbackDelay is a DelayNode in which part of output + * signal is fed back into the delay. + * + * @constructor + * @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 freq = tone.noteToFrequency("A4"); //returns 440 + * 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.prototype.noteToFrequency = function (note) { - //break apart the note by frequency and octave - var parts = note.split(/(\d+)/); - if (parts.length === 3) { - var index = noteToIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - var noteNumber = index + parseInt(octave, 10) * 12; - return Math.pow(2, (noteNumber - 48) / 12) * middleC; - } else { - return 0; - } + Tone.FeedbackDelay = function () { + var options = this.optionsObject(arguments, [ + 'delayTime', + 'feedback' + ], Tone.FeedbackDelay.defaults); + Tone.FeedbackEffect.call(this, options); + /** + * The delayTime of the DelayNode. + * @type {Time} + * @signal + */ + this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + /** + * the delay node + * @type {DelayNode} + * @private + */ + this._delayNode = this.context.createDelay(4); + // connect it up + this.connectEffect(this._delayNode); + this.delayTime.connect(this._delayNode.delayTime); + this._readOnly(['delayTime']); }; + Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); /** - * Test if a string is in note format: i.e. "C4". - * @param {string|number} note The note to test - * @return {boolean} true if it's in the form of a note - * @method isNotation - * @lends Tone.prototype.isNote - * @function + * The default values. + * @const + * @static + * @type {Object} */ - Tone.prototype.isNote = function () { - var noteFormat = new RegExp(/[a-g]{1}([b#]{1}|[b#]{0})[0-9]+$/i); - return function (note) { - if (typeof note === 'string') { - note = note.toLowerCase(); - } - return noteFormat.test(note); - }; - }(); + Tone.FeedbackDelay.defaults = { 'delayTime': 0.25 }; /** - * A pointer to the previous toFrequency method - * @private - * @function + * clean up + * @returns {Tone.FeedbackDelay} this */ - Tone.prototype._overwrittenToFrequency = Tone.prototype.toFrequency; + Tone.FeedbackDelay.prototype.dispose = function () { + Tone.FeedbackEffect.prototype.dispose.call(this); + this.delayTime.dispose(); + this._delayNode.disconnect(); + this._delayNode = null; + this._writable(['delayTime']); + this.delayTime = null; + return this; + }; + return Tone.FeedbackDelay; + }); + Module(function (Tone) { + /** - * A method which accepts frequencies in the form - * of notes (`"C#4"`), frequencies as strings ("49hz"), frequency numbers, - * or Time and converts them to their frequency as a number in hertz. - * @param {Frequency} note the note name or notation - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} the frequency as a number + * an array of comb filter delay values from Freeverb implementation + * @static + * @private + * @type {Array} */ - Tone.prototype.toFrequency = function (note, now) { - if (this.isNote(note)) { - note = this.noteToFrequency(note); - } - return this._overwrittenToFrequency(note, now); - }; + var combFilterTunings = [ + 1557 / 44100, + 1617 / 44100, + 1491 / 44100, + 1422 / 44100, + 1277 / 44100, + 1356 / 44100, + 1188 / 44100, + 1116 / 44100 + ]; /** - * Convert a frequency to a note name (i.e. A4, C#5). - * @param {number} freq - * @return {string} + * an array of allpass filter frequency values from Freeverb implementation + * @private + * @static + * @type {Array} */ - Tone.prototype.frequencyToNote = function (freq) { - var log = Math.log(freq / middleC) / Math.LN2; - var noteNumber = Math.round(12 * log) + 48; - var octave = Math.floor(noteNumber / 12); - var noteName = noteIndexToNote[noteNumber % 12]; - return noteName + octave.toString(); - }; + var allpassFilterFrequencies = [ + 225, + 556, + 441, + 341 + ]; /** - * Convert an interval (in semitones) to a frequency ratio. + * @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). * - * @param {Interval} interval the number of semitones above the base note - * @return {number} the frequency ratio + * @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 - * tone.intervalToFrequencyRatio(0); // returns 1 - * tone.intervalToFrequencyRatio(12); // returns 2 + * var freeverb = new Tone.Freeverb().toMaster(); + * freeverb.dampening.value = 1000; + * //routing synth through the reverb + * var synth = new Tone.AMSynth().connect(freeverb); */ - Tone.prototype.intervalToFrequencyRatio = function (interval) { - return Math.pow(2, interval / 12); + Tone.Freeverb = function () { + var options = this.optionsObject(arguments, [ + 'roomSize', + 'dampening' + ], Tone.Freeverb.defaults); + Tone.StereoEffect.call(this, options); + /** + * The roomSize value between. A larger roomSize + * will result in a longer decay. + * @type {NormalRange} + * @signal + */ + this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); + /** + * The amount of dampening of the reverberant signal. + * @type {Frequency} + * @signal + */ + this.dampening = new Tone.Signal(options.dampening, Tone.Type.Frequency); + /** + * the comb filters + * @type {Array} + * @private + */ + this._combFilters = []; + /** + * the allpass filters on the left + * @type {Array} + * @private + */ + this._allpassFiltersL = []; + /** + * the allpass filters on the right + * @type {Array} + * @private + */ + this._allpassFiltersR = []; + //make the allpass filters on teh right + for (var l = 0; l < allpassFilterFrequencies.length; l++) { + var allpassL = this.context.createBiquadFilter(); + allpassL.type = 'allpass'; + allpassL.frequency.value = allpassFilterFrequencies[l]; + this._allpassFiltersL.push(allpassL); + } + //make the allpass filters on the left + for (var r = 0; r < allpassFilterFrequencies.length; r++) { + var allpassR = this.context.createBiquadFilter(); + allpassR.type = 'allpass'; + allpassR.frequency.value = allpassFilterFrequencies[r]; + this._allpassFiltersR.push(allpassR); + } + //make the comb filters + for (var c = 0; c < combFilterTunings.length; c++) { + var lfpf = new Tone.LowpassCombFilter(combFilterTunings[c]); + if (c < combFilterTunings.length / 2) { + this.effectSendL.chain(lfpf, this._allpassFiltersL[0]); + } else { + this.effectSendR.chain(lfpf, this._allpassFiltersR[0]); + } + this.roomSize.connect(lfpf.resonance); + this.dampening.connect(lfpf.dampening); + this._combFilters.push(lfpf); + } + //chain the allpass filters togetehr + this.connectSeries.apply(this, this._allpassFiltersL); + this.connectSeries.apply(this, this._allpassFiltersR); + this._allpassFiltersL[this._allpassFiltersL.length - 1].connect(this.effectReturnL); + this._allpassFiltersR[this._allpassFiltersR.length - 1].connect(this.effectReturnR); + this._readOnly([ + 'roomSize', + 'dampening' + ]); }; + Tone.extend(Tone.Freeverb, Tone.StereoEffect); /** - * 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" + * @static + * @type {Object} */ - Tone.prototype.midiToNote = function (midiNumber) { - var octave = Math.floor(midiNumber / 12) - 2; - var note = midiNumber % 12; - return noteIndexToNote[note] + octave; + Tone.Freeverb.defaults = { + 'roomSize': 0.7, + 'dampening': 3000 }; /** - * 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 + * Clean up. + * @returns {Tone.Freeverb} this */ - Tone.prototype.noteToMidi = function (note) { - //break apart the note by frequency and octave - var parts = note.split(/(\d+)/); - if (parts.length === 3) { - var index = noteToIndex[parts[0].toLowerCase()]; - var octave = parts[1]; - return index + (parseInt(octave, 10) + 2) * 12; - } else { - return 0; + Tone.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.Note; + return Tone.Freeverb; }); 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/). + * 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. + * * + * @extends {Tone.Effect} * @constructor - * @extends {Tone.Oscillator} - * @param {Frequency} [frequency] The frequency of the oscillator - * @param {NormalRange} [width] The width of the pulse + * @param {NormalRange|Object} [roomSize] Coorelates to the decay time. * @example - * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start(); + * 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.PulseOscillator = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'width' - ], Tone.Oscillator.defaults); - Tone.Source.call(this, options); + Tone.JCReverb = function () { + var options = this.optionsObject(arguments, ['roomSize'], Tone.JCReverb.defaults); + Tone.StereoEffect.call(this, options); /** - * The width of the pulse. + * room size control values between [0,1] * @type {NormalRange} * @signal */ - this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); + this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); /** - * gate the width amount - * @type {GainNode} + * scale the room size + * @type {Tone.Scale} * @private */ - this._widthGate = this.context.createGain(); + this._scaleRoomSize = new Tone.Scale(-0.733, 0.197); /** - * the sawtooth oscillator - * @type {Tone.Oscillator} + * a series of allpass filters + * @type {Array} * @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; + this._allpassFilters = []; /** - * Threshold the signal to turn it into a square - * @type {Tone.WaveShaper} + * parallel feedback comb filters + * @type {Array} * @private */ - this._thresh = new Tone.WaveShaper(function (val) { - if (val < 0) { - return -1; + 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 { - return 1; + fbcf.connect(this.effectReturnR); } - }); - //connections - this._sawtooth.chain(this._thresh, this.output); - this.width.chain(this._widthGate, this._thresh); - this._readOnly([ - 'width', - 'frequency', - 'detune' - ]); - }; - Tone.extend(Tone.PulseOscillator, Tone.Oscillator); - /** - * The default parameters. - * @static - * @const - * @type {Object} - */ - Tone.PulseOscillator.defaults = { - 'frequency': 440, - 'detune': 0, - 'phase': 0, - 'width': 0.2 - }; - /** - * start the oscillator - * @param {Time} time - * @private - */ - Tone.PulseOscillator.prototype._start = function (time) { - time = this.toSeconds(time); - this._sawtooth.start(time); - this._widthGate.gain.setValueAtTime(1, time); - }; - /** - * stop the oscillator - * @param {Time} time - * @private - */ - Tone.PulseOscillator.prototype._stop = function (time) { - time = this.toSeconds(time); - this._sawtooth.stop(time); - //the width is still connected to the output. - //that needs to be stopped also - this._widthGate.gain.setValueAtTime(0, time); - }; - /** - * The phase of the oscillator in degrees. - * @memberOf Tone.PulseOscillator# - * @type {Degrees} - * @name phase - */ - Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', { - get: function () { - return this._sawtooth.phase; - }, - set: function (phase) { - this._sawtooth.phase = phase; + 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 type of the oscillator. Always returns "pulse". - * @readOnly - * @memberOf Tone.PulseOscillator# - * @type {string} - * @name type - */ - Object.defineProperty(Tone.PulseOscillator.prototype, 'type', { - get: function () { - return 'pulse'; - } - }); + * the default values + * @static + * @const + * @type {Object} + */ + Tone.JCReverb.defaults = { 'roomSize': 0.5 }; /** - * Clean up method. - * @return {Tone.PulseOscillator} this + * Clean up. + * @returns {Tone.JCReverb} this */ - Tone.PulseOscillator.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); - this._sawtooth.dispose(); - this._sawtooth = null; - this._writable([ - 'width', - 'frequency', - 'detune' - ]); - this.width.dispose(); - this.width = null; - this._widthGate.disconnect(); - this._widthGate = null; - this._widthGate = null; - this._thresh.disconnect(); - this._thresh = null; - this.frequency = null; - this.detune = null; + 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.PulseOscillator; + return Tone.JCReverb; }); 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 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.Oscillator} + * @extends {Tone.Effect} * @constructor - * @param {Frequency} frequency The starting frequency of the oscillator. - * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse. - * @example - * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start(); */ - Tone.PWMOscillator = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'modulationFrequency' - ], Tone.PWMOscillator.defaults); - Tone.Source.call(this, options); + Tone.MidSideEffect = function () { + Tone.Effect.apply(this, arguments); /** - * the pulse oscillator - * @type {Tone.PulseOscillator} + * The mid/side split + * @type {Tone.MidSideSplit} * @private */ - this._pulse = new Tone.PulseOscillator(options.modulationFrequency); - //change the pulse oscillator type - this._pulse._sawtooth.type = 'sine'; + this._midSideSplit = new Tone.MidSideSplit(); /** - * the modulator - * @type {Tone.Oscillator} + * The mid/side merge + * @type {Tone.MidSideMerge} * @private */ - this._modulator = new Tone.Oscillator({ - 'frequency': options.frequency, - 'detune': options.detune - }); + this._midSideMerge = new Tone.MidSideMerge(); /** - * Scale the oscillator so it doesn't go silent - * at the extreme values. - * @type {Tone.Multiply} + * The mid send. Connect to mid processing + * @type {Tone.Expr} * @private */ - this._scale = new Tone.Multiply(1.01); + this.midSend = this._midSideSplit.mid; /** - * The frequency control. - * @type {Frequency} - * @signal + * The side send. Connect to side processing + * @type {Tone.Expr} + * @private */ - this.frequency = this._modulator.frequency; + this.sideSend = this._midSideSplit.side; /** - * The detune of the oscillator. - * @type {Cents} - * @signal + * The mid return connection + * @type {GainNode} + * @private */ - this.detune = this._modulator.detune; + this.midReturn = this._midSideMerge.mid; /** - * The modulation rate of the oscillator. - * @type {Frequency} - * @signal + * The side return connection + * @type {GainNode} + * @private */ - this.modulationFrequency = this._pulse.frequency; - //connections - this._modulator.chain(this._scale, this._pulse.width); - this._pulse.connect(this.output); - this._readOnly([ - 'modulationFrequency', - 'frequency', - 'detune' - ]); - }; - Tone.extend(Tone.PWMOscillator, Tone.Oscillator); - /** - * default values - * @static - * @type {Object} - * @const - */ - Tone.PWMOscillator.defaults = { - 'frequency': 440, - 'detune': 0, - 'modulationFrequency': 0.4 - }; - /** - * start the oscillator - * @param {Time} [time=now] - * @private - */ - Tone.PWMOscillator.prototype._start = function (time) { - time = this.toSeconds(time); - this._modulator.start(time); - this._pulse.start(time); - }; - /** - * stop the oscillator - * @param {Time} time (optional) timing parameter - * @private - */ - Tone.PWMOscillator.prototype._stop = function (time) { - time = this.toSeconds(time); - this._modulator.stop(time); - this._pulse.stop(time); + this.sideReturn = this._midSideMerge.side; + //the connections + this.effectSend.connect(this._midSideSplit); + this._midSideMerge.connect(this.effectReturn); }; + Tone.extend(Tone.MidSideEffect, Tone.Effect); /** - * The type of the oscillator. Always returns "pwm". - * @readOnly - * @memberOf Tone.PWMOscillator# - * @type {string} - * @name type - */ - Object.defineProperty(Tone.PWMOscillator.prototype, 'type', { - get: function () { - return 'pwm'; - } - }); - /** - * The phase of the oscillator in degrees. - * @memberOf Tone.PWMOscillator# - * @type {number} - * @name phase - */ - Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', { - get: function () { - return this._modulator.phase; - }, - set: function (phase) { - this._modulator.phase = phase; - } - }); - /** - * Clean up. - * @return {Tone.PWMOscillator} this + * Clean up. + * @returns {Tone.MidSideEffect} this */ - Tone.PWMOscillator.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); - this._pulse.dispose(); - this._pulse = null; - this._scale.dispose(); - this._scale = null; - this._modulator.dispose(); - this._modulator = null; - this._writable([ - 'modulationFrequency', - 'frequency', - 'detune' - ]); - this.frequency = null; - this.detune = null; - this.modulationFrequency = null; + Tone.MidSideEffect.prototype.dispose = function () { + Tone.Effect.prototype.dispose.call(this); + this._midSideSplit.dispose(); + this._midSideSplit = null; + this._midSideMerge.dispose(); + this._midSideMerge = null; + this.midSend = null; + this.sideSend = null; + this.midReturn = null; + this.sideReturn = null; return this; }; - return Tone.PWMOscillator; + return Tone.MidSideEffect; }); 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. + * @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.Oscillator} - * @constructor - * @param {Frequency} frequency The initial frequency of the oscillator. - * @param {string} type The type of the oscillator. - * @example - * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); + * @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.OmniOscillator = function () { + Tone.Phaser = function () { + //set the defaults var options = this.optionsObject(arguments, [ 'frequency', - 'type' - ], Tone.OmniOscillator.defaults); - Tone.Source.call(this, options); + 'octaves', + 'baseFrequency' + ], Tone.Phaser.defaults); + Tone.StereoEffect.call(this, options); /** - * The frequency control. - * @type {Frequency} - * @signal + * the lfo which controls the frequency on the left side + * @type {Tone.LFO} + * @private */ - this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); + this._lfoL = new Tone.LFO(options.frequency, 0, 1); /** - * The detune control - * @type {Cents} + * 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; + /** + * The quality factor of the filters + * @type {Positive} * @signal */ - this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); + this.Q = new Tone.Signal(options.Q, Tone.Type.Positive); /** - * the type of the oscillator source - * @type {string} + * the array of filters for the left side + * @type {Array} * @private */ - this._sourceType = undefined; + this._filtersL = this._makeFilters(options.stages, this._lfoL, this.Q); /** - * the oscillator - * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} + * the array of filters for the left side + * @type {Array} * @private */ - this._oscillator = null; - //set the oscillator - this.type = options.type; + 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', - 'detune' + 'Q' ]); }; - Tone.extend(Tone.OmniOscillator, Tone.Oscillator); + Tone.extend(Tone.Phaser, Tone.StereoEffect); /** - * default values + * defaults * @static - * @type {Object} - * @const - */ - Tone.OmniOscillator.defaults = { - 'frequency': 440, - 'detune': 0, - 'type': 'sine', - 'width': 0.4, - //only applies if the oscillator is set to "pulse", - 'modulationFrequency': 0.4 - }; - /** - * @enum {string} - * @private - */ - var OmniOscType = { - PulseOscillator: 'PulseOscillator', - PWMOscillator: 'PWMOscillator', - Oscillator: 'Oscillator' - }; - /** - * start the oscillator - * @param {Time} [time=now] the time to start the oscillator - * @private - */ - Tone.OmniOscillator.prototype._start = function (time) { - this._oscillator.start(time); - }; - /** - * start the oscillator - * @param {Time} [time=now] the time to start the oscillator - * @private + * @type {object} */ - Tone.OmniOscillator.prototype._stop = function (time) { - this._oscillator.stop(time); + Tone.Phaser.defaults = { + 'frequency': 0.5, + 'octaves': 3, + 'stages': 10, + 'Q': 10, + 'baseFrequency': 350 }; /** - * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse. - * @memberOf Tone.OmniOscillator# - * @type {string} - * @name type - */ - Object.defineProperty(Tone.OmniOscillator.prototype, 'type', { - get: function () { - return this._oscillator.type; - }, - set: function (type) { - if (type.indexOf('sine') === 0 || type.indexOf('square') === 0 || type.indexOf('triangle') === 0 || type.indexOf('sawtooth') === 0) { - if (this._sourceType !== OmniOscType.Oscillator) { - this._sourceType = OmniOscType.Oscillator; - this._createNewOscillator(Tone.Oscillator); - } - this._oscillator.type = type; - } else if (type === 'pwm') { - if (this._sourceType !== OmniOscType.PWMOscillator) { - this._sourceType = OmniOscType.PWMOscillator; - this._createNewOscillator(Tone.PWMOscillator); - } - } else if (type === 'pulse') { - if (this._sourceType !== OmniOscType.PulseOscillator) { - this._sourceType = OmniOscType.PulseOscillator; - this._createNewOscillator(Tone.PulseOscillator); - } - } else { - throw new TypeError('Tone.OmniOscillator does not support type ' + type); - } - } - }); - /** - * connect the oscillator to the frequency and detune signals + * @param {number} stages + * @returns {Array} the number of filters all connected together * @private */ - Tone.OmniOscillator.prototype._createNewOscillator = function (OscillatorConstructor) { - //short delay to avoid clicks on the change - var now = this.now() + this.bufferTime; - if (this._oscillator !== null) { - var oldOsc = this._oscillator; - oldOsc.stop(now); - oldOsc.onended = function () { - oldOsc.dispose(); - oldOsc = null; - }; - } - this._oscillator = new OscillatorConstructor(); - this.frequency.connect(this._oscillator.frequency); - this.detune.connect(this._oscillator.detune); - this._oscillator.connect(this.output); - if (this.state === Tone.State.Started) { - this._oscillator.start(now); + 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 phase of the oscillator in degrees. - * @memberOf Tone.OmniOscillator# - * @type {Degrees} - * @name phase + * The number of octaves the phase goes above + * the baseFrequency + * @memberOf Tone.Phaser# + * @type {Positive} + * @name octaves */ - Object.defineProperty(Tone.OmniOscillator.prototype, 'phase', { + Object.defineProperty(Tone.Phaser.prototype, 'octaves', { get: function () { - return this._oscillator.phase; + return this._octaves; }, - set: function (phase) { - this._oscillator.phase = phase; - } - }); - /** - * The width of the oscillator (only if the oscillator is set to pulse) - * @memberOf Tone.OmniOscillator# - * @type {NormalRange} - * @signal - * @name width - * @example - * var omniOsc = new Tone.OmniOscillator(440, "pulse"); - * //can access the width attribute only if type === "pulse" - * omniOsc.width.value = 0.2; - */ - Object.defineProperty(Tone.OmniOscillator.prototype, 'width', { - get: function () { - if (this._sourceType === OmniOscType.PulseOscillator) { - return this._oscillator.width; - } + set: function (octaves) { + this._octaves = octaves; + var max = this._baseFrequency * Math.pow(2, octaves); + this._lfoL.max = max; + this._lfoR.max = max; } }); /** - * The modulationFrequency Signal of the oscillator - * (only if the oscillator type is set to pwm). - * @memberOf Tone.OmniOscillator# - * @type {Frequency} - * @signal - * @name modulationFrequency - * @example - * var omniOsc = new Tone.OmniOscillator(440, "pwm"); - * //can access the modulationFrequency attribute only if type === "pwm" - * omniOsc.modulationFrequency.value = 0.2; + * The the base frequency of the filters. + * @memberOf Tone.Phaser# + * @type {number} + * @name baseFrequency */ - Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationFrequency', { + Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', { get: function () { - if (this._sourceType === OmniOscType.PWMOscillator) { - return this._oscillator.modulationFrequency; - } + return this._baseFrequency; + }, + set: function (freq) { + this._baseFrequency = freq; + this._lfoL.min = freq; + this._lfoR.min = freq; + this.octaves = this._octaves; } }); /** - * Clean up. - * @return {Tone.OmniOscillator} this + * clean up + * @returns {Tone.Phaser} this */ - Tone.OmniOscillator.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); + Tone.Phaser.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); this._writable([ 'frequency', - 'detune' + 'Q' ]); - this.detune.dispose(); - this.detune = null; - this.frequency.dispose(); + 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; - this._oscillator.dispose(); - this._oscillator = null; - this._sourceType = null; return this; }; - return Tone.OmniOscillator; + return Tone.Phaser; }); Module(function (Tone) { /** - * @class Base-class for all instruments - * - * @constructor - * @extends {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"); */ - Tone.Instrument = function (options) { - //get the defaults - options = this.defaultArg(options, Tone.Instrument.defaults); + Tone.PingPongDelay = function () { + var options = this.optionsObject(arguments, [ + 'delayTime', + 'feedback' + ], Tone.PingPongDelay.defaults); + Tone.StereoXFeedbackEffect.call(this, options); /** - * the output - * @type {GainNode} + * the delay node on the left side + * @type {DelayNode} * @private */ - this.output = this.context.createGain(); + this._leftDelay = this.context.createDelay(options.maxDelayTime); /** - * The volume of the instrument. - * @type {Decibels} - * @signal + * the delay node on the right side + * @type {DelayNode} + * @private */ - this.volume = new Tone.Signal({ - 'param': this.output.gain, - 'units': Tone.Type.Decibels, - 'value': options.volume - }); - this._readOnly(['volume']); - }; - Tone.extend(Tone.Instrument); - /** - * the default attributes - * @type {object} - */ - Tone.Instrument.defaults = { - /** the volume of the output in decibels */ - 'volume': 0 + this._rightDelay = this.context.createDelay(options.maxDelayTime); + /** + * the predelay on the right side + * @type {DelayNode} + * @private + */ + this._rightPreDelay = this.context.createDelay(options.maxDelayTime); + /** + * the delay time signal + * @type {Time} + * @signal + */ + this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + //connect it up + this.effectSendL.chain(this._leftDelay, this.effectReturnL); + this.effectSendR.chain(this._rightPreDelay, this._rightDelay, this.effectReturnR); + this.delayTime.fan(this._leftDelay.delayTime, this._rightDelay.delayTime, this._rightPreDelay.delayTime); + //rearranged the feedback to be after the rightPreDelay + this._feedbackLR.disconnect(); + this._feedbackLR.connect(this._rightDelay); + this._readOnly(['delayTime']); }; + Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect); /** - * @abstract - * @param {string|number} note the note to trigger - * @param {Time} [time=now] the time to trigger the ntoe - * @param {number} [velocity=1] the velocity to trigger the note - */ - Tone.Instrument.prototype.triggerAttack = Tone.noOp; - /** - * @abstract - * @param {Time} [time=now] when to trigger the release - */ - Tone.Instrument.prototype.triggerRelease = Tone.noOp; - /** - * Trigger the attack and then the release after the duration. - * @param {Frequency} note The note to trigger. - * @param {Time} duration How long the note should be held for before - * triggering the release. - * @param {Time} [time=now] When the note should be triggered. - * @param {NormalRange} [velocity=1] The velocity the note should be triggered at. - * @returns {Tone.Instrument} this - * @example - * //trigger "C4" for the duration of an 8th note - * synth.triggerAttackRelease("C4", "8n"); + * @static + * @type {Object} */ - Tone.Instrument.prototype.triggerAttackRelease = function (note, duration, time, velocity) { - time = this.toSeconds(time); - duration = this.toSeconds(duration); - this.triggerAttack(note, time, velocity); - this.triggerRelease(time + duration); - return this; + Tone.PingPongDelay.defaults = { + 'delayTime': 0.25, + 'maxDelayTime': 1 }; /** - * clean up - * @returns {Tone.Instrument} this + * Clean up. + * @returns {Tone.PingPongDelay} this */ - Tone.Instrument.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._writable(['volume']); - this.volume.dispose(); - this.volume = 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.Instrument; + return Tone.PingPongDelay; }); Module(function (Tone) { /** - * @class This is a base class for monophonic instruments. - * - * @constructor - * @abstract - * @extends {Tone.Instrument} + * @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.Monophonic = function (options) { - //get the defaults - options = this.defaultArg(options, Tone.Monophonic.defaults); - Tone.Instrument.call(this, options); + Tone.PitchShift = function () { + var options = this.optionsObject(arguments, ['pitch'], Tone.PitchShift.defaults); + Tone.FeedbackEffect.call(this, options); /** - * The glide time between notes. + * The pitch signal + * @type {Tone.Signal} + * @private + */ + this._frequency = new Tone.Signal(0); + /** + * Uses two DelayNodes to cover up the jump in + * the sawtooth wave. + * @type {DelayNode} + * @private + */ + this._delayA = new Tone.Delay(0, 1); + /** + * The first LFO. + * @type {Tone.LFO} + * @private + */ + this._lfoA = new Tone.LFO({ + 'min': 0, + 'max': 0.1, + 'type': 'sawtooth' + }).connect(this._delayA.delayTime); + /** + * The second DelayNode + * @type {DelayNode} + * @private + */ + this._delayB = new Tone.Delay(0, 1); + /** + * The first LFO. + * @type {Tone.LFO} + * @private + */ + 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.portamento = options.portamento; + 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.Monophonic, Tone.Instrument); + Tone.extend(Tone.PitchShift, Tone.FeedbackEffect); /** + * default values * @static - * @const * @type {Object} + * @const */ - Tone.Monophonic.defaults = { 'portamento': 0 }; - /** - * Trigger the attack of the note optionally with a given velocity. - * - * - * @param {Frequency} note The note to trigger. - * @param {Time} [time=now] When the note should start. - * @param {number} [velocity=1] velocity The velocity scaler - * determines how "loud" the note - * will be triggered. - * @returns {Tone.Monophonic} this - * @example - * synth.triggerAttack("C4"); - * @example - * //trigger the note a half second from now at half velocity - * synth.triggerAttack("C4", "+0.5", 0.5); - */ - Tone.Monophonic.prototype.triggerAttack = function (note, time, velocity) { - time = this.toSeconds(time); - this._triggerEnvelopeAttack(time, velocity); - this.setNote(note, time); - return this; - }; - /** - * Trigger the release portion of the envelope - * @param {Time} [time=now] If no time is given, the release happens immediatly - * @returns {Tone.Monophonic} this - * @example - * synth.triggerRelease(); - */ - Tone.Monophonic.prototype.triggerRelease = function (time) { - this._triggerEnvelopeRelease(time); - return this; - }; - /** - * override this method with the actual method - * @abstract - * @private - */ - Tone.Monophonic.prototype._triggerEnvelopeAttack = function () { + Tone.PitchShift.defaults = { + 'pitch': 0, + 'windowSize': 0.1, + 'delayTime': 0, + 'feedback': 0 }; /** - * override this method with the actual method - * @abstract - * @private + * 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 */ - Tone.Monophonic.prototype._triggerEnvelopeRelease = function () { - }; + 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); + } + }); /** - * Set the note at the given time. If no time is given, the note - * will set immediately. - * @param {Frequency} note The note to change to. - * @param {Time} [time=now] The time when the note should be set. - * @returns {Tone.Monophonic} this - * @example - * //change to F#6 in one quarter note from now. - * synth.setNote("F#6", "+4n"); + * 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 - * //change to Bb4 right now - * synth.setNote("Bb4"); + * pitchShift.windowSize = 0.1; */ - Tone.Monophonic.prototype.setNote = function (note, time) { - time = this.toSeconds(time); - if (this.portamento > 0) { - var currentNote = this.frequency.value; - this.frequency.setValueAtTime(currentNote, time); - var portTime = this.toSeconds(this.portamento); - this.frequency.exponentialRampToValueAtTime(note, time + portTime); - } else { - this.frequency.setValueAtTime(note, time); + 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. + * @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.Monophonic; + return Tone.PitchShift; }); Module(function (Tone) { /** - * @class Tone.MonoSynth is composed of one oscillator, one filter, and two envelopes. - * The amplitude of the Tone.Oscillator and the cutoff frequency of the - * Tone.Filter are controlled by Tone.Envelopes. - * - * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var synth = new Tone.MonoSynth({ - * "oscillator" : { - * "type" : "square" - * }, - * "envelope" : { - * "attack" : 0.1 - * } - * }).toMaster(); - * synth.triggerAttackRelease("C4", "8n"); + * @class Base class for stereo feedback effects where the effectReturn + * is fed back into the same channel. + * + * @constructor + * @extends {Tone.FeedbackEffect} */ - Tone.MonoSynth = function (options) { - //get the defaults - options = this.defaultArg(options, Tone.MonoSynth.defaults); - Tone.Monophonic.call(this, options); + Tone.StereoFeedbackEffect = function () { + var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); + Tone.StereoEffect.call(this, options); /** - * The oscillator. - * @type {Tone.OmniOscillator} + * controls the amount of feedback + * @type {NormalRange} + * @signal */ - this.oscillator = new Tone.OmniOscillator(options.oscillator); + this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); /** - * The frequency control. - * @type {Frequency} - * @signal + * the left side feeback + * @type {GainNode} + * @private */ - this.frequency = this.oscillator.frequency; + this._feedbackL = this.context.createGain(); /** - * The detune control. - * @type {Cents} + * the right side feeback + * @type {GainNode} + * @private + */ + this._feedbackR = this.context.createGain(); + //connect it up + this.effectReturnL.chain(this._feedbackL, this.effectSendL); + this.effectReturnR.chain(this._feedbackR, this.effectSendR); + this.feedback.fan(this._feedbackL.gain, this._feedbackR.gain); + this._readOnly(['feedback']); + }; + Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect); + /** + * clean up + * @returns {Tone.StereoFeedbackEffect} this + */ + Tone.StereoFeedbackEffect.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); + this._writable(['feedback']); + this.feedback.dispose(); + this.feedback = null; + this._feedbackL.disconnect(); + this._feedbackL = null; + this._feedbackR.disconnect(); + this._feedbackR = null; + return this; + }; + return Tone.StereoFeedbackEffect; + }); + Module(function (Tone) { + + /** + * @class Applies a width factor 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. + */ + 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.detune = this.oscillator.detune; + this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); /** - * The filter. - * @type {Tone.Filter} + * Mid multiplier + * @type {Tone.Expr} + * @private */ - this.filter = new Tone.Filter(options.filter); + this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))'); /** - * The filter envelope. - * @type {Tone.ScaledEnvelope} + * Side multiplier + * @type {Tone.Expr} + * @private */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + this._sideMult = new Tone.Expr('$0 * ($1 * $2)'); /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} + * constant output of 2 + * @type {Tone} + * @private */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - //connect the oscillators to the output - this.oscillator.chain(this.filter, this.envelope, this.output); - //start the oscillators - this.oscillator.start(); - //connect the filter envelope - this.filterEnvelope.connect(this.filter.frequency); - this._readOnly([ - 'oscillator', - 'frequency', - 'detune', - 'filter', - 'filterEnvelope', - 'envelope' - ]); + 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.MonoSynth, Tone.Monophonic); + Tone.extend(Tone.StereoWidener, Tone.MidSideEffect); /** - * @const + * the default values * @static * @type {Object} */ - Tone.MonoSynth.defaults = { - 'frequency': 'C4', - 'detune': 0, - 'oscillator': { 'type': 'square' }, - 'filter': { - 'Q': 6, - 'type': 'lowpass', - 'rolloff': -24 - }, - 'envelope': { - 'attack': 0.005, - 'decay': 0.1, - 'sustain': 0.9, - 'release': 1 - }, - 'filterEnvelope': { - 'attack': 0.06, - 'decay': 0.2, - 'sustain': 0.5, - 'release': 2, - 'min': 20, - 'max': 4000, - 'exponent': 2 - } - }; - /** - * start the attack portion of the envelope - * @param {Time} [time=now] the time the attack should start - * @param {NormalRange} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.MonoSynth} this - * @private - */ - Tone.MonoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the envelopes - this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); - return this; - }; - /** - * start the release portion of the envelope - * @param {Time} [time=now] the time the release should start - * @returns {Tone.MonoSynth} this - * @private - */ - Tone.MonoSynth.prototype._triggerEnvelopeRelease = function (time) { - this.envelope.triggerRelease(time); - this.filterEnvelope.triggerRelease(time); - return this; - }; + Tone.StereoWidener.defaults = { 'width': 0.5 }; /** - * clean up - * @returns {Tone.MonoSynth} this + * Clean up. + * @returns {Tone.StereoWidener} this */ - Tone.MonoSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'oscillator', - 'frequency', - 'detune', - 'filter', - 'filterEnvelope', - 'envelope' - ]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - this.filterEnvelope.dispose(); - this.filterEnvelope = null; - this.filter.dispose(); - this.filter = null; - this.frequency = null; - this.detune = null; + 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.MonoSynth; + return Tone.StereoWidener; }); 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.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 - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below + * @param {Frequency} [frequency] The rate of the effect. + * @param {NormalRange} [depth] The depth of the effect. * @example - * var synth = new Tone.AMSynth().toMaster(); - * synth.triggerAttackRelease("C4", "4n"); + * //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.AMSynth = function (options) { - options = this.defaultArg(options, Tone.AMSynth.defaults); - Tone.Monophonic.call(this, options); + Tone.Tremolo = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'depth' + ], Tone.Tremolo.defaults); + Tone.StereoEffect.call(this, options); /** - * The carrier voice. - * @type {Tone.MonoSynth} + * The tremelo LFO in the left channel + * @type {Tone.LFO} + * @private */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this._lfoL = new Tone.LFO({ + 'phase': options.spread, + 'min': 1, + 'max': 0 + }); /** - * The modulator voice. - * @type {Tone.MonoSynth} + * The tremelo LFO in the left channel + * @type {Tone.LFO} + * @private */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this._lfoR = new Tone.LFO({ + 'phase': options.spread, + 'min': 1, + 'max': 0 + }); /** - * The frequency. - * @type {Frequency} - * @signal + * Where the gain is multiplied + * @type {Tone.Gain} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + this._amplitudeL = new Tone.Gain(); /** - * 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; + * Where the gain is multiplied + * @type {Tone.Gain} + * @private */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; + this._amplitudeR = new Tone.Gain(); /** - * convert the -1,1 output to 0,1 - * @type {Tone.AudioToGain} - * @private + * The frequency of the tremolo. + * @type {Frequency} + * @signal */ - this._modulationScale = new Tone.AudioToGain(); + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * the node where the modulation happens - * @type {GainNode} - * @private + * 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._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.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange); this._readOnly([ - 'carrier', - 'modulator', 'frequency', - 'harmonicity' + '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.AMSynth, Tone.Monophonic); + Tone.extend(Tone.Tremolo, Tone.StereoEffect); /** * @static + * @const * @type {Object} */ - Tone.AMSynth.defaults = { - 'harmonicity': 3, - 'carrier': { - 'volume': -10, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0.01, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'min': 20000, - 'max': 20000 - }, - 'filter': { - 'Q': 6, - 'type': 'lowpass', - 'rolloff': -24 - } - }, - 'modulator': { - 'volume': -10, - 'oscillator': { 'type': 'square' }, - 'envelope': { - 'attack': 2, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 4, - 'decay': 0.2, - 'sustain': 0.5, - 'release': 0.5, - 'min': 20, - 'max': 1500 - }, - 'filter': { - 'Q': 6, - 'type': 'lowpass', - 'rolloff': -24 - } - } + Tone.Tremolo.defaults = { + 'frequency': 10, + 'type': 'sine', + 'depth': 0.5, + 'spread': 180 }; /** - * 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 + * Start the tremolo. + * @param {Time} [time=now] When the tremolo begins. + * @returns {Tone.Tremolo} this */ - Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { - //the port glide - time = this.toSeconds(time); - //the envelopes - this.carrier.envelope.triggerAttack(time, velocity); - this.modulator.envelope.triggerAttack(time); - this.carrier.filterEnvelope.triggerAttack(time); - this.modulator.filterEnvelope.triggerAttack(time); + Tone.Tremolo.prototype.start = function (time) { + this._lfoL.start(time); + this._lfoR.start(time); return this; }; /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @private - * @returns {Tone.AMSynth} this + * Stop the tremolo. + * @param {Time} [time=now] When the tremolo stops. + * @returns {Tone.Tremolo} this */ - Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + 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.Tremolo.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.Tremolo# + * @type {Degrees} + * @name spread + */ + Object.defineProperty(Tone.Tremolo.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.AMSynth} this + * @returns {Tone.Tremolo} this */ - Tone.AMSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); + Tone.Tremolo.prototype.dispose = function () { + Tone.StereoEffect.prototype.dispose.call(this); this._writable([ - 'carrier', - 'modulator', 'frequency', - 'harmonicity' + 'depth' ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); + 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.harmonicity.dispose(); - this.harmonicity = null; - this._modulationScale.dispose(); - this._modulationScale = null; - this._modulationNode.disconnect(); - this._modulationNode = null; + this.depth = null; return this; }; - return Tone.AMSynth; + return Tone.Tremolo; }); Module(function (Tone) { /** - * @class Tone.DrumSynth 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 - * envelope starts at note * .octaves and ramps to - * note over the duration of .pitchDecay. - * - * @constructor - * @extends {Tone.Instrument} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var synth = new Tone.DrumSynth().toMaster(); - * synth.triggerAttackRelease("C2", "8n"); + * @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.DrumSynth = function (options) { - options = this.defaultArg(options, Tone.DrumSynth.defaults); - Tone.Instrument.call(this, options); + Tone.Vibrato = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'depth' + ], Tone.Vibrato.defaults); + Tone.Effect.call(this, options); /** - * The oscillator. - * @type {Tone.Oscillator} + * The delay node used for the vibrato effect + * @type {Tone.Delay} + * @private */ - this.oscillator = new Tone.Oscillator(options.oscillator).start(); + this._delayNode = new Tone.Delay(0, options.maxDelay); /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} + * The LFO used to control the vibrato + * @type {Tone.LFO} + * @private */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); + 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 number of octaves the pitch envelope ramps. - * @type {Positive} + * The frequency of the vibrato + * @type {Frequency} + * @signal */ - this.octaves = options.octaves; + this.frequency = this._lfo.frequency; /** - * The amount of time the frequency envelope takes. - * @type {Time} + * The depth of the vibrato. + * @type {NormalRange} + * @signal */ - this.pitchDecay = options.pitchDecay; - this.oscillator.chain(this.envelope, this.output); + this.depth = this._lfo.amplitude; + this.depth.value = options.depth; this._readOnly([ - 'oscillator', - 'envelope' + 'frequency', + 'depth' ]); + this.effectSend.chain(this._delayNode, this.effectReturn); }; - Tone.extend(Tone.DrumSynth, Tone.Instrument); - /** - * @static - * @type {Object} - */ - Tone.DrumSynth.defaults = { - 'pitchDecay': 0.05, - 'octaves': 10, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.001, - 'decay': 0.4, - 'sustain': 0.01, - 'release': 1.4, - 'attackCurve': 'exponential' - } - }; + Tone.extend(Tone.Vibrato, Tone.Effect); /** - * 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); + * The defaults + * @type {Object} + * @const */ - 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; + Tone.Vibrato.defaults = { + 'maxDelay': 0.005, + 'frequency': 5, + 'depth': 0.1, + 'type': 'sine' }; /** - * Trigger the release portion of the note. - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.DrumSynth} this + * Type of oscillator attached to the Vibrato. + * @memberOf Tone.Vibrato# + * @type {string} + * @name type */ - Tone.DrumSynth.prototype.triggerRelease = function (time) { - this.envelope.triggerRelease(time); - 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.DrumSynth} this + * @returns {Tone.Vibrato} this */ - Tone.DrumSynth.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(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([ - 'oscillator', - 'envelope' + 'frequency', + 'depth' ]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - return this; + this.frequency = null; + this.depth = null; }; - return Tone.DrumSynth; + return Tone.Vibrato; }); 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. - * + * @class Tone.Event abstracts away Tone.Transport.schedule and provides a schedulable + * callback for a single or repeatable events along the timeline. * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below + * @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 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); + * 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.Event = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'value' + ], Tone.Event.defaults); /** - * the first voice - * @type {Tone.MonoSynth} + * Loop value + * @type {Boolean|Positive} + * @private */ - this.voice0 = new Tone.MonoSynth(options.voice0); - this.voice0.volume.value = -10; + this._loop = options.loop; /** - * the second voice - * @type {Tone.MonoSynth} + * The callback to invoke. + * @type {Function} */ - this.voice1 = new Tone.MonoSynth(options.voice1); - this.voice1.volume.value = -10; + this.callback = options.callback; /** - * The vibrato LFO. - * @type {Tone.LFO} + * The value which is passed to the + * callback function. + * @type {*} * @private */ - this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50); - this._vibrato.start(); + this.value = options.value; /** - * the vibrato frequency - * @type {Frequency} - * @signal + * When the note is scheduled to start. + * @type {Number} + * @private */ - this.vibratoRate = this._vibrato.frequency; + this._loopStart = this.toTicks(options.loopStart); /** - * the vibrato gain - * @type {GainNode} + * When the note is scheduled to start. + * @type {Number} * @private */ - this._vibratoGain = this.context.createGain(); + this._loopEnd = this.toTicks(options.loopEnd); /** - * The amount of vibrato - * @type {Gain} - * @signal + * Tracks the scheduled events + * @type {Tone.TimelineState} + * @private */ - this.vibratoAmount = new Tone.Signal(this._vibratoGain.gain, Tone.Type.Gain); - this.vibratoAmount.value = options.vibratoAmount; + this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * the delay before the vibrato starts - * @type {number} + * The playback speed of the note. A speed of 1 + * is no change. * @private + * @type {Positive} */ - this._vibratoDelay = this.toSeconds(options.vibratoDelay); + this._playbackRate = 1; /** - * the frequency control - * @type {Frequency} - * @signal + * A delay time from when the event is scheduled to start + * @type {Ticks} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + this._startOffset = 0; /** - * 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 + * The probability that the callback will be invoked + * at the scheduled time. + * @type {NormalRange} * @example - * //pitch voice1 an octave below voice0 - * duoSynth.harmonicity.value = 0.5; + * //the callback will be invoked 50% of the time + * event.probability = 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' - ]); + this.probability = options.probability; + /** + * 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.humanize = options.humanize; + /** + * If mute is true, the callback won't be + * invoked. + * @type {Boolean} + */ + this.mute = options.mute; + //set the initial values + this.playbackRate = options.playbackRate; }; - Tone.extend(Tone.DuoSynth, Tone.Monophonic); + Tone.extend(Tone.Event); + /** + * The default values + * @type {Object} + * @const + */ + 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 - 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'); + } + } + }.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 {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._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 + */ + 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); + }; + /** + * 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(); + } + }); /** - * @static - * @type {Object} + * 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; */ - 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 + Object.defineProperty(Tone.Event.prototype, 'playbackRate', { + get: function () { + return this._playbackRate; + }, + set: function (rate) { + this._playbackRate = rate; + this._rescheduleEvents(); + } + }); + /** + * 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 + */ + 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(); } + } + }); + /** + * The time when the loop should start. + * @memberOf Tone.Event# + * @type {Boolean|Positive} + * @name loopStart + */ + Object.defineProperty(Tone.Event.prototype, 'loopStart', { + get: function () { + return this.toNotation(this._loopStart + 'i'); }, - '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 + set: function (loopStart) { + this._loopStart = this.toTicks(loopStart); + if (this._loop) { + this._rescheduleEvents(); + } + } + }); + /** + * 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 + */ + 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 + * @return {Tone.Event} this + */ + Tone.Event.prototype.dispose = function () { + this.cancel(); + this._state.dispose(); + this._state = null; + this.callback = null; + this.value = null; }; + return Tone.Event; + }); + Module(function (Tone) { /** - * 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 + * @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. */ - 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); + 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 + }); + /** + * 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 + }; + /** + * Start the loop at the specified time along the Transport's + * timeline. + * @param {Time=} time When to start the Loop. + * @return {Tone.Loop} this + */ + Tone.Loop.prototype.start = function (time) { + this._event.start(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 + * Stop the loop at the given time. + * @param {Time=} time When to stop the Arpeggio + * @return {Tone.Loop} this */ - Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) { - this.voice0.triggerRelease(time); - this.voice1.triggerRelease(time); + Tone.Loop.prototype.stop = function (time) { + this._event.stop(time); return this; }; /** - * clean up - * @returns {Tone.DuoSynth} 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 */ - 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; + Tone.Loop.prototype.cancel = function (time) { + this._event.cancel(time); return this; }; - return Tone.DuoSynth; + /** + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs + * @private + */ + Tone.Loop.prototype._tick = function (time) { + this.callback(time); + }; + /** + * 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.Loop.prototype, 'probability', { + get: function () { + return this._event.probability; + }, + set: function (prob) { + this._event.probability = prob; + } + }); + /** + * Muting the Loop means that no callbacks are invoked. + * @memberOf Tone.Loop# + * @type {Boolean} + * @name mute + */ + Object.defineProperty(Tone.Loop.prototype, 'mute', { + get: function () { + return this._event.mute; + }, + set: function (mute) { + this._event.mute = mute; + } + }); + /** + * The number of iterations of the loop. The default + * value is Infinity (loop forever). + * @memberOf Tone.Loop# + * @type {Positive} + * @name iterations + */ + Object.defineProperty(Tone.Loop.prototype, 'iterations', { + 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; + } + } + }); + /** + * Clean up + * @return {Tone.Loop} this + */ + Tone.Loop.prototype.dispose = function () { + this._event.dispose(); + this._event = null; + this.callback = null; + }; + return Tone.Loop; }); 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). - * + * @class Tone.Part is a collection Tone.Events which can be + * started/stoped and looped as a single unit. * - * @constructor - * @extends {Tone.Monophonic} - * @param {Object} [options] the options available for the synth - * see defaults below + * @extends {Tone.Event} + * @param {Function} callback The callback to invoke on each event + * @param {Array} events the array of events * @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); + * 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); /** - * The carrier voice. - * @type {Tone.MonoSynth} + * If the part is looping or not + * @type {Boolean|Positive} + * @private */ - this.carrier = new Tone.MonoSynth(options.carrier); - this.carrier.volume.value = -10; + this._loop = options.loop; /** - * The modulator voice. - * @type {Tone.MonoSynth} + * When the note is scheduled to start. + * @type {Ticks} + * @private */ - this.modulator = new Tone.MonoSynth(options.modulator); - this.modulator.volume.value = -10; + this._loopStart = this.toTicks(options.loopStart); /** - * The frequency control. - * @type {Frequency} - * @signal + * When the note is scheduled to start. + * @type {Ticks} + * @private */ - this.frequency = new Tone.Signal(440, Tone.Type.Frequency); + 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; /** - * 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 start offset + * @type {Ticks} + * @private */ - this.harmonicity = new Tone.Multiply(options.harmonicity); - this.harmonicity.units = Tone.Type.Positive; + this._startOffset = 0; /** - * 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 + * Keeps track of the current state + * @type {Tone.TimelineState} + * @private */ - this.modulationIndex = new Tone.Multiply(options.modulationIndex); - this.modulationIndex.units = Tone.Type.Positive; + this._state = new Tone.TimelineState(Tone.State.Stopped); /** - * the node where the modulation happens - * @type {GainNode} + * An array of Objects. + * @type {Array} * @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._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]); + } + } + } }; - Tone.extend(Tone.FMSynth, Tone.Monophonic); + Tone.extend(Tone.Part, Tone.Event); /** - * @static - * @type {Object} + * The default values + * @type {Object} + * @const */ - Tone.FMSynth.defaults = { - 'harmonicity': 3, - 'modulationIndex': 10, - 'carrier': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'sine' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'min': 20000, - 'max': 20000 + Tone.Part.defaults = { + 'callback': Tone.noOp, + 'loop': false, + 'loopEnd': '1m', + 'loopStart': 0, + 'playbackRate': 1, + 'probability': 1, + 'humanize': false, + 'mute': false + }; + /** + * 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 + */ + 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; + }; + /** + * 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 + */ + 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'); } + } + }; + /** + * The start from the scheduled start time + * @type {Ticks} + * @memberOf Tone.Part# + * @name startOffset + * @private + */ + Object.defineProperty(Tone.Part.prototype, 'startOffset', { + get: function () { + return this._startOffset; }, - 'modulator': { - 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'triangle' }, - 'envelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5 - }, - 'filterEnvelope': { - 'attack': 0.01, - 'decay': 0, - 'sustain': 1, - 'release': 0.5, - 'min': 20000, - 'max': 20000 + set: function (offset) { + this._startOffset = offset; + this._forEach(function (event) { + event.startOffset += this._startOffset; + }); + } + }); + /** + * Stop the part at the given time. + * @param {Time} 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); + }); + } + return this; + }; + /** + * 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 {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 + */ + 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; } } + //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; + } }; /** - * 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 + * 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"); + */ + 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; + }; + /** + * Restart the given event + * @param {Tone.Event} event * @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); + 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); + } + }; + /** + * 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 + */ + 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 { + if (event.startOffset === time) { + if (this.isUndef(value) || !this.isUndef(value) && event.value === value) { + this._events.splice(i, 1); + event.dispose(); + } + } + } + } return this; }; /** - * trigger the release portion of the note - * - * @param {Time} [time=now] the time the note will release - * @returns {Tone.FMSynth} this + * Remove all of the notes from the group. + * @return {Tone.Part} this + */ + Tone.Part.prototype.removeAll = function () { + this._forEach(function (event) { + event.dispose(); + }); + this._events = []; + 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 + */ + Tone.Part.prototype.cancel = function (after) { + this._forEach(function (event) { + event.cancel(after); + }); + this._state.cancel(after); + return this; + }; + /** + * Iterate over all of the events + * @param {Function} callback + * @param {Object} ctx The context * @private */ - Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(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; }; /** - * clean up - * @returns {Tone.FMSynth} 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.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.Part.prototype._setAll = function (attr, value) { + this._forEach(function (event) { + event[attr] = value; + }); + }; + /** + * Internal tick method + * @param {Number} time The time of the event in seconds + * @private + */ + Tone.Part.prototype._tick = function (time, value) { + if (!this.mute) { + this.callback(time, value); + } + }; + /** + * Determine if the event should be currently looping + * given the loop boundries of this Part. + * @param {Tone.Event} event The event to test + * @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); + } + } + }; + /** + * 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 + */ + Object.defineProperty(Tone.Part.prototype, 'loopStart', { + get: function () { + return this.toNotation(this._loopStart + 'i'); + }, + set: function (loopStart) { + this._loopStart = this.toTicks(loopStart); + if (this._loop) { + this._forEach(function (event) { + event.loopStart = this.loopStart; + this._testLoopBoundries(event); + }); + } + } + }); + /** + * The playback rate of the part + * @memberOf Tone.Part# + * @type {Positive} + * @name playbackRate + */ + Object.defineProperty(Tone.Part.prototype, 'playbackRate', { + get: function () { + return this._playbackRate; + }, + set: function (rate) { + this._playbackRate = rate; + this._setAll('playbackRate', rate); + } + }); + /** + * The number of scheduled notes in the part. + * @memberOf Tone.Part# + * @type {Positive} + * @name length + * @readOnly + */ + Object.defineProperty(Tone.Part.prototype, 'length', { + get: function () { + return this._events.length; + } + }); + /** + * Clean up + * @return {Tone.Part} this + */ + Tone.Part.prototype.dispose = function () { + this.removeAll(); + this._state.dispose(); + this._state = null; + this.callback = null; + this._events = null; return this; }; - return Tone.FMSynth; + return Tone.Part; }); Module(function (Tone) { - /** - * @class Tone.Noise is a noise generator. It uses looped noise buffers to save on performance. - * Tone.Noise supports the noise types: "pink", "white", and "brown". Read more about - * colors of noise on [Wikipedia](https://en.wikipedia.org/wiki/Colors_of_noise). - * - * @constructor - * @extends {Tone.Source} - * @param {string} type the noise type (white|pink|brown) + * @class Tone.Pattern arpeggiates between the given notes + * in a number of patterns. See Tone.CtrlPattern for + * a full list of patterns. * @example - * //initialize the noise and start - * var noise = new Tone.Noise("pink").start(); - * - * //make an autofilter to shape the noise - * var autoFilter = new Tone.AutoFilter({ - * "frequency" : "8m", - * "min" : 800, - * "max" : 15000 - * }).connect(Tone.Master); - * - * //connect the noise - * noise.connect(autoFilter); - * //start the autofilter LFO - * autoFilter.start() - */ - Tone.Noise = function () { - var options = this.optionsObject(arguments, ['type'], Tone.Noise.defaults); - Tone.Source.call(this, options); - /** - * @private - * @type {AudioBufferSourceNode} - */ - this._source = null; - /** - * the buffer + * 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. + */ + Tone.Pattern = function () { + var options = this.optionsObject(arguments, [ + 'callback', + 'events', + 'pattern' + ], Tone.Pattern.defaults); + Tone.Loop.call(this, options); + /** + * The pattern manager + * @type {Tone.CtrlPattern} * @private - * @type {AudioBuffer} */ - this._buffer = null; - this.type = options.type; + this._pattern = new Tone.CtrlPattern({ + 'values': options.events, + 'type': options.pattern, + 'index': options.index + }); }; - Tone.extend(Tone.Noise, Tone.Source); + Tone.extend(Tone.Pattern, Tone.Loop); /** - * the default parameters - * - * @static + * The defaults * @const - * @type {Object} - */ - Tone.Noise.defaults = { 'type': 'white' }; - /** - * The type of the noise. Can be "white", "brown", or "pink". - * @memberOf Tone.Noise# - * @type {string} - * @name type - * @example - * noise.type = "white"; + * @type {Object} */ - Object.defineProperty(Tone.Noise.prototype, 'type', { - get: function () { - if (this._buffer === _whiteNoise) { - return 'white'; - } else if (this._buffer === _brownNoise) { - return 'brown'; - } else if (this._buffer === _pinkNoise) { - return 'pink'; - } - }, - set: function (type) { - if (this.type !== type) { - switch (type) { - case 'white': - this._buffer = _whiteNoise; - break; - case 'pink': - this._buffer = _pinkNoise; - break; - case 'brown': - this._buffer = _brownNoise; - break; - default: - this._buffer = _whiteNoise; - } - //if it's playing, stop and restart it - if (this.state === Tone.State.Started) { - var now = this.now() + this.bufferTime; - //remove the listener - this._source.onended = undefined; - this._stop(now); - this._start(now); - } - } - } - }); + Tone.Pattern.defaults = { + 'pattern': Tone.CtrlPattern.Type.Up, + 'events': [] + }; /** - * internal start method - * - * @param {Time} time + * Internal function called when the notes should be called + * @param {Number} time The time the event occurs * @private */ - Tone.Noise.prototype._start = function (time) { - this._source = this.context.createBufferSource(); - this._source.buffer = this._buffer; - this._source.loop = true; - this.connectSeries(this._source, this.output); - this._source.start(this.toSeconds(time)); - this._source.onended = this.onended; + Tone.Pattern.prototype._tick = function (time) { + this.callback(time, this._pattern.value); + this._pattern.next(); }; /** - * internal stop method - * - * @param {Time} time - * @private + * The current index in the events array. + * @memberOf Tone.Pattern# + * @type {Positive} + * @name index */ - Tone.Noise.prototype._stop = function (time) { - if (this._source) { - this._source.stop(this.toSeconds(time)); + Object.defineProperty(Tone.Pattern.prototype, 'index', { + get: function () { + return this._pattern.index; + }, + set: function (i) { + this._pattern.index = i; } - }; + }); /** - * Clean up. - * @returns {Tone.Noise} this + * The array of events. + * @memberOf Tone.Pattern# + * @type {Array} + * @name events */ - Tone.Noise.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); - if (this._source !== null) { - this._source.disconnect(); - this._source = null; + Object.defineProperty(Tone.Pattern.prototype, 'events', { + get: function () { + return this._pattern.values; + }, + set: function (vals) { + this._pattern.values = vals; } - this._buffer = null; - return this; - }; - /////////////////////////////////////////////////////////////////////////// - // THE BUFFERS - // borrowed heavily from http://noisehack.com/generate-noise-web-audio-api/ - /////////////////////////////////////////////////////////////////////////// + }); /** - * static noise buffers - * - * @static - * @private - * @type {AudioBuffer} + * The current value of the pattern. + * @memberOf Tone.Pattern# + * @type {*} + * @name value + * @readOnly */ - var _pinkNoise = null, _brownNoise = null, _whiteNoise = null; - Tone._initAudioContext(function (audioContext) { - var sampleRate = audioContext.sampleRate; - //four seconds per buffer - var bufferLength = sampleRate * 4; - //fill the buffers - _pinkNoise = function () { - var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); - for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { - var channel = buffer.getChannelData(channelNum); - var b0, b1, b2, b3, b4, b5, b6; - b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; - for (var i = 0; i < bufferLength; i++) { - var white = Math.random() * 2 - 1; - b0 = 0.99886 * b0 + white * 0.0555179; - b1 = 0.99332 * b1 + white * 0.0750759; - b2 = 0.969 * b2 + white * 0.153852; - b3 = 0.8665 * b3 + white * 0.3104856; - b4 = 0.55 * b4 + white * 0.5329522; - b5 = -0.7616 * b5 - white * 0.016898; - channel[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; - channel[i] *= 0.11; - // (roughly) compensate for gain - b6 = white * 0.115926; - } - } - return buffer; - }(); - _brownNoise = function () { - var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); - for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { - var channel = buffer.getChannelData(channelNum); - var lastOut = 0; - for (var i = 0; i < bufferLength; i++) { - var white = Math.random() * 2 - 1; - channel[i] = (lastOut + 0.02 * white) / 1.02; - lastOut = channel[i]; - channel[i] *= 3.5; // (roughly) compensate for gain - } - } - return buffer; - }(); - _whiteNoise = function () { - var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); - for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { - var channel = buffer.getChannelData(channelNum); - for (var i = 0; i < bufferLength; i++) { - channel[i] = Math.random() * 2 - 1; - } - } - return buffer; - }(); + Object.defineProperty(Tone.Pattern.prototype, 'value', { + get: function () { + return this._pattern.value; + } }); - return Tone.Noise; - }); - Module(function (Tone) { - /** - * @class Tone.NoiseSynth is composed of a noise generator (Tone.Noise), one filter (Tone.Filter), - * and two envelopes (Tone.Envelop). One envelope controls the amplitude - * of the noise and the other is controls the cutoff frequency of the filter. - * - * - * @constructor - * @extends {Tone.Instrument} - * @param {Object} [options] the options available for the synth - * see defaults below - * @example - * var noiseSynth = new Tone.NoiseSynth().toMaster(); - * noiseSynth.triggerAttackRelease("8n"); + * The pattern type. See Tone.CtrlPattern for the full list of patterns. + * @memberOf Tone.Pattern# + * @type {String} + * @name pattern */ - Tone.NoiseSynth = function (options) { - //get the defaults - options = this.defaultArg(options, Tone.NoiseSynth.defaults); - Tone.Instrument.call(this, options); - /** - * The noise source. - * @type {Tone.Noise} - * @example - * 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.ScaledEnvelope} - */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); - /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} - */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - //connect the noise to the output - this.noise.chain(this.filter, this.envelope, this.output); - //start the noise - this.noise.start(); - //connect the filter envelope - this.filterEnvelope.connect(this.filter.frequency); - this._readOnly([ - 'noise', - 'filter', - 'filterEnvelope', - 'envelope' - ]); + 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; }; - Tone.extend(Tone.NoiseSynth, Tone.Instrument); + return Tone.Pattern; + }); + Module(function (Tone) { + /** - * @const - * @static - * @type {Object} + * @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"); + * @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.NoiseSynth.defaults = { - 'noise': { 'type': 'white' }, - 'filter': { - 'Q': 6, - 'type': 'highpass', - 'rolloff': -24 - }, - 'envelope': { - 'attack': 0.005, - 'decay': 0.1, - 'sustain': 0 - }, - 'filterEnvelope': { - 'attack': 0.06, - 'decay': 0.2, - 'sustain': 0, - 'release': 2, - 'min': 20, - 'max': 4000, - 'exponent': 2 + 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; + } + //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); /** - * Start the attack portion of the envelopes. Unlike other - * instruments, Tone.NoiseSynth doesn't have a note. - * @param {Time} [time=now] the time the attack should start - * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.NoiseSynth} this + * The default values. + * @type {Object} + */ + 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 this.toNotation(this._subdivision + 'i'); + } + }); + /** + * Get/Set an index of the sequence. If the index contains a subarray, + * a Tone.Sequence representing that sub-array will be returned. * @example - * noiseSynth.triggerAttack(); + * 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.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); + }; + /** + * 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.NoiseSynth.prototype.triggerAttack = function (time, velocity) { - //the envelopes - this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); + 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); + } + Tone.Part.prototype.add.call(this, this._indexTime(index), value); return this; }; /** - * Start the release portion of the envelopes. - * @param {Time} [time=now] the time the release should start - * @returns {Tone.NoiseSynth} this + * Remove a value from the sequence by index + * @param {Number} index The index of the event to remove + * @returns {Tone.Sequence} this */ - Tone.NoiseSynth.prototype.triggerRelease = function (time) { - this.envelope.triggerRelease(time); - this.filterEnvelope.triggerRelease(time); + Tone.Sequence.prototype.remove = function (index, value) { + Tone.Part.prototype.remove.call(this, this._indexTime(index), value); return this; }; /** - * Trigger the attack and then the release. - * @param {Time} duration the duration of the note - * @param {Time} [time=now] the time of the attack - * @param {number} [velocity=1] the velocity - * @returns {Tone.NoiseSynth} this + * Get the time of the index given the Sequence's subdivision + * @param {Number} index + * @return {Time} The time of that index + * @private */ - Tone.NoiseSynth.prototype.triggerAttackRelease = function (duration, time, velocity) { - time = this.toSeconds(time); - duration = this.toSeconds(duration); - this.triggerAttack(time, velocity); - this.triggerRelease(time + duration); - return this; + Tone.Sequence.prototype._indexTime = function (index) { + if (this.isTicks(index)) { + return index; + } else { + return index * this._subdivision + this.startOffset + 'i'; + } }; /** - * Clean up. - * @returns {Tone.NoiseSynth} this + * Clean up. + * @return {Tone.Sequence} this */ - Tone.NoiseSynth.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(this); - this._writable([ - 'noise', - 'filter', - 'filterEnvelope', - 'envelope' - ]); - this.noise.dispose(); - this.noise = null; - this.envelope.dispose(); - this.envelope = null; - this.filterEnvelope.dispose(); - this.filterEnvelope = null; - this.filter.dispose(); - this.filter = null; + Tone.Sequence.prototype.dispose = function () { + Tone.Part.prototype.dispose.call(this); return this; }; - return Tone.NoiseSynth; + return Tone.Sequence; }); Module(function (Tone) { /** - * @class Karplus-String string synthesis. Often out of tune. - * Will change when the AudioWorkerNode is available across - * browsers. - * + * @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.Instrument} - * @param {Object} [options] see the defaults + * @extends {Tone.Oscillator} + * @param {Frequency} [frequency] The frequency of the oscillator + * @param {NormalRange} [width] The width of the pulse * @example - * var plucky = new Tone.PluckSynth().toMaster(); - * plucky.triggerAttack("C4"); + * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start(); */ - Tone.PluckSynth = function (options) { - options = this.defaultArg(options, Tone.PluckSynth.defaults); - Tone.Instrument.call(this, options); + Tone.PulseOscillator = function () { + var options = this.optionsObject(arguments, [ + 'frequency', + 'width' + ], Tone.Oscillator.defaults); + Tone.Source.call(this, options); /** - * @type {Tone.Noise} - * @private + * The width of the pulse. + * @type {NormalRange} + * @signal */ - this._noise = new Tone.Noise('pink'); + this.width = new Tone.Signal(options.width, Tone.Type.NormalRange); /** - * The amount of noise at the attack. - * Nominal range of [0.1, 20] - * @type {number} + * gate the width amount + * @type {GainNode} + * @private */ - this.attackNoise = 1; + this._widthGate = this.context.createGain(); /** - * the LFCF - * @type {Tone.LowpassCombFilter} + * the sawtooth oscillator + * @type {Tone.Oscillator} * @private */ - this._lfcf = new Tone.LowpassCombFilter({ - 'resonance': options.resonance, - 'dampening': options.dampening + this._sawtooth = new Tone.Oscillator({ + frequency: options.frequency, + detune: options.detune, + type: 'sawtooth', + phase: options.phase }); /** - * The resonance control. - * @type {NormalRange} + * The frequency control. + * @type {Frequency} * @signal */ - this.resonance = this._lfcf.resonance; + this.frequency = this._sawtooth.frequency; /** - * The dampening control. i.e. the lowpass filter frequency of the comb filter - * @type {Frequency} + * The detune in cents. + * @type {Cents} * @signal */ - this.dampening = this._lfcf.dampening; + 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._noise.connect(this._lfcf); - this._lfcf.connect(this.output); + this._sawtooth.chain(this._thresh, this.output); + this.width.chain(this._widthGate, this._thresh); this._readOnly([ - 'resonance', - 'dampening' + 'width', + 'frequency', + 'detune' ]); }; - Tone.extend(Tone.PluckSynth, Tone.Instrument); + Tone.extend(Tone.PulseOscillator, Tone.Oscillator); /** + * The default parameters. * @static * @const * @type {Object} */ - Tone.PluckSynth.defaults = { - 'attackNoise': 1, - 'dampening': 4000, - 'resonance': 0.9 + Tone.PulseOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'width': 0.2 }; /** - * Trigger the note. - * @param {Frequency} note The note to trigger. - * @param {Time} [time=now] When the note should be triggered. - * @returns {Tone.PluckSynth} this + * start the oscillator + * @param {Time} time + * @private */ - Tone.PluckSynth.prototype.triggerAttack = function (note, time) { - note = this.toFrequency(note); + Tone.PulseOscillator.prototype._start = function (time) { time = this.toSeconds(time); - var delayAmount = 1 / note; - this._lfcf.delayTime.setValueAtTime(delayAmount, time); - this._noise.start(time); - this._noise.stop(time + delayAmount * this.attackNoise); - return this; + this._sawtooth.start(time); + this._widthGate.gain.setValueAtTime(1, time); + }; + /** + * stop the oscillator + * @param {Time} time + * @private + */ + Tone.PulseOscillator.prototype._stop = function (time) { + time = this.toSeconds(time); + this._sawtooth.stop(time); + //the width is still connected to the output. + //that needs to be stopped also + this._widthGate.gain.setValueAtTime(0, time); }; /** - * Clean up. - * @returns {Tone.PluckSynth} this + * The phase of the oscillator in degrees. + * @memberOf Tone.PulseOscillator# + * @type {Degrees} + * @name phase + */ + Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', { + get: function () { + return this._sawtooth.phase; + }, + set: function (phase) { + this._sawtooth.phase = phase; + } + }); + /** + * The type of the oscillator. Always returns "pulse". + * @readOnly + * @memberOf Tone.PulseOscillator# + * @type {string} + * @name type + */ + Object.defineProperty(Tone.PulseOscillator.prototype, 'type', { + get: function () { + return 'pulse'; + } + }); + /** + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PulseOscillator# + * @type {Array} + * @name partials + * @private + */ + Object.defineProperty(Tone.PulseOscillator.prototype, 'partials', { + get: function () { + return []; + } + }); + /** + * Clean up method. + * @return {Tone.PulseOscillator} this */ - Tone.PluckSynth.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(this); - this._noise.dispose(); - this._lfcf.dispose(); - this._noise = null; - this._lfcf = null; + Tone.PulseOscillator.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + this._sawtooth.dispose(); + this._sawtooth = null; this._writable([ - 'resonance', - 'dampening' + 'width', + 'frequency', + 'detune' ]); - this.dampening = null; - this.resonance = null; + 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.PluckSynth; + return Tone.PulseOscillator; }); Module(function (Tone) { /** - * @class Tone.PolySynth handles voice creation and allocation for any - * instruments passed in as the second paramter. PolySynth is - * not a synthesizer by itself, it merely manages voices of - * one of the other types of synths, allowing any of the - * monophonic synthesizers to be polyphonic. + * @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 - * @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 {Frequency} frequency The starting frequency of the oscillator. + * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse. * @example - * //a polysynth composed of 6 Voices of MonoSynth - * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster(); - * //set the attributes using the set interface - * synth.set("detune", -1200); - * //play a chord - * synth.triggerAttackRelease(["C4", "E4", "A4"], "4n"); + * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start(); */ - Tone.PolySynth = function () { - Tone.Instrument.call(this); + Tone.PWMOscillator = function () { var options = this.optionsObject(arguments, [ - 'polyphony', - 'voice' - ], Tone.PolySynth.defaults); + 'frequency', + 'modulationFrequency' + ], Tone.PWMOscillator.defaults); + Tone.Source.call(this, options); /** - * the array of voices - * @type {Array} + * the pulse oscillator + * @type {Tone.PulseOscillator} + * @private */ - this.voices = new Array(options.polyphony); + this._pulse = new Tone.PulseOscillator(options.modulationFrequency); + //change the pulse oscillator type + this._pulse._sawtooth.type = 'sine'; /** - * the queue of free voices + * the modulator + * @type {Tone.Oscillator} * @private - * @type {Array} */ - this._freeVoices = []; + this._modulator = new Tone.Oscillator({ + 'frequency': options.frequency, + 'detune': options.detune, + 'phase': options.phase + }); /** - * keeps track of which notes are down + * Scale the oscillator so it doesn't go silent + * at the extreme values. + * @type {Tone.Multiply} * @private - * @type {Object} */ - this._activeVoices = {}; - //create the voices - for (var i = 0; i < options.polyphony; i++) { - var v = new options.voice(arguments[2], arguments[3]); - this.voices[i] = v; - v.connect(this.output); - } - //make a copy of the voices - this._freeVoices = this.voices.slice(0); //get the prototypes and properties + this._scale = new Tone.Multiply(1.01); + /** + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = this._modulator.frequency; + /** + * The detune of the oscillator. + * @type {Cents} + * @signal + */ + this.detune = this._modulator.detune; + /** + * The modulation rate of the oscillator. + * @type {Frequency} + * @signal + */ + this.modulationFrequency = this._pulse.frequency; + //connections + this._modulator.chain(this._scale, this._pulse.width); + this._pulse.connect(this.output); + this._readOnly([ + 'modulationFrequency', + 'frequency', + 'detune' + ]); }; - Tone.extend(Tone.PolySynth, Tone.Instrument); + Tone.extend(Tone.PWMOscillator, Tone.Oscillator); /** - * the defaults - * @const + * default values * @static * @type {Object} + * @const */ - Tone.PolySynth.defaults = { - 'polyphony': 4, - 'voice': Tone.MonoSynth - }; - /** - * Trigger the attack portion of the note - * @param {Frequency|Array} notes The notes to play. Accepts a single - * Frequency or an array of frequencies. - * @param {Time} [time=now] The start time of the note. - * @param {number} [velocity=1] The velocity of the note. - * @returns {Tone.PolySynth} this - * @example - * //trigger a chord immediately with a velocity of 0.2 - * poly.triggerAttack(["Ab3", "C4", "F5"], undefined, 0.2); - */ - Tone.PolySynth.prototype.triggerAttack = function (notes, time, velocity) { - if (!Array.isArray(notes)) { - notes = [notes]; - } - for (var i = 0; i < notes.length; i++) { - var val = notes[i]; - var stringified = JSON.stringify(val); - if (this._activeVoices[stringified]) { - this._activeVoices[stringified].triggerAttack(val, time, velocity); - } else if (this._freeVoices.length > 0) { - var voice = this._freeVoices.shift(); - voice.triggerAttack(val, time, velocity); - this._activeVoices[stringified] = voice; - } - } - return this; + Tone.PWMOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'phase': 0, + 'modulationFrequency': 0.4 }; /** - * Trigger the attack and release after the specified duration - * - * @param {Frequency|Array} notes The notes to play. Accepts a single - * Frequency or an array of frequencies. - * @param {Time} duration the duration of the note - * @param {Time} [time=now] if no time is given, defaults to now - * @param {number} [velocity=1] the velocity of the attack (0-1) - * @returns {Tone.PolySynth} this - * @example - * //trigger a chord for a duration of a half note - * poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n"); + * start the oscillator + * @param {Time} [time=now] + * @private */ - Tone.PolySynth.prototype.triggerAttackRelease = function (notes, duration, time, velocity) { + Tone.PWMOscillator.prototype._start = function (time) { time = this.toSeconds(time); - this.triggerAttack(notes, time, velocity); - this.triggerRelease(notes, time + this.toSeconds(duration)); - return this; + this._modulator.start(time); + this._pulse.start(time); }; /** - * Trigger the release of the note. Unlike monophonic instruments, - * a note (or array of notes) needs to be passed in as the first argument. - * @param {Frequency|Array} notes The notes to play. Accepts a single - * Frequency or an array of frequencies. - * @param {Time} [time=now] When the release will be triggered. - * @returns {Tone.PolySynth} this - * @example - * poly.triggerAttack(["Ab3", "C4", "F5"]); + * stop the oscillator + * @param {Time} time (optional) timing parameter + * @private */ - Tone.PolySynth.prototype.triggerRelease = function (notes, time) { - if (!Array.isArray(notes)) { - notes = [notes]; - } - 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; - } - } - return this; + Tone.PWMOscillator.prototype._stop = function (time) { + time = this.toSeconds(time); + this._modulator.stop(time); + this._pulse.stop(time); }; /** - * Set a member/attribute of the voices. - * @param {Object|string} params - * @param {number=} value - * @param {Time=} rampTime - * @returns {Tone.PolySynth} this - * @example - * poly.set({ - * "filter" : { - * "type" : "highpass" - * }, - * "envelope" : { - * "attack" : 0.25 - * } - * }); + * The type of the oscillator. Always returns "pwm". + * @readOnly + * @memberOf Tone.PWMOscillator# + * @type {string} + * @name type */ - Tone.PolySynth.prototype.set = function (params, value, rampTime) { - for (var i = 0; i < this.voices.length; i++) { - this.voices[i].set(params, value, rampTime); - } - return this; - }; - /** - * Get the synth's attributes. Given no arguments get - * will return all available object properties and their corresponding - * values. Pass in a single attribute to retrieve or an array - * of attributes. The attribute strings can also include a "." - * to access deeper properties. - * @param {Array=} params the parameters to get, otherwise will return - * all available. + Object.defineProperty(Tone.PWMOscillator.prototype, 'type', { + get: function () { + return 'pwm'; + } + }); + /** + * The partials of the waveform. Cannot set partials for this waveform type + * @memberOf Tone.PWMOscillator# + * @type {Array} + * @name partials + * @private */ - Tone.PolySynth.prototype.get = function (params) { - return this.voices[0].get(params); - }; + Object.defineProperty(Tone.PWMOscillator.prototype, 'partials', { + get: function () { + return []; + } + }); /** - * @param {string} presetName the preset name - * @returns {Tone.PolySynth} this - * @private + * The phase of the oscillator in degrees. + * @memberOf Tone.PWMOscillator# + * @type {number} + * @name phase */ - Tone.PolySynth.prototype.setPreset = function (presetName) { - for (var i = 0; i < this.voices.length; i++) { - this.voices[i].setPreset(presetName); + Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', { + get: function () { + return this._modulator.phase; + }, + set: function (phase) { + this._modulator.phase = phase; } - return this; - }; + }); /** * Clean up. - * @returns {Tone.PolySynth} this + * @return {Tone.PWMOscillator} this */ - Tone.PolySynth.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(this); - for (var i = 0; i < this.voices.length; i++) { - this.voices[i].dispose(); - this.voices[i] = null; - } - this.voices = null; - this._activeVoices = null; - this._freeVoices = null; + Tone.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.PolySynth; + return Tone.PWMOscillator; }); Module(function (Tone) { /** - * @class Tone.Player is an audio file player with start, loop, and stop functions. - * + * @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. + * + * @extends {Tone.Oscillator} * @constructor - * @extends {Tone.Source} - * @param {string|AudioBuffer} url Either the AudioBuffer or the url from - * which to load the AudioBuffer - * @param {function=} onload The function to invoke when the buffer is loaded. - * Recommended to use Tone.Buffer.onload instead. + * @param {Frequency} frequency The initial frequency of the oscillator. + * @param {string} type The type of the oscillator. * @example - * var player = new Tone.Player("./path/to/sample.mp3").toMaster(); - * Tone.Buffer.onload = function(){ - * player.start(); - * } + * var omniOsc = new Tone.OmniOscillator("C#4", "pwm"); */ - Tone.Player = function () { + Tone.OmniOscillator = function () { var options = this.optionsObject(arguments, [ - 'url', - 'onload' - ], Tone.Player.defaults); + 'frequency', + 'type' + ], Tone.OmniOscillator.defaults); Tone.Source.call(this, options); /** - * @private - * @type {AudioBufferSourceNode} - */ - this._source = null; - /** - * If the file should play as soon - * as the buffer is loaded. - * @type {boolean} - * @example - * //will play as soon as it's loaded - * var player = new Tone.Player({ - * "url" : "./path/to/sample.mp3", - * "autostart" : true, - * }).toMaster(); - */ - this.autostart = options.autostart; - /** - * the buffer - * @private - * @type {Tone.Buffer} - */ - this._buffer = new Tone.Buffer({ - 'url': options.url, - 'onload': this._onload.bind(this, options.onload), - 'reverse': options.reverse - }); - /** - * if the buffer should loop once it's over - * @type {boolean} - * @private + * The frequency control. + * @type {Frequency} + * @signal */ - this._loop = options.loop; + this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency); /** - * if 'loop' is true, the loop will start at this position - * @type {Time} - * @private + * The detune control + * @type {Cents} + * @signal */ - this._loopStart = options.loopStart; + this.detune = new Tone.Signal(options.detune, Tone.Type.Cents); /** - * if 'loop' is true, the loop will end at this position - * @type {Time} + * the type of the oscillator source + * @type {string} * @private */ - this._loopEnd = options.loopEnd; + this._sourceType = undefined; /** - * the playback rate + * the oscillator + * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator} * @private - * @type {number} - */ - this._playbackRate = options.playbackRate; - /** - * Enabling retrigger will allow a player to be restarted - * before the the previous 'start' is done playing. Otherwise, - * successive calls to Tone.Player.start will only start - * the sample if it had played all the way through. - * @type {boolean} */ - this.retrigger = options.retrigger; + 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; + } }; - Tone.extend(Tone.Player, Tone.Source); + Tone.extend(Tone.OmniOscillator, Tone.Oscillator); /** - * the default parameters + * default values * @static - * @const * @type {Object} + * @const */ - Tone.Player.defaults = { - 'onload': Tone.noOp, - 'playbackRate': 1, - 'loop': false, - 'autostart': false, - 'loopStart': 0, - 'loopEnd': 0, - 'retrigger': false, - 'reverse': false - }; - /** - * Load the audio file as an audio buffer. - * Decodes the audio asynchronously and invokes - * the callback once the audio buffer loads. - * Note: this does not need to be called, if a url - * was passed in to the constructor. Only use this - * if you want to manually load a new url. - * @param {string} url The url of the buffer to load. - * Filetype support depends on the - * browser. - * @param {function=} callback The function to invoke once - * the sample is loaded. - * @returns {Tone.Player} this - */ - Tone.Player.prototype.load = function (url, callback) { - this._buffer.load(url, this._onload.bind(this, callback)); - return this; - }; - /** - * Internal callback when the buffer is loaded. - * @private - */ - Tone.Player.prototype._onload = function (callback) { - callback(this); - if (this.autostart) { - this.start(); - } + Tone.OmniOscillator.defaults = { + 'frequency': 440, + 'detune': 0, + 'type': 'sine', + 'phase': 0, + 'width': 0.4, + //only applies if the oscillator is set to "pulse", + 'modulationFrequency': 0.4 }; /** - * play the buffer between the desired positions - * + * @enum {string} * @private - * @param {Time} [startTime=now] when the player should start. - * @param {Time} [offset=0] the offset from the beginning of the sample - * to start at. - * @param {Time=} duration how long the sample should play. If no duration - * is given, it will default to the full length - * of the sample (minus any offset) - * @returns {Tone.Player} this */ - Tone.Player.prototype._start = function (startTime, offset, duration) { - if (this._buffer.loaded) { - //if it's a loop the default offset is the loopstart point - if (this._loop) { - offset = this.defaultArg(offset, this._loopStart); - } else { - //otherwise the default offset is 0 - offset = this.defaultArg(offset, 0); - } - offset = this.toSeconds(offset); - duration = this.defaultArg(duration, this._buffer.duration - offset); - //the values in seconds - startTime = this.toSeconds(startTime); - duration = this.toSeconds(duration); - //make the source - this._source = this.context.createBufferSource(); - this._source.buffer = this._buffer.get(); - //set the looping properties - if (this._loop) { - this._source.loop = this._loop; - this._source.loopStart = this.toSeconds(this._loopStart); - this._source.loopEnd = this.toSeconds(this._loopEnd); - // this fixes a bug in chrome 42 that breaks looping - // https://code.google.com/p/chromium/issues/detail?id=457099 - duration = 65536; - } else { - this._nextStop = startTime + duration; - } - //and other properties - this._source.playbackRate.value = this._playbackRate; - this._source.onended = this.onended; - this._source.connect(this.output); - //start it - this._source.start(startTime, offset, duration); - } else { - throw Error('tried to start Player before the buffer was loaded'); - } - return this; + var OmniOscType = { + PulseOscillator: 'PulseOscillator', + PWMOscillator: 'PWMOscillator', + Oscillator: 'Oscillator' }; /** - * Stop playback. + * start the oscillator + * @param {Time} [time=now] the time to start the oscillator * @private - * @param {Time} [time=now] - * @returns {Tone.Player} this */ - Tone.Player.prototype._stop = function (time) { - if (this._source) { - this._source.stop(this.toSeconds(time)); - this._source = null; - } - return this; + Tone.OmniOscillator.prototype._start = function (time) { + this._oscillator.start(time); }; /** - * Set the loop start and end. Will only loop if loop is - * set to true. - * @param {Time} loopStart The loop end time - * @param {Time} loopEnd The loop end time - * @returns {Tone.Player} this - * @example - * //loop 0.1 seconds of the file. - * player.setLoopPoints(0.2, 0.3); - * player.loop = true; + * start the oscillator + * @param {Time} [time=now] the time to start the oscillator + * @private */ - Tone.Player.prototype.setLoopPoints = function (loopStart, loopEnd) { - this.loopStart = loopStart; - this.loopEnd = loopEnd; - return this; + Tone.OmniOscillator.prototype._stop = function (time) { + this._oscillator.stop(time); }; /** - * If loop is true, the loop will start at this position. - * @memberOf Tone.Player# - * @type {Time} - * @name loopStart + * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse. + * @memberOf Tone.OmniOscillator# + * @type {string} + * @name type */ - Object.defineProperty(Tone.Player.prototype, 'loopStart', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'type', { get: function () { - return this._loopStart; + return this._oscillator.type; }, - set: function (loopStart) { - this._loopStart = loopStart; - if (this._source) { - this._source.loopStart = this.toSeconds(loopStart); + 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; + } else if (type === 'pwm') { + if (this._sourceType !== OmniOscType.PWMOscillator) { + this._sourceType = OmniOscType.PWMOscillator; + this._createNewOscillator(Tone.PWMOscillator); + } + } else if (type === 'pulse') { + if (this._sourceType !== OmniOscType.PulseOscillator) { + this._sourceType = OmniOscType.PulseOscillator; + this._createNewOscillator(Tone.PulseOscillator); + } + } else { + throw new Error('Tone.OmniOscillator does not support type ' + type); } } }); /** - * If loop is true, the loop will end at this position. - * @memberOf Tone.Player# - * @type {Time} - * @name loopEnd + * The partials of the 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.OmniOscillator# + * @type {Array} + * @name partials + * @example + * osc.partials = [1, 0.2, 0.01]; */ - Object.defineProperty(Tone.Player.prototype, 'loopEnd', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'partials', { get: function () { - return this._loopEnd; + return this._oscillator.partials; }, - set: function (loopEnd) { - this._loopEnd = loopEnd; - if (this._source) { - this._source.loopEnd = this.toSeconds(loopEnd); + set: function (partials) { + if (this._sourceType !== OmniOscType.Oscillator) { + this.type = Tone.Oscillator.Type.Custom; } + this._oscillator.partials = partials; } }); /** - * The audio buffer belonging to the player. - * @memberOf Tone.Player# - * @type {AudioBuffer} - * @name buffer + * connect the oscillator to the frequency and detune signals + * @private */ - Object.defineProperty(Tone.Player.prototype, 'buffer', { - get: function () { - return this._buffer; - }, - set: function (buffer) { - this._buffer.set(buffer); + 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); + } + }; /** - * If the buffer should loop once it's over. - * @memberOf Tone.Player# - * @type {boolean} - * @name loop + * The phase of the oscillator in degrees. + * @memberOf Tone.OmniOscillator# + * @type {Degrees} + * @name phase */ - Object.defineProperty(Tone.Player.prototype, 'loop', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'phase', { get: function () { - return this._loop; + return this._oscillator.phase; }, - set: function (loop) { - this._loop = loop; - if (this._source) { - this._source.loop = loop; - } + set: function (phase) { + this._oscillator.phase = phase; } }); /** - * The playback speed. 1 is normal speed. - * Note that this is not a Tone.Signal because of a bug in Blink. - * Please star [this issue](https://code.google.com/p/chromium/issues/detail?id=311284) - * if this an important thing to you. - * @memberOf Tone.Player# - * @type {number} - * @name playbackRate + * The width of the oscillator (only if the oscillator is set to pulse) + * @memberOf Tone.OmniOscillator# + * @type {NormalRange} + * @signal + * @name width + * @example + * var omniOsc = new Tone.OmniOscillator(440, "pulse"); + * //can access the width attribute only if type === "pulse" + * omniOsc.width.value = 0.2; */ - Object.defineProperty(Tone.Player.prototype, 'playbackRate', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'width', { get: function () { - return this._playbackRate; - }, - set: function (rate) { - this._playbackRate = rate; - if (this._source) { - this._source.playbackRate.value = rate; + if (this._sourceType === OmniOscType.PulseOscillator) { + return this._oscillator.width; } } }); /** - * The direction the buffer should play in - * @memberOf Tone.Player# - * @type {boolean} - * @name reverse + * The modulationFrequency Signal of the oscillator + * (only if the oscillator type is set to pwm). + * @memberOf Tone.OmniOscillator# + * @type {Frequency} + * @signal + * @name modulationFrequency + * @example + * var omniOsc = new Tone.OmniOscillator(440, "pwm"); + * //can access the modulationFrequency attribute only if type === "pwm" + * omniOsc.modulationFrequency.value = 0.2; */ - Object.defineProperty(Tone.Player.prototype, 'reverse', { + Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationFrequency', { get: function () { - return this._buffer.reverse; - }, - set: function (rev) { - this._buffer.reverse = rev; + if (this._sourceType === OmniOscType.PWMOscillator) { + return this._oscillator.modulationFrequency; + } } }); /** - * Dispose and disconnect. - * @return {Tone.Player} this + * Clean up. + * @return {Tone.OmniOscillator} this */ - Tone.Player.prototype.dispose = function () { + Tone.OmniOscillator.prototype.dispose = function () { Tone.Source.prototype.dispose.call(this); - if (this._source !== null) { - this._source.disconnect(); - this._source = null; - } - this._buffer.dispose(); - this._buffer = null; + this._writable([ + 'frequency', + 'detune' + ]); + this.detune.dispose(); + this.detune = null; + this.frequency.dispose(); + this.frequency = null; + this._oscillator.dispose(); + this._oscillator = null; + this._sourceType = null; return this; }; - return Tone.Player; + return Tone.OmniOscillator; }); 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 Base-class for all instruments + * * @constructor - * @extends {Tone.Instrument} - * @param {Object|string} urls the urls of the audio file - * @param {Object} [options] the options object for the synth - * @example - * var sampler = new Sampler({ - * A : { - * 1 : {"./audio/casio/A1.mp3", - * 2 : "./audio/casio/A2.mp3", - * }, - * "B.1" : "./audio/casio/B1.mp3", - * }).toMaster(); - * - * //listen for when all the samples have loaded - * Tone.Buffer.onload = function(){ - * sampler.triggerAttack("A.1", time, velocity); - * }; + * @extends {Tone} */ - Tone.Sampler = function (urls, options) { - options = this.defaultArg(options, Tone.Sampler.defaults); - Tone.Instrument.call(this, options); - /** - * The sample player. - * @type {Tone.Player} - */ - this.player = new Tone.Player(options.player); - this.player.retrigger = true; - /** - * the buffers - * @type {Object} - * @private - */ - this._buffers = {}; - /** - * The amplitude envelope. - * @type {Tone.AmplitudeEnvelope} - */ - this.envelope = new Tone.AmplitudeEnvelope(options.envelope); - /** - * The filter envelope. - * @type {Tone.ScaledEnvelope} - */ - this.filterEnvelope = new Tone.ScaledEnvelope(options.filterEnvelope); + Tone.Instrument = function (options) { + //get the defaults + options = this.defaultArg(options, Tone.Instrument.defaults); /** - * The name of the current sample. - * @type {string} + * The output and volume triming node + * @type {Tone.Volume} * @private */ - this._sample = options.sample; - /** - * the private reference to the pitch - * @type {number} - * @private - */ - this._pitch = options.pitch; + this._volume = this.output = new Tone.Volume(options.volume); /** - * The filter. - * @type {Tone.Filter} + * The volume of the output in decibels. + * @type {Decibels} + * @signal + * @example + * source.volume.value = -6; */ - 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.volume = this._volume.volume; + this._readOnly('volume'); }; - Tone.extend(Tone.Sampler, Tone.Instrument); + Tone.extend(Tone.Instrument); /** - * the default parameters - * @static + * the default attributes + * @type {object} */ - Tone.Sampler.defaults = { - 'sample': 0, - 'pitch': 0, - 'player': { 'loop': false }, - 'envelope': { - 'attack': 0.001, - 'decay': 0, - 'sustain': 1, - 'release': 0.1 - }, - 'filterEnvelope': { - 'attack': 0.001, - 'decay': 0.001, - 'sustain': 1, - 'release': 0.5, - 'min': 20, - 'max': 20000, - 'exponent': 2 - }, - 'filter': { 'type': 'lowpass' } + Tone.Instrument.defaults = { + /** the volume of the output in decibels */ + 'volume': 0 }; /** - * load the buffers - * @param {Object} urls the urls - * @private + * @abstract + * @param {string|number} note the note to trigger + * @param {Time} [time=now] the time to trigger the ntoe + * @param {number} [velocity=1] the velocity to trigger the note */ - Tone.Sampler.prototype._loadBuffers = function (urls) { - if (typeof urls === 'string') { - this._buffers['0'] = new Tone.Buffer(urls, function () { - this.sample = '0'; - }.bind(this)); - } else { - urls = this._flattenUrls(urls); - for (var buffName in urls) { - this._sample = buffName; - var urlString = urls[buffName]; - this._buffers[buffName] = new Tone.Buffer(urlString); - } - } - }; + Tone.Instrument.prototype.triggerAttack = Tone.noOp; /** - * Flatten an object into a single depth object. - * thanks to https://gist.github.com/penguinboy/762197 - * @param {Object} ob - * @return {Object} - * @private + * @abstract + * @param {Time} [time=now] when to trigger the release */ - Tone.Sampler.prototype._flattenUrls = function (ob) { - var toReturn = {}; - for (var i in ob) { - if (!ob.hasOwnProperty(i)) - continue; - if (typeof ob[i] == 'object') { - var flatObject = this._flattenUrls(ob[i]); - for (var x in flatObject) { - if (!flatObject.hasOwnProperty(x)) - continue; - toReturn[i + '.' + x] = flatObject[x]; - } - } else { - toReturn[i] = ob[i]; - } - } - return toReturn; - }; + Tone.Instrument.prototype.triggerRelease = Tone.noOp; /** - * Start the sample and simultaneously trigger the envelopes. - * @param {string=} sample The name of the sample to trigger, defaults to - * the last sample used. - * @param {Time} [time=now] The time when the sample should start - * @param {number} [velocity=1] The velocity of the note - * @returns {Tone.Sampler} this + * Trigger the attack and then the release after the duration. + * @param {Frequency} note The note to trigger. + * @param {Time} duration How long the note should be held for before + * triggering the release. + * @param {Time} [time=now] When the note should be triggered. + * @param {NormalRange} [velocity=1] The velocity the note should be triggered at. + * @returns {Tone.Instrument} this * @example - * sampler.triggerAttack("B.1"); + * //trigger "C4" for the duration of an 8th note + * synth.triggerAttackRelease("C4", "8n"); */ - Tone.Sampler.prototype.triggerAttack = function (name, time, velocity) { + Tone.Instrument.prototype.triggerAttackRelease = function (note, duration, time, velocity) { time = this.toSeconds(time); - if (name) { - this.sample = name; - } - this.player.start(time); - this.envelope.triggerAttack(time, velocity); - this.filterEnvelope.triggerAttack(time); + duration = this.toSeconds(duration); + this.triggerAttack(note, time, velocity); + this.triggerRelease(time + duration); return this; }; /** - * Start the release portion of the sample. Will stop the sample once the - * envelope has fully released. + * clean up + * @returns {Tone.Instrument} this + */ + Tone.Instrument.prototype.dispose = function () { + Tone.prototype.dispose.call(this); + this._volume.dispose(); + this._volume = null; + this._writable(['volume']); + this.volume = null; + return this; + }; + return Tone.Instrument; + }); + Module(function (Tone) { + + /** + * @class This is an abstract base class for other monophonic instruments to + * extend. IMPORTANT: It does not make any sound on its own and + * shouldn't be directly instantiated. + * + * @constructor + * @abstract + * @extends {Tone.Instrument} + */ + Tone.Monophonic = function (options) { + //get the defaults + options = this.defaultArg(options, Tone.Monophonic.defaults); + Tone.Instrument.call(this, options); + /** + * The glide time between notes. + * @type {Time} + */ + this.portamento = options.portamento; + }; + Tone.extend(Tone.Monophonic, Tone.Instrument); + /** + * @static + * @const + * @type {Object} + */ + Tone.Monophonic.defaults = { 'portamento': 0 }; + /** + * Trigger the attack of the note optionally with a given velocity. * - * @param {Time} [time=now] The time when the note should release - * @returns {Tone.Sampler} this + * + * @param {Frequency} note The note to trigger. + * @param {Time} [time=now] When the note should start. + * @param {number} [velocity=1] velocity The velocity scaler + * determines how "loud" the note + * will be triggered. + * @returns {Tone.Monophonic} this * @example - * sampler.triggerRelease(); + * synth.triggerAttack("C4"); + * @example + * //trigger the note a half second from now at half velocity + * synth.triggerAttack("C4", "+0.5", 0.5); */ - Tone.Sampler.prototype.triggerRelease = function (time) { + Tone.Monophonic.prototype.triggerAttack = function (note, time, velocity) { time = this.toSeconds(time); - this.filterEnvelope.triggerRelease(time); - this.envelope.triggerRelease(time); - this.player.stop(this.toSeconds(this.envelope.release) + time); + this._triggerEnvelopeAttack(time, velocity); + this.setNote(note, time); return this; }; /** - * The name of the sample to trigger. - * @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"; + * Trigger the release portion of the envelope + * @param {Time} [time=now] If no time is given, the release happens immediatly + * @returns {Tone.Monophonic} this + * @example + * synth.triggerRelease(); */ - Object.defineProperty(Tone.Sampler.prototype, 'sample', { - get: function () { - return this._sample; - }, - set: function (name) { - if (this._buffers.hasOwnProperty(name)) { - this._sample = name; - this.player.buffer = this._buffers[name]; - } else { - throw new Error('Sampler does not have a sample named ' + name); - } - } - }); + Tone.Monophonic.prototype.triggerRelease = function (time) { + this._triggerEnvelopeRelease(time); + return this; + }; /** - * The direction the buffer should play in - * @memberOf Tone.Sampler# - * @type {boolean} - * @name reverse + * override this method with the actual method + * @abstract + * @private */ - Object.defineProperty(Tone.Sampler.prototype, 'reverse', { - get: function () { - for (var i in this._buffers) { - return this._buffers[i].reverse; - } - }, - set: function (rev) { - for (var i in this._buffers) { - this._buffers[i].reverse = rev; - } - } - }); + Tone.Monophonic.prototype._triggerEnvelopeAttack = function () { + }; /** - * 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 + * override this method with the actual method + * @abstract + * @private */ - Object.defineProperty(Tone.Sampler.prototype, 'pitch', { - get: function () { - return this._pitch; - }, - set: function (interval) { - this._pitch = interval; - this.player.playbackRate = this.intervalToFrequencyRatio(interval); - } - }); + Tone.Monophonic.prototype._triggerEnvelopeRelease = function () { + }; /** - * Clean up. - * @returns {Tone.Sampler} this + * Set the note at the given time. If no time is given, the note + * will set immediately. + * @param {Frequency} note The note to change to. + * @param {Time} [time=now] The time when the note should be set. + * @returns {Tone.Monophonic} this + * @example + * //change to F#6 in one quarter note from now. + * synth.setNote("F#6", "+4n"); + * @example + * //change to Bb4 right now + * synth.setNote("Bb4"); */ - Tone.Sampler.prototype.dispose = function () { - Tone.Instrument.prototype.dispose.call(this); - this._writable([ - 'player', - 'filterEnvelope', - 'envelope', - 'filter' - ]); - this.player.dispose(); - this.filterEnvelope.dispose(); - this.envelope.dispose(); - this.filter.dispose(); - this.player = null; - this.filterEnvelope = null; - this.envelope = null; - this.filter = null; - for (var sample in this._buffers) { - this._buffers[sample].dispose(); - this._buffers[sample] = null; + Tone.Monophonic.prototype.setNote = function (note, time) { + time = this.toSeconds(time); + if (this.portamento > 0) { + var currentNote = this.frequency.value; + this.frequency.setValueAtTime(currentNote, time); + var portTime = this.toSeconds(this.portamento); + this.frequency.exponentialRampToValueAtTime(note, time + portTime); + } else { + this.frequency.setValueAtTime(note, time); } - this._buffers = null; return this; }; - return Tone.Sampler; + return Tone.Monophonic; }); Module(function (Tone) { /** - * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator - * routed through a Tone.AmplitudeEnvelope. - * - * + * @class Tone.MonoSynth is composed of one oscillator, one filter, and two envelopes. + * The amplitude of the Tone.Oscillator and the cutoff frequency of the + * Tone.Filter are controlled by Tone.Envelopes. + * + * * @constructor * @extends {Tone.Monophonic} * @param {Object} [options] the options available for the synth * see defaults below * @example - * var synth = new Tone.SimpleSynth().toMaster(); + * var synth = new Tone.MonoSynth({ + * "oscillator" : { + * "type" : "square" + * }, + * "envelope" : { + * "attack" : 0.1 + * } + * }).toMaster(); * synth.triggerAttackRelease("C4", "8n"); */ - Tone.SimpleSynth = function (options) { + Tone.MonoSynth = function (options) { //get the defaults - options = this.defaultArg(options, Tone.SimpleSynth.defaults); + options = this.defaultArg(options, Tone.MonoSynth.defaults); Tone.Monophonic.call(this, options); /** * The oscillator. @@ -11483,273 +16531,154 @@ * @signal */ this.detune = this.oscillator.detune; + /** + * 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 oscillators to the output - this.oscillator.chain(this.envelope, this.output); + this.oscillator.chain(this.filter, this.envelope, this.output); //start the oscillators this.oscillator.start(); + //connect the filter envelope + this.filterEnvelope.connect(this.filter.frequency); this._readOnly([ 'oscillator', 'frequency', 'detune', + 'filter', + 'filterEnvelope', 'envelope' ]); }; - Tone.extend(Tone.SimpleSynth, Tone.Monophonic); + Tone.extend(Tone.MonoSynth, Tone.Monophonic); /** * @const * @static * @type {Object} */ - Tone.SimpleSynth.defaults = { - 'oscillator': { 'type': 'triangle' }, + Tone.MonoSynth.defaults = { + 'frequency': 'C4', + 'detune': 0, + 'oscillator': { 'type': 'square' }, + 'filter': { + 'Q': 6, + 'type': 'lowpass', + 'rolloff': -24 + }, 'envelope': { 'attack': 0.005, 'decay': 0.1, - 'sustain': 0.3, + 'sustain': 0.9, 'release': 1 + }, + 'filterEnvelope': { + 'attack': 0.06, + 'decay': 0.2, + 'sustain': 0.5, + 'release': 2, + 'baseFrequency': 200, + 'octaves': 7, + 'exponent': 2 } }; /** * start the attack portion of the envelope * @param {Time} [time=now] the time the attack should start - * @param {number} [velocity=1] the velocity of the note (0-1) - * @returns {Tone.SimpleSynth} this + * @param {NormalRange} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.MonoSynth} this * @private */ - Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + Tone.MonoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { //the envelopes this.envelope.triggerAttack(time, velocity); + this.filterEnvelope.triggerAttack(time); return this; }; /** * start the release portion of the envelope * @param {Time} [time=now] the time the release should start - * @returns {Tone.SimpleSynth} this - * @private - */ - Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function (time) { - this.envelope.triggerRelease(time); - return this; - }; - /** - * clean up - * @returns {Tone.SimpleSynth} this - */ - Tone.SimpleSynth.prototype.dispose = function () { - Tone.Monophonic.prototype.dispose.call(this); - this._writable([ - 'oscillator', - 'frequency', - 'detune', - 'envelope' - ]); - this.oscillator.dispose(); - this.oscillator = null; - this.envelope.dispose(); - this.envelope = null; - this.frequency = null; - this.detune = null; - return this; - }; - return Tone.SimpleSynth; - }); - Module(function (Tone) { - - /** - * @class AMSynth uses the output of one Tone.SimpleSynth to modulate the - * amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between - * the two signals) affects the timbre of the output signal the most. - * Read more about Amplitude Modulation Synthesis on [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 + * @returns {Tone.MonoSynth} this * @private */ - Tone.SimpleAM.prototype._triggerEnvelopeRelease = function (time) { - this.carrier.triggerRelease(time); - this.modulator.triggerRelease(time); + Tone.MonoSynth.prototype._triggerEnvelopeRelease = function (time) { + this.envelope.triggerRelease(time); + this.filterEnvelope.triggerRelease(time); return this; }; /** * clean up - * @returns {Tone.SimpleAM} this + * @returns {Tone.MonoSynth} this */ - Tone.SimpleAM.prototype.dispose = function () { + Tone.MonoSynth.prototype.dispose = function () { Tone.Monophonic.prototype.dispose.call(this); this._writable([ - 'carrier', - 'modulator', + 'oscillator', 'frequency', - 'harmonicity' + 'detune', + 'filter', + 'filterEnvelope', + 'envelope' ]); - this.carrier.dispose(); - this.carrier = null; - this.modulator.dispose(); - this.modulator = null; - this.frequency.dispose(); + 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.harmonicity.dispose(); - this.harmonicity = null; - this._modulationScale.dispose(); - this._modulationScale = null; - this._modulationNode.disconnect(); - this._modulationNode = null; + this.detune = null; return this; }; - return Tone.SimpleAM; + return Tone.MonoSynth; }); 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). - * + * @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). + * * * @constructor * @extends {Tone.Monophonic} * @param {Object} [options] the options available for the synth - * see defaults below + * see defaults below * @example - * var fmSynth = new Tone.SimpleFM().toMaster(); - * fmSynth.triggerAttackRelease("C4", "8n"); + * var synth = new Tone.AMSynth().toMaster(); + * synth.triggerAttackRelease("C4", "4n"); */ - Tone.SimpleFM = function (options) { - options = this.defaultArg(options, Tone.SimpleFM.defaults); + Tone.AMSynth = function (options) { + options = this.defaultArg(options, Tone.AMSynth.defaults); Tone.Monophonic.call(this, options); /** * The carrier voice. - * @type {Tone.SimpleSynth} + * @type {Tone.MonoSynth} */ - this.carrier = new Tone.SimpleSynth(options.carrier); + this.carrier = new Tone.MonoSynth(options.carrier); this.carrier.volume.value = -10; /** * The modulator voice. - * @type {Tone.SimpleSynth} + * @type {Tone.MonoSynth} */ - this.modulator = new Tone.SimpleSynth(options.modulator); + this.modulator = new Tone.MonoSynth(options.modulator); this.modulator.volume.value = -10; /** - * the frequency control + * The frequency. * @type {Frequency} * @signal */ @@ -11766,14 +16695,11 @@ 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 + * convert the -1,1 output to 0,1 + * @type {Tone.AudioToGain} + * @private */ - this.modulationIndex = new Tone.Multiply(options.modulationIndex); - this.modulationIndex.units = Tone.Type.Positive; + this._modulationScale = new Tone.AudioToGain(); /** * the node where the modulation happens * @type {GainNode} @@ -11783,48 +16709,66 @@ //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.modulator.chain(this._modulationScale, this._modulationNode.gain); + this.carrier.chain(this._modulationNode, this.output); this._readOnly([ 'carrier', 'modulator', 'frequency', - 'harmonicity', - 'modulationIndex' + 'harmonicity' ]); - ; }; - Tone.extend(Tone.SimpleFM, Tone.Monophonic); + Tone.extend(Tone.AMSynth, Tone.Monophonic); /** * @static * @type {Object} */ - Tone.SimpleFM.defaults = { + Tone.AMSynth.defaults = { 'harmonicity': 3, - 'modulationIndex': 10, 'carrier': { 'volume': -10, - 'portamento': 0, 'oscillator': { 'type': 'sine' }, 'envelope': { 'attack': 0.01, - 'decay': 0, + '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 } }, 'modulator': { 'volume': -10, - 'portamento': 0, - 'oscillator': { 'type': 'triangle' }, + 'oscillator': { 'type': 'square' }, 'envelope': { - 'attack': 0.01, + '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 } } }; @@ -11832,42 +16776,43 @@ * 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 + * @param {NormalRange} [velocity=1] the velocity of the note * @private + * @returns {Tone.AMSynth} this */ - Tone.SimpleFM.prototype._triggerEnvelopeAttack = function (time, velocity) { + Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { //the port glide time = this.toSeconds(time); //the envelopes this.carrier.envelope.triggerAttack(time, velocity); this.modulator.envelope.triggerAttack(time); + this.carrier.filterEnvelope.triggerAttack(time); + this.modulator.filterEnvelope.triggerAttack(time); return this; }; /** * trigger the release portion of the note * * @param {Time} [time=now] the time the note will release - * @returns {Tone.SimpleFM} this * @private + * @returns {Tone.AMSynth} this */ - Tone.SimpleFM.prototype._triggerEnvelopeRelease = function (time) { + Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) { this.carrier.triggerRelease(time); this.modulator.triggerRelease(time); return this; }; /** * clean up - * @returns {Tone.SimpleFM} this + * @returns {Tone.AMSynth} this */ - Tone.SimpleFM.prototype.dispose = function () { + Tone.AMSynth.prototype.dispose = function () { Tone.Monophonic.prototype.dispose.call(this); this._writable([ 'carrier', 'modulator', 'frequency', - 'harmonicity', - 'modulationIndex' + 'harmonicity' ]); this.carrier.dispose(); this.carrier = null; @@ -11875,2384 +16820,2520 @@ this.modulator = null; this.frequency.dispose(); this.frequency = 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; return this; }; - return Tone.SimpleFM; - }); - 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.Effect.prototype.dispose = function () { - Tone.prototype.dispose.call(this); - this._dryWet.dispose(); - this._dryWet = null; - this.effectSend.disconnect(); - this.effectSend = null; - this.effectReturn.disconnect(); - this.effectReturn = null; - this._writable(['wet']); - this.wet = null; - return this; - }; - return Tone.Effect; + return Tone.AMSynth; }); 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.DrumSynth 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 + * envelope starts at note * .octaves and ramps to + * note over the duration of .pitchDecay. * * @constructor - * @extends {Tone.Effect} - * @param {Time|Object} [frequency] The rate of the LFO. - * @param {Frequency} [min] The lower value of the LFOs oscillation - * @param {Frequency} [max] The upper value of the LFOs oscillation. + * @extends {Tone.Instrument} + * @param {Object} [options] the options available for the synth + * see defaults below * @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(); + * var synth = new Tone.DrumSynth().toMaster(); + * synth.triggerAttackRelease("C2", "8n"); */ - Tone.AutoFilter = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'min', - 'max' - ], Tone.AutoFilter.defaults); - Tone.Effect.call(this, options); + Tone.DrumSynth = function (options) { + options = this.defaultArg(options, Tone.DrumSynth.defaults); + Tone.Instrument.call(this, options); /** - * the lfo which drives the filter cutoff - * @type {Tone.LFO} - * @private + * The oscillator. + * @type {Tone.Oscillator} */ - this._lfo = new Tone.LFO({ - 'frequency': options.frequency, - 'amplitude': options.depth, - 'min': options.min, - 'max': options.max - }); + this.oscillator = new Tone.Oscillator(options.oscillator).start(); /** - * The range of the filter modulating between the min and max frequency. - * 0 = no modulation. 1 = full modulation. - * @type {NormalRange} - * @signal + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} */ - this.depth = this._lfo.amplitude; + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); /** - * How fast the filter modulates between min and max. - * @type {Frequency} - * @signal + * The number of octaves the pitch envelope ramps. + * @type {Positive} */ - this.frequency = this._lfo.frequency; + this.octaves = options.octaves; /** - * The filter node - * @type {Tone.Filter} + * The amount of time the frequency envelope takes. + * @type {Time} */ - this.filter = new Tone.Filter(options.filter); - //connections - this.connectEffect(this.filter); - this._lfo.connect(this.filter.frequency); - this.type = options.type; + this.pitchDecay = options.pitchDecay; + this.oscillator.chain(this.envelope, this.output); this._readOnly([ - 'frequency', - 'depth' + 'oscillator', + 'envelope' ]); }; - //extend Effect - Tone.extend(Tone.AutoFilter, Tone.Effect); + Tone.extend(Tone.DrumSynth, Tone.Instrument); /** - * defaults * @static * @type {Object} */ - Tone.AutoFilter.defaults = { - 'frequency': 1, - 'type': 'sine', - 'depth': 1, - 'min': 200, - 'max': 1200, - 'filter': { - 'type': 'lowpass', - 'rolloff': -12, - 'Q': 1 + Tone.DrumSynth.defaults = { + 'pitchDecay': 0.05, + 'octaves': 10, + 'oscillator': { 'type': 'sine' }, + 'envelope': { + 'attack': 0.001, + 'decay': 0.4, + 'sustain': 0.01, + 'release': 1.4, + 'attackCurve': 'exponential' } }; /** - * Start the effect. - * @param {Time} [time=now] When the LFO will start. - * @returns {Tone.AutoFilter} this - */ - Tone.AutoFilter.prototype.start = function (time) { - this._lfo.start(time); - return this; - }; - /** - * Stop the effect. - * @param {Time} [time=now] When the LFO will stop. - * @returns {Tone.AutoFilter} this - */ - Tone.AutoFilter.prototype.stop = function (time) { - this._lfo.stop(time); - return this; - }; - /** - * Sync the filter to the transport. - * @param {Time} [delay=0] Delay time before starting the effect after the - * Transport has started. - * @returns {Tone.AutoFilter} this + * 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.AutoFilter.prototype.sync = function (delay) { - this._lfo.sync(delay); + 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; }; /** - * Unsync the filter from the transport. - * @returns {Tone.AutoFilter} this + * Trigger the release portion of the note. + * + * @param {Time} [time=now] the time the note will release + * @returns {Tone.DrumSynth} this */ - Tone.AutoFilter.prototype.unsync = function () { - this._lfo.unsync(); + Tone.DrumSynth.prototype.triggerRelease = function (time) { + this.envelope.triggerRelease(time); return 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 LFO attached to the cutoff frequency of the filter. - * @memberOf Tone.AutoFilter# - * @type {Frequency} - * @name min - */ - Object.defineProperty(Tone.AutoFilter.prototype, 'min', { - get: function () { - return this._lfo.min; - }, - set: function (min) { - this._lfo.min = min; - } - }); - /** - * The minimum value of the LFO attached to the cutoff frequency of the filter. - * @memberOf Tone.AutoFilter# - * @type {Frequency} - * @name max - */ - Object.defineProperty(Tone.AutoFilter.prototype, 'max', { - get: function () { - return this._lfo.max; - }, - set: function (max) { - this._lfo.max = max; - } - }); - /** - * Clean up. - * @returns {Tone.AutoFilter} this + * Clean up. + * @returns {Tone.DrumSynth} this */ - Tone.AutoFilter.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._lfo.dispose(); - this._lfo = null; - this.filter.dispose(); - this.filter = null; + Tone.DrumSynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); this._writable([ - 'frequency', - 'depth' + 'oscillator', + 'envelope' ]); - this.frequency = null; - this.depth = null; + this.oscillator.dispose(); + this.oscillator = null; + this.envelope.dispose(); + this.envelope = null; return this; }; - return Tone.AutoFilter; + return Tone.DrumSynth; }); 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 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.Effect} - * @param {Frequency|Object} [frequency] Rate of left-right oscillation. + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below * @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(); + * var duoSynth = new Tone.DuoSynth().toMaster(); + * duoSynth.triggerAttackRelease("C4", "2n"); */ - Tone.AutoPanner = function () { - var options = this.optionsObject(arguments, ['frequency'], Tone.AutoPanner.defaults); - Tone.Effect.call(this, options); + Tone.DuoSynth = function (options) { + options = this.defaultArg(options, Tone.DuoSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the lfo which drives the panning + * 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._lfo = new Tone.LFO({ - 'frequency': options.frequency, - 'amplitude': options.depth, - 'min': 0, - 'max': 1, - //start at the middle of the cycle - 'phase': 90 - }); + this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50); + this._vibrato.start(); /** - * The amount of panning between left and right. - * 0 = always center. 1 = full range between left and right. - * @type {NormalRange} + * the vibrato frequency + * @type {Frequency} * @signal */ - this.depth = this._lfo.amplitude; + this.vibratoRate = this._vibrato.frequency; /** - * the panner node which does the panning - * @type {Tone.Panner} + * 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._panner = new Tone.Panner(); + this._vibratoDelay = this.toSeconds(options.vibratoDelay); + /** + * the frequency control + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); /** - * How fast the panner modulates between left and right. - * @type {Frequency} - * @signal + * 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.frequency = this._lfo.frequency; - //connections - this.connectEffect(this._panner); - this._lfo.connect(this._panner.pan); - this.type = options.type; + 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([ - 'depth', - 'frequency' + 'voice0', + 'voice1', + 'frequency', + 'vibratoAmount', + 'vibratoRate' ]); }; - //extend Effect - Tone.extend(Tone.AutoPanner, Tone.Effect); + Tone.extend(Tone.DuoSynth, Tone.Monophonic); /** - * 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.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 + } + } }; /** - * 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 + * 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.AutoPanner.prototype.sync = function (delay) { - this._lfo.sync(delay); + 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; }; /** - * Unsync the panner from the transport - * @returns {Tone.AutoPanner} this + * start the release portion of the envelopes + * + * @param {Time} [time=now] the time the release should start + * @returns {Tone.DuoSynth} this + * @private */ - Tone.AutoPanner.prototype.unsync = function () { - this._lfo.unsync(); + Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) { + this.voice0.triggerRelease(time); + this.voice1.triggerRelease(time); return this; }; - /** - * 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; - } - }); /** * clean up - * @returns {Tone.AutoPanner} this + * @returns {Tone.DuoSynth} this */ - Tone.AutoPanner.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._lfo.dispose(); - this._lfo = null; - this._panner.dispose(); - this._panner = null; + Tone.DuoSynth.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); this._writable([ - 'depth', - 'frequency' + 'voice0', + 'voice1', + 'frequency', + 'vibratoAmount', + 'vibratoRate' ]); + this.voice0.dispose(); + this.voice0 = null; + this.voice1.dispose(); + this.voice1 = null; + this.frequency.dispose(); this.frequency = null; - this.depth = 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.AutoPanner; + return Tone.DuoSynth; }); 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 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). + * * * @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. + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below * @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") + * var fmSynth = new Tone.FMSynth().toMaster(); + * fmSynth.triggerAttackRelease("C5", "4n"); */ - Tone.AutoWah = 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); - /** - * @type {number} - * @private - */ - this._baseFrequency = options.baseFrequency; + Tone.FMSynth = function (options) { + options = this.defaultArg(options, Tone.FMSynth.defaults); + Tone.Monophonic.call(this, options); /** - * @type {number} - * @private + * The carrier voice. + * @type {Tone.MonoSynth} */ - this._octaves = options.octaves; + this.carrier = new Tone.MonoSynth(options.carrier); + this.carrier.volume.value = -10; /** - * the input gain to adjust the sensitivity - * @type {GainNode} - * @private + * The modulator voice. + * @type {Tone.MonoSynth} */ - this._inputBoost = this.context.createGain(); + this.modulator = new Tone.MonoSynth(options.modulator); + this.modulator.volume.value = -10; /** - * @type {BiquadFilterNode} - * @private - */ - this._bandpass = new Tone.Filter({ - 'rolloff': -48, - 'frequency': 0, - 'Q': options.Q - }); + * The frequency control. + * @type {Frequency} + * @signal + */ + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); /** - * @type {Tone.Filter} - * @private + * 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._peaking = new Tone.Filter(0, 'peaking'); - this._peaking.gain.value = options.gain; + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; /** - * The gain of the filter. - * @type {Gain} - * @signal + * 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.gain = this._peaking.gain; + this.modulationIndex = new Tone.Multiply(options.modulationIndex); + this.modulationIndex.units = Tone.Type.Positive; /** - * The quality of the filter. - * @type {Positive} - * @signal + * the node where the modulation happens + * @type {GainNode} + * @private */ - 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._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([ - 'gain', - 'Q' + 'carrier', + 'modulator', + 'frequency', + 'harmonicity', + 'modulationIndex' ]); }; - Tone.extend(Tone.AutoWah, Tone.Effect); + Tone.extend(Tone.FMSynth, Tone.Monophonic); /** * @static * @type {Object} */ - Tone.AutoWah.defaults = { - 'baseFrequency': 100, - 'octaves': 6, - 'sensitivity': 0, - 'Q': 2, - 'gain': 2, - 'follower': { - 'attack': 0.3, - 'release': 0.5 - } - }; - /** - * The number of octaves that the filter will sweep 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; + 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 + } }, - set: function (baseFreq) { - this._baseFrequency = baseFreq; - this._setSweepRange(); + '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 + } } - }); + }; /** - * The sensitivity to control how responsive to the input signal the filter is. - * @memberOf Tone.AutoWah# - * @type {Decibels} - * @name sensitivity + * 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 */ - 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); - } - }); + Tone.FMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + //the port glide + time = this.toSeconds(time); + //the envelopes + this.carrier.envelope.triggerAttack(time, velocity); + this.modulator.envelope.triggerAttack(time); + this.carrier.filterEnvelope.triggerAttack(time); + this.modulator.filterEnvelope.triggerAttack(time); + return this; + }; /** - * sets the sweep range of the scaler + * trigger the release portion of the note + * + * @param {Time} [time=now] the time the note will release + * @returns {Tone.FMSynth} this * @private */ - Tone.AutoWah.prototype._setSweepRange = function () { - this._sweepRange.min = this._baseFrequency; - this._sweepRange.max = Math.min(this._baseFrequency * Math.pow(2, this._octaves), this.context.sampleRate / 2); + Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) { + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); + return this; }; /** - * Clean up. - * @returns {Tone.AutoWah} this + * clean up + * @returns {Tone.FMSynth} 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; + Tone.FMSynth.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); this._writable([ - 'gain', - 'Q' + 'carrier', + 'modulator', + 'frequency', + 'harmonicity', + 'modulationIndex' ]); - this.gain = null; - this.Q = null; + 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.AutoWah; + return Tone.FMSynth; }); 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.Noise is a noise generator. It uses looped noise buffers to save on performance. + * Tone.Noise supports the noise types: "pink", "white", and "brown". Read more about + * colors of noise on [Wikipedia](https://en.wikipedia.org/wiki/Colors_of_noise). * * @constructor - * @extends {Tone.Effect} - * @param {Number} bits The number of bits to downsample the signal. Nominal range - * of 1 to 8. + * @extends {Tone.Source} + * @param {string} type the noise type (white|pink|brown) * @example - * //initialize crusher and route a synth through it - * var crusher = new Tone.BitCrusher(4).toMaster(); - * var synth = new Tone.MonoSynth().connect(crusher); + * //initialize the noise and start + * var noise = new Tone.Noise("pink").start(); + * + * //make an autofilter to shape the noise + * var autoFilter = new Tone.AutoFilter({ + * "frequency" : "8m", + * "min" : 800, + * "max" : 15000 + * }).connect(Tone.Master); + * + * //connect the noise + * noise.connect(autoFilter); + * //start the autofilter LFO + * autoFilter.start() */ - 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); + Tone.Noise = function () { + var options = this.optionsObject(arguments, ['type'], Tone.Noise.defaults); + Tone.Source.call(this, options); /** - * Subtract the input signal and the modulus of the input signal - * @type {Tone.Subtract} * @private + * @type {AudioBufferSourceNode} */ - this._subtract = new Tone.Subtract(); + this._source = null; /** - * The mod function - * @type {Tone.Modulo} + * the buffer * @private + * @type {AudioBuffer} */ - this._modulo = new Tone.Modulo(invStepSize); + this._buffer = null; /** - * keeps track of the bits - * @type {number} - * @private + * The playback rate of the noise. Affects + * the "frequency" of the noise. + * @type {Positive} + * @signal */ - 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); + this._playbackRate = options.playbackRate; + this.type = options.type; }; - Tone.extend(Tone.BitCrusher, Tone.Effect); + Tone.extend(Tone.Noise, Tone.Source); /** - * the default values + * the default parameters + * * @static + * @const * @type {Object} */ - Tone.BitCrusher.defaults = { 'bits': 4 }; + Tone.Noise.defaults = { + 'type': 'white', + 'playbackRate': 1 + }; /** - * The bit depth of the effect. Nominal range of 1-8. - * @memberOf Tone.BitCrusher# - * @type {number} - * @name bits + * The type of the noise. Can be "white", "brown", or "pink". + * @memberOf Tone.Noise# + * @type {string} + * @name type + * @example + * noise.type = "white"; */ - Object.defineProperty(Tone.BitCrusher.prototype, 'bits', { + Object.defineProperty(Tone.Noise.prototype, 'type', { get: function () { - return this._bits; + if (this._buffer === _whiteNoise) { + return 'white'; + } else if (this._buffer === _brownNoise) { + return 'brown'; + } else if (this._buffer === _pinkNoise) { + return 'pink'; + } }, - set: function (bits) { - this._bits = bits; - var invStepSize = 1 / Math.pow(2, bits - 1); - this._modulo.value = invStepSize; + set: function (type) { + if (this.type !== type) { + switch (type) { + case 'white': + this._buffer = _whiteNoise; + break; + case 'pink': + this._buffer = _pinkNoise; + break; + case 'brown': + this._buffer = _brownNoise; + break; + default: + throw new Error('invalid noise type: ' + type); + } + //if it's playing, stop and restart it + if (this.state === Tone.State.Started) { + var now = this.now() + this.blockTime; + //remove the listener + this._stop(now); + this._start(now); + } + } } }); /** - * Clean up. - * @returns {Tone.BitCrusher} this + * The playback rate of the noise. Affects + * the "frequency" of the noise. + * @type {Positive} + * @signal */ - 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) { - + Object.defineProperty(Tone.Noise.prototype, 'playbackRate', { + get: function () { + return this._playbackRate; + }, + set: function (rate) { + this._playbackRate = rate; + if (this._source) { + this._source.playbackRate.value = rate; + } + } + }); /** - * @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). + * internal start method * - * @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); + * @param {Time} time + * @private */ - Tone.Chebyshev = function () { - var options = this.optionsObject(arguments, ['order'], Tone.Chebyshev.defaults); - Tone.Effect.call(this); - /** - * @type {WaveShaperNode} - * @private - */ - this._shaper = new Tone.WaveShaper(4096); - /** - * holds onto the order of the filter - * @type {number} - * @private - */ - this._order = options.order; - this.connectEffect(this._shaper); - this.order = options.order; - this.oversample = options.oversample; + Tone.Noise.prototype._start = function (time) { + this._source = this.context.createBufferSource(); + this._source.buffer = this._buffer; + this._source.loop = true; + this._source.playbackRate.value = this._playbackRate; + this._source.connect(this.output); + this._source.start(this.toSeconds(time)); }; - Tone.extend(Tone.Chebyshev, Tone.Effect); /** - * @static - * @const - * @type {Object} + * internal stop method + * + * @param {Time} time + * @private */ - Tone.Chebyshev.defaults = { - 'order': 1, - 'oversample': 'none' + Tone.Noise.prototype._stop = function (time) { + if (this._source) { + this._source.stop(this.toSeconds(time)); + } }; /** - * 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 + * Clean up. + * @returns {Tone.Noise} this */ - 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); + Tone.Noise.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + if (this._source !== null) { + this._source.disconnect(); + this._source = null; } - return memo[degree]; + this._buffer = null; + return this; }; + /////////////////////////////////////////////////////////////////////////// + // THE BUFFERS + // borrowed heavily from http://noisehack.com/generate-noise-web-audio-api/ + /////////////////////////////////////////////////////////////////////////// /** - * 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 + * static noise buffers + * + * @static + * @private + * @type {AudioBuffer} */ - Object.defineProperty(Tone.Chebyshev.prototype, 'order', { - get: function () { - return this._order; - }, - set: function (order) { - this._order = order; - var curve = new Array(4096); - var len = curve.length; - for (var i = 0; i < len; ++i) { - var x = i * 2 / len - 1; - if (x === 0) { - //should output 0 when input is 0 - curve[i] = 0; - } else { - curve[i] = this._getCoefficient(x, order, {}); + var _pinkNoise = null, _brownNoise = null, _whiteNoise = null; + Tone._initAudioContext(function (audioContext) { + var sampleRate = audioContext.sampleRate; + //four seconds per buffer + var bufferLength = sampleRate * 4; + //fill the buffers + _pinkNoise = function () { + var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); + for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { + var channel = buffer.getChannelData(channelNum); + var b0, b1, b2, b3, b4, b5, b6; + b0 = b1 = b2 = b3 = b4 = b5 = b6 = 0; + for (var i = 0; i < bufferLength; i++) { + var white = Math.random() * 2 - 1; + b0 = 0.99886 * b0 + white * 0.0555179; + b1 = 0.99332 * b1 + white * 0.0750759; + b2 = 0.969 * b2 + white * 0.153852; + b3 = 0.8665 * b3 + white * 0.3104856; + b4 = 0.55 * b4 + white * 0.5329522; + b5 = -0.7616 * b5 - white * 0.016898; + channel[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362; + channel[i] *= 0.11; + // (roughly) compensate for gain + b6 = white * 0.115926; + } + } + return buffer; + }(); + _brownNoise = function () { + var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); + for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { + var channel = buffer.getChannelData(channelNum); + var lastOut = 0; + for (var i = 0; i < bufferLength; i++) { + var white = Math.random() * 2 - 1; + channel[i] = (lastOut + 0.02 * white) / 1.02; + lastOut = channel[i]; + channel[i] *= 3.5; // (roughly) compensate for gain + } + } + return buffer; + }(); + _whiteNoise = function () { + var buffer = audioContext.createBuffer(2, bufferLength, sampleRate); + for (var channelNum = 0; channelNum < buffer.numberOfChannels; channelNum++) { + var channel = buffer.getChannelData(channelNum); + for (var i = 0; i < bufferLength; i++) { + channel[i] = Math.random() * 2 - 1; } } - this._shaper.curve = curve; - } - }); - /** - * The oversampling of the effect. Can either be "none", "2x" or "4x". - * @memberOf Tone.Chebyshev# - * @type {string} - * @name oversample - */ - Object.defineProperty(Tone.Chebyshev.prototype, 'oversample', { - get: function () { - return this._shaper.oversample; - }, - set: function (oversampling) { - this._shaper.oversample = oversampling; - } + return buffer; + }(); }); - /** - * 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; + return Tone.Noise; }); Module(function (Tone) { /** - * @class Base class for Stereo effects. Provides effectSendL/R and effectReturnL/R. + * @class Tone.NoiseSynth is composed of a noise generator (Tone.Noise), one filter (Tone.Filter), + * and two envelopes (Tone.Envelop). One envelope controls the amplitude + * of the noise and the other is controls the cutoff frequency of the filter. + * * - * @constructor - * @extends {Tone.Effect} + * @constructor + * @extends {Tone.Instrument} + * @param {Object} [options] the options available for the synth + * see defaults below + * @example + * var noiseSynth = new Tone.NoiseSynth().toMaster(); + * noiseSynth.triggerAttackRelease("8n"); */ - Tone.StereoEffect = function () { - Tone.call(this); + Tone.NoiseSynth = function (options) { //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; + options = this.defaultArg(options, Tone.NoiseSynth.defaults); + Tone.Instrument.call(this, options); /** - * the effects send RIGHT - * @type {GainNode} - * @private + * The noise source. + * @type {Tone.Noise} + * @example + * noiseSynth.set("noise.type", "brown"); */ - this.effectSendR = this._split.right; + this.noise = new Tone.Noise(); /** - * the stereo effect merger - * @type {Tone.Merge} - * @private + * The filter. + * @type {Tone.Filter} */ - this._merge = new Tone.Merge(); + this.filter = new Tone.Filter(options.filter); /** - * the effect return LEFT - * @type {GainNode} - * @private + * The filter envelope. + * @type {Tone.FrequencyEnvelope} */ - this.effectReturnL = this._merge.left; + this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** - * the effect return RIGHT - * @type {GainNode} - * @private + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} */ - 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']); + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); + //connect the noise to the output + this.noise.chain(this.filter, this.envelope, this.output); + //start the noise + this.noise.start(); + //connect the filter envelope + this.filterEnvelope.connect(this.filter.frequency); + this._readOnly([ + 'noise', + 'filter', + 'filterEnvelope', + 'envelope' + ]); + }; + Tone.extend(Tone.NoiseSynth, Tone.Instrument); + /** + * @const + * @static + * @type {Object} + */ + Tone.NoiseSynth.defaults = { + 'noise': { 'type': 'white' }, + 'filter': { + 'Q': 6, + 'type': 'highpass', + 'rolloff': -24 + }, + 'envelope': { + 'attack': 0.005, + 'decay': 0.1, + 'sustain': 0 + }, + 'filterEnvelope': { + 'attack': 0.06, + 'decay': 0.2, + 'sustain': 0, + 'release': 2, + 'baseFrequency': 20, + 'octaves': 5 + } + }; + /** + * Start the attack portion of the envelopes. Unlike other + * instruments, Tone.NoiseSynth doesn't have a note. + * @param {Time} [time=now] the time the attack should start + * @param {number} [velocity=1] the velocity of the note (0-1) + * @returns {Tone.NoiseSynth} this + * @example + * noiseSynth.triggerAttack(); + */ + Tone.NoiseSynth.prototype.triggerAttack = function (time, velocity) { + //the envelopes + this.envelope.triggerAttack(time, velocity); + this.filterEnvelope.triggerAttack(time); + return this; + }; + /** + * Start the release portion of the envelopes. + * @param {Time} [time=now] the time the release should start + * @returns {Tone.NoiseSynth} this + */ + Tone.NoiseSynth.prototype.triggerRelease = function (time) { + this.envelope.triggerRelease(time); + this.filterEnvelope.triggerRelease(time); + return this; + }; + /** + * Trigger the attack and then the release. + * @param {Time} duration the duration of the note + * @param {Time} [time=now] the time of the attack + * @param {number} [velocity=1] the velocity + * @returns {Tone.NoiseSynth} this + */ + Tone.NoiseSynth.prototype.triggerAttackRelease = function (duration, time, velocity) { + time = this.toSeconds(time); + duration = this.toSeconds(duration); + this.triggerAttack(time, velocity); + this.triggerRelease(time + duration); + return this; }; - Tone.extend(Tone.StereoEffect, Tone.Effect); /** * Clean up. - * @returns {Tone.StereoEffect} this + * @returns {Tone.NoiseSynth} 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.NoiseSynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); + this._writable([ + 'noise', + 'filter', + 'filterEnvelope', + 'envelope' + ]); + this.noise.dispose(); + this.noise = null; + this.envelope.dispose(); + this.envelope = null; + this.filterEnvelope.dispose(); + this.filterEnvelope = null; + this.filter.dispose(); + this.filter = null; return this; }; - return Tone.StereoEffect; + return Tone.NoiseSynth; }); 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 Karplus-String string synthesis. Often out of tune. + * Will change when the AudioWorkerNode is available across + * browsers. + * * @constructor - * @extends {Tone.Effect} - * @param {NormalRange|Object} [feedback] The initial feedback value. + * @extends {Tone.Instrument} + * @param {Object} [options] see the defaults + * @example + * var plucky = new Tone.PluckSynth().toMaster(); + * plucky.triggerAttack("C4"); */ - Tone.FeedbackEffect = function () { - var options = this.optionsObject(arguments, ['feedback']); - options = this.defaultArg(options, Tone.FeedbackEffect.defaults); - Tone.Effect.call(this, options); + Tone.PluckSynth = function (options) { + options = this.defaultArg(options, Tone.PluckSynth.defaults); + Tone.Instrument.call(this, options); + /** + * @type {Tone.Noise} + * @private + */ + this._noise = new Tone.Noise('pink'); + /** + * The amount of noise at the attack. + * Nominal range of [0.1, 20] + * @type {number} + */ + this.attackNoise = 1; /** - * The amount of signal which is fed back into the effect input. + * the LFCF + * @type {Tone.LowpassCombFilter} + * @private + */ + this._lfcf = new Tone.LowpassCombFilter({ + 'resonance': options.resonance, + 'dampening': options.dampening + }); + /** + * The resonance control. * @type {NormalRange} * @signal */ - this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); + this.resonance = this._lfcf.resonance; /** - * the gain which controls the feedback - * @type {GainNode} - * @private + * The dampening control. i.e. the lowpass filter frequency of the comb filter + * @type {Frequency} + * @signal */ - 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.dampening = this._lfcf.dampening; + //connections + this._noise.connect(this._lfcf); + this._lfcf.connect(this.output); + this._readOnly([ + 'resonance', + 'dampening' + ]); }; - Tone.extend(Tone.FeedbackEffect, Tone.Effect); + Tone.extend(Tone.PluckSynth, Tone.Instrument); /** * @static + * @const * @type {Object} */ - Tone.FeedbackEffect.defaults = { 'feedback': 0.125 }; + Tone.PluckSynth.defaults = { + 'attackNoise': 1, + 'dampening': 4000, + 'resonance': 0.9 + }; + /** + * Trigger the note. + * @param {Frequency} note The note to trigger. + * @param {Time} [time=now] When the note should be triggered. + * @returns {Tone.PluckSynth} this + */ + Tone.PluckSynth.prototype.triggerAttack = function (note, time) { + note = this.toFrequency(note); + time = this.toSeconds(time); + var delayAmount = 1 / note; + this._lfcf.delayTime.setValueAtTime(delayAmount, time); + this._noise.start(time); + this._noise.stop(time + delayAmount * this.attackNoise); + return this; + }; /** * Clean up. - * @returns {Tone.FeedbackEffect} this + * @returns {Tone.PluckSynth} 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.PluckSynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); + this._noise.dispose(); + this._lfcf.dispose(); + this._noise = null; + this._lfcf = null; + this._writable([ + 'resonance', + 'dampening' + ]); + this.dampening = null; + this.resonance = null; return this; }; - return Tone.FeedbackEffect; + return Tone.PluckSynth; }); 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. + * @class Tone.PolySynth handles voice creation and allocation for any + * instruments passed in as the second paramter. PolySynth is + * not a synthesizer by itself, it merely manages voices of + * one of the other types of synths, allowing any of the + * monophonic synthesizers to be polyphonic. * - * @constructor - * @extends {Tone.FeedbackEffect} + * @constructor + * @extends {Tone.Instrument} + * @param {number|Object} [polyphony=4] The number of voices to create + * @param {function} [voice=Tone.MonoSynth] The constructor of the voices + * uses Tone.MonoSynth by default. + * @example + * //a polysynth composed of 6 Voices of MonoSynth + * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster(); + * //set the attributes using the set interface + * synth.set("detune", -1200); + * //play a chord + * synth.triggerAttackRelease(["C4", "E4", "A4"], "4n"); */ - Tone.StereoXFeedbackEffect = function () { - var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults); - Tone.StereoEffect.call(this, options); + Tone.PolySynth = function () { + Tone.Instrument.call(this); + var options = this.optionsObject(arguments, [ + 'polyphony', + 'voice' + ], Tone.PolySynth.defaults); /** - * The amount of feedback from the output - * back into the input of the effect (routed - * across left and right channels). - * @type {NormalRange} - * @signal + * the array of voices + * @type {Array} */ - this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange); + this.voices = new Array(options.polyphony); /** - * the left side feeback - * @type {GainNode} + * 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 * @private + * @type {Array} */ - this._feedbackLR = this.context.createGain(); + this._freeVoices = []; /** - * the right side feeback - * @type {GainNode} + * keeps track of which notes are down * @private + * @type {Object} */ - 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']); + this._activeVoices = {}; + //create the voices + for (var i = 0; i < options.polyphony; i++) { + var v = new options.voice(arguments[2], arguments[3]); + this.voices[i] = v; + v.connect(this.output); + } + //make a copy of the voices + this._freeVoices = this.voices.slice(0); //get the prototypes and properties + }; + Tone.extend(Tone.PolySynth, Tone.Instrument); + /** + * the defaults + * @const + * @static + * @type {Object} + */ + Tone.PolySynth.defaults = { + 'polyphony': 4, + 'voice': Tone.MonoSynth + }; + /** + * Trigger the attack portion of the note + * @param {Frequency|Array} notes The notes to play. Accepts a single + * Frequency or an array of frequencies. + * @param {Time} [time=now] The start time of the note. + * @param {number} [velocity=1] The velocity of the note. + * @returns {Tone.PolySynth} this + * @example + * //trigger a chord immediately with a velocity of 0.2 + * poly.triggerAttack(["Ab3", "C4", "F5"], undefined, 0.2); + */ + Tone.PolySynth.prototype.triggerAttack = function (notes, time, velocity) { + if (!Array.isArray(notes)) { + notes = [notes]; + } + 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; + } + } + } + return this; + }; + /** + * Trigger the attack and release after the specified duration + * + * @param {Frequency|Array} notes The notes to play. Accepts a single + * Frequency or an array of frequencies. + * @param {Time} duration the duration of the note + * @param {Time} [time=now] if no time is given, defaults to now + * @param {number} [velocity=1] the velocity of the attack (0-1) + * @returns {Tone.PolySynth} this + * @example + * //trigger a chord for a duration of a half note + * poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n"); + */ + 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)); + return this; + }; + /** + * Trigger the release of the note. Unlike monophonic instruments, + * a note (or array of notes) needs to be passed in as the first argument. + * @param {Frequency|Array} notes The notes to play. Accepts a single + * Frequency or an array of frequencies. + * @param {Time} [time=now] When the release will be triggered. + * @returns {Tone.PolySynth} this + * @example + * poly.triggerRelease(["Ab3", "C4", "F5"], "+2n"); + */ + Tone.PolySynth.prototype.triggerRelease = function (notes, time) { + if (!Array.isArray(notes)) { + notes = [notes]; + } + 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; + } + } + return this; + }; + /** + * Set a member/attribute of the voices. + * @param {Object|string} params + * @param {number=} value + * @param {Time=} rampTime + * @returns {Tone.PolySynth} this + * @example + * poly.set({ + * "filter" : { + * "type" : "highpass" + * }, + * "envelope" : { + * "attack" : 0.25 + * } + * }); + */ + Tone.PolySynth.prototype.set = function (params, value, rampTime) { + for (var i = 0; i < this.voices.length; i++) { + this.voices[i].set(params, value, rampTime); + } + return this; + }; + /** + * Get the synth's attributes. Given no arguments get + * will return all available object properties and their corresponding + * values. Pass in a single attribute to retrieve or an array + * of attributes. The attribute strings can also include a "." + * to access deeper properties. + * @param {Array=} params the parameters to get, otherwise will return + * all available. + */ + Tone.PolySynth.prototype.get = function (params) { + return this.voices[0].get(params); }; - Tone.extend(Tone.StereoXFeedbackEffect, Tone.FeedbackEffect); /** - * clean up - * @returns {Tone.StereoXFeedbackEffect} this + * Trigger the release portion of all the currently active voices. + * @param {Time} [time=now] When the notes should be released. + * @return {Tone.PolySynth} this */ - Tone.StereoXFeedbackEffect.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - this._writable(['feedback']); - this.feedback.dispose(); - this.feedback = null; - this._feedbackLR.disconnect(); - this._feedbackLR = null; - this._feedbackRL.disconnect(); - this._feedbackRL = null; + Tone.PolySynth.prototype.releaseAll = function (time) { + for (var i = 0; i < this.voices.length; i++) { + this.voices[i].triggerRelease(time); + } return this; }; - return Tone.StereoXFeedbackEffect; + /** + * Clean up. + * @returns {Tone.PolySynth} this + */ + Tone.PolySynth.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); + for (var i = 0; i < this.voices.length; i++) { + this.voices[i].dispose(); + this.voices[i] = null; + } + this.voices = null; + this._activeVoices = null; + this._freeVoices = null; + return this; + }; + return Tone.PolySynth; }); Module(function (Tone) { /** - * @class 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.StereoXFeedbackEffect} - * @param {Frequency|Object} [frequency] The frequency of the LFO. - * @param {Number} [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"); + * @class Tone.Player is an audio file player with start, loop, and stop functions. + * + * @constructor + * @extends {Tone.Source} + * @param {string|AudioBuffer} url Either the AudioBuffer or the url from + * which to load the AudioBuffer + * @param {function=} onload The function to invoke when the buffer is loaded. + * Recommended to use Tone.Buffer.onload instead. + * @example + * var player = new Tone.Player("./path/to/sample.mp3").toMaster(); + * Tone.Buffer.onload = function(){ + * player.start(); + * } */ - Tone.Chorus = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'delayTime', - 'depth' - ], Tone.Chorus.defaults); - Tone.StereoXFeedbackEffect.call(this, options); + Tone.Player = function (url) { + var options; + if (url instanceof Tone.Buffer) { + url = url.get(); + options = Tone.Player.defaults; + } else { + options = this.optionsObject(arguments, [ + 'url', + 'onload' + ], Tone.Player.defaults); + } + Tone.Source.call(this, options); /** - * the depth of the chorus - * @type {number} * @private + * @type {AudioBufferSourceNode} */ - this._depth = options.depth; + this._source = null; /** - * the delayTime - * @type {number} + * If the file should play as soon + * as the buffer is loaded. + * @type {boolean} + * @example + * //will play as soon as it's loaded + * var player = new Tone.Player({ + * "url" : "./path/to/sample.mp3", + * "autostart" : true, + * }).toMaster(); + */ + this.autostart = options.autostart; + /** + * the buffer * @private + * @type {Tone.Buffer} */ - this._delayTime = options.delayTime / 1000; + this._buffer = new Tone.Buffer({ + 'url': options.url, + 'onload': this._onload.bind(this, options.onload), + 'reverse': options.reverse + }); + if (url instanceof AudioBuffer) { + this._buffer.set(url); + } /** - * the lfo which controls the delayTime - * @type {Tone.LFO} + * if the buffer should loop once it's over + * @type {boolean} * @private */ - this._lfoL = new Tone.LFO(options.rate, 0, 1); + this._loop = options.loop; /** - * another LFO for the right side with a 180 degree phase diff - * @type {Tone.LFO} + * if 'loop' is true, the loop will start at this position + * @type {Time} * @private */ - this._lfoR = new Tone.LFO(options.rate, 0, 1); - this._lfoR.phase = 180; + this._loopStart = options.loopStart; /** - * delay for left - * @type {DelayNode} + * if 'loop' is true, the loop will end at this position + * @type {Time} * @private */ - this._delayNodeL = this.context.createDelay(); + this._loopEnd = options.loopEnd; /** - * delay for right - * @type {DelayNode} + * the playback rate * @private + * @type {number} */ - this._delayNodeR = this.context.createDelay(); + this._playbackRate = options.playbackRate; /** - * The frequency of the LFO which modulates the delayTime. - * @type {Frequency} - * @signal + * Enabling retrigger will allow a player to be restarted + * before the the previous 'start' is done playing. Otherwise, + * successive calls to Tone.Player.start will only start + * the sample if it had played all the way through. + * @type {boolean} */ - this.frequency = this._lfoL.frequency; - //connections - this.connectSeries(this.effectSendL, this._delayNodeL, this.effectReturnL); - this.connectSeries(this.effectSendR, this._delayNodeR, this.effectReturnR); - //and pass through to make the detune apparent - this.input.connect(this.output); - //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.retrigger = options.retrigger; + }; + Tone.extend(Tone.Player, Tone.Source); + /** + * the default parameters + * @static + * @const + * @type {Object} + */ + Tone.Player.defaults = { + 'onload': Tone.noOp, + 'playbackRate': 1, + 'loop': false, + 'autostart': false, + 'loopStart': 0, + 'loopEnd': 0, + 'retrigger': false, + 'reverse': false + }; + /** + * Load the audio file as an audio buffer. + * Decodes the audio asynchronously and invokes + * the callback once the audio buffer loads. + * Note: this does not need to be called if a url + * was passed in to the constructor. Only use this + * if you want to manually load a new url. + * @param {string} url The url of the buffer to load. + * Filetype support depends on the + * browser. + * @param {function=} callback The function to invoke once + * the sample is loaded. + * @returns {Tone.Player} this + */ + Tone.Player.prototype.load = function (url, callback) { + this._buffer.load(url, this._onload.bind(this, callback)); + return this; + }; + /** + * Internal callback when the buffer is loaded. + * @private + */ + Tone.Player.prototype._onload = function (callback) { + callback(this); + if (this.autostart) { + this.start(); + } + }; + /** + * play the buffer between the desired positions + * + * @private + * @param {Time} [startTime=now] when the player should start. + * @param {Time} [offset=0] the offset from the beginning of the sample + * to start at. + * @param {Time=} duration how long the sample should play. If no duration + * is given, it will default to the full length + * of the sample (minus any offset) + * @returns {Tone.Player} this + */ + Tone.Player.prototype._start = function (startTime, offset, duration) { + if (this._buffer.loaded) { + //if it's a loop the default offset is the loopstart point + if (this._loop) { + offset = this.defaultArg(offset, this._loopStart); + } else { + //otherwise the default offset is 0 + offset = this.defaultArg(offset, 0); + } + offset = this.toSeconds(offset); + duration = this.defaultArg(duration, this._buffer.duration - offset); + //the values in seconds + startTime = this.toSeconds(startTime); + duration = this.toSeconds(duration); + //make the source + this._source = this.context.createBufferSource(); + this._source.buffer = this._buffer.get(); + //set the looping properties + if (this._loop) { + this._source.loop = this._loop; + this._source.loopStart = this.toSeconds(this._loopStart); + this._source.loopEnd = this.toSeconds(this._loopEnd); + } else { + //if it's not looping, set the state change at the end of the sample + this._state.setStateAtTime(Tone.State.Stopped, startTime + duration); + } + //and other properties + this._source.playbackRate.value = this._playbackRate; + this._source.connect(this.output); + //start it + if (this._loop) { + this._source.start(startTime, offset); + } else { + this._source.start(startTime, offset, duration); + } + } else { + throw Error('tried to start Player before the buffer was loaded'); + } + return this; + }; + /** + * Stop playback. + * @private + * @param {Time} [time=now] + * @returns {Tone.Player} this + */ + Tone.Player.prototype._stop = function (time) { + if (this._source) { + this._source.stop(this.toSeconds(time)); + this._source = null; + } + return this; }; - Tone.extend(Tone.Chorus, Tone.StereoXFeedbackEffect); /** - * @static - * @type {Object} + * Set the loop start and end. Will only loop if loop is + * set to true. + * @param {Time} loopStart The loop end time + * @param {Time} loopEnd The loop end time + * @returns {Tone.Player} this + * @example + * //loop 0.1 seconds of the file. + * player.setLoopPoints(0.2, 0.3); + * player.loop = true; */ - Tone.Chorus.defaults = { - 'frequency': 1.5, - 'delayTime': 3.5, - 'depth': 0.7, - 'feedback': 0.1, - 'type': 'sine' + Tone.Player.prototype.setLoopPoints = function (loopStart, loopEnd) { + this.loopStart = loopStart; + this.loopEnd = loopEnd; + return this; }; /** - * 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 + * If loop is true, the loop will start at this position. + * @memberOf Tone.Player# + * @type {Time} + * @name loopStart */ - Object.defineProperty(Tone.Chorus.prototype, 'depth', { + Object.defineProperty(Tone.Player.prototype, 'loopStart', { get: function () { - return this._depth; + return this._loopStart; }, - 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; + set: function (loopStart) { + this._loopStart = loopStart; + if (this._source) { + this._source.loopStart = this.toSeconds(loopStart); + } } }); /** - * 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 {Number} - * @name delayTime + * If loop is true, the loop will end at this position. + * @memberOf Tone.Player# + * @type {Time} + * @name loopEnd */ - Object.defineProperty(Tone.Chorus.prototype, 'delayTime', { + Object.defineProperty(Tone.Player.prototype, 'loopEnd', { get: function () { - return this._delayTime * 1000; + return this._loopEnd; }, - set: function (delayTime) { - this._delayTime = delayTime / 1000; - this.depth = this._depth; + set: function (loopEnd) { + this._loopEnd = loopEnd; + if (this._source) { + this._source.loopEnd = this.toSeconds(loopEnd); + } } }); /** - * The oscillator type of the LFO. - * @memberOf Tone.Chorus# - * @type {string} - * @name type + * The audio buffer belonging to the player. + * @memberOf Tone.Player# + * @type {Tone.Buffer} + * @name buffer */ - Object.defineProperty(Tone.Chorus.prototype, 'type', { + Object.defineProperty(Tone.Player.prototype, 'buffer', { get: function () { - return this._lfoL.type; + return this._buffer; }, - set: function (type) { - this._lfoL.type = type; - this._lfoR.type = type; + set: function (buffer) { + this._buffer.set(buffer); } }); /** - * Clean up. - * @returns {Tone.Chorus} this - */ - Tone.Chorus.prototype.dispose = function () { - Tone.StereoXFeedbackEffect.prototype.dispose.call(this); - this._lfoL.dispose(); - this._lfoL = null; - this._lfoR.dispose(); - this._lfoR = null; - this._delayNodeL.disconnect(); - this._delayNodeL = null; - this._delayNodeR.disconnect(); - this._delayNodeR = null; - this._writable('frequency'); - this.frequency = null; - return this; - }; - return Tone.Chorus; - }); - Module(function (Tone) { - - /** - * @class 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 {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"); - * }; - */ - Tone.Convolver = function () { - var options = this.optionsObject(arguments, ['url'], Tone.Convolver.defaults); - Tone.Effect.call(this, options); - /** - * convolver node - * @type {ConvolverNode} - * @private - */ - this._convolver = this.context.createConvolver(); - /** - * the convolution buffer - * @type {Tone.Buffer} - * @private - */ - this._buffer = new Tone.Buffer(options.url, function (buffer) { - this.buffer = buffer; - options.onload(); - }.bind(this)); - this.connectEffect(this._convolver); - }; - Tone.extend(Tone.Convolver, Tone.Effect); - /** - * @static - * @const - * @type {Object} - */ - Tone.Convolver.defaults = { - 'url': '', - 'onload': Tone.noOp - }; - /** - * The convolver's buffer - * @memberOf Tone.Convolver# - * @type {AudioBuffer} - * @name buffer + * If the buffer should loop once it's over. + * @memberOf Tone.Player# + * @type {boolean} + * @name loop */ - Object.defineProperty(Tone.Convolver.prototype, 'buffer', { + Object.defineProperty(Tone.Player.prototype, 'loop', { get: function () { - return this._buffer.get(); + return this._loop; }, - set: function (buffer) { - this._buffer.set(buffer); - this._convolver.buffer = this._buffer.get(); + set: function (loop) { + this._loop = loop; + if (this._source) { + this._source.loop = loop; + } } }); /** - * 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.Convolver} this - */ - Tone.Convolver.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._convolver.disconnect(); - this._convolver = null; - this._buffer.dispose(); - this._buffer = null; - return this; - }; - return Tone.Convolver; - }); - Module(function (Tone) { - - /** - * @class 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 {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"); - */ - Tone.Distortion = function () { - var options = this.optionsObject(arguments, ['distortion'], Tone.Distortion.defaults); - Tone.Effect.call(this, options); - /** - * @type {Tone.WaveShaper} - * @private - */ - this._shaper = new Tone.WaveShaper(4096); - /** - * holds the distortion amount - * @type {number} - * @private - */ - this._distortion = options.distortion; - this.connectEffect(this._shaper); - this.distortion = options.distortion; - this.oversample = options.oversample; - }; - Tone.extend(Tone.Distortion, Tone.Effect); - /** - * @static - * @const - * @type {Object} - */ - Tone.Distortion.defaults = { - 'distortion': 0.4, - 'oversample': 'none' - }; - /** - * The amount of distortion. Range between 0-1. - * @memberOf Tone.Distortion# + * The playback speed. 1 is normal speed. This is not a signal because + * Safari and iOS currently don't support playbackRate as a signal. + * @memberOf Tone.Player# * @type {number} - * @name distortion + * @name playbackRate */ - Object.defineProperty(Tone.Distortion.prototype, 'distortion', { + Object.defineProperty(Tone.Player.prototype, 'playbackRate', { get: function () { - return this._distortion; + return this._playbackRate; }, - 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 (rate) { + this._playbackRate = rate; + if (this._source) { + this._source.playbackRate.value = rate; + } } }); /** - * The oversampling of the effect. Can either be "none", "2x" or "4x". - * @memberOf Tone.Distortion# - * @type {string} - * @name oversample + * The direction the buffer should play in + * @memberOf Tone.Player# + * @type {boolean} + * @name reverse */ - Object.defineProperty(Tone.Distortion.prototype, 'oversample', { + Object.defineProperty(Tone.Player.prototype, 'reverse', { get: function () { - return this._shaper.oversample; + return this._buffer.reverse; }, - set: function (oversampling) { - this._shaper.oversample = oversampling; + set: function (rev) { + this._buffer.reverse = rev; } }); /** - * Clean up. - * @returns {Tone.Distortion} this + * Dispose and disconnect. + * @return {Tone.Player} this */ - Tone.Distortion.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._shaper.dispose(); - this._shaper = null; + Tone.Player.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + if (this._source !== null) { + this._source.disconnect(); + this._source = null; + } + this._buffer.dispose(); + this._buffer = null; return this; }; - return Tone.Distortion; + return Tone.Player; }); Module(function (Tone) { /** - * @class Tone.FeedbackDelay is a DelayNode in which part of output - * signal is fed back into the delay. + * @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). + * * * @constructor - * @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. + * @extends {Tone.Instrument} + * @param {Object|string} urls the urls of the audio file + * @param {Object} [options] the options object for the synth * @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 sampler = new Sampler({ + * A : { + * 1 : "./audio/casio/A1.mp3", + * 2 : "./audio/casio/A2.mp3", + * }, + * "B.1" : "./audio/casio/B1.mp3", + * }).toMaster(); + * + * //listen for when all the samples have loaded + * Tone.Buffer.onload = function(){ + * sampler.triggerAttack("A.1", time, velocity); + * }; */ - Tone.FeedbackDelay = function () { - var options = this.optionsObject(arguments, [ - 'delayTime', - 'feedback' - ], Tone.FeedbackDelay.defaults); - Tone.FeedbackEffect.call(this, options); + Tone.Sampler = function (urls, options) { + options = this.defaultArg(options, Tone.Sampler.defaults); + Tone.Instrument.call(this, options); /** - * The delayTime of the DelayNode. - * @type {Time} - * @signal + * The sample player. + * @type {Tone.Player} */ - this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time); + this.player = new Tone.Player(options.player); + this.player.retrigger = true; /** - * the delay node - * @type {DelayNode} + * the buffers + * @type {Object} * @private */ - this._delayNode = this.context.createDelay(4); - // connect it up - this.connectEffect(this._delayNode); - this.delayTime.connect(this._delayNode.delayTime); - this._readOnly(['delayTime']); - }; - Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); - /** - * The default values. - * @const - * @static - * @type {Object} - */ - Tone.FeedbackDelay.defaults = { 'delayTime': 0.25 }; - /** - * clean up - * @returns {Tone.FeedbackDelay} this - */ - Tone.FeedbackDelay.prototype.dispose = function () { - Tone.FeedbackEffect.prototype.dispose.call(this); - this.delayTime.dispose(); - this._delayNode.disconnect(); - this._delayNode = null; - this._writable(['delayTime']); - this.delayTime = null; - return this; - }; - return Tone.FeedbackDelay; - }); - Module(function (Tone) { - - /** - * an array of comb filter delay values from Freeverb implementation - * @static - * @private - * @type {Array} - */ - var combFilterTunings = [ - 1557 / 44100, - 1617 / 44100, - 1491 / 44100, - 1422 / 44100, - 1277 / 44100, - 1356 / 44100, - 1188 / 44100, - 1116 / 44100 - ]; - /** - * an array of allpass filter frequency values from Freeverb implementation - * @private - * @static - * @type {Array} - */ - var allpassFilterFrequencies = [ - 225, - 556, - 441, - 341 - ]; - /** - * @class 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); - */ - Tone.Freeverb = function () { - var options = this.optionsObject(arguments, [ - 'roomSize', - 'dampening' - ], Tone.Freeverb.defaults); - Tone.StereoEffect.call(this, options); - /** - * The roomSize value between. A larger roomSize - * will result in a longer decay. - * @type {NormalRange} - * @signal - */ - this.roomSize = new Tone.Signal(options.roomSize, Tone.Type.NormalRange); + this._buffers = {}; /** - * The amount of dampening of the reverberant signal. - * @type {Frequency} - * @signal + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} */ - this.dampening = new Tone.Signal(options.dampening, Tone.Type.Frequency); + this.envelope = new Tone.AmplitudeEnvelope(options.envelope); /** - * the comb filters - * @type {Array} - * @private + * The filter envelope. + * @type {Tone.FrequencyEnvelope} */ - this._combFilters = []; + this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope); /** - * the allpass filters on the left - * @type {Array} + * The name of the current sample. + * @type {string} * @private */ - this._allpassFiltersL = []; + this._sample = options.sample; /** - * the allpass filters on the right - * @type {Array} - * @private + * the private reference to the pitch + * @type {number} + * @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._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([ - 'roomSize', - 'dampening' + 'player', + 'filterEnvelope', + 'envelope', + 'filter' ]); }; - Tone.extend(Tone.Freeverb, Tone.StereoEffect); + Tone.extend(Tone.Sampler, Tone.Instrument); /** + * the default parameters * @static - * @type {Object} */ - Tone.Freeverb.defaults = { - 'roomSize': 0.7, - 'dampening': 3000 + Tone.Sampler.defaults = { + 'sample': 0, + 'pitch': 0, + 'player': { 'loop': false }, + 'envelope': { + 'attack': 0.001, + 'decay': 0, + 'sustain': 1, + 'release': 0.1 + }, + 'filterEnvelope': { + 'attack': 0.001, + 'decay': 0.001, + 'sustain': 1, + 'release': 0.5, + 'baseFrequency': 20, + 'octaves': 10 + }, + 'filter': { 'type': 'lowpass' } }; /** - * Clean up. - * @returns {Tone.Freeverb} this + * load the buffers + * @param {Object} urls the urls + * @private */ - Tone.Freeverb.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - for (var al = 0; al < this._allpassFiltersL.length; al++) { - this._allpassFiltersL[al].disconnect(); - this._allpassFiltersL[al] = null; - } - this._allpassFiltersL = null; - for (var ar = 0; ar < this._allpassFiltersR.length; ar++) { - this._allpassFiltersR[ar].disconnect(); - this._allpassFiltersR[ar] = null; - } - this._allpassFiltersR = null; - for (var cf = 0; cf < this._combFilters.length; cf++) { - this._combFilters[cf].dispose(); - this._combFilters[cf] = null; + 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); + } } - this._combFilters = null; - this._writable([ - 'roomSize', - 'dampening' - ]); - this.roomSize.dispose(); - this.roomSize = null; - this.dampening.dispose(); - this.dampening = null; - return this; }; - return Tone.Freeverb; - }); - Module(function (Tone) { - /** - * an array of the comb filter delay time values + * Flatten an object into a single depth object. + * thanks to https://gist.github.com/penguinboy/762197 + * @param {Object} ob + * @return {Object} * @private - * @static - * @type {Array} */ - var combFilterDelayTimes = [ - 1687 / 25000, - 1601 / 25000, - 2053 / 25000, - 2251 / 25000 - ]; + 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; + }; /** - * the resonances of each of the comb filters - * @private - * @static - * @type {Array} + * Start the sample and simultaneously trigger the envelopes. + * @param {string=} sample The name of the sample to trigger, defaults to + * the last sample used. + * @param {Time} [time=now] The time when the sample should start + * @param {number} [velocity=1] The velocity of the note + * @returns {Tone.Sampler} this + * @example + * sampler.triggerAttack("B.1"); */ - var combFilterResonances = [ - 0.773, - 0.802, - 0.753, - 0.733 - ]; + Tone.Sampler.prototype.triggerAttack = function (name, time, velocity) { + time = this.toSeconds(time); + if (name) { + this.sample = name; + } + this.player.start(time); + this.envelope.triggerAttack(time, velocity); + this.filterEnvelope.triggerAttack(time); + return this; + }; /** - * the allpass filter frequencies - * @private - * @static - * @type {Array} + * Start the release portion of the sample. Will stop the sample once the + * envelope has fully released. + * + * @param {Time} [time=now] The time when the note should release + * @returns {Tone.Sampler} this + * @example + * sampler.triggerRelease(); */ - var allpassFilterFreqs = [ - 347, - 113, - 37 - ]; + 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; + }; /** - * @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"); + * The name of the sample to trigger. + * @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"; */ - 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); + Object.defineProperty(Tone.Sampler.prototype, 'sample', { + get: function () { + return this._sample; + }, + set: function (name) { + if (this._buffers.hasOwnProperty(name)) { + this._sample = name; + this.player.buffer = this._buffers[name]; } else { - fbcf.connect(this.effectReturnR); + throw new Error('Sampler does not have a sample named ' + name); + } + } + }); + /** + * The direction the buffer should play in + * @memberOf Tone.Sampler# + * @type {boolean} + * @name reverse + */ + Object.defineProperty(Tone.Sampler.prototype, 'reverse', { + get: function () { + for (var i in this._buffers) { + return this._buffers[i].reverse; + } + }, + set: function (rev) { + for (var i in this._buffers) { + this._buffers[i].reverse = rev; } - 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} + * 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 */ - Tone.JCReverb.defaults = { 'roomSize': 0.5 }; + Object.defineProperty(Tone.Sampler.prototype, 'pitch', { + get: function () { + return this._pitch; + }, + set: function (interval) { + this._pitch = interval; + this.player.playbackRate = this.intervalToFrequencyRatio(interval); + } + }); /** - * Clean up. - * @returns {Tone.JCReverb} this + * Clean up. + * @returns {Tone.Sampler} this */ - Tone.JCReverb.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - for (var apf = 0; apf < this._allpassFilters.length; apf++) { - this._allpassFilters[apf].disconnect(); - this._allpassFilters[apf] = null; - } - this._allpassFilters = null; - for (var fbcf = 0; fbcf < this._feedbackCombFilters.length; fbcf++) { - this._feedbackCombFilters[fbcf].dispose(); - this._feedbackCombFilters[fbcf] = null; + Tone.Sampler.prototype.dispose = function () { + Tone.Instrument.prototype.dispose.call(this); + this._writable([ + 'player', + 'filterEnvelope', + 'envelope', + 'filter' + ]); + this.player.dispose(); + this.filterEnvelope.dispose(); + this.envelope.dispose(); + this.filter.dispose(); + this.player = null; + this.filterEnvelope = null; + this.envelope = null; + this.filter = null; + for (var sample in this._buffers) { + this._buffers[sample].dispose(); + this._buffers[sample] = null; } - this._feedbackCombFilters = null; - this._writable(['roomSize']); - this.roomSize.dispose(); - this.roomSize = null; - this._scaleRoomSize.dispose(); - this._scaleRoomSize = null; + this._buffers = null; return this; }; - return Tone.JCReverb; + return Tone.Sampler; }); 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. - * Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587). - *

- * This is a base-class for Mid/Side Effects. + * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator + * routed through a Tone.AmplitudeEnvelope. + * * - * @extends {Tone.Effect} * @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.MidSideEffect = function () { - Tone.Effect.call(this); - /** - * The mid/side split - * @type {Tone.MidSideSplit} - * @private - */ - this._midSideSplit = new Tone.MidSideSplit(); - /** - * The mid/side merge - * @type {Tone.MidSideMerge} - * @private - */ - this._midSideMerge = new Tone.MidSideMerge(); - /** - * The mid send. Connect to mid processing - * @type {Tone.Expr} - * @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.MidSideEffect} this - */ - Tone.MidSideEffect.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._midSideSplit.dispose(); - this._midSideSplit = null; - this._midSideMerge.dispose(); - this._midSideMerge = null; - this.midSend = null; - this.sideSend = null; - this.midReturn = null; - this.sideReturn = null; - return this; - }; - return Tone.MidSideEffect; - }); - Module(function (Tone) { - - /** - * @class 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.StereoEffect} - * @constructor - * @param {Frequency|Object} [frequency] The speed of the phasing. - * @param {number} [depth] The depth of the effect. - * @param {Frequency} [baseFrequency] The base frequency of the filters. - * @example - * var phaser = new Tone.Phaser({ - * "frequency" : 15, - * "depth" : 5, - * "baseFrequency" : 1000 - * }).toMaster(); - * var synth = new Tone.FMSynth().connect(phaser); - * synth.triggerAttackRelease("E3", "2n"); - */ - Tone.Phaser = function () { - //set the defaults - var options = this.optionsObject(arguments, [ - 'frequency', - 'depth', - 'baseFrequency' - ], Tone.Phaser.defaults); - Tone.StereoEffect.call(this, options); - /** - * the lfo which controls the frequency on the left side - * @type {Tone.LFO} - * @private - */ - this._lfoL = new Tone.LFO(options.frequency, 0, 1); - /** - * the lfo which controls the frequency on the right side - * @type {Tone.LFO} - * @private - */ - this._lfoR = new Tone.LFO(options.frequency, 0, 1); - this._lfoR.phase = 180; - /** - * the base modulation frequency - * @type {number} - * @private - */ - this._baseFrequency = options.baseFrequency; + Tone.SimpleSynth = function (options) { + //get the defaults + options = this.defaultArg(options, Tone.SimpleSynth.defaults); + Tone.Monophonic.call(this, options); /** - * the depth of the phasing - * @type {number} - * @private + * The oscillator. + * @type {Tone.OmniOscillator} */ - this._depth = options.depth; + this.oscillator = new Tone.OmniOscillator(options.oscillator); /** - * The quality factor of the filters - * @type {Positive} + * The frequency control. + * @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); + this.frequency = this.oscillator.frequency; /** - * the array of filters for the left side - * @type {Array} - * @private + * The detune control. + * @type {Cents} + * @signal */ - this._filtersR = this._makeFilters(options.stages, this._lfoR, this.Q); + this.detune = this.oscillator.detune; /** - * the frequency of the effect - * @type {Tone.Signal} + * The amplitude envelope. + * @type {Tone.AmplitudeEnvelope} */ - 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.depth = options.depth; - //start the lfo - this._lfoL.start(); - this._lfoR.start(); + 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', - 'Q' + 'detune', + 'envelope' ]); }; - Tone.extend(Tone.Phaser, Tone.StereoEffect); + Tone.extend(Tone.SimpleSynth, Tone.Monophonic); /** - * defaults + * @const * @static - * @type {object} + * @type {Object} */ - Tone.Phaser.defaults = { - 'frequency': 0.5, - 'depth': 10, - 'stages': 10, - 'Q': 10, - 'baseFrequency': 350 + Tone.SimpleSynth.defaults = { + 'oscillator': { 'type': 'triangle' }, + 'envelope': { + 'attack': 0.005, + 'decay': 0.1, + 'sustain': 0.3, + 'release': 1 + } }; /** - * @param {number} stages - * @returns {Array} the number of filters all connected together + * 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.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.SimpleSynth.prototype._triggerEnvelopeAttack = function (time, velocity) { + //the envelopes + this.envelope.triggerAttack(time, velocity); + return this; }; /** - * The depth of the effect. - * @memberOf Tone.Phaser# - * @type {number} - * @name depth - */ - Object.defineProperty(Tone.Phaser.prototype, 'depth', { - get: function () { - return this._depth; - }, - set: function (depth) { - this._depth = depth; - var max = this._baseFrequency + this._baseFrequency * depth; - this._lfoL.max = max; - this._lfoR.max = max; - } - }); - /** - * The the base frequency of the filters. - * @memberOf Tone.Phaser# - * @type {number} - * @name baseFrequency + * start the release portion of the envelope + * @param {Time} [time=now] the time the release should start + * @returns {Tone.SimpleSynth} this + * @private */ - Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', { - get: function () { - return this._baseFrequency; - }, - set: function (freq) { - this._baseFrequency = freq; - this._lfoL.min = freq; - this._lfoR.min = freq; - this.depth = this._depth; - } - }); + Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function (time) { + this.envelope.triggerRelease(time); + return this; + }; /** * clean up - * @returns {Tone.Phaser} this + * @returns {Tone.SimpleSynth} this */ - Tone.Phaser.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); + Tone.SimpleSynth.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); this._writable([ + 'oscillator', 'frequency', - 'Q' + 'detune', + 'envelope' ]); - 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.oscillator.dispose(); + this.oscillator = null; + this.envelope.dispose(); + this.envelope = null; this.frequency = null; + this.detune = null; return this; }; - return Tone.Phaser; + return Tone.SimpleSynth; }); 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. + * @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.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. + * @constructor + * @extends {Tone.Monophonic} + * @param {Object} [options] the options available for the synth + * see defaults below * @example - * var pingPong = new Tone.PingPongDelay("4n", 0.2).toMaster(); - * var drum = new Tone.DrumSynth().connect(pingPong); - * drum.triggerAttackRelease("C4", "32n"); + * var synth = new Tone.SimpleAM().toMaster(); + * synth.triggerAttackRelease("C4", "8n"); */ - Tone.PingPongDelay = function () { - var options = this.optionsObject(arguments, [ - 'delayTime', - 'feedback' - ], Tone.PingPongDelay.defaults); - Tone.StereoXFeedbackEffect.call(this, options); + Tone.SimpleAM = function (options) { + options = this.defaultArg(options, Tone.SimpleAM.defaults); + Tone.Monophonic.call(this, options); /** - * the delay node on the left side - * @type {DelayNode} - * @private + * The carrier voice. + * @type {Tone.SimpleSynth} */ - this._leftDelay = this.context.createDelay(options.maxDelayTime); + this.carrier = new Tone.SimpleSynth(options.carrier); /** - * the delay node on the right side - * @type {DelayNode} - * @private + * The modulator voice. + * @type {Tone.SimpleSynth} */ - this._rightDelay = this.context.createDelay(options.maxDelayTime); + this.modulator = new Tone.SimpleSynth(options.modulator); /** - * the predelay on the right side - * @type {DelayNode} - * @private + * the frequency control + * @type {Frequency} + * @signal */ - this._rightPreDelay = this.context.createDelay(options.maxDelayTime); + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); /** - * the delay time signal - * @type {Time} + * 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.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.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.PingPongDelay, Tone.StereoXFeedbackEffect); + Tone.extend(Tone.SimpleAM, Tone.Monophonic); /** * @static * @type {Object} */ - Tone.PingPongDelay.defaults = { - 'delayTime': 0.25, - 'maxDelayTime': 1 + 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 + } + } }; /** - * Clean up. - * @returns {Tone.PingPongDelay} this - */ - Tone.PingPongDelay.prototype.dispose = function () { - Tone.StereoXFeedbackEffect.prototype.dispose.call(this); - this._leftDelay.disconnect(); - this._leftDelay = null; - this._rightDelay.disconnect(); - this._rightDelay = null; - this._rightPreDelay.disconnect(); - this._rightPreDelay = null; - this._writable(['delayTime']); - this.delayTime.dispose(); - this.delayTime = null; + * 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; }; - return Tone.PingPongDelay; - }); - Module(function (Tone) { - /** - * @class Base class for stereo feedback effects where the effectReturn - * is fed back into the same channel. - * - * @constructor - * @extends {Tone.FeedbackEffect} + * trigger the release portion of the note + * + * @param {Time} [time=now] the time the note will release + * @returns {Tone.SimpleAM} this + * @private */ - 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.SimpleAM.prototype._triggerEnvelopeRelease = function (time) { + this.carrier.triggerRelease(time); + this.modulator.triggerRelease(time); + return this; }; - Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect); /** * clean up - * @returns {Tone.StereoFeedbackEffect} this + * @returns {Tone.SimpleAM} this */ - Tone.StereoFeedbackEffect.prototype.dispose = function () { - Tone.StereoEffect.prototype.dispose.call(this); - this._writable(['feedback']); - this.feedback.dispose(); - this.feedback = null; - this._feedbackL.disconnect(); - this._feedbackL = null; - this._feedbackR.disconnect(); - this._feedbackR = null; + 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.StereoFeedbackEffect; + return Tone.SimpleAM; }); Module(function (Tone) { /** - * @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 - *
+ * @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). + * * - * @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. + * @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.StereoWidener = function () { - var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults); - Tone.MidSideEffect.call(this, options); + Tone.SimpleFM = function (options) { + options = this.defaultArg(options, Tone.SimpleFM.defaults); + Tone.Monophonic.call(this, options); /** - * The width control. 0 = 100% mid. 1 = 100% side. 0.5 = no change. - * @type {NormalRange} + * 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.width = new Tone.Signal(0.5, Tone.Type.NormalRange); + this.frequency = new Tone.Signal(440, Tone.Type.Frequency); /** - * Mid multiplier - * @type {Tone.Expr} - * @private + * 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._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))'); + this.harmonicity = new Tone.Multiply(options.harmonicity); + this.harmonicity.units = Tone.Type.Positive; /** - * Side multiplier - * @type {Tone.Expr} - * @private + * 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._sideMult = new Tone.Expr('$0 * ($1 * $2)'); + this.modulationIndex = new Tone.Multiply(options.modulationIndex); + this.modulationIndex.units = Tone.Type.Positive; /** - * constant output of 2 - * @type {Tone} + * the node where the modulation happens + * @type {GainNode} * @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']); + 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.StereoWidener, Tone.MidSideEffect); + Tone.extend(Tone.SimpleFM, Tone.Monophonic); /** - * the default values * @static * @type {Object} */ - Tone.StereoWidener.defaults = { 'width': 0.5 }; + 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 + } + } + }; /** - * Clean up. - * @returns {Tone.StereoWidener} 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 */ - 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; + 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; }; - return Tone.StereoWidener; + /** + * clean up + * @returns {Tone.SimpleFM} this + */ + Tone.SimpleFM.prototype.dispose = function () { + Tone.Monophonic.prototype.dispose.call(this); + this._writable([ + 'carrier', + 'modulator', + 'frequency', + 'harmonicity', + 'modulationIndex' + ]); + this.carrier.dispose(); + this.carrier = null; + this.modulator.dispose(); + this.modulator = null; + this.frequency.dispose(); + this.frequency = null; + this.modulationIndex.dispose(); + this.modulationIndex = null; + this.harmonicity.dispose(); + this.harmonicity = null; + this._modulationNode.disconnect(); + this._modulationNode = null; + return this; + }; + return Tone.SimpleFM; }); Module(function (Tone) { + //polyfill for getUserMedia + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; /** - * @class Tone.Tremelo modulates the amplitude of an incoming signal using a Tone.LFO. - * The type, frequency, and depth of the LFO is controllable. - * - * @extends {Tone.Effect} + * @class Tone.ExternalInput is a WebRTC Audio Input. Check + * [Media Stream API Support](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_API) + * to see which browsers are supported. As of + * writing this, Chrome, Firefox, and Opera + * support Media Stream. Chrome allows enumeration + * of the sources, and access to device name over a + * secure (HTTPS) connection. See [https://simpl.info](https://simpl.info/getusermedia/sources/index.html) + * vs [http://simple.info](https://simpl.info/getusermedia/sources/index.html) + * on a Chrome browser for the difference. + * * @constructor - * @param {Frequency|Object} [frequency] The rate of the effect. - * @param {NormalRange} [depth] The depth of the wavering. + * @extends {Tone.Source} + * @param {number} [inputNum=0] If multiple inputs are present, select the input number. Chrome only. * @example - * //create an 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(); + * //select the third input + * var motu = new Tone.ExternalInput(3); + * + * //opening the input asks the user to activate their mic + * motu.open(function(){ + * //opening is activates the microphone + * //starting lets audio through + * motu.start(10); + * }); */ - Tone.Tremolo = function () { - var options = this.optionsObject(arguments, [ - 'frequency', - 'depth' - ], Tone.Tremolo.defaults); - Tone.Effect.call(this, options); + Tone.ExternalInput = function () { + var options = this.optionsObject(arguments, ['inputNum'], Tone.ExternalInput.defaults); + Tone.Source.call(this, options); /** - * The tremelo LFO - * @type {Tone.LFO} + * The MediaStreamNode + * @type {MediaStreamAudioSourceNode} * @private */ - this._lfo = new Tone.LFO({ - 'frequency': options.frequency, - 'amplitude': options.depth, - 'min': 1, - 'max': 0 - }); + this._mediaStream = null; /** - * Where the gain is multiplied - * @type {GainNode} + * The media stream created by getUserMedia. + * @type {LocalMediaStream} * @private */ - this._amplitude = this.context.createGain(); + this._stream = null; /** - * The frequency of the tremolo. - * @type {Frequency} - * @signal + * The constraints argument for getUserMedia + * @type {Object} + * @private */ - this.frequency = this._lfo.frequency; + this._constraints = { 'audio': true }; /** - * 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 input source position in Tone.ExternalInput.sources. + * Set before ExternalInput.open(). + * @type {Number} + * @private */ - this.depth = this._lfo.amplitude; - this._readOnly([ - 'frequency', - 'depth' - ]); - this.connectEffect(this._amplitude); - this._lfo.connect(this._amplitude.gain); - this.type = options.type; + this._inputNum = options.inputNum; + /** + * Gates the input signal for start/stop. + * Initially closed. + * @type {GainNode} + * @private + */ + this._gate = new Tone.Gain(0).connect(this.output); }; - Tone.extend(Tone.Tremolo, Tone.Effect); + Tone.extend(Tone.ExternalInput, Tone.Source); /** - * @static - * @const - * @type {Object} + * the default parameters + * @type {Object} */ - Tone.Tremolo.defaults = { - 'frequency': 10, - 'type': 'sine', - 'depth': 0.5 + Tone.ExternalInput.defaults = { 'inputNum': 0 }; + /** + * wrapper for getUserMedia function + * @param {function} callback + * @private + */ + Tone.ExternalInput.prototype._getUserMedia = function (callback) { + if (!Tone.ExternalInput.supported) { + throw new Error('browser does not support \'getUserMedia\''); + } + if (Tone.ExternalInput.sources[this._inputNum]) { + this._constraints = { audio: { optional: [{ sourceId: Tone.ExternalInput.sources[this._inputNum].id }] } }; + } + navigator.getUserMedia(this._constraints, function (stream) { + this._onStream(stream); + callback(); + }.bind(this), function (err) { + callback(err); + }); }; /** - * Start the tremolo. - * @param {Time} [time=now] When the tremolo begins. - * @returns {Tone.Tremolo} this + * called when the stream is successfully setup + * @param {LocalMediaStream} stream + * @private */ - Tone.Tremolo.prototype.start = function (time) { - this._lfo.start(time); + Tone.ExternalInput.prototype._onStream = function (stream) { + if (!this.isFunction(this.context.createMediaStreamSource)) { + throw new Error('browser does not support the \'MediaStreamSourceNode\''); + } + //can only start a new source if the previous one is closed + if (!this._stream) { + this._stream = stream; + //Wrap a MediaStreamSourceNode around the live input stream. + this._mediaStream = this.context.createMediaStreamSource(stream); + //Connect the MediaStreamSourceNode to a gate gain node + this._mediaStream.connect(this._gate); + } + }; + /** + * Open the media stream + * @param {function=} callback The callback function to + * execute when the stream is open + * @return {Tone.ExternalInput} this + */ + Tone.ExternalInput.prototype.open = function (callback) { + callback = this.defaultArg(callback, Tone.noOp); + Tone.ExternalInput.getSources(function () { + this._getUserMedia(callback); + }.bind(this)); return this; }; /** - * Stop the tremolo. - * @param {Time} [time=now] When the tremolo stops. - * @returns {Tone.Tremolo} this + * Close the media stream + * @return {Tone.ExternalInput} this */ - Tone.Tremolo.prototype.stop = function (time) { - this._lfo.stop(time); + Tone.ExternalInput.prototype.close = function () { + if (this._stream) { + var track = this._stream.getTracks()[this._inputNum]; + if (!this.isUndef(track)) { + track.stop(); + } + this._stream = null; + } 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 + * Start the stream + * @private */ - Tone.Tremolo.prototype.sync = function (delay) { - this._lfo.sync(delay); + Tone.ExternalInput.prototype._start = function (time) { + time = this.toSeconds(time); + this._gate.gain.setValueAtTime(1, time); return this; }; /** - * Unsync the filter from the transport - * @returns {Tone.Tremolo} this + * Stops the stream. + * @private */ - Tone.Tremolo.prototype.unsync = function () { - this._lfo.unsync(); + Tone.ExternalInput.prototype._stop = function (time) { + time = this.toSeconds(time); + this._gate.gain.setValueAtTime(0, time); return this; }; /** - * Type of oscillator attached to the Tremolo. - * @memberOf Tone.Tremolo# - * @type {string} - * @name type + * Clean up. + * @return {Tone.ExternalInput} this */ - Object.defineProperty(Tone.Tremolo.prototype, 'type', { + Tone.ExternalInput.prototype.dispose = function () { + Tone.Source.prototype.dispose.call(this); + this.close(); + if (this._mediaStream) { + this._mediaStream.disconnect(); + this._mediaStream = null; + } + this._constraints = null; + this._gate.dispose(); + this._gate = null; + return this; + }; + /////////////////////////////////////////////////////////////////////////// + // STATIC METHODS + /////////////////////////////////////////////////////////////////////////// + /** + * The array of available sources, different depending on whether connection is secure + * @type {Array} + * @static + */ + Tone.ExternalInput.sources = []; + /** + * indicates whether browser supports MediaStreamTrack.getSources (i.e. Chrome vs Firefox) + * @type {Boolean} + * @private + */ + Tone.ExternalInput._canGetSources = !Tone.prototype.isUndef(window.MediaStreamTrack) && Tone.prototype.isFunction(MediaStreamTrack.getSources); + /** + * If getUserMedia is supported by the browser. + * @type {Boolean} + * @memberOf Tone.ExternalInput# + * @name supported + * @static + * @readOnly + */ + Object.defineProperty(Tone.ExternalInput, 'supported', { get: function () { - return this._lfo.type; - }, - set: function (type) { - this._lfo.type = type; + return Tone.prototype.isFunction(navigator.getUserMedia); } }); /** - * clean up - * @returns {Tone.Tremolo} this + * Populates the source list. Invokes the callback with an array of + * possible audio sources. + * @param {function=} callback Callback to be executed after populating list + * @return {Tone.ExternalInput} this + * @static + * @example + * var soundflower = new Tone.ExternalInput(); + * Tone.ExternalInput.getSources(selectSoundflower); + * + * function selectSoundflower(sources){ + * for(var i = 0; i < sources.length; i++){ + * if(sources[i].label === "soundflower"){ + * soundflower.inputNum = i; + * soundflower.open(function(){ + * soundflower.start(); + * }); + * break; + * } + * } + * }; */ - Tone.Tremolo.prototype.dispose = function () { - Tone.Effect.prototype.dispose.call(this); - this._writable([ - 'frequency', - 'depth' - ]); - this._lfo.dispose(); - this._lfo = null; - this._amplitude.disconnect(); - this._amplitude = null; - this.frequency = null; - this.depth = null; + Tone.ExternalInput.getSources = function (callback) { + if (Tone.ExternalInput.sources.length === 0 && Tone.ExternalInput._canGetSources) { + MediaStreamTrack.getSources(function (media_sources) { + for (var i = 0; i < media_sources.length; i++) { + if (media_sources[i].kind === 'audio') { + Tone.ExternalInput.sources[i] = media_sources[i]; + } + } + callback(Tone.ExternalInput.sources); + }); + } else { + callback(Tone.ExternalInput.sources); + } return this; }; - return Tone.Tremolo; + return Tone.ExternalInput; + }); + Module(function (Tone) { + + /** + * @class Opens up the default source (typically the microphone). + * + * @constructor + * @extends {Tone.ExternalInput} + * @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); + */ + Tone.Microphone = function () { + Tone.ExternalInput.call(this, 0); + }; + Tone.extend(Tone.Microphone, Tone.ExternalInput); + /** + * If getUserMedia is supported by the browser. + * @type {Boolean} + * @memberOf Tone.Microphone# + * @name supported + * @static + * @readOnly + */ + Object.defineProperty(Tone.Microphone, 'supported', { + get: function () { + return Tone.ExternalInput.supported; + } + }); + return Tone.Microphone; }); Module(function (Tone) { @@ -14588,103 +19669,10 @@ }; return Tone.Switch; }); - Module(function (Tone) { - - /** - * @class Tone.Microphone is a WebRTC Microphone. Check - * [Media Stream API Support](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_API) - * to see which browsers are supported. - * - * @constructor - * @extends {Tone.Source} - * @param {number} [inputNum=0] If multiple inputs are present, select the input number. - * @example - * //mic will feedback if played through master - * var mic = new Tone.Microphone(); - * mic.start(); - */ - Tone.Microphone = function (inputNum) { - Tone.Source.call(this); - /** - * @type {MediaStreamAudioSourceNode} - * @private - */ - this._mediaStream = null; - /** - * @type {LocalMediaStream} - * @private - */ - this._stream = null; - /** - * @type {Object} - * @private - */ - this._constraints = { 'audio': true }; - //get the option - var self = this; - MediaStreamTrack.getSources(function (media_sources) { - if (inputNum < media_sources.length) { - self.constraints.audio = { optional: [{ sourceId: media_sources[inputNum].id }] }; - } - }); - }; - Tone.extend(Tone.Microphone, Tone.Source); - /** - * start the stream. - * @private - */ - Tone.Microphone.prototype._start = function () { - navigator.getUserMedia(this._constraints, this._onStream.bind(this), this._onStreamError.bind(this)); - }; - /** - * stop the stream. - * @private - */ - Tone.Microphone.prototype._stop = function () { - this._stream.stop(); - return this; - }; - /** - * called when the stream is successfully setup - * @param {LocalMediaStream} stream - * @private - */ - Tone.Microphone.prototype._onStream = function (stream) { - this._stream = stream; - // Wrap a MediaStreamSourceNode around the live input stream. - this._mediaStream = this.context.createMediaStreamSource(stream); - this._mediaStream.connect(this.output); - }; - /** - * called on error - * @param {Error} e - * @private - */ - Tone.Microphone.prototype._onStreamError = function (e) { - console.error(e); - }; - /** - * Clean up. - * @return {Tone.Microphone} this - */ - Tone.Microphone.prototype.dispose = function () { - Tone.Source.prototype.dispose.call(this); - if (this._mediaStream) { - this._mediaStream.disconnect(); - this._mediaStream = null; - } - this._stream = null; - this._constraints = null; - return this; - }; - //polyfill - navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - return Tone.Microphone; - }); //UMD if ( typeof define === "function" && define.amd ) { - define( "Tone", [], function() { + define(function() { return Tone; }); } else if (typeof module === "object") { diff --git a/build/Tone.min.js b/build/Tone.min.js index 3c138e342..a74dcb5ec 100644 --- a/build/Tone.min.js +++ b/build/Tone.min.js @@ -4,8 +4,10 @@ * @license http://opensource.org/licenses/MIT MIT License * @copyright 2014-2015 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,l,a,h,u;"object"==typeof e?n=i:"string"==typeof e&&(o={},o[e]=i,e=o);for(r in e){if(i=e[r],l=this,-1!==r.indexOf(".")){for(a=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){if(this._isSafari()){var e=t[0];t.unshift(e)}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){this._shaper.oversample=t}}),t.WaveShaper.prototype._isSafari=function(){var t=navigator.userAgent.toLowerCase();return-1!==t.indexOf("safari")&&-1===t.indexOf("chrome")},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(t){return t.Signal=function(){var e=this.optionsObject(arguments,["value","units"],t.Signal.defaults);this.units=e.units,this.convert=e.convert,this.overridden=!1,this.output=this._scaler=this.context.createGain(),this.input=this._value=this._scaler.gain,e.value instanceof AudioParam?(this._scaler.connect(e.value),e.value.value=0):(this.isUndef(e.param)||(this._scaler.connect(e.param),e.param.value=0),this.value=e.value),t.Signal._constant.chain(this._scaler)},t.extend(t.Signal,t.SignalBase),t.Signal.defaults={value:0,param:void 0,units:t.Type.Default,convert:!0},Object.defineProperty(t.Signal.prototype,"value",{get:function(){return this._toUnits(this._value.value)},set:function(t){var e=this._fromUnits(t);this.cancelScheduledValues(0),this._value.value=e}}),t.Signal.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.Signal.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.Signal.prototype.setValueAtTime=function(t,e){return t=this._fromUnits(t),this._value.setValueAtTime(t,this.toSeconds(e)),this},t.Signal.prototype.setCurrentValueNow=function(t){t=this.defaultArg(t,this.now());var e=this._value.value;return this.cancelScheduledValues(t),this._value.setValueAtTime(e,t),this},t.Signal.prototype.linearRampToValueAtTime=function(t,e){return t=this._fromUnits(t),this._value.linearRampToValueAtTime(t,this.toSeconds(e)),this},t.Signal.prototype.exponentialRampToValueAtTime=function(t,e){return t=this._fromUnits(t),t=Math.max(1e-5,t),this._value.exponentialRampToValueAtTime(t,this.toSeconds(e)),this},t.Signal.prototype.exponentialRampToValueNow=function(t,e){var i=this.now(),s=this.value;return this.setValueAtTime(Math.max(s,1e-4),i),this.exponentialRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Signal.prototype.linearRampToValueNow=function(t,e){var i=this.now();return this.setCurrentValueNow(i),this.linearRampToValueAtTime(t,i+this.toSeconds(e)),this},t.Signal.prototype.setTargetAtTime=function(t,e,i){return t=this._fromUnits(t),i=Math.max(1e-5,i),this._value.setTargetAtTime(t,this.toSeconds(e),i),this},t.Signal.prototype.setValueCurveAtTime=function(t,e,i){for(var s=0;se?this._nextAttack<=e&&this._nextDecay>e?t.Envelope.Phase.Attack:this._nextDecay<=e&&this._nextSustain>e?t.Envelope.Phase.Decay:this._nextSustain<=e&&this._nextRelease>e?t.Envelope.Phase.Sustain:t.Envelope.Phase.Standby:this._nextReleasee?t.Envelope.Phase.Release:t.Envelope.Phase.Standby},t.Envelope.prototype._exponentialApproach=function(t,e,i,s,n){return i+(e-i)*Math.exp(-(n-t)/s)},t.Envelope.prototype._linearInterpolate=function(t,e,i,s,n){return e+(s-e)*((n-t)/(i-t))},t.Envelope.prototype._exponentialInterpolate=function(t,e,i,s,n){return e*Math.pow(s/e,(n-t)/(i-t))},t.Envelope.prototype._valueAtTime=function(e){var i=this.toSeconds(this.attack),s=this.toSeconds(this.decay),n=this.toSeconds(this.release);switch(this._phaseAtTime(e)){case t.Envelope.Phase.Attack:return this._attackCurve===t.Envelope.Type.Linear?this._linearInterpolate(this._nextAttack,this._minOutput,this._nextAttack+i,this._peakValue,e):this._exponentialInterpolate(this._nextAttack,this._minOutput,this._nextAttack+i,this._peakValue,e);case t.Envelope.Phase.Decay:return this._exponentialApproach(this._nextDecay,this._peakValue,this.sustain*this._peakValue,s*this._timeMult,e);case t.Envelope.Phase.Release:return this._exponentialApproach(this._nextRelease,this._peakValue,this._minOutput,n*this._timeMult,e);case t.Envelope.Phase.Sustain:return this.sustain*this._peakValue;case t.Envelope.Phase.Standby:return this._minOutput}},t.Envelope.prototype.triggerAttack=function(e,i){var s,n,o,r,l,a;return e=this.toSeconds(e),s=this.toSeconds(this.attack),n=this.toSeconds(this.decay),o=this._valueAtTime(e),r=o*s,this._nextAttack=e-r,this._nextDecay=this._nextAttack+s,this._nextSustain=this._nextDecay+n,this._nextRelease=1/0,this._peakValue=this.defaultArg(i,1),l=this._peakValue,a=this.sustain*l,this._sig.cancelScheduledValues(e),this._sig.setValueAtTime(o,e),this._attackCurve===t.Envelope.Type.Linear?this._sig.linearRampToValueAtTime(l,this._nextDecay):this._sig.exponentialRampToValueAtTime(l,this._nextDecay),this._sig.setTargetAtTime(a,this._nextDecay,n*this._timeMult),this},t.Envelope.prototype.triggerRelease=function(e){var i,s,n;return e=this.toSeconds(e),i=this._phaseAtTime(e),s=this.toSeconds(this.release),n=this._valueAtTime(e),this._peakValue=n,this._nextRelease=e,this._nextStandby=this._nextRelease+s,this._sig.cancelScheduledValues(this._nextRelease),i===t.Envelope.Phase.Attack?(this._sig.setCurrentValueNow(),this.attackCurve===t.Envelope.Type.Linear?this._sig.linearRampToValueAtTime(this._peakValue,this._nextRelease):this._sig.exponentialRampToValueAtTime(this._peakValue,this._nextRelease)):this._sig.setValueAtTime(this._peakValue,this._nextRelease),this._sig.setTargetAtTime(this._minOutput,this._nextRelease,s*this._timeMult),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=this.context.createGain(),this._sig.connect(this.output.gain)},t.extend(t.AmplitudeEnvelope,t.Envelope),t.AmplitudeEnvelope}),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.Signal(this._compressor.attack,t.Type.Time),this.release=new t.Signal(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._value=this.input[1]=new t.Signal(e),this._value.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._value.dispose(),this._value=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._value=this.input[1]=this.output.gain,this._value.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._value=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._value=this.input[1]=new t.Signal(e),this._value.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._value.dispose(),this._value=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._value=this.input[0]=new t.Subtract(e),this.input[1]=this._value.input[1],this._gtz=this.output=new t.GreaterThanZero,this._value.connect(this._gtz)},t.extend(t.GreaterThan,t.Signal),t.GreaterThan.prototype.dispose=function(){return t.prototype.dispose.call(this),this._value.dispose(),this._value=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._value=this.input[1]=new t.Signal(e),this._neg.connect(this._gt),this._value.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._value.dispose(),this._value=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._value=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._value.connect(this._ifThenElse["else"]),this._value.connect(this._gt,0,1)},t.extend(t.Max,t.Signal),t.Max.prototype.dispose=function(){return t.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._gt.dispose(),this._value=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._value=this.input[1]=new t.Signal(e),this.input[0].chain(this._lt,this._ifThenElse["if"]),this.input[0].connect(this._ifThenElse.then),this._value.connect(this._ifThenElse["else"]),this._value.connect(this._lt,0,1)},t.extend(t.Min,t.Signal),t.Min.prototype.dispose=function(){return t.prototype.dispose.call(this),this._value.dispose(),this._ifThenElse.dispose(),this._lt.dispose(),this._value=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){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}}},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,l=t.Expr._Expressions[i];if(!u(e))for(n in l)if(o=l[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(),l(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 l(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=a()),e=h.next(),!i(e,")"))throw new SyntaxError('Expected ) in a function call "'+t.value+'"');return{method:t.method,args:s,name:name}}function a(){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;ti;i++)s=this.context.createBiquadFilter(),s.type=this._type,this.frequency.connect(s.frequency),this.detune.connect(s.detune),this.Q.connect(s.Q),this.gain.connect(s.gain),this._filters[i]=s;n=[this.input].concat(this._filters).concat([this.output]),this.connectSeries.apply(this,n)}}),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.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.Clock=function(e,i){this._oscillator=null,this._jsNode=this.context.createScriptProcessor(this.bufferSize,1,1),this._jsNode.onaudioprocess=this._processBuffer.bind(this),this.frequency=new t.Signal(e,t.Type.Frequency),this._upTick=!1,this.tick=i,this.onended=t.noOp,this._jsNode.noGC()},t.extend(t.Clock),t.Clock.prototype.start=function(t){if(!this._oscillator){this._oscillator=this.context.createOscillator(),this._oscillator.type="square",this._oscillator.connect(this._jsNode),this.frequency.connect(this._oscillator.frequency),this._upTick=!1;var e=this.toSeconds(t);this._oscillator.start(e)}return this},t.Clock.prototype.stop=function(t){var e,i;return this._oscillator&&(e=this.now(),i=this.toSeconds(t,e),this._oscillator.stop(i),this._oscillator=null,t?setTimeout(this.onended,1e3*(i-e)):this.onended()),this},t.Clock.prototype._processBuffer=function(t){var e,i,s=this.defaultArg(t.playbackTime,this.now()),n=this._jsNode.bufferSize,o=t.inputBuffer.getChannelData(0),r=this._upTick,l=this;for(e=0;n>e;e++)i=o[e],i>0&&!r?(r=!0,setTimeout(function(){var t=s+l.samplesToSeconds(e+2*n);return function(){l.tick&&l.tick(t)}}(),0)):0>i&&r&&(r=!1);this._upTick=r},t.Clock.prototype.dispose=function(){return this._jsNode.disconnect(),this.frequency.dispose(),this.frequency=null,this._oscillator&&(this._oscillator.disconnect(),this._oscillator=null),this._jsNode.onaudioprocess=t.noOp,this._jsNode=null,this.tick=null,this.onended=t.noOp,this},t.Clock}),Module(function(Tone){var tatum,timelineTicks,transportTicks,swingSubdivision,swingTatum,swingAmount,transportTimeSignature,loopStart,loopEnd,intervals,timeouts,transportTimeline,timelineProgress,SyncedSources,SyncedSignals,processIntervals,processTimeouts,processTimeline,TimelineEventIDCounter,TimelineEvent,TransportConstructor;return Tone.Transport=function(){this._clock=new Tone.Clock(0,this._processTick.bind(this)),this._clock.onended=this._onended.bind(this),this.loop=!1,this.bpm=new Tone.Signal(120,Tone.Type.BPM),this._bpmMult=new Tone.Multiply(1/60*tatum),this.state=Tone.State.Stopped,this.bpm.chain(this._bpmMult,this._clock.frequency)},Tone.extend(Tone.Transport),Tone.Transport.defaults={bpm:120,swing:0,swingSubdivision:"16n",timeSignature:4,loopStart:0,loopEnd:"4m"},tatum=12,timelineTicks=0,transportTicks=0,swingSubdivision="16n",swingTatum=3,swingAmount=0,transportTimeSignature=4,loopStart=0,loopEnd=4*tatum,intervals=[],timeouts=[],transportTimeline=[],timelineProgress=0,SyncedSources=[],SyncedSignals=[],Tone.Transport.prototype._processTick=function(t){this.state===Tone.State.Started&&(swingAmount>0&&timelineTicks%tatum!==0&&timelineTicks%swingTatum===0&&(t+=this._ticksToSeconds(swingTatum)*swingAmount),processIntervals(t),processTimeouts(t),processTimeline(t),transportTicks+=1,timelineTicks+=1,this.loop&&timelineTicks===loopEnd&&this._setTicks(loopStart))},Tone.Transport.prototype._setTicks=function(t){var e,i;for(timelineTicks=t,e=0;e=t){timelineProgress=e;break}},processIntervals=function(t){var e,i,s;for(e=0,i=intervals.length;i>e;e++)s=intervals[e],s.testInterval(transportTicks)&&s.doCallback(t)},processTimeouts=function(t){var e,i,s,n,o=0;for(e=0,i=timeouts.length;i>e;e++)if(s=timeouts[e],n=s.callbackTick(),transportTicks>=n)s.doCallback(t),o++;else if(n>transportTicks)break;timeouts.splice(0,o)},processTimeline=function(t){var e,i,s,n;for(e=timelineProgress,i=transportTimeline.length;i>e;e++)if(s=transportTimeline[e],n=s.callbackTick(),n===timelineTicks)timelineProgress=e,s.doCallback(t);else if(n>timelineTicks)break},Tone.Transport.prototype.setInterval=function(t,e,i){var s=this._toTicks(e),n=new TimelineEvent(t,i,s,transportTicks);return intervals.push(n),n.id},Tone.Transport.prototype.clearInterval=function(t){var e,i;for(e=0;e0;return intervals=[],t},Tone.Transport.prototype.setTimeout=function(t,e,i){var s,n,o,r=this._toTicks(e),l=new TimelineEvent(t,i,r+transportTicks,0);for(s=0,n=timeouts.length;n>s;s++)if(o=timeouts[s],o.callbackTick()>l.callbackTick())return timeouts.splice(s,0,l),l.id;return timeouts.push(l),l.id},Tone.Transport.prototype.clearTimeout=function(t){var e,i;for(e=0;e0;return timeouts=[],t},Tone.Transport.prototype.setTimeline=function(t,e,i){var s,n,o,r=this._toTicks(e),l=new TimelineEvent(t,i,r,0);for(s=timelineProgress,n=transportTimeline.length;n>s;s++)if(o=transportTimeline[s],o.callbackTick()>l.callbackTick())return transportTimeline.splice(s,0,l),l.id;return transportTimeline.push(l),l.id},Tone.Transport.prototype.clearTimeline=function(t){var e,i;for(e=0;e0;return transportTimeline=[],t},Tone.Transport.prototype._toTicks=function(t){var e=this.toSeconds(t),i=this.notationToSeconds("4n"),s=e/i,n=s*tatum;return Math.round(n)},Tone.Transport.prototype._ticksToSeconds=function(t,e,i){t=Math.floor(t);var s=this.notationToSeconds("4n",e,i);return s*t/tatum},Tone.Transport.prototype.nextBeat=function(t){var e,i,s;return t=this.defaultArg(t,"4n"),e=this._toTicks(t),i=transportTicks%e,s=i,i>0&&(s=e-i),this._ticksToSeconds(s)},Tone.Transport.prototype.start=function(t,e){var i,s,n,o;if(this.state===Tone.State.Stopped||this.state===Tone.State.Paused)for(this.isUndef(e)||this._setTicks(this._toTicks(e)),this.state=Tone.State.Started,i=this.toSeconds(t),this._clock.start(i),s=0;s1){for(originalTime=time,i=0;ie?t.State.Started:this._nextStop<=e?t.State.Stopped:t.State.Stopped},t.Source.prototype.start=function(e){return e=this.toSeconds(e),(this._stateAtTime(e)!==t.State.Started||this.retrigger)&&(this._nextStart=e,this._nextStop=1/0,this._start.apply(this,arguments)),this},t.Source.prototype.stop=function(e){var i,s=this.now();return e=this.toSeconds(e,s),this._stateAtTime(e)===t.State.Started&&(this._nextStop=this.toSeconds(e),clearTimeout(this._timeout),i=e-s,i>0?this._timeout=setTimeout(this.onended,1e3*i+20):this.onended(),this._stop.apply(this,arguments)),this},t.Source.prototype.pause=function(t){return this.stop(t),this},t.Source.prototype.sync=function(e){return t.Transport.syncSource(this,e),this},t.Source.prototype.unsync=function(){return t.Transport.unsyncSource(this),this},t.Source.prototype.dispose=function(){t.prototype.dispose.call(this),this.stop(),clearTimeout(this._timeout),this.onended=t.noOp,this._writable("volume"),this.volume.dispose(),this.volume=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._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},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,i,s,n,o,r=t,l=4096,a=l/2,h=new Float32Array(a),u=new Float32Array(a),c=1,p=/(sine|triangle|square|sawtooth)(\d+)$/.exec(t);for(p&&(c=parseInt(p[2]),t=p[1],c=Math.max(c,2),a=c),e=this._phase,i=1;a>i;++i){switch(s=2/(i*Math.PI),t){case"sine":n=c>=i?1:0;break;case"square":n=1&i?2*s:0;break;case"sawtooth":n=s*(1&i?1:-1);break;case"triangle":n=1&i?2*s*s*(i-1>>1&1?-1:1):0;break;default:throw new TypeError("invalid oscillator type: "+t)}0!==n?(h[i]=-n*Math.sin(e*i),u[i]=n*Math.cos(e*i)):(h[i]=0,u[i]=0)}o=this.context.createPeriodicWave(h,u),this._wave=o,null!==this._oscillator&&this._oscillator.setPeriodicWave(this._wave),this._type=r}}),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},t.Oscillator}),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){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,phase:e.phase+90}),this.frequency=this.oscillator.frequency,this.amplitude=this.oscillator.volume,this.amplitude.units=t.Type.NormalRange,this.amplitude.value=e.amplitude,this._a2g=new t.AudioToGain,this._scaler=this.output=new t.Scale(e.min,e.max),this._units=t.Type.Default,this.oscillator.chain(this._a2g,this._scaler),this._readOnly(["amplitude","frequency","oscillator"])},t.extend(t.LFO,t.Oscillator),t.LFO.defaults={type:"sine",min:0,max:1,phase:0,frequency:"4n",amplitude:1},t.LFO.prototype.start=function(t){return this.oscillator.start(t),this},t.LFO.prototype.stop=function(t){return 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}}),Object.defineProperty(t.LFO.prototype,"phase",{get:function(){return this.oscillator.phase-90},set:function(t){this.oscillator.phase=t+90}}),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}}),t.LFO.prototype.connect=function(e){return e.constructor===t.Signal&&(this.convert=e.convert,this.units=e.units),t.Signal.prototype.connect.apply(this,arguments),this},t.LFO.prototype._fromUnits=t.Signal.prototype._fromUnits,t.LFO.prototype._toUnits=t.Signal.prototype._toUnits,t.LFO.prototype.dispose=function(){return t.prototype.dispose.call(this),this._writable(["amplitude","frequency","oscillator"]),this.oscillator.dispose(),this.oscillator=null,this._scaler.dispose(),this._scaler=null,this._a2g.dispose(),this._a2g=null,this.frequency=null,this.amplitude=null,this},t.LFO}),Module(function(t){return t.Limiter=function(e){this._compressor=this.input=this.output=new t.Compressor({attack:.001,decay:.001,threshold:e}),this.threshold=this._compressor.threshold,this._readOnly("threshold")},t.extend(t.Limiter),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.Signal(this._lowpass.frequency,t.Type.Frequency),this.dampening.value=e.dampening,this._feedback=this.context.createGain(),this.resonance=new t.Signal(e.resonance,t.Type.NormalRange),this._delay.chain(this._lowpass,this._feedback,this._delay),this.delayTime.connect(this._delay.delayTime),this.resonance.connect(this._feedback.gain),this.dampening.connect(this._lowpass.frequency),this._readOnly(["dampening","resonance","delayTime"])},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)},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(e,i,s){t.call(this),this._channels=this.defaultArg(e,1),this._smoothing=this.defaultArg(i,.8),this._clipMemory=1e3*this.defaultArg(s,.5),this._volume=new Array(this._channels),this._values=new Array(this._channels);for(var n=0;nl;l++)o=i[l],!r&&o>.95&&(r=!0,this._lastClip=Date.now()),n+=o,s+=o*o;a=n/u,h=Math.sqrt(s/u),this._volume[e]=Math.max(h,this._volume[e]*c),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(){return Date.now()-this._lastClip0){if(t.Buffer._currentDownloads.length0){for(e=0;r>e;e++)i=t.Buffer._currentDownloads[e],o+=i.progress;l=o}s=r-l,n=t.Buffer._totalDownloads-t.Buffer._queue.length-s,t.Buffer.onprogress(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},t.Buffer.onload=t.noOp,t.Buffer.onprogress=t.noOp,t.Buffer.onerror=t.noOp,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){function e(t,e,s){var n,o,r,l;if(i.hasOwnProperty(t))for(n=i[t],o=0,r=n.length;r>o;o++)l=n[o],Array.isArray(s)?l.apply(window,[e].concat(s)):l(e,s)}var i,s,n,o;return 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.Tranport.clearTimeline(this._timelineID),this.value=null,this},i={},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,l,a,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;nt?-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"}}),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}),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,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,"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._readOnly(["frequency","detune"])},t.extend(t.OmniOscillator,t.Oscillator),t.OmniOscillator.defaults={frequency:440,detune:0,type:"sine",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"))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 TypeError("Tone.OmniOscillator does not support type "+i);this._sourceType!==e.PulseOscillator&&(this._sourceType=e.PulseOscillator,this._createNewOscillator(t.PulseOscillator))}}}),t.OmniOscillator.prototype._createNewOscillator=function(e){var i,s=this.now()+this.bufferTime;null!==this._oscillator&&(i=this._oscillator,i.stop(s),i.onended=function(){i.dispose(),i=null}),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.output=this.context.createGain(),this.volume=new t.Signal({param:this.output.gain,units:t.Type.Decibels,value:e.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._writable(["volume"]),this.volume.dispose(),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.ScaledEnvelope(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,min:20,max:4e3,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,min:2e4,max:2e4},filter:{Q:6,type:"lowpass",rolloff:-24}},modulator:{volume:-10,oscillator:{type:"square"},envelope:{attack:2,decay:0,sustain:1,release:.5},filterEnvelope:{attack:4,decay:.2,sustain:.5,release:.5,min:20,max:1500},filter:{Q:6,type:"lowpass",rolloff:-24}}},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.Signal(this._vibratoGain.gain,t.Type.Gain),this.vibratoAmount.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,min:2e4,max:2e4}},modulator:{volume:-10,portamento:0,oscillator:{type:"triangle"},envelope:{attack:.01,decay:0,sustain:1,release:.5},filterEnvelope:{attack:.01,decay:0,sustain:1,release:.5,min:2e4,max:2e4}}},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.type=e.type},t.extend(t.Noise,t.Source),t.Noise.defaults={type:"white"},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:this._buffer=s}if(this.state===t.State.Started){var o=this.now()+this.bufferTime;this._source.onended=void 0,this._stop(o),this._start(o)}}}}),t.Noise.prototype._start=function(t){this._source=this.context.createBufferSource(),this._source.buffer=this._buffer,this._source.loop=!0,this.connectSeries(this._source,this.output),this._source.start(this.toSeconds(t)),this._source.onended=this.onended},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,l,a,h,u,c,p,d,f=t.createBuffer(2,o,n);for(e=0;ep;p++)d=2*Math.random()-1,s=.99886*s+.0555179*d,r=.99332*r+.0750759*d,l=.969*l+.153852*d,a=.8665*a+.3104856*d,h=.55*h+.5329522*d,u=-.7616*u-.016898*d,i[p]=s+r+l+a+h+u+c+.5362*d,i[p]*=.11,c=.115926*d;return f}(),i=function(){var e,i,s,r,l,a=t.createBuffer(2,o,n);for(e=0;er;r++)l=2*Math.random()-1,i[r]=(s+.02*l)/1.02,s=i[r],i[r]*=3.5;return a}(),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.ScaledEnvelope(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,min:20,max:4e3,exponent:2}},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._freeVoices=[],this._activeVoices={},i=0;i0&&(r=this._freeVoices.shift(),r.triggerAttack(n,e,i),this._activeVoices[o]=r);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;is;++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(e.rate,0,1),this._lfoR=new t.LFO(e.rate,0,1),this._lfoR.phase=180,this._delayNodeL=this.context.createDelay(),this._delayNodeR=this.context.createDelay(),this.frequency=this._lfoL.frequency,this.connectSeries(this.effectSendL,this._delayNodeL,this.effectReturnL),this.connectSeries(this.effectSendR,this._delayNodeR,this.effectReturnR),this.input.connect(this.output),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"])},t.extend(t.Chorus,t.StereoXFeedbackEffect),t.Chorus.defaults={frequency:1.5,delayTime:3.5,depth:.7,feedback:.1,type:"sine"},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}}),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,l,a,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,"depth",{get:function(){return this._depth},set:function(t){this._depth=t;var e=this._baseFrequency+this._baseFrequency*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.depth=this._depth}}),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;ei){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,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;e + + + + Analyser + + + + + + + + + + + + + + + + +
+
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 + in the range 0-255. +
+ +
+ + + + + + \ No newline at end of file diff --git a/examples/buses.html b/examples/buses.html index be2615ad1..b232b41db 100644 --- a/examples/buses.html +++ b/examples/buses.html @@ -5,11 +5,13 @@ Buses + - + + @@ -75,15 +77,6 @@ - + + @@ -61,7 +63,8 @@ //create an oscillator and connect it to the envelope var osc = new Tone.Oscillator({ - "type" : "triangle", + "partials" : [3, 2, 1], + "type" : "custom", "frequency" : "C#4", "volume" : -8, }).connect(env).start(); @@ -70,23 +73,12 @@ + + + + + + + + + + + +
+
Events
+
+ 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 + and lets you create precise, rhythmic events which are startable, stoppable and loopable. + +
+
+ + + + + \ No newline at end of file diff --git a/examples/fmSynth.html b/examples/fmSynth.html index bb271b5d3..993e4a2f4 100644 --- a/examples/fmSynth.html +++ b/examples/fmSynth.html @@ -5,13 +5,16 @@ SimpleFM + - + + + + + @@ -24,12 +27,21 @@ +
Using p5
- Click on the button to play the example. This sketch uses p5.js for visual components. -

By accessing the envelope's current value, we can create responsive visuals that are directly tied to what is heard. +

+ This sketch uses p5.js for visual components and the + p5 version of Tone.js + which adds a p5 preload hook for buffer loading.
+ @@ -34,26 +36,24 @@ margin-bottom: 40px; } -
+
+
+
- + + @@ -73,22 +75,9 @@ + + + + + + + + + + + + + +
+
Microphone
+
+ If supported, Tone.Microphone uses getUserMedia to open + the user's microphone where it can then be processed with Tone.js. +
+
+ + + + + \ No newline at end of file diff --git a/examples/monoSynth.html b/examples/monoSynth.html index a273aac9a..4420adaf4 100644 --- a/examples/monoSynth.html +++ b/examples/monoSynth.html @@ -5,11 +5,13 @@ MonoSynth + - + + @@ -37,14 +39,23 @@ - + + @@ -42,18 +44,9 @@ - + + @@ -22,7 +24,7 @@
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 amplitude. + 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.

Tone.Oscillator docs.
@@ -36,21 +38,12 @@ + + + + + + + + + + + + +
+
Piano Phase
+
+ By slightly slowing down the playbackRate of the Tone.Sequence in the right channel, + the two identical melodies phase against each other in interesting ways. + Composition by Steve Reich. Inspiration from Alexander Chen. +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/examples/pingPongDelay.html b/examples/pingPongDelay.html index 48af854c3..b151423fd 100644 --- a/examples/pingPongDelay.html +++ b/examples/pingPongDelay.html @@ -5,11 +5,13 @@ PINGPONG DELAY + - + + @@ -46,15 +48,6 @@ - + + @@ -22,13 +24,8 @@
Grains
- Click on the button to play short looped section (also called a grain) + Click on the button to play short looped section of the audio file using Tone.Player. - When the loop becomes very small, it takes on another pitch value. -

- The duration of the sample is ⁓2.5 seconds. Any values for loopStart or loopEnd - larger than the duration will just loop to the end of the sample. Also make sure - that loopStart is smaller than loopEnd.
+ + @@ -37,7 +40,11 @@ diff --git a/examples/quantization.html b/examples/quantization.html new file mode 100644 index 000000000..1d1bf6e74 --- /dev/null +++ b/examples/quantization.html @@ -0,0 +1,86 @@ + + + + + Quantize + + + + + + + + + + + + + + + + +
+
Quantization
+
+ Using the "@" symbol, Time + expressions can be quantized + (aligned to a subdivision). In this example, a note's start time is aligned to the given subdivision. +
+
+ + + + + \ No newline at end of file diff --git a/examples/rampTo.html b/examples/rampTo.html index ae66be3a9..19f112348 100644 --- a/examples/rampTo.html +++ b/examples/rampTo.html @@ -5,10 +5,12 @@ SIGNAL RAMP + - + + @@ -53,7 +55,7 @@ var reverb = new Tone.JCReverb().toMaster(); - for (var i = 0; i < 7; i++){ + for (var i = 0; i < 8; i++){ oscillators["node" + i] = new Tone.Oscillator({ "frequency" : bassFreq * i, "type" : "sawtooth10", @@ -68,7 +70,7 @@ nx.onload = function(){ nx.colorize("#7F33ED"); - joints1.nodeSize = 45; + joints1.nodeSize = 25; joints1.val.x = Math.random(); joints1.val.y = Math.random(); joints1.resize($("#Content").width(), 250); @@ -76,7 +78,6 @@ var width = joints1.width; var height = joints1.height; joints1.threshold = Math.max($("#Content").width() / 1.5, 60); - randomPlacement(); joints1.init(); joints1.draw(); @@ -86,18 +87,6 @@ joints1.draw(); }); - function randomPlacement(){ - var width = $("#Content").width(); - var height = joints1.height; - var len = Object.keys(oscillators).length; - for (var i = 0; i < len; i++){ - joints1.joints[i] = { - x : Math.random() * width, - y : Math.random() * height - } - } - } - function setValues(data){ for (var n in oscillators){ if (data.hasOwnProperty(n)){ diff --git a/examples/score.html b/examples/score.html deleted file mode 100644 index 6b536c9e0..000000000 --- a/examples/score.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - SCORE - - - - - - - - - - - - - - -
-
Scores
-
- Scores lets you to schedule a note on a particular "channel". Then use Tone.Note.route - to listen for events on that channel. Multiple notes can be put into a JSON-friendly score - which can be parsed using Tone.Note.parseScore. -

- Take a look at source to see the score format. - MIDI files can also be converted to JSON score format using utils/MidiToScore.js. -

- Docs for Tone.Note.route - and Tone.Note.parseScore -
-
- - - - - \ No newline at end of file diff --git a/examples/scripts/ExampleList.js b/examples/scripts/ExampleList.js index 8727b0d0c..5707a95cb 100644 --- a/examples/scripts/ExampleList.js +++ b/examples/scripts/ExampleList.js @@ -3,7 +3,8 @@ var ExampleList = { "Oscillators" : "oscillator", "Envelope" : "envelope", "Noise" : "noises", - "Player" : "player" + "Player" : "player", + "Microphone" : "mic" }, "Instruments" : { "SimpleSynth" : "simpleSynth", @@ -17,16 +18,19 @@ var ExampleList = { "Buses" : "buses", }, "Sequencing / Timing" : { - "Scores" : "score", "Step Sequencer" : "stepSequencer", + "Events" : "events", "Play Along" : "shiny", - "Visualizing Envelopes": "funkyShape" + "Visualizing Envelopes": "funkyShape", + "Quantization" : "quantization", + "Playback Rate" : "pianoPhase", }, "Signals" : { "Control Voltage" : "signal", "Ramping Values" : "rampTo", }, - "Advanced" : { + "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 1e893508a..f40d51602 100644 --- a/examples/scripts/Interface.js +++ b/examples/scripts/Interface.js @@ -14,11 +14,15 @@ var Interface = { $(function(){ var topbar = $("
").attr("id", "TopBar"); $("body").prepend(topbar); - $("
") - .attr("id", "Homepage") - .attr("title", "github") - .html("Tone.js") - .appendTo(topbar); + + if (typeof Tone !== "undefined"){ + var logo = new Logo({ + "container" : topbar, + "height" : topbar.height() - 6, + "width" : 140 + }); + + } $("
") .attr("id", "Examples") .attr("title", "examples") @@ -30,34 +34,14 @@ $(function(){ $("body").addClass("Mobile"); var element = $("
", {"id" : "MobileStart"}).appendTo("body"); $("
").attr("id", "Button") - .text("\u25B6") - .on("touchstart", function(e){ + .text("Enter") + .on("touchend", function(e){ e.preventDefault(); Tone.startMobile(); element.remove(); }) .appendTo(element); } - //get the master output - if (typeof Tone !== "undefined"){ - var meter = new Tone.Meter(2); - Tone.Master.connect(meter); - var meterElement = $("
").attr("id", "Meter").appendTo(topbar); - var leftLevel = $("
").addClass("Level") - .attr("id", "Left") - .appendTo(meterElement); - var rightLevel = $("
").addClass("Level") - .attr("id", "Right") - .appendTo(meterElement); - function update(){ - requestAnimationFrame(update); - var leftHeight = 100 - Math.max(Math.min(Math.abs(meter.getDb(0)), 100), 0); - var rightHeight = 100 - Math.max(Math.min(Math.abs(meter.getDb(1)), 100), 0); - leftLevel.height(leftHeight + "%"); - rightLevel.height(rightHeight + "%"); - } - update(); - } }); /** @@ -75,9 +59,9 @@ Interface.Loader = function(){ "text" : "Loading" }).appendTo(this.element); - Tone.Buffer.onload = function(){ + Tone.Buffer.on("load", function(){ this.element.addClass("Loaded"); - }.bind(this); + }.bind(this)); }; /** @@ -97,9 +81,9 @@ Interface.Dragger = function(params){ this.container = $("#DragContainer"); /** - * the gui + * the tone object */ - this.gui = params.gui; + this.tone = params.tone; /** * callbacks @@ -113,7 +97,7 @@ Interface.Dragger = function(params){ /** * the name */ - var name = params.name ? params.name : this.gui.name ? this.gui.name : ""; + var name = params.name ? params.name : this.tone ? this.tone.toString() : ""; /** * elements @@ -142,7 +126,7 @@ Interface.Dragger = function(params){ var xParams = params.x; xParams.axis = "x"; xParams.element = this.element; - xParams.gui = this.gui; + xParams.tone = this.tone; xParams.container = this.container; this.xAxis = new Interface.Slider(xParams); @@ -152,7 +136,7 @@ Interface.Dragger = function(params){ var yParams = params.y; yParams.axis = "y"; yParams.element = this.element; - yParams.gui = this.gui; + yParams.tone = this.tone; yParams.container = this.container; this.yAxis = new Interface.Slider(yParams); @@ -203,9 +187,12 @@ Interface.Dragger.prototype._onend = function(e){ */ Interface.Slider = function(params){ - this.gui = params.gui; + this.tone = params.tone; - var name = params.name ? params.name : this.gui ? this.gui.name : ""; + /** + * the name + */ + var name = params.name ? params.name : this.tone ? this.tone.toString() : ""; /** * callback functions @@ -291,7 +278,10 @@ Interface.Slider = function(params){ maxSize = this.container[this.maxAxis]() - maxSize; } - var paramValue = typeof params.value !== "undefined" ? params.value : this.gui.params[this.parameter].get(); + var paramValue = typeof params.value !== "undefined" ? params.value : this.tone.get(this.parameter); + if (paramValue.hasOwnProperty(this.parameter)){ + paramValue = paramValue[this.parameter]; + } if (this.options){ paramValue = this.options.indexOf(paramValue); @@ -355,8 +345,8 @@ Interface.Slider.prototype._onend = function(){ }; Interface.Slider.prototype._setParam = function(value){ - if (this.parameter && this.gui){ - this.gui.params[this.parameter].set(value); + if (this.parameter && this.tone){ + this.tone.set(this.parameter, value); } }; @@ -452,4 +442,28 @@ Interface.Button.prototype._keyup = function(e){ e.preventDefault(); this._end(); } +}; + +/** + * + * TRANSPORT + * + */ +Interface.Transport = function(){ + + this.element = $("
", { + "class" : "Transport", + }).appendTo("#Content"); + + this.position = $("
", { + "id" : "Position" + }).appendTo(this.element); + + this._boundLoop = this._loop.bind(this); + this._loop(); +}; + +Interface.Transport.prototype._loop = function(){ + setTimeout(this._boundLoop, 50); + this.position.text(Tone.Transport.position); }; \ No newline at end of file diff --git a/examples/scripts/Logo.js b/examples/scripts/Logo.js new file mode 100644 index 000000000..682419dc8 --- /dev/null +++ b/examples/scripts/Logo.js @@ -0,0 +1 @@ +!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", {\n "class" : "Parameter",\n "title" : desc\n });\n\n this.title = $("", {\n "id" : "Name",\n "text" : name,\n }).appendTo(this.element);\n\n this.value = $("").prop("id", "Value")\n .appendTo(this.element);\n\n this.unitText = $("", {\n "id" : "Units",\n }).appendTo(this.element);\n\n this._isVisible = false;\n\n this.setUnits(type);\n };\n\n MicroEvent.mixin(Parameter);\n\n Parameter.prototype.setUnits = function(unit){\n var unitText = "";\n this.units = unit;\n for (var typeName in TypeDefs){\n var type = TypeDefs[typeName];\n if (unit === type.value){\n this.units = type.name;\n }\n }\n switch(this.units){\n case "Frequency" : unitText = "hz"; break;\n case "NormalRange" : unitText = "0-1"; break;\n case "Cents" : unitText = "cents"; break;\n case "Time" : unitText = "time"; break;\n case "Positive" : unitText = "0+"; break;\n case "Decibels" : unitText = "db"; break;\n case "Degrees" : unitText = "deg"; break;\n default : unitText = "";\n }\n this.unitText.text(unitText);\n };\n\n Parameter.prototype.validateInput = function(value){\n switch(this.units){\n case "Frequency" : return Math.max(value, 0);\n case "NormalRange" : return Math.max(Math.min(value, 1), 0);\n case "Degrees" : return Math.max(Math.min(value, 360), 0);\n case "Time" : return Math.max(value, 0);\n case "Decibels" : return Math.min(value, 20);\n case "Positive" : return Math.max(value, 0);\n default : return value;\n }\n };\n\n Parameter.prototype.set = function(val){\n try {\n this.param[this.name] = this.validateInput(val);\n //if it\'s not open, open it\n if (this.element.hasClass("Hidden")){\n this.open();\n }\n } catch (e){\n this.value.addClass("Error");\n setTimeout(function(){\n this.value.removeClass("Error");\n }.bind(this), 10);\n }\n return this.get();\n };\n\n Parameter.prototype.get = function(){\n return this.param[this.name];\n };\n\n Parameter.prototype.update = function(){\n this.set(this.get());\n };\n\n Parameter.prototype.open = function(){\n this.element.removeClass("Hidden");\n this._isVisible = true;\n };\n\n Parameter.prototype.close = function(){\n this.element.addClass("Hidden");\n this._isVisible = false;\n };\n\n Parameter.prototype.dispose = function(){\n this.element.children().remove();\n this.element.remove();\n this.param = null;\n };\n\n Parameter.isType = function(param, name){\n return false;\n };\n\n\n /*Parameter.prototype.error = function(){\n //problem with this attribute\n };*/\n\n return Parameter;\n}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));\n\n/*****************\n ** WEBPACK FOOTER\n ** ./GUI/param/Parameter.js\n ** module id = 3\n ** module chunks = 0\n **/\n//# sourceURL=webpack:///./GUI/param/Parameter.js?')},function(module,exports,__webpack_require__){eval('/*\r\n MIT License http://www.opensource.org/licenses/mit-license.php\r\n Author Tobias Koppers @sokra\r\n*/\r\n// css base code, injected by the css-loader\r\nmodule.exports = function() {\r\n var list = [];\r\n\r\n // return the list of modules as css string\r\n list.toString = function toString() {\r\n var result = [];\r\n for(var i = 0; i < this.length; i++) {\r\n var item = this[i];\r\n if(item[2]) {\r\n result.push("@media " + item[2] + "{" + item[1] + "}");\r\n } else {\r\n result.push(item[1]);\r\n }\r\n }\r\n return result.join("");\r\n };\r\n\r\n // import a list of modules into the list\r\n list.i = function(modules, mediaQuery) {\r\n if(typeof modules === "string")\r\n modules = [[null, modules, ""]];\r\n var alreadyImportedModules = {};\r\n for(var i = 0; i < this.length; i++) {\r\n var id = this[i][0];\r\n if(typeof id === "number")\r\n alreadyImportedModules[id] = true;\r\n }\r\n for(i = 0; i < modules.length; i++) {\r\n var item = modules[i];\r\n // skip already imported module\r\n // this implementation is not 100% perfect for weird media query combinations\r\n // when a module is imported multiple times with different media queries.\r\n // I hope this will never occur (Hey this way we have smaller bundles)\r\n if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {\r\n if(mediaQuery && !item[2]) {\r\n item[2] = mediaQuery;\r\n } else if(mediaQuery) {\r\n item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";\r\n }\r\n list.push(item);\r\n }\r\n }\r\n };\r\n return list;\r\n};\r\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/css-loader/lib/css-base.js\n ** module id = 4\n ** module chunks = 0\n **/\n//# sourceURL=webpack:///./~/css-loader/lib/css-base.js?')},function(module,exports,__webpack_require__){eval('/*\r\n MIT License http://www.opensource.org/licenses/mit-license.php\r\n Author Tobias Koppers @sokra\r\n*/\r\nvar stylesInDom = {},\r\n memoize = function(fn) {\r\n var memo;\r\n return function () {\r\n if (typeof memo === "undefined") memo = fn.apply(this, arguments);\r\n return memo;\r\n };\r\n },\r\n isOldIE = memoize(function() {\r\n return /msie [6-9]\\b/.test(window.navigator.userAgent.toLowerCase());\r\n }),\r\n getHeadElement = memoize(function () {\r\n return document.head || document.getElementsByTagName("head")[0];\r\n }),\r\n singletonElement = null,\r\n singletonCounter = 0;\r\n\r\nmodule.exports = function(list, options) {\r\n if(false) {\r\n if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");\r\n }\r\n\r\n options = options || {};\r\n // Force single-tag solution on IE6-9, which has a hard limit on the # of -
+
Tone.Transport
Tone.Transport @@ -44,32 +46,26 @@ "E" : "./audio/casio/E2.mp3", "F#" : "./audio/casio/Fs2.mp3", }, { - "envelope" : { - "release" : 0.2 - } + "volume" : -10, }).toMaster(); - keys.volume.value = -15; - //keep track of steps and notes - var stepNumber = 0; - var noteNames = ["A", "C#", "E", "F#"]; + // var keys = new Tone.PolySynth(4, Tone.SimpleSynth).toMaster(); + + //the notes + var noteNames = ["F#", "E", "C#", "A"]; + // var noteNames = ["F#3", "E3", "C#3", "A3"]; - //the repeated callback - Tone.Transport.setInterval(function(time){ - //get the notes at the step - var column = matrix1.matrix[stepNumber]; + var loop = new Tone.Sequence(function(time, col){ + var column = matrix1.matrix[col]; for (var i = 0; i < 4; i++){ if (column[i] === 1){ keys.triggerAttackRelease(noteNames[i], "32n", time); } } - stepNumber++; - stepNumber = stepNumber % 16; - }, "16n"); + }, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "16n"); + + Tone.Transport.start(); - //transport settings - Tone.Transport.loopEnd = "1m"; - Tone.Transport.loop = true; - - - + + + + + + +
- \ No newline at end of file diff --git a/test/instrument/AMSynth.js b/test/instrument/AMSynth.js new file mode 100644 index 000000000..bf3147c4a --- /dev/null +++ b/test/instrument/AMSynth.js @@ -0,0 +1,54 @@ +define(["Tone/instrument/AMSynth", "helper/Basic", "helper/InstrumentTests"], function (AMSynth, Basic, InstrumentTest) { + + describe("AMSynth", function(){ + + Basic(AMSynth); + InstrumentTest(AMSynth, "C4"); + + context("API", function(){ + + it ("can get and set carrier attributes", function(){ + var amSynth = new AMSynth(); + amSynth.carrier.oscillator.type = "triangle"; + expect(amSynth.carrier.oscillator.type).to.equal("triangle"); + amSynth.dispose(); + }); + + it ("can get and set modulator attributes", function(){ + var amSynth = new AMSynth(); + amSynth.modulator.envelope.attack = 0.24; + expect(amSynth.modulator.envelope.attack).to.equal(0.24); + amSynth.dispose(); + }); + + it ("can get and set harmonicity", function(){ + var amSynth = new AMSynth(); + amSynth.harmonicity.value = 2; + expect(amSynth.harmonicity.value).to.equal(2); + amSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var amSynth = new AMSynth({ + "carrier" : { + "filter" : { + "rolloff" : -24 + } + } + }); + expect(amSynth.carrier.filter.rolloff).to.equal(-24); + amSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var amSynth = new AMSynth(); + amSynth.set({ + "harmonicity" : 1.5 + }); + expect(amSynth.get().harmonicity).to.equal(1.5); + amSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/DrumSynth.js b/test/instrument/DrumSynth.js new file mode 100644 index 000000000..0ed242b94 --- /dev/null +++ b/test/instrument/DrumSynth.js @@ -0,0 +1,54 @@ +define(["Tone/instrument/DrumSynth", "helper/Basic", "helper/InstrumentTests"], function (DrumSynth, Basic, InstrumentTest) { + + describe("DrumSynth", function(){ + + Basic(DrumSynth); + InstrumentTest(DrumSynth, "C2"); + + context("API", function(){ + + it ("can get and set oscillator attributes", function(){ + var drumSynth = new DrumSynth(); + drumSynth.oscillator.type = "triangle"; + expect(drumSynth.oscillator.type).to.equal("triangle"); + drumSynth.dispose(); + }); + + it ("can get and set envelope attributes", function(){ + var drumSynth = new DrumSynth(); + drumSynth.envelope.attack = 0.24; + expect(drumSynth.envelope.attack).to.equal(0.24); + drumSynth.dispose(); + }); + + it ("can get and set the octaves and pitch decay", function(){ + var drumSynth = new DrumSynth(); + drumSynth.octaves = 12; + drumSynth.pitchDecay = 0.2; + expect(drumSynth.pitchDecay).to.equal(0.2); + expect(drumSynth.octaves).to.equal(12); + drumSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var drumSynth = new DrumSynth({ + "envelope" : { + "sustain" : 0.3 + } + }); + expect(drumSynth.envelope.sustain).to.equal(0.3); + drumSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var drumSynth = new DrumSynth(); + drumSynth.set({ + "envelope.decay" : 0.24 + }); + expect(drumSynth.get().envelope.decay).to.equal(0.24); + drumSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/DuoSynth.js b/test/instrument/DuoSynth.js new file mode 100644 index 000000000..126bff79e --- /dev/null +++ b/test/instrument/DuoSynth.js @@ -0,0 +1,61 @@ +define(["Tone/instrument/DuoSynth", "helper/Basic", "helper/InstrumentTests"], function (DuoSynth, Basic, InstrumentTest) { + + describe("DuoSynth", function(){ + + Basic(DuoSynth); + InstrumentTest(DuoSynth, "C4"); + + context("API", function(){ + + it ("can get and set voice0 attributes", function(){ + var duoSynth = new DuoSynth(); + duoSynth.voice0.oscillator.type = "triangle"; + expect(duoSynth.voice0.oscillator.type).to.equal("triangle"); + duoSynth.dispose(); + }); + + it ("can get and set voice1 attributes", function(){ + var duoSynth = new DuoSynth(); + duoSynth.voice1.envelope.attack = 0.24; + expect(duoSynth.voice1.envelope.attack).to.equal(0.24); + duoSynth.dispose(); + }); + + it ("can get and set harmonicity", function(){ + var duoSynth = new DuoSynth(); + duoSynth.harmonicity.value = 2; + expect(duoSynth.harmonicity.value).to.equal(2); + duoSynth.dispose(); + }); + + it ("can get and set vibratoRate", function(){ + var duoSynth = new DuoSynth(); + duoSynth.vibratoRate.value = 2; + expect(duoSynth.vibratoRate.value).to.equal(2); + duoSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var duoSynth = new DuoSynth({ + "voice0" : { + "filter" : { + "rolloff" : -24 + } + } + }); + expect(duoSynth.voice0.filter.rolloff).to.equal(-24); + duoSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var duoSynth = new DuoSynth(); + duoSynth.set({ + "harmonicity" : 1.5 + }); + expect(duoSynth.get().harmonicity).to.equal(1.5); + duoSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/FMSynth.js b/test/instrument/FMSynth.js new file mode 100644 index 000000000..eafb97d4c --- /dev/null +++ b/test/instrument/FMSynth.js @@ -0,0 +1,54 @@ +define(["Tone/instrument/FMSynth", "helper/Basic", "helper/InstrumentTests"], function (FMSynth, Basic, InstrumentTest) { + + describe("FMSynth", function(){ + + Basic(FMSynth); + InstrumentTest(FMSynth, "C4"); + + context("API", function(){ + + it ("can get and set carrier attributes", function(){ + var fmSynth = new FMSynth(); + fmSynth.carrier.oscillator.type = "triangle"; + expect(fmSynth.carrier.oscillator.type).to.equal("triangle"); + fmSynth.dispose(); + }); + + it ("can get and set modulator attributes", function(){ + var fmSynth = new FMSynth(); + fmSynth.modulator.envelope.attack = 0.24; + expect(fmSynth.modulator.envelope.attack).to.equal(0.24); + fmSynth.dispose(); + }); + + it ("can get and set harmonicity", function(){ + var fmSynth = new FMSynth(); + fmSynth.harmonicity.value = 2; + expect(fmSynth.harmonicity.value).to.equal(2); + fmSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var fmSynth = new FMSynth({ + "carrier" : { + "filter" : { + "rolloff" : -24 + } + } + }); + expect(fmSynth.carrier.filter.rolloff).to.equal(-24); + fmSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var fmSynth = new FMSynth(); + fmSynth.set({ + "harmonicity" : 1.5 + }); + expect(fmSynth.get().harmonicity).to.equal(1.5); + fmSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/Instrument.js b/test/instrument/Instrument.js new file mode 100644 index 000000000..2be871543 --- /dev/null +++ b/test/instrument/Instrument.js @@ -0,0 +1,27 @@ +define(["Tone/instrument/Instrument", "helper/Basic"], function (Instrument, Basic) { + + describe("Instrument", function(){ + + Basic(Instrument); + + context("API", function(){ + + it ("can be constructed with an options object", function(){ + var instr = new Instrument({ + "volume" : -12 + }); + expect(instr.volume.value).to.be.closeTo(-12, 0.1); + instr.dispose(); + }); + + it ("can get/set attributes", function(){ + var instr = new Instrument(); + instr.set({ + "volume" : 2 + }); + expect(instr.get().volume).to.be.closeTo(2, 0.1); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/MonoSynth.js b/test/instrument/MonoSynth.js new file mode 100644 index 000000000..a321fcb07 --- /dev/null +++ b/test/instrument/MonoSynth.js @@ -0,0 +1,59 @@ +define(["Tone/instrument/MonoSynth", "helper/Basic", "helper/InstrumentTests"], function (MonoSynth, Basic, InstrumentTest) { + + describe("MonoSynth", function(){ + + Basic(MonoSynth); + InstrumentTest(MonoSynth, "C4"); + + context("API", function(){ + + it ("can get and set oscillator attributes", function(){ + var monoSynth = new MonoSynth(); + monoSynth.oscillator.type = "triangle"; + expect(monoSynth.oscillator.type).to.equal("triangle"); + monoSynth.dispose(); + }); + + it ("can get and set envelope attributes", function(){ + var monoSynth = new MonoSynth(); + monoSynth.envelope.attack = 0.24; + expect(monoSynth.envelope.attack).to.equal(0.24); + monoSynth.dispose(); + }); + + it ("can get and set filter attributes", function(){ + var monoSynth = new MonoSynth(); + monoSynth.filter.Q.value = 0.4; + expect(monoSynth.filter.Q.value).to.be.closeTo(0.4, 0.001); + monoSynth.dispose(); + }); + + it ("can get and set filterEnvelope attributes", function(){ + var monoSynth = new MonoSynth(); + monoSynth.filterEnvelope.baseFrequency = 400; + expect(monoSynth.filterEnvelope.baseFrequency).to.equal(400); + monoSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var monoSynth = new MonoSynth({ + "envelope" : { + "sustain" : 0.3 + } + }); + expect(monoSynth.envelope.sustain).to.equal(0.3); + monoSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var monoSynth = new MonoSynth(); + monoSynth.set({ + "envelope.decay" : 0.24 + }); + expect(monoSynth.get().envelope.decay).to.equal(0.24); + monoSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/Monophonic.js b/test/instrument/Monophonic.js new file mode 100644 index 000000000..e6af3af39 --- /dev/null +++ b/test/instrument/Monophonic.js @@ -0,0 +1,26 @@ +define(["Tone/instrument/Monophonic", "helper/Basic"], function (Monophonic, Basic) { + + describe("Monophonic", function(){ + + Basic(Monophonic); + + context("API", function(){ + + it ("can be constructed with an options object", function(){ + var instr = new Monophonic({ + "portamento" : 0.2 + }); + expect(instr.portamento).to.equal(0.2); + instr.dispose(); + }); + + it ("set the portamento", function(){ + var instr = new Monophonic(); + instr.portamento = 0.4; + expect(instr.portamento).to.equal(0.4); + instr.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/NoiseSynth.js b/test/instrument/NoiseSynth.js new file mode 100644 index 000000000..90d8da8d5 --- /dev/null +++ b/test/instrument/NoiseSynth.js @@ -0,0 +1,59 @@ +define(["Tone/instrument/NoiseSynth", "helper/Basic", "helper/InstrumentTests"], function (NoiseSynth, Basic, InstrumentTest) { + + describe("NoiseSynth", function(){ + + Basic(NoiseSynth); + InstrumentTest(NoiseSynth); + + context("API", function(){ + + it ("can get and set noise type", function(){ + var noiseSynth = new NoiseSynth(); + noiseSynth.noise.type = "pink"; + expect(noiseSynth.noise.type).to.equal("pink"); + noiseSynth.dispose(); + }); + + it ("can get and set envelope attributes", function(){ + var noiseSynth = new NoiseSynth(); + noiseSynth.envelope.attack = 0.24; + expect(noiseSynth.envelope.attack).to.equal(0.24); + noiseSynth.dispose(); + }); + + it ("can get and set filter attributes", function(){ + var noiseSynth = new NoiseSynth(); + noiseSynth.filter.Q.value = 0.4; + expect(noiseSynth.filter.Q.value).to.be.closeTo(0.4, 0.001); + noiseSynth.dispose(); + }); + + it ("can get and set filterEnvelope attributes", function(){ + var noiseSynth = new NoiseSynth(); + noiseSynth.filterEnvelope.baseFrequency = 400; + expect(noiseSynth.filterEnvelope.baseFrequency).to.equal(400); + noiseSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var noiseSynth = new NoiseSynth({ + "envelope" : { + "sustain" : 0.3 + } + }); + expect(noiseSynth.envelope.sustain).to.equal(0.3); + noiseSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var noiseSynth = new NoiseSynth(); + noiseSynth.set({ + "envelope.decay" : 0.24 + }); + expect(noiseSynth.get().envelope.decay).to.equal(0.24); + noiseSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/PluckSynth.js b/test/instrument/PluckSynth.js new file mode 100644 index 000000000..a219c96ef --- /dev/null +++ b/test/instrument/PluckSynth.js @@ -0,0 +1,49 @@ +define(["Tone/instrument/PluckSynth", "helper/Basic", "helper/InstrumentTests"], function (PluckSynth, Basic, InstrumentTest) { + + describe("PluckSynth", function(){ + + Basic(PluckSynth); + InstrumentTest(PluckSynth, "C3"); + + context("API", function(){ + + it ("can get and set resonance", function(){ + var pluck = new PluckSynth(); + pluck.resonance.value = 0.4; + expect(pluck.resonance.value).to.be.closeTo(0.4, 0.001); + pluck.dispose(); + }); + + it ("can get and set dampening", function(){ + var pluck = new PluckSynth(); + pluck.dampening.value = 2000; + expect(pluck.dampening.value).to.be.closeTo(2000, 0.1); + pluck.dispose(); + }); + + it ("can get and set the attackNoise", function(){ + var pluck = new PluckSynth(); + pluck.attackNoise = 0.2; + expect(pluck.attackNoise).to.be.closeTo(0.2, 0.1); + pluck.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var pluck = new PluckSynth({ + "dampening" : 300 + }); + expect(pluck.dampening.value).to.be.closeTo(300, 0.1); + pluck.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var pluck = new PluckSynth({ + "resonance" : 0.5 + }); + expect(pluck.resonance.value).to.be.closeTo(0.5, 0.001); + pluck.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/SimpleAM.js b/test/instrument/SimpleAM.js new file mode 100644 index 000000000..a26162a83 --- /dev/null +++ b/test/instrument/SimpleAM.js @@ -0,0 +1,54 @@ +define(["Tone/instrument/SimpleAM", "helper/Basic", "helper/InstrumentTests"], function (SimpleAM, Basic, InstrumentTest) { + + describe("SimpleAM", function(){ + + Basic(SimpleAM); + InstrumentTest(SimpleAM, "C4"); + + context("API", function(){ + + it ("can get and set carrier attributes", function(){ + var amSynth = new SimpleAM(); + amSynth.carrier.oscillator.type = "triangle"; + expect(amSynth.carrier.oscillator.type).to.equal("triangle"); + amSynth.dispose(); + }); + + it ("can get and set modulator attributes", function(){ + var amSynth = new SimpleAM(); + amSynth.modulator.envelope.attack = 0.24; + expect(amSynth.modulator.envelope.attack).to.equal(0.24); + amSynth.dispose(); + }); + + it ("can get and set harmonicity", function(){ + var amSynth = new SimpleAM(); + amSynth.harmonicity.value = 2; + expect(amSynth.harmonicity.value).to.equal(2); + amSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var amSynth = new SimpleAM({ + "carrier" : { + "oscillator" : { + "type" : "square2" + } + } + }); + expect(amSynth.carrier.oscillator.type).to.equal("square2"); + amSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var amSynth = new SimpleAM(); + amSynth.set({ + "harmonicity" : 1.5 + }); + expect(amSynth.get().harmonicity).to.equal(1.5); + amSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/SimpleFM.js b/test/instrument/SimpleFM.js new file mode 100644 index 000000000..160dfef85 --- /dev/null +++ b/test/instrument/SimpleFM.js @@ -0,0 +1,54 @@ +define(["Tone/instrument/SimpleFM", "helper/Basic", "helper/InstrumentTests"], function (SimpleFM, Basic, InstrumentTest) { + + describe("SimpleFM", function(){ + + Basic(SimpleFM); + InstrumentTest(SimpleFM, "C4"); + + context("API", function(){ + + it ("can get and set carrier attributes", function(){ + var fmSynth = new SimpleFM(); + fmSynth.carrier.oscillator.type = "triangle"; + expect(fmSynth.carrier.oscillator.type).to.equal("triangle"); + fmSynth.dispose(); + }); + + it ("can get and set modulator attributes", function(){ + var fmSynth = new SimpleFM(); + fmSynth.modulator.envelope.attack = 0.24; + expect(fmSynth.modulator.envelope.attack).to.equal(0.24); + fmSynth.dispose(); + }); + + it ("can get and set harmonicity", function(){ + var fmSynth = new SimpleFM(); + fmSynth.harmonicity.value = 2; + expect(fmSynth.harmonicity.value).to.equal(2); + fmSynth.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var fmSynth = new SimpleFM({ + "carrier" : { + "oscillator" : { + "type" : "square2" + } + } + }); + expect(fmSynth.carrier.oscillator.type).to.equal("square2"); + fmSynth.dispose(); + }); + + it ("can get/set attributes", function(){ + var fmSynth = new SimpleFM(); + fmSynth.set({ + "harmonicity" : 1.5 + }); + expect(fmSynth.get().harmonicity).to.equal(1.5); + fmSynth.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/instrument/SimpleSynth.js b/test/instrument/SimpleSynth.js new file mode 100644 index 000000000..714852db8 --- /dev/null +++ b/test/instrument/SimpleSynth.js @@ -0,0 +1,45 @@ +define(["Tone/instrument/SimpleSynth", "helper/Basic", "helper/InstrumentTests"], function (SimpleSynth, Basic, InstrumentTest) { + + describe("SimpleSynth", function(){ + + Basic(SimpleSynth); + InstrumentTest(SimpleSynth, "C4"); + + context("API", function(){ + + it ("can get and set oscillator attributes", function(){ + var simple = new SimpleSynth(); + simple.oscillator.type = "triangle"; + expect(simple.oscillator.type).to.equal("triangle"); + simple.dispose(); + }); + + it ("can get and set envelope attributes", function(){ + var simple = new SimpleSynth(); + simple.envelope.attack = 0.24; + expect(simple.envelope.attack).to.equal(0.24); + simple.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var simple = new SimpleSynth({ + "envelope" : { + "sustain" : 0.3 + } + }); + expect(simple.envelope.sustain).to.equal(0.3); + simple.dispose(); + }); + + it ("can get/set attributes", function(){ + var simple = new SimpleSynth(); + simple.set({ + "envelope.decay" : 0.24 + }); + expect(simple.get().envelope.decay).to.equal(0.24); + simple.dispose(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/signal/AND.js b/test/signal/AND.js new file mode 100644 index 000000000..4b690b736 --- /dev/null +++ b/test/signal/AND.js @@ -0,0 +1,106 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/AND", "Tone/signal/Signal"], +function (Offline, Basic, AND, Signal) { + + describe("AND", function(){ + + Basic(AND); + + describe("Logic", function(){ + + it("outputs 1 when both inputs are 1", function(done){ + var signal0, signal1, and; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(1); + signal1 = new Signal(1); + and = new AND(2); + signal0.connect(and, 0, 0); + signal1.connect(and, 0, 1); + and.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + and.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when only one input is 1", function(done){ + var signal0, signal1, and; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(1); + signal1 = new Signal(0); + and = new AND(2); + signal0.connect(and); + signal1.connect(and); + and.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + and.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when only the inputs are 0", function(done){ + var signal0, signal1, and; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(0); + signal1 = new Signal(0); + and = new AND(2); + signal0.connect(and); + signal1.connect(and); + and.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + and.dispose(); + done(); + }); + offline.run(); + }); + + it("works with three signals", function(done){ + var signal0, signal1, signal2, and; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(1); + signal1 = new Signal(1); + signal2 = new Signal(1); + and = new AND(3); + signal0.connect(and); + signal1.connect(and); + signal2.connect(and); + and.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + signal2.dispose(); + and.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Abs.js b/test/signal/Abs.js new file mode 100644 index 000000000..0f63b69f0 --- /dev/null +++ b/test/signal/Abs.js @@ -0,0 +1,60 @@ +define(["Test", "Tone/signal/Abs", "helper/Basic", "Tone/signal/Signal", "helper/Offline"], +function (Test, Abs, BasicTest, Signal, Offline) { + + describe("Abs", function(){ + + BasicTest(Abs); + + context("Absolute Value", function(){ + + it("handles input and output connections", function(){ + var abs = new Abs(); + Test.connect(abs); + abs.connect(Test); + abs.dispose(); + }); + + it("outputs the same value for positive values", function(done){ + var signal, abs; + var offline = new Offline(0.2); + offline.before(function(dest){ + signal = new Signal(100); + abs = new Abs(); + signal.connect(abs); + abs.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(100, 0.01); + }); + offline.after(function(){ + signal.dispose(); + abs.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the absolute value for negative numbers", function(done){ + var signal, abs; + var offline = new Offline(0.2); + offline.before(function(dest){ + signal = new Signal(-10); + abs = new Abs(); + signal.connect(abs); + abs.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(10, 0.01); + }); + offline.after(function(){ + signal.dispose(); + abs.dispose(); + done(); + }); + offline.run(); + }); + + }); + + }); +}); \ No newline at end of file diff --git a/test/signal/Add.js b/test/signal/Add.js new file mode 100644 index 000000000..f6e71f55e --- /dev/null +++ b/test/signal/Add.js @@ -0,0 +1,82 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/Add", "Tone/signal/Signal", "Test"], +function (Offline, Basic, Add, Signal, Test) { + describe("Add", function(){ + + Basic(Add); + + context("Addition", function(){ + + it("handles input and output connections", function(){ + var add = new Add(); + Test.connect(add); + Test.connect(add, 0); + Test.connect(add, 1); + add.connect(Test); + add.dispose(); + }); + + it("correctly sums a signal and a number", function(done){ + var signal, adder; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + adder = new Add(3); + signal.connect(adder); + adder.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.closeTo(3, 0.01); + }); + offline.after(function(){ + signal.dispose(); + adder.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle negative values", function(done){ + var signal, adder; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + adder = new Add(-1); + signal.connect(adder); + adder.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.closeTo(9, 0.01); + }); + offline.after(function(){ + signal.dispose(); + adder.dispose(); + done(); + }); + offline.run(); + }); + + it("can sum two signals", function(done){ + var sigA, sigB, adder; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(1); + sigB = new Signal(4); + adder = new Add(); + sigA.connect(adder, 0, 0); + sigB.connect(adder, 0, 1); + adder.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.closeTo(5, 0.01); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + adder.dispose(); + done(); + }); + offline.run(); + }); + }); + }) +}); \ No newline at end of file diff --git a/test/signal/AudioToGain.js b/test/signal/AudioToGain.js new file mode 100644 index 000000000..e5278457b --- /dev/null +++ b/test/signal/AudioToGain.js @@ -0,0 +1,100 @@ +define(["helper/Offline", "Tone/signal/AudioToGain", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, AudioToGain, Basic, Test, Oscillator, Signal) { + + describe("AudioToGain", function(){ + + Basic(AudioToGain); + + it("handles input and output connections", function(){ + var a2g = new AudioToGain(); + a2g.connect(Test); + Test.connect(a2g); + a2g.dispose(); + }); + + it("normalizes an oscillator to 0,1", function(done){ + //make an oscillator to drive the signal + var osc, a2g; + var offline = new Offline(); + offline.before(function(dest){ + osc = new Oscillator(1000); + a2g = new AudioToGain(); + osc.connect(a2g); + a2g.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.within(0, 1); + }); + offline.after(function(){ + osc.dispose(); + a2g.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0.5 for an input value of 0", function(done){ + //make an oscillator to drive the signal + var sig, a2g; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(0); + a2g = new AudioToGain(); + sig.connect(a2g); + a2g.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.5, 0.01); + }); + offline.after(function(){ + sig.dispose(); + a2g.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 for an input value of 1", function(done){ + //make an oscillator to drive the signal + var sig, a2g; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(1); + a2g = new AudioToGain(); + sig.connect(a2g); + a2g.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(1, 0.01); + }); + offline.after(function(){ + sig.dispose(); + a2g.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 for an input value of -1", function(done){ + //make an oscillator to drive the signal + var sig, a2g; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(-1); + a2g = new AudioToGain(); + sig.connect(a2g); + a2g.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0, 0.01); + }); + offline.after(function(){ + sig.dispose(); + a2g.dispose(); + done(); + }); + offline.run(); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Clip.js b/test/signal/Clip.js new file mode 100644 index 000000000..ad84c11ac --- /dev/null +++ b/test/signal/Clip.js @@ -0,0 +1,79 @@ +define(["helper/Offline", "Tone/signal/Clip", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Clip, Basic, Test, Oscillator, Signal) { + + describe("Clip", function(){ + + Basic(Clip); + + describe("Range Clipping", function(){ + + it("handles input and output connections", function(){ + var clip = new Clip(0, 1); + Test.connect(clip); + clip.connect(Test); + clip.dispose(); + }); + + it("output the upper limit when signal is greater than clip", function(done){ + var signal, clip; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(4); + clip = new Clip(2, 3); + signal.connect(clip); + clip.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(3); + }); + offline.after(function(){ + signal.dispose(); + clip.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the incoming signal when in between upper and lower limit", function(done){ + var signal, clip; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-12); + clip = new Clip(-14, 14); + signal.connect(clip); + clip.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-12); + }); + offline.after(function(){ + signal.dispose(); + clip.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the lower limit when incoming signal is less than the lower limit", function(done){ + var signal, clip; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-12); + clip = new Clip(0, 8); + signal.connect(clip); + clip.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + clip.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Equal.js b/test/signal/Equal.js new file mode 100644 index 000000000..9b9a0ef4a --- /dev/null +++ b/test/signal/Equal.js @@ -0,0 +1,146 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/Equal", "Tone/signal/Signal", "Test"], +function (Offline, Basic, Equal, Signal, Test) { + describe("Equal", function(){ + + Basic(Equal); + + context("Comparison", function(){ + + it("handles input and output connections", function(){ + var eq = new Equal(); + Test.connect(eq); + Test.connect(eq, 0); + Test.connect(eq, 1); + eq.connect(Test); + eq.dispose(); + }); + + it("outputs 0 when values are not equal", function(done){ + var signal, eq; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + eq = new Equal(3); + signal.connect(eq); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when values are equal", function(done){ + var signal, eq; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(40); + eq = new Equal(40); + signal.connect(eq); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle negative values", function(done){ + var signal, eq; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-1); + eq = new Equal(-1); + signal.connect(eq); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + + it("can set a new value", function(done){ + var signal, eq; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(2); + eq = new Equal(-100); + eq.value = 2; + signal.connect(eq); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + + it("can compare unequal signals", function(done){ + var sigA, sigB, eq; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(1); + sigB = new Signal(4); + eq = new Equal(); + sigA.connect(eq, 0, 0); + sigB.connect(eq, 0, 1); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + + it("can compare equal signals", function(done){ + var sigA, sigB, eq; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(12.01); + sigB = new Signal(12.01); + eq = new Equal(); + sigA.connect(eq, 0, 0); + sigB.connect(eq, 0, 1); + eq.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + eq.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/EqualPowerGain.js b/test/signal/EqualPowerGain.js new file mode 100644 index 000000000..00a20a493 --- /dev/null +++ b/test/signal/EqualPowerGain.js @@ -0,0 +1,53 @@ +define(["helper/Offline", "Tone/signal/EqualPowerGain", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal", "helper/PassAudio"], + function (Offline, EqualPowerGain, Basic, Test, Oscillator, Signal, PassAudio) { + + describe("EqualPowerGain", function(){ + + Basic(EqualPowerGain); + + context("Equal Power Gain", function(){ + + it("handles input and output connections", function(){ + var eqGain = new EqualPowerGain(); + Test.connect(eqGain); + eqGain.connect(Test); + eqGain.dispose(); + }); + + it ("passes audio through", function(done){ + var eqGain; + PassAudio(function(input, output){ + eqGain = new EqualPowerGain(); + input.chain(eqGain, output); + }, function(){ + eqGain.dispose(); + done(); + }); + }); + + it("scales the input on an equal power scale", function(done){ + //make an oscillator to drive the signal + var sig, eqGain; + var offline = new Offline(1); + offline.before(function(dest){ + sig = new Signal(0); + eqGain = new EqualPowerGain(); + sig.connect(eqGain); + eqGain.connect(dest); + sig.setValueAtTime(0, 0); + sig.linearRampToValueAtTime(1, 1); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(eqGain.equalPowerScale(time), 0.01); + }); + offline.after(function(){ + sig.dispose(); + eqGain.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/EqualZero.js b/test/signal/EqualZero.js new file mode 100644 index 000000000..ec20de075 --- /dev/null +++ b/test/signal/EqualZero.js @@ -0,0 +1,91 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/EqualZero", "Tone/signal/Signal"], +function (Offline, Basic, EqualZero, Signal) { + + describe("EqualZero", function(){ + + Basic(EqualZero); + + describe("Comparison", function(){ + + it("outputs 0 when the value is less than 0", function(done){ + var signal, eq0; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-1); + eq0 = new EqualZero(); + signal.connect(eq0); + eq0.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + eq0.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when the value is greater than 0", function(done){ + var signal, eq0; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + eq0 = new EqualZero(); + signal.connect(eq0); + eq0.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + eq0.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when the value is equal to 0", function(done){ + var signal, eq0; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + eq0 = new EqualZero(); + signal.connect(eq0); + eq0.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + eq0.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when the value is slightly above 0", function(done){ + var signal, eq0; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.001); + eq0 = new EqualZero(); + signal.connect(eq0); + eq0.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + eq0.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Expr.js b/test/signal/Expr.js new file mode 100644 index 000000000..ef2184b5a --- /dev/null +++ b/test/signal/Expr.js @@ -0,0 +1,810 @@ + +define(["Tone/signal/Signal", "Tone/signal/Expr", "Test", "helper/Basic", + "helper/OutputAudio", "helper/PassAudio", "helper/Offline"], +function(Signal, Expr, Test, Basic, OutputAudio, PassAudio, Offline){ + + describe("Expr", function(){ + + Basic(Expr, "1"); + + context("I/O", function(){ + + it("can create inputs", function(){ + var exp = new Expr("$0 + $1"); + Test.connect(exp, 0); + Test.connect(exp, 1); + exp.dispose(); + }); + + it("has an output", function(){ + var exp = new Expr("0 + 0"); + exp.connect(Test); + exp.dispose(); + }); + + it("outputs audio", function(done){ + var exp; + OutputAudio(function(out){ + exp = new Expr("1.1"); + exp.connect(out); + }, function(){ + exp.dispose(); + done(); + }); + }); + + it("passes input", function(done){ + var exp; + PassAudio(function(input, output){ + exp = new Expr("$0"); + input.connect(exp); + exp.connect(output); + }, function(){ + exp.dispose(); + done(); + }); + }); + }); + + context("Parsing", function(){ + + it("can do string replacements", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("% + %", 0.2, 0.8); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(1, 0.001); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("can do string replacements with strings", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("%", "1 + 2"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(3, 0.001); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles precendence", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("8 + 16 * 4 * (2 - 1)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(72, 0.01); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles complex precendence", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("2 * 2 + 1 > 0 == 0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("tolerates inconsistent spacing", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("2 * 3-2 *4 "); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-2); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles parens", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("(8 + 16) * (4 - 1)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(72); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Math", function(){ + + it("does signal addition", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1 + 3"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(4); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("does signal multiplication", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1.5 * 6"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(9); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("does signal subtraction", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("8 - 16"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-8); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Logic", function(){ + + it("correctly outputs 1 for 1 && 1", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1 && 1"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 0 for 0 && 1", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("0 && 1"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 0 for 0 || 0", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("0||0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 0 || 1", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("0 || 1"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 1 || 0", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1 || 0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 1 > 0", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1 > 0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 100 > 99", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1 > 0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 0 for -10 > -9", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("-10 > -9"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 1.001 < 1.002", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("1.001 < 1.002"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 0 for 11 < 1.002", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("11 < 1.002"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 for 11.001 == 11.001", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("11.001 == 11.001"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 0 for 11.002 == 11.001", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("11.002 == 11.001"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Unary Operators", function(){ + + it("correctly outputs negative", function(done){ + var exp, sig; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(1); + exp = new Expr("-$0"); + sig.connect(exp); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-1); + }); + offline.after(function(){ + exp.dispose(); + sig.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly handles NOT (!)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("!0"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Functions", function(){ + + it("handles if(false)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("if(0, 2, 11)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(11); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles if(true)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("if(1, 2, 11)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(2); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles abs(-10)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("abs(-10)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles abs(11)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("abs(11)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(11); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles min(10, 11)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("min(10, 11)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles min(7, -100)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("min(7, -100)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-100); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles max(10, 11)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("max(10, 11)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(11); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles max(7, -100)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("max(7, -100)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(7); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles mod(0.1, 0.9)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("mod(0.1, 0.9)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.1, 0.0001); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("handles mod(0.5, 0.25)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("mod(0.6, 0.25)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.1, 0.0001); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 for gt0(9)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("gt0(9)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 for gt0(-9)", function(done){ + var exp; + var offline = new Offline(); + offline.before(function(dest){ + exp = new Expr("gt0(-9)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 for eq0(0)", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("eq0(0)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 for eq0(-10)", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("eq0(-10)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("computes pow(0.2, 3)", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("pow(0.2, 3)"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.008, 0.001); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Nested Operators", function(){ + + it("correctly outputs 1 in if(2 * 4 > 8, max(1, 2), min(1, 2))", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("if(2 * 4 > 8, max(1, 2), min(1, 2))"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 1 in if(2 * 4 > 8, max(1, 2), min(1, 2))", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("if(2 * 4 > 8, max(1, 2), min(1, 2))"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 29 for abs(if(0 < -2, -10, -29))", function(done){ + var exp; + var offline = new Offline(); offline.before(function(dest){ + exp = new Expr("abs(if(0 < -2, -10, -29))"); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(29); + }); + offline.after(function(){ + exp.dispose(); + done(); + }); + offline.run(); + }); + + }); + + context("Signal Inputs", function(){ + + it("correctly outputs 1 in if($0 * $1 > 8, $1, $0) with inputs 1 and 7", function(done){ + var exp, sig0, sig1; + var offline = new Offline(); offline.before(function(dest){ + sig0 = new Signal(1); + sig1 = new Signal(7); + exp = new Expr("if($0 * $1 > 8, $1, $0)"); + sig0.connect(exp, 0, 0); + sig1.connect(exp, 0, 1); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + sig0.dispose(); + sig1.dispose(); + exp.dispose(); + done(); + }); + offline.run(); + }); + + it("correctly outputs 3 in max($0 * $1, $0 + $1) with inputs 1 and 2", function(done){ + var exp, sig0, sig1; + var offline = new Offline(); offline.before(function(dest){ + sig0 = new Signal(1); + sig1 = new Signal(2); + exp = new Expr("max($0 * $1, $0 + $1)"); + sig0.connect(exp, 0, 0); + sig1.connect(exp, 0, 1); + exp.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(3); + }); + offline.after(function(){ + sig0.dispose(); + sig1.dispose(); + exp.dispose(); + done(); + }); + offline.run(); + }); + }); + }); + +}); \ No newline at end of file diff --git a/test/signal/GainToAudio.js b/test/signal/GainToAudio.js new file mode 100644 index 000000000..c6380f9c6 --- /dev/null +++ b/test/signal/GainToAudio.js @@ -0,0 +1,82 @@ +define(["helper/Offline", "Tone/signal/GainToAudio", "helper/Basic", + "Test", "Tone/signal/Signal"], + function (Offline, GainToAudio, Basic, Test, Signal) { + + describe("GainToAudio", function(){ + + Basic(GainToAudio); + + context("Gain To Audio", function(){ + + it("handles input and output connections", function(){ + var g2a = new GainToAudio(); + Test.connect(g2a); + g2a.connect(Test); + g2a.dispose(); + }); + + it("outputs 0 for an input value of 0.5", function(done){ + //make an oscillator to drive the signal + var sig, g2a; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(0.5); + g2a = new GainToAudio(); + sig.connect(g2a); + g2a.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0, 0.01); + }); + offline.after(function(){ + sig.dispose(); + g2a.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 for an input value of 1", function(done){ + //make an oscillator to drive the signal + var sig, g2a; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(1); + g2a = new GainToAudio(); + sig.connect(g2a); + g2a.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(1, 0.01); + }); + offline.after(function(){ + sig.dispose(); + g2a.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs -1 for an input value of 0", function(done){ + //make an oscillator to drive the signal + var sig, g2a; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(0); + g2a = new GainToAudio(); + sig.connect(g2a); + g2a.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(-1, 0.01); + }); + offline.after(function(){ + sig.dispose(); + g2a.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/GreaterThan.js b/test/signal/GreaterThan.js new file mode 100644 index 000000000..a878fecd6 --- /dev/null +++ b/test/signal/GreaterThan.js @@ -0,0 +1,166 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/GreaterThan", "Tone/signal/Signal", "Test"], +function (Offline, Basic, GreaterThan, Signal, Test) { + describe("GreaterThan", function(){ + + Basic(GreaterThan); + + context("Comparison", function(){ + + it("handles input and output connections", function(){ + var gt = new GreaterThan(); + Test.connect(gt); + Test.connect(gt, 0); + Test.connect(gt, 1); + gt.connect(Test); + gt.dispose(); + }); + + it("outputs 0 when signal is less than value", function(done){ + var signal, gt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + gt = new GreaterThan(20); + signal.connect(gt); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when signal is equal to the value", function(done){ + var signal, gt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + gt = new GreaterThan(10); + signal.connect(gt); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 value is greater than", function(done){ + var signal, gt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(80); + gt = new GreaterThan(40); + signal.connect(gt); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle negative values", function(done){ + var signal, gt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-2); + gt = new GreaterThan(-4); + signal.connect(gt); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("can set a new value", function(done){ + var signal, gt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(2); + gt = new GreaterThan(-100); + gt.value = 1; + signal.connect(gt); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when first signal is greater than second", function(done){ + var sigA, sigB, gt; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(1); + sigB = new Signal(4); + gt = new GreaterThan(); + sigA.connect(gt, 0, 0); + sigB.connect(gt, 0, 1); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when first signal is less than second", function(done){ + var sigA, sigB, gt; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(5); + sigB = new Signal(2); + gt = new GreaterThan(); + sigA.connect(gt, 0, 0); + sigB.connect(gt, 0, 1); + gt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + gt.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/GreaterThanZero.js b/test/signal/GreaterThanZero.js new file mode 100644 index 000000000..053580780 --- /dev/null +++ b/test/signal/GreaterThanZero.js @@ -0,0 +1,91 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/GreaterThanZero", "Tone/signal/Signal"], +function (Offline, Basic, GreaterThanZero, Signal) { + + describe("GreaterThanZero", function(){ + + Basic(GreaterThanZero); + + describe("Comparison", function(){ + + it("Outputs 0 when the value is less than 0", function(done){ + var signal, gtz; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-1); + gtz = new GreaterThanZero(); + signal.connect(gtz); + gtz.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + gtz.dispose(); + done(); + }); + offline.run(); + }); + + it("Outputs 1 when the value is greater than 0", function(done){ + var signal, gtz; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + gtz = new GreaterThanZero(); + signal.connect(gtz); + gtz.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + gtz.dispose(); + done(); + }); + offline.run(); + }); + + it("Outputs 0 when the value is equal to 0", function(done){ + var signal, gtz; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + gtz = new GreaterThanZero(); + signal.connect(gtz); + gtz.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + gtz.dispose(); + done(); + }); + offline.run(); + }); + + it("Outputs 1 when the value is slightly above 0", function(done){ + var signal, gtz; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.001); + gtz = new GreaterThanZero(); + signal.connect(gtz); + gtz.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + gtz.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/IfThenElse.js b/test/signal/IfThenElse.js new file mode 100644 index 000000000..dca2ddcb9 --- /dev/null +++ b/test/signal/IfThenElse.js @@ -0,0 +1,104 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/IfThenElse", "Tone/signal/Signal", "Test"], +function (Offline, Basic, IfThenElse, Signal, Test) { + + describe("IfThenElse", function(){ + + Basic(IfThenElse); + + describe("Conditional", function(){ + + it ("handles input and output connections", function(){ + var ite = new IfThenElse(); + ite.connect(Test); + Test.connect(ite); + Test.connect(ite.if); + Test.connect(ite.then); + Test.connect(ite.else); + ite.dispose(); + }); + + it("selects the second input (then) when input 0 (if) is 1", function(done){ + var ifSignal, thenSignal, elseSignal, ite; + var offline = new Offline(); + offline.before(function(dest){ + ifSignal = new Signal(1); + thenSignal = new Signal(10); + elseSignal = new Signal(20); + ite = new IfThenElse(); + ifSignal.connect(ite, 0, 0); + thenSignal.connect(ite, 0, 1); + elseSignal.connect(ite, 0, 2); + ite.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + ifSignal.dispose(); + thenSignal.dispose(); + elseSignal.dispose(); + ite.dispose(); + done(); + }); + offline.run(); + }); + + it("selects the third input (else) when input 0 (if) is 0", function(done){ + var ifSignal, thenSignal, elseSignal, ite; + var offline = new Offline(); + offline.before(function(dest){ + ifSignal = new Signal(0); + thenSignal = new Signal(10); + elseSignal = new Signal(20); + ite = new IfThenElse(); + ifSignal.connect(ite, 0, 0); + thenSignal.connect(ite, 0, 1); + elseSignal.connect(ite, 0, 2); + ite.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(20); + }); + offline.after(function(){ + ifSignal.dispose(); + thenSignal.dispose(); + elseSignal.dispose(); + ite.dispose(); + done(); + }); + offline.run(); + }); + + it("changes output when the if signal changes", function(done){ + var ifSignal, thenSignal, elseSignal, ite; + var offline = new Offline(0.5); + offline.before(function(dest){ + ifSignal = new Signal(0); + thenSignal = new Signal(5); + elseSignal = new Signal(15); + ite = new IfThenElse(); + ifSignal.connect(ite, 0, 0); + thenSignal.connect(ite, 0, 1); + elseSignal.connect(ite, 0, 2); + ite.connect(dest); + ifSignal.setValueAtTime(1, 0.2); + }); + offline.test(function(sample, time){ + if (time >= 0.2){ + expect(sample).to.equal(5); + } else { + expect(sample).to.equal(15); + } + }); + offline.after(function(){ + ifSignal.dispose(); + thenSignal.dispose(); + elseSignal.dispose(); + ite.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/LessThan.js b/test/signal/LessThan.js new file mode 100644 index 000000000..c06bc0b50 --- /dev/null +++ b/test/signal/LessThan.js @@ -0,0 +1,166 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/LessThan", "Tone/signal/Signal", "Test"], +function (Offline, Basic, LessThan, Signal, Test) { + describe("LessThan", function(){ + + Basic(LessThan); + + context("Comparison", function(){ + + it("handles input and output connections", function(){ + var lt = new LessThan(); + Test.connect(lt); + Test.connect(lt, 0); + Test.connect(lt, 1); + lt.connect(Test); + lt.dispose(); + }); + + it("outputs 1 when signal is less than the value", function(done){ + var signal, lt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(4); + lt = new LessThan(20.02); + signal.connect(lt); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when signal is equal to the value", function(done){ + var signal, lt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(16.001); + lt = new LessThan(16.001); + signal.connect(lt); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 value is greater than", function(done){ + var signal, lt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(20); + lt = new LessThan(10); + signal.connect(lt); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle negative values", function(done){ + var signal, lt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-8); + lt = new LessThan(-4); + signal.connect(lt); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("can set a new value", function(done){ + var signal, lt; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(2); + lt = new LessThan(-100); + lt.value = 10; + signal.connect(lt); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when first signal is less than second", function(done){ + var sigA, sigB, lt; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(1); + sigB = new Signal(4); + lt = new LessThan(); + sigA.connect(lt, 0, 0); + sigB.connect(lt, 0, 1); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when first signal is greater than second", function(done){ + var sigA, sigB, lt; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(8.001); + sigB = new Signal(8); + lt = new LessThan(); + sigA.connect(lt, 0, 0); + sigB.connect(lt, 0, 1); + lt.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + lt.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Max.js b/test/signal/Max.js new file mode 100644 index 000000000..3c03693c8 --- /dev/null +++ b/test/signal/Max.js @@ -0,0 +1,104 @@ +define(["helper/Offline", "Tone/signal/Max", "helper/Basic", + "Test", "Tone/signal/Signal"], + function (Offline, Max, Basic, Test, Signal) { + + describe("Max", function(){ + + Basic(Max); + + context("Maximum", function(){ + + it("handles input and output connections", function(){ + var max = new Max(); + Test.connect(max, 0); + Test.connect(max, 1); + max.connect(Test); + max.dispose(); + }); + + it("outputs the set value when less than the incoming signal", function(done){ + var signal, max; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + max = new Max(2); + signal.connect(max); + max.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(2); + }); + offline.after(function(){ + signal.dispose(); + max.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the incoming signal when greater than the max", function(done){ + var signal, max; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + max = new Max(-1); + signal.connect(max); + max.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + signal.dispose(); + max.dispose(); + done(); + }); + offline.run(); + }); + + it("can be set to a new value", function(done){ + var signal, max; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + max = new Max(-1); + signal.connect(max); + max.value = 12; + max.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(12); + }); + offline.after(function(){ + signal.dispose(); + max.dispose(); + done(); + }); + offline.run(); + }); + + it("can use two signals", function(done){ + var sigA, sigB, max; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(50); + max = new Max(); + sigA.connect(max, 0, 0); + sigB.connect(max, 0, 1); + max.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(50); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + max.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Min.js b/test/signal/Min.js new file mode 100644 index 000000000..1fc167882 --- /dev/null +++ b/test/signal/Min.js @@ -0,0 +1,104 @@ +define(["helper/Offline", "Tone/signal/Min", "helper/Basic", + "Test", "Tone/signal/Signal"], + function (Offline, Min, Basic, Test, Signal) { + + describe("Min", function(){ + + Basic(Min); + + context("Minimum", function(){ + + it("handles input and output connections", function(){ + var min = new Min(); + Test.connect(min, 0); + Test.connect(min, 1); + min.connect(Test); + min.dispose(); + }); + + it("outputs the set value when greater than the incoming signal", function(done){ + var signal, min; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(4); + min = new Min(2); + signal.connect(min); + min.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(2); + }); + offline.after(function(){ + signal.dispose(); + min.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the incoming signal when less than the min", function(done){ + var signal, min; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-12); + min = new Min(-4); + signal.connect(min); + min.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-12); + }); + offline.after(function(){ + signal.dispose(); + min.dispose(); + done(); + }); + offline.run(); + }); + + it("can be set to a new value", function(done){ + var signal, min; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(3); + min = new Min(-4); + signal.connect(min); + min.value = 4; + min.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(3); + }); + offline.after(function(){ + signal.dispose(); + min.dispose(); + done(); + }); + offline.run(); + }); + + it("can use two signals", function(done){ + var sigA, sigB, min; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(5); + min = new Min(); + sigA.connect(min, 0, 0); + sigB.connect(min, 0, 1); + min.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(3); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + min.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Modulo.js b/test/signal/Modulo.js new file mode 100644 index 000000000..36494610e --- /dev/null +++ b/test/signal/Modulo.js @@ -0,0 +1,81 @@ +define(["helper/Offline", "Tone/signal/Modulo", "helper/Basic", + "Test", "Tone/signal/Signal"], + function (Offline, Modulo, Basic, Test, Signal) { + + describe("Modulo", function(){ + + Basic(Modulo); + + context("Exponential Scaling", function(){ + + it("handles input and output connections", function(){ + var mod = new Modulo(); + Test.connect(mod); + mod.connect(Test); + mod.dispose(); + }); + + it("can evaluate 0.45 % 0.3", function(done){ + var signal, mod; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.45); + mod = new Modulo(0.3); + signal.connect(mod); + mod.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.15, 0.0001); + }); + offline.after(function(){ + signal.dispose(); + mod.dispose(); + done(); + }); + offline.run(); + }); + + it("can evaluate 0.1 % 0.2", function(done){ + var signal, mod; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.1); + mod = new Modulo(0.2); + signal.connect(mod); + mod.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.1, 0.0001); + }); + offline.after(function(){ + signal.dispose(); + mod.dispose(); + done(); + }); + offline.run(); + }); + + it("can set a new modulo value", function(done){ + var signal, mod; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.4); + mod = new Modulo(0.1); + mod.value = 0.35; + expect(mod.value).to.be.closeTo(0.35, 0.001); + signal.connect(mod); + mod.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.05, 0.0001); + }); + offline.after(function(){ + signal.dispose(); + mod.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Multiply.js b/test/signal/Multiply.js new file mode 100644 index 000000000..e34b3eedc --- /dev/null +++ b/test/signal/Multiply.js @@ -0,0 +1,63 @@ +define(["helper/Offline", "Tone/signal/Multiply", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Multiply, Basic, Test, Oscillator, Signal) { + + describe("Multiply", function(){ + + Basic(Multiply); + + describe("Multiplication", function(){ + + it("handles input and output connections", function(){ + var mult = new Multiply(); + Test.connect(mult, 0); + Test.connect(mult, 1); + mult.connect(Test); + mult.dispose(); + }); + + it("correctly multiplys a signal and a scalar", function(done){ + var signal, mult; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(2); + mult = new Multiply(10); + signal.connect(mult); + mult.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(20); + }); + offline.after(function(){ + signal.dispose(); + mult.dispose(); + done(); + }); + offline.run(); + }); + + it("can multiply two signals", function(done){ + var sigA, sigB, mult; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(5); + mult = new Multiply(); + sigA.connect(mult, 0, 0); + sigB.connect(mult, 0, 1); + mult.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(15); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + mult.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/NOT.js b/test/signal/NOT.js new file mode 100644 index 000000000..d23ac415b --- /dev/null +++ b/test/signal/NOT.js @@ -0,0 +1,51 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/NOT", "Tone/signal/Signal"], +function (Offline, Basic, NOT, Signal) { + + describe("NOT", function(){ + + Basic(NOT); + + describe("Logic", function(){ + + it("outputs 0 when the input is 1", function(done){ + var signal, not; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + not = new NOT(); + signal.connect(not); + not.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + not.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when the input is 0", function(done){ + var signal, not; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + not = new NOT(); + signal.connect(not); + not.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal.dispose(); + not.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Negate.js b/test/signal/Negate.js new file mode 100644 index 000000000..6f3c51ae7 --- /dev/null +++ b/test/signal/Negate.js @@ -0,0 +1,59 @@ +define(["helper/Offline", "Tone/signal/Negate", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Negate, Basic, Test, Oscillator, Signal) { + + describe("Negate", function(){ + + Basic(Negate); + + context("Negating", function(){ + + it("handles input and output connections", function(){ + var negate = new Negate(); + Test.connect(negate); + negate.connect(Test); + negate.dispose(); + }); + + it("negateates a positive value", function(done){ + var signal, negate; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + negate = new Negate(); + signal.connect(negate); + negate.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-1); + }); + offline.after(function(){ + signal.dispose(); + negate.dispose(); + done(); + }); + offline.run(); + }); + + it("makes a negateative value positive", function(done){ + var signal, negate; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-10); + negate = new Negate(); + signal.connect(negate); + negate.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + signal.dispose(); + negate.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Normalize.js b/test/signal/Normalize.js new file mode 100644 index 000000000..110f1ef73 --- /dev/null +++ b/test/signal/Normalize.js @@ -0,0 +1,105 @@ +define(["helper/Offline", "Tone/signal/Normalize", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Normalize, Basic, Test, Oscillator, Signal) { + + describe("Normalize", function(){ + + Basic(Normalize); + + context("Normalizing", function(){ + + it("handles input and output connections", function(){ + var norm = new Normalize(); + Test.connect(norm); + norm.connect(Test); + norm.dispose(); + }); + + it("normalizes an oscillator to 0,1", function(done){ + //make an oscillator to drive the signal + var osc, norm; + var offline = new Offline(); + offline.before(function(dest){ + osc = new Oscillator(1000); + norm = new Normalize(-1, 1); + osc.connect(norm); + norm.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.within(0, 1); + }); + offline.after(function(){ + osc.dispose(); + norm.dispose(); + done(); + }); + offline.run(); + }); + + it("normalizes an input at the max range to 1", function(done){ + //make an oscillator to drive the signal + var sig, norm; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(1000); + norm = new Normalize(0, 1000); + sig.connect(norm); + norm.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(1, 0.001); + }); + offline.after(function(){ + sig.dispose(); + norm.dispose(); + done(); + }); + offline.run(); + }); + + it("normalizes an input at the min range to 0", function(done){ + //make an oscillator to drive the signal + var sig, norm; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(-10); + norm = new Normalize(-10, 1000); + sig.connect(norm); + norm.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0, 0.001); + }); + offline.after(function(){ + sig.dispose(); + norm.dispose(); + done(); + }); + offline.run(); + }); + + it("can set the min and max", function(done){ + //make an oscillator to drive the signal + var sig, norm; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(10); + norm = new Normalize(0, 1); + norm.min = 5; + norm.max = 15; + sig.connect(norm); + norm.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.5, 0.001); + }); + offline.after(function(){ + sig.dispose(); + norm.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/OR.js b/test/signal/OR.js new file mode 100644 index 000000000..10ab92003 --- /dev/null +++ b/test/signal/OR.js @@ -0,0 +1,106 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/OR", "Tone/signal/Signal"], +function (Offline, Basic, OR, Signal) { + + describe("OR", function(){ + + Basic(OR); + + describe("Logic", function(){ + + it("outputs 1 when both inputs are 1", function(done){ + var signal0, signal1, or; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(1); + signal1 = new Signal(1); + or = new OR(2); + signal0.connect(or, 0, 0); + signal1.connect(or, 0, 1); + or.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + or.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 1 when only one input is 1", function(done){ + var signal0, signal1, or; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(1); + signal1 = new Signal(0); + or = new OR(2); + signal0.connect(or, 0, 0); + signal1.connect(or, 0, 1); + or.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + or.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs 0 when both the inputs are 0", function(done){ + var signal0, signal1, or; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(0); + signal1 = new Signal(0); + or = new OR(2); + signal0.connect(or, 0, 0); + signal1.connect(or, 0, 1); + or.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + or.dispose(); + done(); + }); + offline.run(); + }); + + it("works with three signals", function(done){ + var signal0, signal1, signal2, or; + var offline = new Offline(); + offline.before(function(dest){ + signal0 = new Signal(0); + signal1 = new Signal(0); + signal2 = new Signal(1); + or = new OR(3); + signal0.connect(or, 0, 0); + signal1.connect(or, 0, 1); + signal2.connect(or, 0, 2); + or.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(1); + }); + offline.after(function(){ + signal0.dispose(); + signal1.dispose(); + signal2.dispose(); + or.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Pow.js b/test/signal/Pow.js new file mode 100644 index 000000000..09cf60223 --- /dev/null +++ b/test/signal/Pow.js @@ -0,0 +1,80 @@ +define(["helper/Offline", "Tone/signal/Pow", "helper/Basic", + "Test", "Tone/signal/Signal"], + function (Offline, Pow, Basic, Test, Signal) { + + describe("Pow", function(){ + + Basic(Pow); + + context("Exponential Scaling", function(){ + + it("handles input and output connections", function(){ + var pow = new Pow(); + Test.connect(pow); + pow.connect(Test); + pow.dispose(); + }); + + it("can do powers of 2", function(done){ + var signal, pow; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.3); + pow = new Pow(2); + signal.connect(pow); + pow.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.09, 0.01); + }); + offline.after(function(){ + signal.dispose(); + pow.dispose(); + done(); + }); + offline.run(); + }); + + it("can compute negative values and powers less than 1", function(done){ + var signal, pow; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-0.49); + pow = new Pow(0.5); + signal.connect(pow); + pow.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.7, 0.01); + }); + offline.after(function(){ + signal.dispose(); + pow.dispose(); + done(); + }); + offline.run(); + }); + + it("can set a new exponent", function(done){ + var signal, pow; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.5); + pow = new Pow(1); + pow.value = 3; + signal.connect(pow); + pow.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.125, 0.01); + }); + offline.after(function(){ + signal.dispose(); + pow.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Route.js b/test/signal/Route.js new file mode 100644 index 000000000..c0fa23855 --- /dev/null +++ b/test/signal/Route.js @@ -0,0 +1,134 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/Route", "Tone/signal/Signal", "Test", "Tone/component/Merge"], +function (Offline, Basic, Route, Signal, Test, Merge) { + + describe("Route", function(){ + + Basic(Route); + + describe("Routeing Logic", function(){ + + it ("handles input and output connections", function(){ + var route = new Route(); + route.connect(Test); + Test.connect(route); + Test.connect(route.gate); + route.dispose(); + }); + + it("can route a signal to the first output", function(done){ + var signal, route, merge; + var offline = new Offline(0.3, 2); + offline.before(function(dest){ + signal = new Signal(10); + route = new Route(2); + merge = new Merge(); + signal.connect(route); + route.connect(merge, 0, 0); + route.connect(merge, 1, 1); + merge.connect(dest); + }); + offline.test(function(samples){ + expect(samples[0]).to.equal(10); + expect(samples[1]).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + route.dispose(); + merge.dispose(); + done(); + }); + offline.run(); + }); + + it("can route a signal to the second output", function(done){ + var signal, route, merge; + var offline = new Offline(0.3, 2); + offline.before(function(dest){ + signal = new Signal(10); + route = new Route(2); + route.select(1); + merge = new Merge(); + signal.connect(route); + route.connect(merge, 0, 0); + route.connect(merge, 1, 1); + merge.connect(dest); + }); + offline.test(function(samples){ + expect(samples[0]).to.equal(0); + expect(samples[1]).to.equal(10); + }); + offline.after(function(){ + signal.dispose(); + route.dispose(); + merge.dispose(); + done(); + }); + offline.run(); + }); + + it("can schedule a route", function(done){ + var signal, route, merge; + var offline = new Offline(0.5, 2); + offline.before(function(dest){ + signal = new Signal(5); + route = new Route(2); + route.select(1, 0.3); + merge = new Merge(); + signal.connect(route); + route.connect(merge, 0, 0); + route.connect(merge, 1, 1); + merge.connect(dest); + }); + offline.test(function(samples, time){ + if (time < 0.3){ + expect(samples[0]).to.equal(5); + expect(samples[1]).to.equal(0); + } else { + expect(samples[0]).to.equal(0); + expect(samples[1]).to.equal(5); + } + }); + offline.after(function(){ + signal.dispose(); + route.dispose(); + merge.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle 3 outputs", function(done){ + var signal, route, merge; + var offline = new Offline(0.5, 2); + offline.before(function(dest){ + signal = new Signal(5); + route = new Route(3); + route.select(1, 0); + route.select(2, 0.3); + merge = new Merge(); + signal.connect(route); + route.connect(merge, 1, 0); + route.connect(merge, 2, 1); + merge.connect(dest); + }); + offline.test(function(samples, time){ + if (time < 0.3){ + expect(samples[0]).to.equal(5); + expect(samples[1]).to.equal(0); + } else { + expect(samples[0]).to.equal(0); + expect(samples[1]).to.equal(5); + } + }); + offline.after(function(){ + signal.dispose(); + route.dispose(); + merge.dispose(); + done(); + }); + offline.run(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Scale.js b/test/signal/Scale.js new file mode 100644 index 000000000..1a5c904a9 --- /dev/null +++ b/test/signal/Scale.js @@ -0,0 +1,92 @@ +define(["helper/Offline", "Tone/signal/Scale", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Scale, Basic, Test, Oscillator, Signal) { + + describe("Scale", function(){ + + Basic(Scale); + + context("Scaling", function(){ + + it("handles input and output connections", function(){ + var scale = new Scale(0, 100); + Test.connect(scale); + scale.connect(Test); + scale.dispose(); + }); + + it("can set the min and max values", function(){ + var scale = new Scale(0, 100); + scale.min = -0.01; + expect(scale.min).to.be.closeTo(-0.01, 0.001); + scale.max = 1000; + expect(scale.max).to.be.closeTo(1000, 0.001); + scale.dispose(); + }); + + it("scales to the min when the input is 0", function(done){ + //make an signalillator to drive the signal + var signal, scale; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + scale = new Scale(-10, 8); + signal.connect(scale); + scale.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(-10, 0.001); + }); + offline.after(function(){ + signal.dispose(); + scale.dispose(); + done(); + }); + offline.run(); + }); + + it("scales to the max when the input is 1", function(done){ + //make an signalillator to drive the signal + var signal, scale; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + scale = new Scale(-10, 0); + scale.max = 8; + signal.connect(scale); + scale.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(8, 0.001); + }); + offline.after(function(){ + signal.dispose(); + scale.dispose(); + done(); + }); + offline.run(); + }); + + it("scales an input of 0.5 to 15 (10, 20)", function(done){ + //make an signalillator to drive the signal + var signal, scale; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.5); + scale = new Scale(10, 20); + signal.connect(scale); + scale.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(15, 0.001); + }); + offline.after(function(){ + signal.dispose(); + scale.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/ScaleExp.js b/test/signal/ScaleExp.js new file mode 100644 index 000000000..a04548374 --- /dev/null +++ b/test/signal/ScaleExp.js @@ -0,0 +1,56 @@ +define(["helper/Offline", "Tone/signal/ScaleExp", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, ScaleExp, Basic, Test, Oscillator, Signal) { + + describe("ScaleExp", function(){ + + Basic(ScaleExp); + + context("Scaling", function(){ + + it("handles input and output connections", function(){ + var scale = new ScaleExp(0, 100, 2); + Test.connect(scale); + scale.connect(Test); + scale.dispose(); + }); + + it("can set the min and max values", function(){ + var scale = new ScaleExp(-20, 10, 2); + scale.min = -0.01; + expect(scale.min).to.be.closeTo(-0.01, 0.001); + scale.max = 1000; + expect(scale.max).to.be.closeTo(1000, 0.001); + scale.dispose(); + }); + + it("can set the exponent value", function(){ + var scale = new ScaleExp(0, 100, 2); + expect(scale.exponent).to.be.closeTo(2, 0.001); + scale.exponent = 3; + expect(scale.exponent).to.be.closeTo(3, 0.001); + scale.dispose(); + }); + + it("scales a signal exponentially", function(done){ + var signal, scale; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0.5); + scale = new ScaleExp(0, 1, 2); + signal.connect(scale); + scale.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.25, 0.01); + }); + offline.after(function(){ + signal.dispose(); + scale.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Select.js b/test/signal/Select.js new file mode 100644 index 000000000..b0737816a --- /dev/null +++ b/test/signal/Select.js @@ -0,0 +1,92 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/Select", "Tone/signal/Signal", "Test"], +function (Offline, Basic, Select, Signal, Test) { + + describe("Select", function(){ + + Basic(Select); + + describe("Selecting Logic", function(){ + + it ("handles input and output connections", function(){ + var sel = new Select(); + sel.connect(Test); + Test.connect(sel); + Test.connect(sel.gate); + sel.dispose(); + }); + + it("can select the first input", function(done){ + var sigA, sigB, sel; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(4); + sel = new Select().connect(dest); + sigA.connect(sel, 0, 0); + sigB.connect(sel, 0, 1); + sel.select(0); + }); + offline.test(function(sample){ + expect(sample).to.equal(3); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + sel.dispose(); + done(); + }); + offline.run(); + }); + + it("can select the second input", function(done){ + var sigA, sigB, sel; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(4); + sel = new Select().connect(dest); + sigA.connect(sel, 0, 0); + sigB.connect(sel, 0, 1); + sel.select(1); + }); + offline.test(function(sample){ + expect(sample).to.equal(4); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + sel.dispose(); + done(); + }); + offline.run(); + }); + + it("can select between 3 inputs", function(done){ + var sigA, sigB, sigC, sel; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(3); + sigB = new Signal(4); + sigC = new Signal(5); + sel = new Select(3).connect(dest); + sigA.connect(sel, 0, 0); + sigB.connect(sel, 0, 1); + sigC.connect(sel, 0, 2); + sel.select(2); + }); + offline.test(function(sample){ + expect(sample).to.equal(5); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + sigC.dispose(); + sel.dispose(); + done(); + }); + offline.run(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Signal.js b/test/signal/Signal.js new file mode 100644 index 000000000..5b648b2f1 --- /dev/null +++ b/test/signal/Signal.js @@ -0,0 +1,385 @@ +define(["helper/Offline", "helper/Basic", "Test", "Tone/signal/Signal", "Tone/core/Type", "Tone/core/Transport"], + function (Offline, Basic, Test, Signal, Tone, Transport) { + + describe("Signal", function(){ + + Basic(Signal); + + context("Signal Rate Value", function(){ + + it("handles input and output connections", function(){ + var signal = new Signal(); + Test.connect(signal); + signal.connect(Test); + signal.dispose(); + }); + + it("can be created with an options object", function(){ + var signal = new Signal({ + "value" : 0.2, + "units" : Tone.Type.Positive + }); + expect(signal.value).to.be.closeTo(0.2, 0.001); + expect(signal.units).to.equal(Tone.Type.Positive); + signal.dispose(); + }); + + it("can start with a value initially", function(){ + var signal = new Signal(2); + expect(signal.value).to.equal(2); + signal.dispose(); + }); + + it("can set a value", function(){ + var signal = new Signal(0); + signal.value = 10; + expect(signal.value).to.equal(10); + signal.dispose(); + }); + + it("takes on another signal's value when connected", function(done){ + var sigA, sigB; + var offline = new Offline(0.2); + offline.before(function(dest){ + sigA = new Signal(0).connect(dest); + sigB = new Signal(3).connect(sigA); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(3, 0.001); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + done(); + }); + offline.run(); + }); + }); + + context("Scheduling", function(){ + + it ("can be scheduled to set a value in the future", function(done){ + var sig; + var offline = new Offline(); + offline.before(function(dest){ + sig = new Signal(0).connect(dest); + sig.setValueAtTime(2, 0.2); + }); + offline.test(function(sample, time){ + if (time < 0.2){ + expect(sample).to.be.closeTo(0, 0.001); + } else if (time > 0.21){ + expect(sample).to.be.closeTo(2, 0.001); + } + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it ("can linear ramp from the current value to another value in the future", function(done){ + var sig; + var offline = new Offline(1); + offline.before(function(dest){ + sig = new Signal(0).connect(dest); + sig.setValueAtTime(0, 0); + sig.linearRampToValueAtTime(1, 1); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(time, 0.001); + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it ("can schedule an exponential ramp", function(){ + var sig = new Signal(1); + sig.exponentialRampToValueAtTime(3, 1); + sig.dispose(); + }); + + it ("can approach a target value", function(){ + var sig = new Signal(1); + sig.setTargetAtTime(0.2, 1, 2); + sig.dispose(); + }); + + it ("can set a ramp point at the current value", function(){ + var sig = new Signal(1); + sig.setRampPoint(); + sig.dispose(); + }); + + it ("can schedule multiple automations", function(done){ + var sig; + var offline = new Offline(1); + offline.before(function(dest){ + sig = new Signal(0).connect(dest); + sig.setValueAtTime(0, 0); + sig.linearRampToValueAtTime(0.5, 0.5); + sig.linearRampToValueAtTime(0, 1); + }); + offline.test(function(sample, time){ + if (time < 0.5){ + expect(sample).to.be.closeTo(time, 0.01); + } else { + expect(sample).to.be.closeTo(1 - time, 0.01); + } + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it ("can cancel an automation", function(done){ + var sig = new Signal(1); + sig.setValueAtTime(4, 0.1); + sig.exponentialRampToValueAtTime(3, 0.2); + sig.cancelScheduledValues(0); + setTimeout(function(){ + expect(sig.value).to.equal(1); + sig.dispose(); + done(); + }, 400); + }); + + it ("can set a linear ramp from the current time", function(done){ + var sig; + var offline = new Offline(0.5); + offline.before(function(dest){ + sig = new Signal(0).connect(dest); + sig.linearRampToValue(2, 0.3); + }); + offline.test(function(sample, time){ + if (time > 0.3){ + expect(sample).to.be.closeTo(2, 0.02); + } + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it ("can set an exponential ramp from the current time", function(done){ + var sig; + var offline = new Offline(0.5); + offline.before(function(dest){ + sig = new Signal(1).connect(dest); + sig.exponentialRampToValue(50, 0.4); + }); + offline.test(function(sample, time){ + if (time >= 0.4){ + expect(sample).to.be.closeTo(50, 0.5); + } else { + expect(sample).to.be.lessThan(50); + } + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it ("rampTo ramps from the current value", function(done){ + var sig; + var offline = new Offline(0.5); + offline.before(function(dest){ + sig = new Signal(3).connect(dest); + sig.rampTo(0.2, 0.1); + }); + offline.test(function(sample, time){ + if (time >= 0.1){ + expect(sample).to.be.closeTo(0.2, 0.1); + } else { + expect(sample).to.be.greaterThan(0.2); + } + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + }); + + context("Units", function(){ + + it("can be created with specific units", function(){ + var signal = new Signal(0, Tone.Type.BPM); + expect(signal.units).to.equal(Tone.Type.BPM); + signal.dispose(); + }); + + it("can evaluate the given units", function(){ + var signal = new Signal(2, Tone.Type.Time); + signal.value = "4n"; + expect(signal.value).to.be.closeTo(0.5, 0.001); + signal.dispose(); + }); + + it("converts the given units when passed in the constructor", function(done){ + var signal; + var offline = new Offline(0.2); + offline.before(function(dest){ + signal = new Signal({ + "value" : -10, + "units" : Tone.Type.Decibels, + }).connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(0.315, 0.01); + }); + offline.after(function(){ + signal.dispose(); + done(); + }); + offline.run(); + }); + + it("can be set to not convert the given units", function(done){ + var signal; + var offline = new Offline(0.2); + offline.before(function(dest){ + signal = new Signal({ + "value" : -10, + "units" : Tone.Type.Decibels, + "convert" : false + }).connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(-10, 0.01); + }); + offline.after(function(){ + signal.dispose(); + done(); + }); + offline.run(); + }); + + it("converts Frequency units", function(){ + var signal = new Signal("50hz", Tone.Type.Frequency); + expect(signal.value).to.be.closeTo(50, 0.01); + signal.dispose(); + }); + + it("converts Time units", function(){ + var signal = new Signal("4n", Tone.Type.Time); + expect(signal.value).to.be.closeTo(0.5, 0.01); + signal.dispose(); + }); + + it("converts NormalRange units", function(){ + var signal = new Signal(2, Tone.Type.NormalRange); + expect(signal.value).to.be.closeTo(1, 0.01); + signal.dispose(); + }); + + it("converts AudioRange units", function(){ + var signal = new Signal(-2, Tone.Type.AudioRange); + expect(signal.value).to.be.closeTo(-1, 0.01); + signal.dispose(); + }); + + it("converts Positive units", function(){ + var signal = new Signal(-2, Tone.Type.Positive); + expect(signal.value).to.be.closeTo(0, 0.01); + signal.dispose(); + }); + + }); + + context("Transport Syncing", function(){ + + it("maintains its original value after being synced to the transport", function(done){ + var sig; + var offline = new Offline(0.2); + offline.before(function(dest){ + sig = new Signal(3).connect(dest); + Transport.syncSignal(sig); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(3, 0.01); + }); + offline.after(function(){ + sig.dispose(); + done(); + }); + offline.run(); + }); + + it("keeps the ratio when the bpm changes", function(done){ + var sig; + var offline = new Offline(0.2); + offline.before(function(dest){ + Transport.bpm.value = 120; + sig = new Signal(5).connect(dest); + Transport.syncSignal(sig); + Transport.bpm.value = 240; + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(10, 0.01); + }); + offline.after(function(){ + sig.dispose(); + Transport.bpm.value = 120; + done(); + }); + offline.run(); + }); + + it("can ramp along with the bpm", function(done){ + var sig; + var offline = new Offline(0.7); + offline.before(function(dest){ + Transport.bpm.value = 120; + sig = new Signal(2).connect(dest); + Transport.syncSignal(sig); + Transport.bpm.rampTo(240, 0.5); + }); + offline.test(function(sample, time){ + if (time >= 0.5){ + expect(sample).to.be.closeTo(4, 0.04); + } + }); + offline.after(function(){ + sig.dispose(); + Transport.bpm.value = 120; + done(); + }); + offline.run(); + }); + + it("returns to the original value when unsynced", function(done){ + var sig; + var offline = new Offline(0.2); + offline.before(function(dest){ + Transport.bpm.value = 120; + sig = new Signal(5).connect(dest); + Transport.syncSignal(sig); + Transport.bpm.value = 240; + Transport.unsyncSignal(sig); + }); + offline.test(function(sample){ + expect(sample).to.be.closeTo(5, 0.01); + }); + offline.after(function(){ + sig.dispose(); + Transport.bpm.value = 120; + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Subtract.js b/test/signal/Subtract.js new file mode 100644 index 000000000..ffb65dc96 --- /dev/null +++ b/test/signal/Subtract.js @@ -0,0 +1,104 @@ +define(["helper/Offline", "Tone/signal/Subtract", "helper/Basic", + "Test", "Tone/source/Oscillator", "Tone/signal/Signal"], + function (Offline, Subtract, Basic, Test, Oscillator, Signal) { + + describe("Subtract", function(){ + + Basic(Subtract); + + context("Subtraction", function(){ + + it("handles input and output connections", function(){ + var subtract = new Subtract(); + Test.connect(subtract, 0); + Test.connect(subtract, 1); + subtract.connect(Test); + subtract.dispose(); + }); + + it("correctly subtracts a signal and a number", function(done){ + var signal, sub; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(0); + sub = new Subtract(3); + signal.connect(sub); + sub.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-3); + }); + offline.after(function(){ + signal.dispose(); + sub.dispose(); + done(); + }); + offline.run(); + }); + + it("can set the scalar value after construction", function(done){ + var signal, sub; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-2); + sub = new Subtract(0); + sub.value = 4; + signal.connect(sub); + sub.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-6); + }); + offline.after(function(){ + signal.dispose(); + sub.dispose(); + done(); + }); + offline.run(); + }); + + it("can handle negative values", function(done){ + var signal, sub; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(4); + sub = new Subtract(-2); + signal.connect(sub); + sub.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(6); + }); + offline.after(function(){ + signal.dispose(); + sub.dispose(); + done(); + }); + offline.run(); + }); + + it("can subtract two signals", function(done){ + var sigA, sigB, sub; + var offline = new Offline(); + offline.before(function(dest){ + sigA = new Signal(1); + sigB = new Signal(4); + sub = new Subtract(); + sigA.connect(sub, 0, 0); + sigB.connect(sub, 0, 1); + sub.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-3); + }); + offline.after(function(){ + sigA.dispose(); + sigB.dispose(); + sub.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/Switch.js b/test/signal/Switch.js new file mode 100644 index 000000000..e48056505 --- /dev/null +++ b/test/signal/Switch.js @@ -0,0 +1,86 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/Switch", "Tone/signal/Signal", "Test"], +function (Offline, Basic, Switch, Signal, Test) { + + describe("Switch", function(){ + + Basic(Switch); + + describe("Switching Logic", function(){ + + it ("handles input and output connections", function(){ + var gate = new Switch(); + gate.connect(Test); + Test.connect(gate); + Test.connect(gate.gate); + gate.dispose(); + }); + + it("is closed by default", function(done){ + var signal, gate; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + gate = new Switch(); + signal.connect(gate); + gate.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(0); + }); + offline.after(function(){ + signal.dispose(); + gate.dispose(); + done(); + }); + offline.run(); + }); + + it("can be opened", function(done){ + var signal, gate; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + gate = new Switch(); + signal.connect(gate); + gate.connect(dest); + gate.open(); + }); + offline.test(function(sample){ + expect(sample).to.equal(10); + }); + offline.after(function(){ + signal.dispose(); + gate.dispose(); + done(); + }); + offline.run(); + }); + + it("can be scheduled to close", function(done){ + var signal, gate; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + gate = new Switch(); + signal.connect(gate); + gate.connect(dest); + gate.open(); + gate.close(0.4); + }); + offline.test(function(sample, time){ + if (time >= 0.4){ + expect(sample).to.equal(0); + } else { + expect(sample).to.equal(10); + } + }); + offline.after(function(){ + signal.dispose(); + gate.dispose(); + done(); + }); + offline.run(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/TimelineSignal.js b/test/signal/TimelineSignal.js new file mode 100644 index 000000000..497258055 --- /dev/null +++ b/test/signal/TimelineSignal.js @@ -0,0 +1,169 @@ +define(["Test", "Tone/signal/TimelineSignal", "helper/Offline", "Tone/core/Type"], + function (Test, TimelineSignal, Offline, Tone) { + + describe("TimelineSignal", function(){ + + it("can be created and disposed", function(){ + var sched = new TimelineSignal(); + sched.dispose(); + Test.wasDisposed(sched); + }); + + it("can schedule a change in the future", function(){ + var sched = new TimelineSignal(1); + sched.setValueAtTime(2, 0.2); + sched.dispose(); + }); + + it("can schedule a ramp in the future", function(){ + var sched = new TimelineSignal(1); + sched.setValueAtTime(2, 0); + sched.linearRampToValueAtTime(0.1, 0.2); + sched.exponentialRampToValueAtTime(1, 0.4); + sched.setTargetAtTime(0.5, 0.45, 1); + sched.dispose(); + }); + + it("can get a setValueAtTime value in the future", function(){ + var sched = new TimelineSignal(1); + sched.setValueAtTime(0, 0); + sched.setValueAtTime(1, 1); + sched.setValueAtTime(2, 2); + sched.setValueAtTime(3, 3); + expect(sched.getValueAtTime(0)).to.equal(0); + expect(sched.getValueAtTime(1)).to.equal(1); + expect(sched.getValueAtTime(1.1)).to.equal(1); + expect(sched.getValueAtTime(2)).to.equal(2); + expect(sched.getValueAtTime(3)).to.equal(3); + expect(sched.getValueAtTime(4)).to.equal(3); + sched.dispose(); + }); + + it("can get linear ramp value in the future", function(){ + var sched = new TimelineSignal(1); + sched.setValueAtTime(0, 0); + sched.linearRampToValueAtTime(1, 1); + sched.linearRampToValueAtTime(0, 2); + expect(sched.getValueAtTime(0)).to.equal(0); + expect(sched.getValueAtTime(0.5)).to.equal(0.5); + expect(sched.getValueAtTime(1)).to.equal(1); + expect(sched.getValueAtTime(1.5)).to.equal(0.5); + expect(sched.getValueAtTime(2)).to.equal(0); + sched.dispose(); + }); + + it("can get exponential ramp value in the future", function(done){ + var sched; + var offline = new Offline(2); + offline.before(function(dest){ + sched = new TimelineSignal().connect(dest); + sched.setValueAtTime(0.5, 0); + sched.exponentialRampToValueAtTime(1, 1); + sched.exponentialRampToValueAtTime(0.5, 2); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(sched.getValueAtTime(time), 0.01); + }); + offline.after(function(){ + sched.dispose(); + done(); + }); + offline.run(); + }); + + it("can get set target value in the future", function(done){ + var sched; + var offline = new Offline(2); + offline.before(function(dest){ + sched = new TimelineSignal(1).connect(dest); + sched.setValueAtTime(3, 0); + sched.setTargetAtTime(0.5, 0.5, 2); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(sched.getValueAtTime(time), 0.01); + }); + offline.after(function(){ + sched.dispose(); + done(); + }); + offline.run(); + }); + + it("can match a complex scheduled curve", function(done){ + var sched; + var offline = new Offline(4); + offline.before(function(dest){ + sched = new TimelineSignal(1).connect(dest); + sched.setValueAtTime(0.2, 0.3); + sched.setTargetAtTime(0.5, 0.5, 2); + sched.setValueAtTime(0.4, 1); + sched.linearRampToValueAtTime(5, 1.4); + sched.exponentialRampToValueAtTime(2, 1.6); + sched.setValueAtTime(2.5, 2); + sched.linearRampToValueAtTime(2.4, 2.5); + sched.linearRampToValueAtTime(5, 3); + sched.setTargetAtTime(2, 3.5, 5); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(sched.getValueAtTime(time), 0.01); + }); + offline.after(function(){ + sched.dispose(); + done(); + }); + offline.run(); + }); + + it("can schedule a linear ramp between two times", function(){ + var sched = new TimelineSignal(0); + sched.linearRampToValueBetween(1, 1, 2); + expect(sched.getValueAtTime(0)).to.equal(0); + expect(sched.getValueAtTime(0.5)).to.equal(0); + expect(sched.getValueAtTime(1)).to.equal(0); + expect(sched.getValueAtTime(1.5)).to.equal(0.5); + expect(sched.getValueAtTime(2)).to.equal(1); + }); + + it("can get exponential ramp value between two times", function(done){ + var sched; + var offline = new Offline(2); + offline.before(function(dest){ + sched = new TimelineSignal(1).connect(dest); + sched.linearRampToValueBetween(3, 1, 2); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo(sched.getValueAtTime(time), 0.01); + }); + offline.after(function(){ + sched.dispose(); + done(); + }); + offline.run(); + }); + + it("can automate values with different units", function(done){ + var sched; + var offline = new Offline(1.2); + offline.before(function(dest){ + sched = new TimelineSignal(-10, Tone.Type.Decibels).connect(dest); + sched.setValueAtTime(-5, 0); + sched.linearRampToValueAtTime(-12, 0.5); + sched.exponentialRampToValueBetween(-6, 1, 1.1); + }); + offline.test(function(sample, time){ + if (time < 0.5){ + expect(sample).to.be.within(sched.dbToGain(-12), sched.dbToGain(-5)); + } else if (time < 1){ + expect(sample).to.be.a.percentageFrom(sched.dbToGain(-12), 0.01); + } else if (time > 1.1){ + expect(sample).to.be.a.percentageFrom(sched.dbToGain(-6), 0.01); + } + }); + offline.after(function(){ + sched.dispose(); + done(); + }); + offline.run(); + }); + }); +}); \ No newline at end of file diff --git a/test/signal/WaveShaper.js b/test/signal/WaveShaper.js new file mode 100644 index 000000000..7ea44ad2f --- /dev/null +++ b/test/signal/WaveShaper.js @@ -0,0 +1,133 @@ +define(["helper/Offline", "helper/Basic", "Tone/signal/WaveShaper", "Tone/signal/Signal"], +function (Offline, Basic, WaveShaper, Signal) { + + describe("WaveShaper", function(){ + + Basic(WaveShaper); + + describe("Construction Options", function(){ + + it ("can be constructed with an array", function(){ + var waveshaper = new WaveShaper([1, 2, 3, 4, 5, 6]); + expect(waveshaper.curve[0]).to.equal(1); + expect(waveshaper.curve[2]).to.equal(3); + }); + + it ("can be constructed with a mapping function", function(){ + var waveshaper = new WaveShaper(function(){ + return -2; + }); + expect(waveshaper.curve[0]).to.equal(-2); + expect(waveshaper.curve[1]).to.equal(-2); + }); + + it ("can be constructed with a length and then set with a map", function(){ + var waveshaper = new WaveShaper(2048).setMap(function(){ + return 10; + }); + expect(waveshaper.curve.length).to.equal(2048); + expect(waveshaper.curve[0]).to.equal(10); + expect(waveshaper.curve[1]).to.equal(10); + }); + + it ("can be set to oversample", function(){ + var waveshaper = new WaveShaper(); + expect(waveshaper.oversample).to.equal("none"); + waveshaper.oversample = "2x"; + expect(waveshaper.oversample).to.equal("2x"); + expect(function(){ + waveshaper.oversample = "3x"; + }).to.throw(Error); + }); + + }); + + describe("Logic", function(){ + + it("shapes the output of the incoming signal", function(done){ + var signal, waveshaper; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(1); + waveshaper = new WaveShaper([-10, -10, -10]); + signal.connect(waveshaper); + waveshaper.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-10); + }); + offline.after(function(){ + signal.dispose(); + waveshaper.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the last curve value when the input is above 1", function(done){ + var signal, waveshaper; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(10); + waveshaper = new WaveShaper([-20, 20]); + signal.connect(waveshaper); + waveshaper.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(20); + }); + offline.after(function(){ + signal.dispose(); + waveshaper.dispose(); + done(); + }); + offline.run(); + }); + + it("outputs the first curve value when the input is below -1", function(done){ + var signal, waveshaper; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-1); + waveshaper = new WaveShaper([-20, 20]); + signal.connect(waveshaper); + waveshaper.connect(dest); + }); + offline.test(function(sample){ + expect(sample).to.equal(-20); + }); + offline.after(function(){ + signal.dispose(); + waveshaper.dispose(); + done(); + }); + offline.run(); + }); + + it("maps the input through the waveshaping curve", function(done){ + var signal, waveshaper; + var offline = new Offline(); + offline.before(function(dest){ + signal = new Signal(-1); + waveshaper = new WaveShaper(function(input){ + return input * 2; + }); + signal.connect(waveshaper); + waveshaper.connect(dest); + signal.setValueAtTime(-1, 0); + signal.linearRampToValueAtTime(1, 1); + }); + offline.test(function(sample, time){ + expect(sample).to.be.closeTo( 2 * ((time * 2) - 1), 0.005); + }); + offline.after(function(){ + signal.dispose(); + waveshaper.dispose(); + done(); + }); + offline.run(); + }); + + }); + }); +}); \ No newline at end of file diff --git a/test/sound.html b/test/sound.html deleted file mode 100644 index c681aad48..000000000 --- a/test/sound.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - SOUND TEST - - - - - - -
- - - \ No newline at end of file diff --git a/test/source/ExternalInput.js b/test/source/ExternalInput.js new file mode 100644 index 000000000..3f54a67e7 --- /dev/null +++ b/test/source/ExternalInput.js @@ -0,0 +1,98 @@ +define(["helper/Basic", "Tone/source/ExternalInput", "Test", "Tone/source/Source"], + function (BasicTests, ExternalInput, Test, Source) { + + describe("ExternalInput", function(){ + + //run the common tests + BasicTests(ExternalInput); + + context("Source Tests", function(){ + + it ("can connect the output", function(){ + var extIn = new ExternalInput(); + extIn.connect(Test); + extIn.dispose(); + }); + + it ("extends Tone.Source", function(){ + var extIn = new ExternalInput(); + expect(extIn).to.be.an.instanceof(Source); + extIn.dispose(); + }); + + it ("can be constructed with the input number", function(){ + var extIn = new ExternalInput(3); + extIn.dispose(); + }); + + it ("can be constructed with an options object", function(){ + var extIn = new ExternalInput({ + "inputNum" : 2, + "volume" : -10 + }); + expect(extIn.volume.value).to.be.closeTo(-10, 0.1); + extIn.dispose(); + }); + + it("starts and stops", function(done){ + var extIn = new ExternalInput(); + expect(extIn.state).to.equal("stopped"); + extIn.start().stop("+0.2"); + setTimeout(function(){ + expect(extIn.state).to.equal("started"); + }, 100); + setTimeout(function(){ + expect(extIn.state).to.equal("stopped"); + extIn.dispose(); + done(); + }, 300); + }); + }); + + context("Static methods/members", function(){ + + it ("can get a list of sources", function(done){ + ExternalInput.getSources(function(sources){ + expect(sources).to.be.an.array; + done(); + }); + }); + + it ("has a list of sources", function(){ + expect(ExternalInput.sources).to.be.an.array; + }); + + it ("indicates if the browser has UserMedia support", function(){ + expect(ExternalInput.supported).to.be.a.boolean; + }); + }); + + //if it is a manual test (i.e. there is a person to 'allow' the microphone) + if (window.MANUAL_TEST && ExternalInput.supported){ + + context("Opening and closing", function(){ + + //long timeout to give testers time to allow the microphone + this.timeout(100000); + + it ("can open an input", function(done){ + var extIn = new ExternalInput(); + extIn.open(function(){ + extIn.dispose(); + done(); + }); + }); + + it ("can can close an input", function(done){ + var extIn = new ExternalInput(); + extIn.open(function(){ + extIn.close(); + extIn.dispose(); + done(); + }); + }); + }); + } + + }); +}); \ No newline at end of file diff --git a/test/source/Microphone.js b/test/source/Microphone.js new file mode 100644 index 000000000..e2fd6100b --- /dev/null +++ b/test/source/Microphone.js @@ -0,0 +1,17 @@ +define(["Tone/source/Microphone", "helper/Basic", "Tone/source/ExternalInput"], + function (Microphone, Basic, ExternalInput) { + + describe("Microphone", function(){ + Basic(Microphone); + + it ("extends ExternalInput", function(){ + var mic = new Microphone(); + expect(mic).to.be.instanceOf(ExternalInput); + mic.dispose(); + }); + + it ("indicates if the browser has UserMedia support", function(){ + expect(Microphone.supported).to.be.a.boolean; + }); + }); +}); \ No newline at end of file diff --git a/test/source/Noise.js b/test/source/Noise.js new file mode 100644 index 000000000..fba2885b6 --- /dev/null +++ b/test/source/Noise.js @@ -0,0 +1,144 @@ +define(["helper/Basic", "Tone/source/Noise", "helper/SourceTests", "helper/OutputAudio"], + function (BasicTests, Noise, SourceTests, OutputAudio) { + + describe("Noise", function(){ + + //run the common tests + BasicTests(Noise); + SourceTests(Noise); + + context("Get/Set", function(){ + + it("can be constructed with an options object", function(){ + var noise = new Noise({ + "type" : "brown" + }); + expect(noise.type).to.equal("brown"); + noise.dispose(); + }); + + it("can set the playbackRate in the constructor", function(){ + var noise = new Noise({ + "playbackRate" : 2 + }); + expect(noise.playbackRate).to.equal(2); + noise.dispose(); + }); + + it("can set the playbackRate", function(){ + var noise = new Noise(); + noise.playbackRate = 3; + expect(noise.playbackRate).to.equal(3); + noise.dispose(); + }); + + }); + + context("Type", function(){ + + it ("can be set to 3 noise types", function(){ + var noise = new Noise(); + var types = ["white", "brown", "pink"]; + for (var i = 0; i < types.length; i++){ + noise.type = types[i]; + expect(noise.type).to.equal(types[i]); + + } + noise.dispose(); + }); + + it ("cant set invalid type", function(){ + var noise = new Noise(); + expect(function(){ + noise.type = "else"; + }).to.throw(Error); + noise.dispose(); + }); + + it("outputs white noise", function(done){ + var noise; + OutputAudio(function(dest){ + noise = new Noise("white"); + noise.connect(dest); + noise.start(); + }, function(){ + noise.dispose(); + done(); + }); + }); + + it("outputs pink noise", function(done){ + var noise; + OutputAudio(function(dest){ + noise = new Noise("pink"); + noise.connect(dest); + noise.start(); + }, function(){ + noise.dispose(); + done(); + }); + }); + + it("outputs brown noise", function(done){ + var noise; + OutputAudio(function(dest){ + noise = new Noise("brown"); + noise.connect(dest); + noise.start(); + }, function(){ + noise.dispose(); + done(); + }); + }); + }); + + + /*it("be scheduled to start in the future", function(done){ + var noise; + var offline = new Offline(); + Test.offlineTest(1, function(dest){ + noise = new Noise(); + noise.connect(dest); + noise.start("+0.1"); + }, function(sample, time){ + if (sample !== 0){ + expect(time).to.be.at.least(0.1); + } + }, function(){ + noise.dispose(); + done(); + }); + }); + + it("can set the noise types", function(){ + var noise = new Noise(); + noise.type = "brown"; + noise.type = "white"; + noise.type = "pink"; + //even after started + noise.start(); + noise.type = "brown"; + noise.type = "white"; + noise.type = "pink"; + noise.stop(); + noise.dispose(); + }); + + it("can be created with an options object", function(){ + var noise = new Noise({ + "type" : "brown" + }); + expect(noise.type).to.equal("brown"); + noise.dispose(); + }); + + it("can be set with an options object", function(){ + var noise = new Noise(); + noise.set({ + "type" : "pink" + }); + expect(noise.type).to.equal("pink"); + noise.dispose(); + });*/ + }); +}); \ No newline at end of file diff --git a/test/source/OmniOscillator.js b/test/source/OmniOscillator.js new file mode 100644 index 000000000..289854d5c --- /dev/null +++ b/test/source/OmniOscillator.js @@ -0,0 +1,127 @@ +define(["helper/Basic", "Tone/source/OmniOscillator", "helper/Offline", "helper/SourceTests", "helper/OscillatorTests", "helper/OutputAudio"], + function (BasicTests, OmniOscillator, Offline, SourceTests, OscillatorTests, OutputAudio) { + + describe("OmniOscillator", function(){ + + //run the common tests + BasicTests(OmniOscillator); + SourceTests(OmniOscillator); + OscillatorTests(OmniOscillator); + + context("Sound", function(){ + + it("makes a sound", function(done){ + var osc; + OutputAudio(function(dest){ + osc = new OmniOscillator(); + osc.connect(dest); + osc.start(); + }, function(){ + osc.dispose(); + done(); + }); + }); + + it("makes a sound when set to square", function(done){ + var osc; + OutputAudio(function(dest){ + osc = new OmniOscillator(440, "square"); + osc.connect(dest); + osc.start(); + }, function(){ + osc.dispose(); + done(); + }); + }); + + it("makes a sound when set to pulse", function(done){ + var osc; + OutputAudio(function(dest){ + osc = new OmniOscillator(440, "pulse"); + osc.connect(dest); + osc.start(); + }, function(){ + osc.dispose(); + done(); + }); + }); + + it("makes a sound when set to pwm", function(done){ + var osc; + OutputAudio(function(dest){ + osc = new OmniOscillator(440, "pwm"); + osc.connect(dest); + osc.start(); + }, function(){ + osc.dispose(); + done(); + }); + }); + + }); + + context("Type", function(){ + + it ("can get and set the type", function(){ + var osc = new OmniOscillator({ + "type" : "sawtooth", + }); + expect(osc.type).to.equal("sawtooth"); + osc.dispose(); + }); + + it ("handles 6 types", function(){ + var osc = new OmniOscillator(); + var types = ["triangle", "sawtooth", "sine", "square", "pulse", "pwm"]; + for (var i = 0; i < types.length; i++){ + osc.type = types[i]; + expect(osc.type).to.equal(types[i]); + } + osc.dispose(); + }); + + it ("throws an error if invalid type is set", function(){ + var osc = new OmniOscillator(); + expect(function(){ + osc.type = "invalid"; + }).to.throw(Error); + osc.dispose(); + }); + + it ("can set extended types", function(){ + var osc = new OmniOscillator(); + osc.type = "sine5"; + expect(osc.type).to.equal("sine5"); + osc.type = "triangle2"; + expect(osc.type).to.equal("triangle2"); + osc.dispose(); + }); + + it("can set the modulation frequency only when type is pwm", function(){ + var omni = new OmniOscillator(); + omni.type = "pwm"; + expect(function(){ + omni.modulationFrequency.value = 0.2; + }).to.not.throw(Error); + omni.type = "pulse"; + expect(function(){ + omni.modulationFrequency.value = 0.2; + }).to.throw(Error); + omni.dispose(); + }); + + it("can set the modulation width only when type is pulse", function(){ + var omni = new OmniOscillator(); + omni.type = "pulse"; + expect(function(){ + omni.width.value = 0.2; + }).to.not.throw(Error); + omni.type = "sine"; + expect(function(){ + omni.width.value = 0.2; + }).to.throw(Error); + omni.dispose(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/source/Oscillator.js b/test/source/Oscillator.js new file mode 100644 index 000000000..2645dea65 --- /dev/null +++ b/test/source/Oscillator.js @@ -0,0 +1,220 @@ +define(["helper/Basic", "Tone/source/Oscillator", "helper/Offline", "helper/SourceTests", + "helper/OscillatorTests", "helper/OutputAudio", "Tone/core/Transport"], + function (BasicTests, Oscillator, Offline, SourceTests, OscillatorTests, OutputAudio, Transport) { + + describe("Oscillator", function(){ + + //run the common tests + BasicTests(Oscillator); + SourceTests(Oscillator); + OscillatorTests(Oscillator); + + context("Get/Set", function(){ + + it("can be set with an options object", function(){ + var osc = new Oscillator(); + osc.set({ + "frequency" : 231, + "detune" : -21, + "type" : "square" + }); + expect(osc.frequency.value).to.equal(231); + expect(osc.detune.value).to.equal(-21); + expect(osc.type).to.equal("square"); + osc.dispose(); + }); + + it("can be get the values as an object", function(){ + var osc = new Oscillator(450, "square"); + expect(osc.get().frequency).to.equal(450); + expect(osc.get().type).to.equal("square"); + osc.dispose(); + }); + + + }); + + context("Phase Rotation", function(){ + it ("can change the phase to 90", function(done){ + var instance; + var offline = new Offline(1); + offline.before(function(dest){ + instance = new Oscillator({ + "phase" : 90, + "frequency" : 1 + }); + instance.connect(dest); + instance.start(0); + }); + offline.test(function(sample, time){ + if (time < 0.25){ + expect(sample).to.be.within(-1, 0); + } else if (time < 0.5){ + expect(sample).to.be.within(0, 1); + } + }); + offline.after(function(){ + instance.dispose(); + done(); + }); + offline.run(); + }); + + it ("can change the phase to -90", function(done){ + var instance; + var offline = new Offline(1); + offline.before(function(dest){ + instance = new Oscillator({ + "phase" : 270, + "frequency" : 1 + }); + instance.connect(dest); + instance.start(0); + }); + offline.test(function(sample, time){ + if (time < 0.25){ + expect(sample).to.be.within(0, 1); + } else if (time < 0.5){ + expect(sample).to.be.within(-1, 0); + } + }); + offline.after(function(){ + instance.dispose(); + done(); + }); + offline.run(); + }); + + }); + + context("Type", function(){ + + it ("can get and set the type", function(){ + var osc = new Oscillator({ + "type" : "sawtooth", + }); + expect(osc.type).to.equal("sawtooth"); + osc.dispose(); + }); + + it ("handles 4 basic types", function(){ + var osc = new Oscillator(); + var types = ["triangle", "sawtooth", "sine", "square"]; + for (var i = 0; i < types.length; i++){ + osc.type = types[i]; + expect(osc.type).to.equal(types[i]); + } + osc.dispose(); + }); + + it ("throws an error if invalid type is set", function(){ + var osc = new Oscillator(); + expect(function(){ + osc.type = "invalid"; + }).to.throw(Error); + osc.dispose(); + }); + + it ("can set extended types", function(){ + var osc = new Oscillator(); + osc.type = "sine5"; + expect(osc.type).to.equal("sine5"); + osc.type = "triangle2"; + expect(osc.type).to.equal("triangle2"); + osc.dispose(); + }); + }); + + context("Partials", function(){ + + it ("can pass partials in the constructor", function(){ + var osc = new Oscillator({ + "partials" : [1, 0.3, 0.3], + "type" : "custom" + }); + expect(osc.type).to.equal("custom"); + expect(osc.partials[1]).to.equal(0.3); + osc.dispose(); + }); + + it ("can set partials", function(){ + var osc = new Oscillator(); + osc.partials = [1, 0.2, 0.2, 0.2]; + expect(osc.type).to.equal("custom"); + expect(osc.partials[1]).to.equal(0.2); + osc.dispose(); + }); + + it ("makes a sound with custom partials", function(done){ + var osc; + OutputAudio(function(dest){ + osc = new Oscillator().connect(dest).start(); + osc.partials = [1, 0.2, 0.2, 0.2]; + }, function(){ + osc.dispose(); + done(); + }); + }); + + it ("outputs empty array when type is not 'custom'", function(){ + var osc = new Oscillator({ + "partials" : [1, 0.3, 0.3], + "type" : "custom" + }); + expect(osc.type).to.equal("custom"); + expect(osc.partials[1]).to.equal(0.3); + osc.type = "sine2"; + expect(osc.type).to.equal("sine2"); + expect(osc.partials.length).to.equal(0); + osc.dispose(); + }); + + }); + + context("Synchronization", function(){ + it("can sync the frequency to the Transport", function(done){ + var osc; + var offline = new Offline(0.1); + offline.before(function(dest){ + Transport.bpm.value = 120; + osc = new Oscillator(2); + osc.frequency.connect(dest); + osc.syncFrequency(); + Transport.bpm.value = 240; + }); + offline.test(function(freq){ + expect(freq).to.be.closeTo(4, 0.001); + }); + offline.after(function(){ + Transport.bpm.value = 120; + osc.dispose(); + done(); + }); + offline.run(); + }); + + it("can unsync the frequency from the Transport", function(done){ + var osc; + var offline = new Offline(0.1); + offline.before(function(dest){ + Transport.bpm.value = 120; + osc = new Oscillator(2); + osc.frequency.connect(dest); + osc.syncFrequency(); + Transport.bpm.value = 240; + osc.unsyncFrequency(); + }); + offline.test(function(freq){ + expect(freq).to.be.closeTo(2, 0.001); + }); + offline.after(function(){ + Transport.bpm.value = 120; + osc.dispose(); + done(); + }); + offline.run(); + }); + }); + + }); +}); \ No newline at end of file diff --git a/test/source/PWMOscillator.js b/test/source/PWMOscillator.js new file mode 100644 index 000000000..3a74b39f2 --- /dev/null +++ b/test/source/PWMOscillator.js @@ -0,0 +1,29 @@ +define(["helper/Basic", "Tone/source/PWMOscillator", "helper/Offline", "helper/SourceTests", "helper/OscillatorTests", "Test"], + function (BasicTests, PWMOscillator, Offline, SourceTests, OscillatorTests, Test) { + + describe("PWMOscillator", function(){ + + //run the common tests + BasicTests(PWMOscillator); + SourceTests(PWMOscillator); + OscillatorTests(PWMOscillator); + + context("Modulation Frequency", function(){ + + it("can set the modulation frequency", function(){ + var pwm = new PWMOscillator(); + pwm.modulationFrequency.value = 0.2; + expect(pwm.modulationFrequency.value).to.be.closeTo(0.2, 0.001); + pwm.dispose(); + }); + + it("can connect a signal to the modulationFrequency", function(){ + var pwm = new PWMOscillator(); + Test.connect(pwm.modulationFrequency); + pwm.dispose(); + }); + + }); + }); + +}); \ No newline at end of file diff --git a/test/source/Player.js b/test/source/Player.js new file mode 100644 index 000000000..b38d616f6 --- /dev/null +++ b/test/source/Player.js @@ -0,0 +1,267 @@ +define(["helper/Basic", "Tone/source/Player", "helper/Offline", "helper/SourceTests", "Tone/core/Buffer", "helper/Meter"], + function (BasicTests, Player, Offline, SourceTests, Buffer, Meter) { + + describe("Player", function(){ + + var buffer = new Buffer(); + + beforeEach(function(done){ + buffer.load("./audio/sine.wav", function(){ + done(); + }); + }); + + //run the common tests + BasicTests(Player, buffer); + SourceTests(Player, buffer); + + context("Constructor", function(){ + + it ("can be constructed with a Tone.Buffer", function(done){ + var player = new Player(buffer); + expect(player.buffer.get()).to.equal(buffer.get()); + player.dispose(); + done(); + }); + + it ("can be constructed with an AudioBuffer", function(done){ + var player = new Player(buffer.get()); + expect(player.buffer.get()).to.equal(buffer.get()); + player.dispose(); + done(); + }); + }); + + context("Loading", function(){ + + it("loads a url which was passed in", function(done){ + var player = new Player("./audio/sine.wav", function(){ + player.dispose(); + done(); + }); + }); + + it("loads a url using the load method", function(done){ + var player = new Player(); + player.load("./audio/sine.wav", function(){ + expect(player._buffer).to.be.instanceof(Buffer); + done(); + }); + }); + + it("can be created with an options object", function(){ + var player = new Player({ + "url" : "./audio/sine.wav", + "loop" : true + }); + player.dispose(); + }); + + it("can autostart after loading", function(done){ + var player = new Player({ + "url" : "./audio/sine.wav", + "autostart" : true, + "onload" : function(){ + setTimeout(function(){ + expect(player.state).to.be.equal("started"); + done(); + }, 10); + } + }); + }); + + }); + + context("Reverse", function(){ + + it("can be played in reverse", function(done){ + var player; + var offline = new Offline(); + var audioBuffer = buffer.get().getChannelData(0); + var lastSample = audioBuffer[audioBuffer.length - 1]; + offline.before(function(dest){ + player = new Player(buffer.get()).connect(dest); + player.reverse = true; + player.start(0); + }); + offline.test(function(sample, time){ + if (time === 0){ + expect(sample).to.equal(lastSample); + } + }); + offline.after(function(){ + player.dispose(); + buffer.reverse = false; + done(); + }); + offline.run(); + }); + + }); + + context("Looping", function(){ + + it("can be set to loop", function(){ + var player = new Player(); + player.loop = true; + expect(player.loop).to.be.true; + player.dispose(); + }); + + it("can set the loop points", function(){ + var player = new Player(); + player.loopStart = 0.4; + expect(player.loopStart).to.equal(0.4); + player.loopEnd = 0.5; + expect(player.loopEnd).to.equal(0.5); + player.setLoopPoints(0, 0.2); + expect(player.loopStart).to.equal(0); + expect(player.loopEnd).to.equal(0.2); + player.dispose(); + }); + + it("loops the audio", function(done){ + var player; + var meter = new Meter(buffer.duration * 2); + meter.before(function(dest){ + player = new Player(buffer); + player.loop = true; + player.connect(dest); + player.start(0); + }); + meter.test(function(sample, time){ + if (time > 0.01){ + expect(sample).to.be.above(0); + } + }); + meter.after(function(){ + player.dispose(); + done(); + }); + meter.run(); + }); + + }); + + context("Get/Set", function(){ + + it("can be set with an options object", function(){ + var player = new Player(); + expect(player.loop).to.be.false; + player.set({ + "loop" : true, + "loopStart" : 0.4 + }); + expect(player.loop).to.be.true; + expect(player.loopStart).to.equal(0.4); + player.dispose(); + }); + + it("can get an options object", function(){ + var player = new Player({ + "url" : "./audio/sine.wav", + "loopStart" : 0.2, + "loopEnd" : 0.3, + "loop" : true, + "reverse" : true + }); + expect(player.get().loopStart).to.equal(0.2); + expect(player.get().loopEnd).to.equal(0.3); + expect(player.get().loop).to.be.true; + expect(player.get().reverse).to.be.true; + player.dispose(); + }); + + it("can get/set the playbackRate", function(){ + var player = new Player(); + player.playbackRate = 0.5; + expect(player.playbackRate).to.equal(0.5); + player.dispose(); + }); + + }); + + context("Start Scheduling", function(){ + + it("can be start with an offset", function(done){ + var player; + var offline = new Offline(0.4, 1); + var audioBuffer = buffer.get().getChannelData(0); + var testSample = audioBuffer[buffer.secondsToSamples(0.1)]; + offline.before(function(dest){ + player = new Player(buffer.get()); + player.connect(dest); + player.start(0, 0.1); + }); + offline.test(function(sample, time){ + if (time === 0){ + expect(sample).to.equal(testSample); + } + }); + offline.after(function(){ + player.dispose(); + done(); + }); + offline.run(); + }); + + it("can be play for a specific duration", function(done){ + var player; + var meter = new Meter(0.4); + meter.before(function(dest){ + player = new Player(buffer); + player.connect(dest); + player.start(0).stop(0.1); + }); + meter.test(function(sample, time){ + if (sample < 0.001){ + expect(time).to.at.least(0.1); + expect(player.state).to.equal("stopped"); + } + }); + meter.after(function(){ + player.dispose(); + done(); + }); + meter.run(); + }); + + it("can be play for a specific duration passed in the 'start' method", function(done){ + var player; + var meter = new Meter(0.4); + meter.before(function(dest){ + player = new Player(buffer); + player.connect(dest); + player.start(0, 0.1); + }); + meter.test(function(sample, time){ + if (sample < 0.001){ + expect(time).to.at.least(0.1); + expect(player.state).to.equal("stopped"); + } + }); + meter.after(function(){ + player.dispose(); + done(); + }); + meter.run(); + }); + + it("reports itself as stopped after a single iterations of the buffer", function(done){ + var player = new Player("./audio/kick.mp3", function(){ + var duration = player.buffer.duration; + player.start(); + setTimeout(function(){ + expect(player.state).to.equal("started"); + }, 100); + setTimeout(function(){ + expect(player.state).to.equal("stopped"); + done(); + }, duration * 1000 + 200); + }); + }); + + }); + + }); +}); \ No newline at end of file diff --git a/test/source/PulseOscillator.js b/test/source/PulseOscillator.js new file mode 100644 index 000000000..f743cf026 --- /dev/null +++ b/test/source/PulseOscillator.js @@ -0,0 +1,130 @@ +define(["helper/Basic", "Tone/source/PulseOscillator", "helper/Offline", "helper/SourceTests", "helper/OscillatorTests"], + function (BasicTests, PulseOscillator, Offline, SourceTests, OscillatorTests) { + + describe("PulseOscillator", function(){ + + //run the common tests + BasicTests(PulseOscillator); + SourceTests(PulseOscillator); + OscillatorTests(PulseOscillator); + + context("Phase Rotation", function(){ + it ("can change the phase to 90", function(done){ + var instance; + var offline = new Offline(1); + offline.before(function(dest){ + instance = new PulseOscillator({ + "phase" : 90, + "frequency" : 1 + }); + instance.connect(dest); + instance.start(0); + }); + offline.test(function(sample, time){ + if (time < 0.25){ + expect(sample).to.be.within(-1, 0); + } else if (time < 0.5){ + expect(sample).to.be.within(0, 1); + } + }); + offline.after(function(){ + instance.dispose(); + done(); + }); + offline.run(); + }); + + it ("can change the phase to -90", function(done){ + var instance; + var offline = new Offline(1); + offline.before(function(dest){ + instance = new PulseOscillator({ + "phase" : 270, + "frequency" : 1 + }); + instance.connect(dest); + instance.start(0); + }); + offline.test(function(sample, time){ + if (time < 0.25){ + expect(sample).to.be.within(0, 1); + } else if (time < 0.5){ + expect(sample).to.be.within(-1, 0); + } + }); + offline.after(function(){ + instance.dispose(); + done(); + }); + offline.run(); + }); + + }); + + context("Width", function(){ + + it ("can set the width", function(){ + var osc = new PulseOscillator({ + "width" : 0.2, + }); + expect(osc.width.value).to.be.closeTo(0.2, 0.001); + osc.dispose(); + }); + + it ("outputs correctly with a width of 0", function(done){ + var osc; + var offline = new Offline(1); + offline.before(function(dest){ + osc = new PulseOscillator({ + "width" : 0, + "frequency" : 1 + }); + osc.connect(dest); + osc.start(0); + }); + var lastTime = 0; + offline.test(function(sample, time){ + lastTime = time; + if (time > 0.5){ + expect(sample).to.be.within(-1, 0); + } + }); + offline.after(function(){ + osc.dispose(); + done(); + }); + offline.run(); + }); + + it ("outputs correctly with a width of 0.5", function(done){ + var osc; + var offline = new Offline(1); + offline.before(function(dest){ + osc = new PulseOscillator({ + "width" : 0.5, + "frequency" : 1 + }); + osc.connect(dest); + osc.start(0); + }); + var lastTime = 0; + offline.test(function(sample, time){ + lastTime = time; + if (time <= 0.5){ + expect(sample).to.be.within(0, 1); + } else if (time >= 0.51 && time <= 0.7){ + expect(sample).to.be.within(-1, 0); + } else if (time > 0.71){ + expect(sample).to.be.within(0, 1); + } + }); + offline.after(function(){ + osc.dispose(); + done(); + }); + offline.run(); + }); + }); + + }); +}); \ No newline at end of file diff --git a/test/source/Source.js b/test/source/Source.js new file mode 100644 index 000000000..2195fd848 --- /dev/null +++ b/test/source/Source.js @@ -0,0 +1,138 @@ +define(["Test", "Tone/source/Source", "Tone/core/Transport"], function (Test, Source, Transport) { + + describe("Source", function(){ + + it("can be created and disposed", function(){ + var source = new Source(); + source.dispose(); + Test.wasDisposed(source); + }); + + it("can be started and stopped", function(){ + var source = new Source(); + source.start(0); + source.stop(1); + source.dispose(); + }); + + it("can be constructed with an options object", function(){ + var source = new Source({ + "volume" : -20, + }); + expect(source.volume.value).to.be.closeTo(-20, 0.1); + source.dispose(); + }); + + it("can set the volume", function(){ + var source = new Source(); + source.volume.value = -8; + expect(source.volume.value).to.be.closeTo(-8, 0.1); + source.dispose(); + }); + + it("can get and set values with an object", function(){ + var source = new Source(); + source.set("volume", -10); + expect(source.get().volume).to.be.closeTo(-10, 0.1); + source.dispose(); + }); + + it("is initally stopped", function(){ + var source = new Source(); + expect(source.state).to.equal("stopped"); + source.dispose(); + }); + + it("cannot be scheduled to stop/start twice in a row", function(){ + var source = new Source(); + source.start(0).start(1); + source.stop(2).stop(3); + source.dispose(); + }); + + it("has an output", function(){ + var source = new Source(); + source.connect(Test); + source.dispose(); + }); + + it("can be scheduled with multiple starts/stops", function(){ + var source = new Source(); + source.start(0).stop(0.5).start(0.75).stop(1).start(1.25).stop(1.5); + expect(source._state.getStateAtTime(0)).to.equal("started"); + expect(source._state.getStateAtTime(0.5)).to.equal("stopped"); + expect(source._state.getStateAtTime(0.8)).to.equal("started"); + expect(source._state.getStateAtTime(1)).to.equal("stopped"); + expect(source._state.getStateAtTime(1.25)).to.equal("started"); + expect(source._state.getStateAtTime(1.6)).to.equal("stopped"); + source.dispose(); + }); + + it ("can sync its start to the Transport", function(done){ + var source = new Source(); + source.sync(); + Transport.start(); + setTimeout(function(){ + expect(source.state).to.equal("started"); + source.dispose(); + Transport.stop(); + done(); + }, 200); + }); + + it ("can sync its stop to the Transport", function(done){ + var source = new Source(); + source.sync(); + Transport.start().stop("+0.4"); + setTimeout(function(){ + expect(source.state).to.equal("started"); + setTimeout(function(){ + expect(source.state).to.equal("stopped"); + source.dispose(); + done(); + }, 300); + }, 200); + }); + + it ("can sync its start to the Transport after a delay", function(done){ + var source = new Source(); + source.sync(0.3); + Transport.start(); + setTimeout(function(){ + expect(source.state).to.equal("stopped"); + setTimeout(function(){ + expect(source.state).to.equal("started"); + source.dispose(); + Transport.stop(); + done(); + }, 300); + }, 200); + }); + + it ("can unsync after it was synced", function(done){ + var source = new Source(); + source.sync(); + source.unsync(); + Transport.start(); + setTimeout(function(){ + expect(source.state).to.equal("stopped"); + source.dispose(); + done(); + }, 100); + }); + + it ("correctly returns the scheduled play state", function(done){ + var source = new Source(); + expect(source.state).to.equal("stopped"); + source.start().stop("+0.5"); + setTimeout(function(){ + expect(source.state).to.equal("started"); + }, 100); + setTimeout(function(){ + expect(source.state).to.equal("stopped"); + source.dispose(); + done(); + }, 600); + }); + }); +}); \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 9626a0663..000000000 --- a/test/test.js +++ /dev/null @@ -1,19 +0,0 @@ -require.config({ - baseUrl:"./", - paths : { - "Tone" : "../Tone", - "chai" : "./testDeps/chai", - "Recorder" : "./testDeps/Tone.Recorder" - }, -}); - -var maxTimeout = 1000; - -var allTests = ["tests/Core", "tests/Timing", "tests/Signal", "tests/SignalComparison", -"tests/SignalMath", "tests/Transport", "tests/Sources", "tests/Components", -"tests/Effect", "tests/Instruments", "tests/EffectPresets", "tests/InstrumentPresets", "tests/Expr"]; -// var allTests = ["tests/Core", "tests/Signal"]; - -require(allTests, function(){ - mocha.run(); -}); \ No newline at end of file diff --git a/test/tests/Common.js b/test/tests/Common.js deleted file mode 100644 index cec34492c..000000000 --- a/test/tests/Common.js +++ /dev/null @@ -1,169 +0,0 @@ -define(["Tone/core/Tone", "chai", "Recorder", "Tone/core/Master", "Tone/signal/Signal"], -function(Tone, chai, Recorder, Master, Signal){ - - var expect = chai.expect; - - var audioContext = Tone.context; - - var recorder = new Recorder(); - - var noFun = function(){}; - - function offlineTest(duration, setup, test, end){ - setup = setup || noFun; - test = test || noFun; - end = end || noFun; - var sampleRate = 44100; - var offline = new OfflineAudioContext(1, sampleRate * duration, sampleRate); - offline.oncomplete = function(e){ - var buffer = e.renderedBuffer.getChannelData(0); - for (var i = 0; i < buffer.length; i++){ - test(buffer[i], i / sampleRate); - } - end(); - }; - Tone.setContext(offline); - setup(offline.destination); - offline.startRendering(); - } - - function offlineStereoTest(duration, setup, test, end){ - setup = setup || noFun; - test = test || noFun; - end = end || noFun; - var sampleRate = 44100; - var offline = new OfflineAudioContext(2, sampleRate * duration, sampleRate); - offline.oncomplete = function(e){ - var bufferL = e.renderedBuffer.getChannelData(0); - var bufferR = e.renderedBuffer.getChannelData(1); - for (var i = 0; i < bufferL.length; i++){ - test(bufferL[i], bufferR[i], i / sampleRate); - } - end(); - }; - Tone.setContext(offline); - setup(offline.destination); - offline.startRendering(); - } - - function onlineTest(duration, setup, test, end){ - setup = setup || noFun; - test = test || noFun; - end = end || noFun; - Tone.setContext(audioContext); - var sampleRate = audioContext.sampleRate; - setup(recorder); - recorder.record(duration, 0.1, function(buffers){ - var buffer = buffers[0]; - for (var i = 0; i < buffer.length; i++){ - test(buffer[i], i / sampleRate - 0.1); - } - end(); - }); - } - - function wasDisposed(obj){ - for (var prop in obj){ - var member = obj[prop]; - if (typeof member !== "function" && - typeof member !== "string" && - typeof member !== "number" && - typeof member !== "boolean" && - prop !== "preset" && - !(member instanceof AudioContext)){ - if (member !== null){ - throw Error("property was not completely disposed: "+prop); - } - } - } - } - - function acceptsInput(node, inputNumber){ - inputNumber = inputNumber || 0; - var inputNode = node.context.createGain(); - inputNode.connect(node, 0, inputNumber); - inputNode.disconnect(); - inputNode = null; - } - - function acceptsOutput(node, outputNumber){ - outputNumber = outputNumber || 0; - var outputNode = node.context.createGain(); - node.connect(outputNode, outputNumber, 0); - node.disconnect(outputNumber); - outputNode = null; - } - - function outputsAudio(setup, end){ - var sampleRate = 44100; - var offline = new OfflineAudioContext(2, sampleRate * 0.4, sampleRate); - offline.oncomplete = function(e){ - var buffer = e.renderedBuffer.getChannelData(0); - for (var i = 0; i < buffer.length; i++){ - if (buffer[i] !== 0){ - end(); - return; - } - } - throw new Error("node outputs silence"); - }; - Tone.setContext(offline); - setup(offline.destination); - offline.startRendering(); - } - - function passesAudio(setup, end){ - var sampleRate = 44100; - var duration = 0.5; - var offline = new OfflineAudioContext(2, sampleRate * duration, sampleRate); - offline.oncomplete = function(e){ - var buffer = e.renderedBuffer.getChannelData(0); - for (var i = 0; i < buffer.length; i++){ - if (i > duration / 2 && buffer[i] !== 0){ - signal.dispose(); - end(); - return; - } else if (i < duration / 2) { - expect(buffer[i]).to.be.closeTo(0, 0.001); - // throw new Error("node outputs sound when no signal is fed in"); - } - } - throw new Error("node outputs silence"); - }; - Tone.setContext(offline); - var signal = new Signal(0); - setup(signal, offline.destination); - signal.setValueAtTime(1, duration / 2); - offline.startRendering(); - } - - function validatePresets(node){ - if (node.preset){ - for (var name in node.preset){ - node.setPreset(name); - } - } - } - - return { - offlineTest : offlineTest, - offlineStereoTest : offlineStereoTest, - wasDisposed : wasDisposed, - onlineTest : onlineTest, - onlineContext : function(){ - if (Tone.context !== audioContext){ - Tone.setContext(audioContext); - } - Master.mute = true; - }, - acceptsInput : acceptsInput, - acceptsOutput : acceptsOutput, - acceptsInputAndOutput : function(node){ - acceptsInput(node); - acceptsOutput(node); - }, - outputsAudio : outputsAudio, - passesAudio : passesAudio, - validatePresets : validatePresets, - }; -}); \ No newline at end of file diff --git a/test/tests/Components.js b/test/tests/Components.js deleted file mode 100644 index 18b88a831..000000000 --- a/test/tests/Components.js +++ /dev/null @@ -1,1229 +0,0 @@ -/* global it, describe, maxTimeout*/ - -define(["tests/Core", "chai", "Tone/component/CrossFade", "Tone/core/Master", "Tone/signal/Signal", -"Recorder", "Tone/component/Panner", "Tone/component/LFO", "Tone/component/Gate", -"Tone/component/Follower", "Tone/component/Envelope", "Tone/component/Filter", "Tone/component/EQ3", -"Tone/component/Merge", "Tone/component/Split", "tests/Common", "Tone/component/AmplitudeEnvelope", -"Tone/component/LowpassCombFilter", "Tone/component/FeedbackCombFilter", "Tone/component/Mono", -"Tone/component/MultibandSplit", "Tone/component/Compressor", "Tone/component/PanVol", -"Tone/component/MultibandCompressor", "Tone/component/ScaledEnvelope", "Tone/component/Limiter", -"Tone/core/Transport", "Tone/component/Volume", "Tone/component/MidSideSplit", -"Tone/component/MidSideMerge", "Tone/component/MidSideCompressor"], -function(coreTest, chai, CrossFade, Master, Signal, Recorder, Panner, LFO, Gate, Follower, Envelope, - Filter, EQ3, Merge, Split, Test, AmplitudeEnvelope, LowpassCombFilter, FeedbackCombFilter, - Mono, MultibandSplit, Compressor, PanVol, MultibandCompressor, ScaledEnvelope, Limiter, Transport, - Volume, MidSideSplit, MidSideMerge, MidSideCompressor){ - var expect = chai.expect; - - Master.mute = true; - - describe("Tone.CrossFade", function(){ - this.timeout(maxTimeout); - - var crossFade, drySignal, wetSignal, recorder; - - it("can be created and disposed", function(){ - var dw = new CrossFade(); - dw.dispose(); - Test.wasDisposed(dw); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var crossFade = new CrossFade(); - Test.acceptsInput(crossFade, 0); - Test.acceptsInput(crossFade, 1); - Test.acceptsOutput(crossFade); - crossFade.dispose(); - }); - - it("pass 100% dry signal", function(done){ - Test.offlineTest(0.1, function(dest){ - crossFade = new CrossFade(); - drySignal = new Signal(10); - wetSignal = new Signal(20); - drySignal.connect(crossFade, 0, 0); - wetSignal.connect(crossFade, 0, 1); - recorder = new Recorder(); - crossFade.fade.value = 0; - crossFade.connect(dest); - }, function(sample){ - expect(sample).to.closeTo(10, 0.01); - }, function(){ - crossFade.dispose(); - drySignal.dispose(); - wetSignal.dispose(); - done(); - }); - }); - - it("pass 100% wet signal", function(done){ - Test.offlineTest(0.1, function(dest){ - crossFade = new CrossFade(); - drySignal = new Signal(10); - wetSignal = new Signal(20); - drySignal.connect(crossFade, 0, 0); - wetSignal.connect(crossFade, 0, 1); - recorder = new Recorder(); - crossFade.fade.value = 1; - crossFade.connect(dest); - }, function(sample){ - expect(sample).to.closeTo(20, 0.01); - }, function(){ - crossFade.dispose(); - drySignal.dispose(); - wetSignal.dispose(); - done(); - }); - }); - - it("can mix two signals", function(done){ - Test.offlineTest(0.1, function(dest){ - crossFade = new CrossFade(); - drySignal = new Signal(0.5); - wetSignal = new Signal(0.5); - drySignal.connect(crossFade, 0, 0); - wetSignal.connect(crossFade, 0, 1); - recorder = new Recorder(); - crossFade.fade.value = 0.5; - crossFade.connect(dest); - }, function(sample){ - expect(sample).to.closeTo(0.707, 0.01); - }, function(){ - crossFade.dispose(); - drySignal.dispose(); - wetSignal.dispose(); - done(); - }); - }); - }); - - describe("Tone.Panner", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var panner = new Panner(); - panner.dispose(); - Test.wasDisposed(panner); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var panner = new Panner(); - Test.acceptsInputAndOutput(panner); - panner.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var panner; - Test.passesAudio(function(input, output){ - panner = new Panner(); - input.connect(panner); - panner.connect(output); - }, function(){ - panner.dispose(); - done(); - }); - }); - - it("can pan an incoming signal", function(done){ - //pan hard right - var signal, panner; - Test.offlineStereoTest(0.1, function(dest){ - panner = new Panner(); - signal = new Signal(1); - signal.connect(panner); - panner.pan.value = 1; - panner.connect(dest); - }, function(L, R){ - expect(L).to.be.closeTo(0, 0.01); - expect(R).to.be.closeTo(1, 0.01); - }, function(){ - panner.dispose(); - signal.dispose(); - done(); - }); - }); - }); - - describe("Tone.LFO", function(){ - - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var l = new LFO(); - l.dispose(); - Test.wasDisposed(l); - }); - - it("can be started and stopped", function(){ - Test.onlineContext(); - var lfo = new LFO(); - lfo.start(); - lfo.stop(); - lfo.dispose(); - }); - - it("handles output connections", function(){ - Test.onlineContext(); - var lfo = new LFO(); - Test.acceptsOutput(lfo); - lfo.dispose(); - }); - - it("can sync to Transport", function(done){ - var lfo; - Test.offlineTest(0.1, function(dest){ - Transport.bpm.value = 120; - lfo = new LFO(2); - lfo.frequency.connect(dest); - lfo.sync(); - Transport.bpm.value = 240; - }, function(freq){ - expect(freq).to.be.closeTo(4, 0.001); - }, function(){ - lfo.dispose(); - done(); - }); - }); - - it("can unsync to Transport", function(done){ - var lfo; - Test.offlineTest(0.1, function(dest){ - Transport.bpm.value = 120; - lfo = new LFO(2); - lfo.frequency.connect(dest); - lfo.sync(); - Transport.bpm.value = 240; - lfo.unsync(); - }, function(freq){ - expect(freq).to.be.closeTo(2, 0.001); - }, function(){ - lfo.dispose(); - done(); - }); - }); - - it("can be creates an oscillation in a specific range", function(done){ - var lfo; - Test.offlineTest(0.1, function(dest){ - lfo = new LFO(100, 10, 20); - lfo.connect(dest); - lfo.start(); - }, function(sample){ - expect(sample).to.be.within(10, 20); - }, function(){ - lfo.dispose(); - done(); - }); - }); - - it("can change the oscillation range", function(done){ - var lfo; - Test.offlineTest(0.1, function(dest){ - lfo = new LFO(100, 10, 20); - lfo.connect(dest); - lfo.start(); - lfo.min = 15; - lfo.max = 18; - }, function(sample){ - expect(sample).to.be.within(15, 18); - }, function(){ - lfo.dispose(); - done(); - }); - }); - - it("handles getters/setters as objects", function(){ - var lfo = new LFO(); - var values = { - "type" : "square", - "min" : -1, - "max" : 2, - "phase" : 180, - "frequency" : "8n", - }; - lfo.set(values); - expect(lfo.get()).to.contain.keys(Object.keys(values)); - expect(lfo.type).to.equal(values.type); - expect(lfo.min).to.equal(values.min); - expect(lfo.max).to.equal(values.max); - expect(lfo.phase).to.equal(values.phase); - lfo.dispose(); - }); - }); - - describe("Tone.Gate", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var g = new Gate(); - g.dispose(); - Test.wasDisposed(g); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var gate = new Gate(); - Test.acceptsInputAndOutput(gate); - gate.dispose(); - }); - - it("handles getter/setters", function(){ - Test.onlineContext(); - var gate = new Gate(); - var values = { - "attack" : "4n", - "release" : "8n", - "threshold" : -25, - }; - gate.set(values); - expect(gate.get()).to.have.keys(["attack", "release", "threshold"]); - expect(gate.attack).to.equal(values.attack); - expect(gate.decay).to.equal(values.decay); - expect(gate.threshold).to.be.closeTo(values.threshold, 0.1); - gate.dispose(); - }); - - it("won't let signals below a db thresh through", function(done){ - var gate, sig; - Test.offlineTest(0.5, function(dest){ - gate = new Gate(-10, 0.01); - sig = new Signal(gate.dbToGain(-11)); - sig.connect(gate); - gate.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - gate.dispose(); - sig.dispose(); - done(); - }); - }); - - it("lets signals above the db thresh through", function(done){ - var gate, sig, level; - Test.offlineTest(0.5, function(dest){ - gate = new Gate(-8, 0.01); - level = gate.dbToGain(-6); - sig = new Signal(level); - sig.connect(gate); - gate.connect(dest); - }, function(sample, time){ - if (time >= 0.1){ - expect(sample).to.be.closeTo(level, 0.001); - } - }, function(){ - gate.dispose(); - sig.dispose(); - done(); - }); - }); - }); - - describe("Tone.Follower", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var f = new Follower(); - f.dispose(); - Test.wasDisposed(f); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var foll = new Follower(0.1, 0.5); - Test.acceptsInputAndOutput(foll); - foll.dispose(); - }); - - it("smoothes the incoming signal", function(done){ - var foll, sig; - Test.offlineTest(0.1, function(dest){ - foll = new Follower(0.1, 0.5); - sig = new Signal(0); - sig.connect(foll); - foll.connect(dest); - sig.setValueAtTime(1, "+0.1"); - }, function(sample){ - expect(sample).to.lessThan(1); - }, function(){ - foll.dispose(); - sig.dispose(); - done(); - }); - }); - - it("handles getter/setter as Object", function(){ - var foll = new Follower(); - var values = { - "attack" : "8n", - "release" : "4n" - }; - foll.set(values); - expect(foll.get()).to.have.keys(["attack", "release"]); - expect(foll.get()).to.deep.equal(values); - foll.dispose(); - }); - }); - - - describe("Tone.Envelope", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var e = new Envelope(); - e.dispose(); - Test.wasDisposed(e); - }); - - it("handles output connections", function(){ - Test.onlineContext(); - var e = new Envelope(); - Test.acceptsOutput(e); - e.dispose(); - }); - - it ("can take parameters as both an object and as arguments", function(){ - var e0 = new Envelope({ - "attack" : 0, - "decay" : 0.5, - "sustain" : 1 - }); - expect(e0.attack).to.equal(0); - expect(e0.decay).to.equal(0.5); - expect(e0.sustain).to.equal(1); - e0.dispose(); - var e1 = new Envelope(0.1, 0.2, 0.3); - expect(e1.attack).to.equal(0.1); - expect(e1.decay).to.equal(0.2); - expect(e1.sustain).to.equal(0.3); - e1.dispose(); - }); - - it ("can schedule an ADSR envelope", function(done){ - var env; - Test.offlineTest(0.7, function(dest){ - env = new Envelope(0.1, 0.2, 0.5, 0.1); - env.connect(dest); - env.triggerAttack(0); - env.triggerRelease(0.4); - }, function(sample, time){ - if (time < 0.1){ - expect(sample).to.be.within(0, 1); - } else if (time < 0.3){ - expect(sample).to.be.within(0.5, 1); - } else if (time < 0.4){ - expect(sample).to.be.within(0.499, 0.51); - } else if (time < 0.5){ - expect(sample).to.be.within(0, 0.51); - } else { - expect(sample).to.be.below(0.1); - } - }, function(){ - env.dispose(); - done(); - }); - }); - - it ("can get and set values an Objects", function(){ - var env = new Envelope(); - var values = { - "attack" : 0, - "decay" : 0.5, - "sustain" : 1, - "release" : "4n" - }; - env.set(values); - expect(env.get()).to.contain.keys(Object.keys(values)); - env.dispose(); - }); - - it ("can schedule an attackRelease", function(done){ - var env; - Test.offlineTest(0.7, function(dest){ - env = new Envelope(0.1, 0.2, 0.5, 0.1); - env.connect(dest); - env.triggerAttackRelease(0.4, 0); - }, function(sample, time){ - if (time < 0.1){ - expect(sample).to.be.within(0, 1); - } else if (time < 0.3){ - expect(sample).to.be.within(0.5, 1); - } else if (time < 0.4){ - expect(sample).to.be.within(0.499, 0.51); - } else if (time < 0.5){ - expect(sample).to.be.within(0, 0.51); - } else { - expect(sample).to.be.below(0.1); - } - }, function(){ - env.dispose(); - done(); - }); - }); - }); - - - describe("Tone.Filter", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var f = new Filter(); - f.dispose(); - Test.wasDisposed(f); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var f = new Filter(); - Test.acceptsInputAndOutput(f); - f.dispose(); - }); - - it("can set/get values as an Object", function(){ - var f = new Filter(); - var values = { - "type" : "highpass", - "frequency" : 440, - "rolloff" : -24, - "Q" : 2, - "gain" : -6, - }; - f.set(values); - expect(f.get()).to.have.keys(["type", "frequency", "rolloff", "Q", "gain"]); - expect(f.type).to.equal(values.type); - expect(f.frequency.value).to.equal(values.frequency); - expect(f.rolloff).to.equal(values.rolloff); - expect(f.Q.value).to.equal(values.Q); - expect(f.gain.value).to.be.closeTo(values.gain, 0.04); - f.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var filter; - Test.passesAudio(function(input, output){ - filter = new Filter(); - input.connect(filter); - filter.connect(output); - }, function(){ - filter.dispose(); - done(); - }); - }); - - it ("can take parameters as both an object and as arguments", function(){ - Test.onlineContext(); - var f0 = new Filter({ - "frequency" : 1000, - "type" : "highpass" - }); - expect(f0.frequency.value).to.equal(1000); - expect(f0.type).to.equal("highpass"); - f0.dispose(); - var f1 = new Filter(200, "bandpass"); - expect(f1.frequency.value).to.equal(200); - expect(f1.type).to.equal("bandpass"); - f1.dispose(); - }); - }); - - describe("Tone.EQ33", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var eq = new EQ3(); - eq.dispose(); - Test.wasDisposed(eq); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var eq = new EQ3(); - Test.acceptsInputAndOutput(eq); - eq.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var eq; - Test.passesAudio(function(input, output){ - eq = new EQ3(); - input.connect(eq); - eq.connect(output); - }, function(){ - eq.dispose(); - done(); - }); - }); - - it("can set/get values as an Object", function(){ - Test.onlineContext(); - var eq = new EQ3(); - var values = { - "high" : -12, - "mid" : -24, - "low" : -1 - }; - eq.set(values); - expect(eq.high.value).to.be.closeTo(values.high, 0.1); - expect(eq.mid.value).to.be.closeTo(values.mid, 0.1); - expect(eq.low.value).to.be.closeTo(values.low, 0.1); - eq.dispose(); - }); - }); - - //MERGE - describe("Tone.Merge", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var mer = new Merge(); - mer.dispose(); - Test.wasDisposed(mer); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var mer = new Merge(); - Test.acceptsInput(mer.left); - Test.acceptsInput(mer.right); - Test.acceptsOutput(mer); - mer.dispose(); - }); - - it("merge two signal into one stereo signal", function(done){ - //make an oscillator to drive the signal - var sigL, sigR, merger; - Test.offlineStereoTest(0.1, function(dest){ - sigL = new Signal(1); - sigR = new Signal(2); - merger = new Merge(); - sigL.connect(merger.left); - sigR.connect(merger.right); - merger.connect(dest); - }, function(L, R){ - expect(L).to.equal(1); - expect(R).to.equal(2); - }, function(){ - sigL.dispose(); - sigR.dispose(); - merger.dispose(); - done(); - }); - }); - }); - - describe("Tone.Split", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var split = new Split(); - split.dispose(); - Test.wasDisposed(split); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var split = new Split(); - Test.acceptsInput(split); - Test.acceptsOutput(split.left); - Test.acceptsOutput(split.right); - split.dispose(); - }); - - it("merges two signal into one stereo signal and then split them back into two signals on left side", function(done){ - var sigL, sigR, merger, split; - Test.offlineTest(0.1, function(dest){ - sigL = new Signal(1); - sigR = new Signal(2); - merger = new Merge(); - split = new Split(); - sigL.connect(merger.left); - sigR.connect(merger.right); - merger.connect(split); - split.connect(dest, 0, 0); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - sigL.dispose(); - sigR.dispose(); - merger.dispose(); - split.dispose(); - done(); - }); - }); - - it("merges two signal into one stereo signal and then split them back into two signals on right side", function(done){ - var sigL, sigR, merger, split; - Test.offlineTest(0.1, function(dest){ - sigL = new Signal(1); - sigR = new Signal(2); - merger = new Merge(); - split = new Split(); - sigL.connect(merger.left); - sigR.connect(merger.right); - merger.connect(split); - split.connect(dest, 1, 0); - }, function(sample){ - expect(sample).to.equal(2); - }, function(){ - sigL.dispose(); - sigR.dispose(); - merger.dispose(); - split.dispose(); - done(); - }); - }); - }); - - describe("Tone.AmplitudeEnvelope", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var ampEnv = new AmplitudeEnvelope(); - ampEnv.dispose(); - Test.wasDisposed(ampEnv); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var ampEnv = new AmplitudeEnvelope(); - Test.acceptsInputAndOutput(ampEnv); - ampEnv.dispose(); - }); - - it("inherits all methods from Envelope", function(){ - var ampEnv = new AmplitudeEnvelope(); - expect(ampEnv).to.be.instanceOf(Envelope); - ampEnv.dispose(); - }); - }); - - describe("Tone.LowpassCombFilter", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var lfcf = new LowpassCombFilter(); - lfcf.dispose(); - Test.wasDisposed(lfcf); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var lfcf = new LowpassCombFilter(); - Test.acceptsInputAndOutput(lfcf); - lfcf.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var lfcf; - Test.passesAudio(function(input, output){ - lfcf = new LowpassCombFilter(); - input.connect(lfcf); - lfcf.connect(output); - }, function(){ - lfcf.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - Test.onlineContext(); - var lfcf = new LowpassCombFilter(); - var values = { - "resonance" : 0.4, - "dampening" : 4000, - "delayTime" : "4n" - }; - lfcf.set(values); - expect(lfcf.get()).to.have.keys(["resonance", "dampening", "delayTime"]); - lfcf.dispose(); - }); - }); - - describe("Tone.FeedbackCombFilter", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var fbcf = new FeedbackCombFilter(); - fbcf.dispose(); - Test.wasDisposed(fbcf); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var fbcf = new FeedbackCombFilter(); - Test.acceptsInputAndOutput(fbcf); - fbcf.dispose(); - }); - - it("can set delayTime", function(){ - Test.onlineContext(); - var fbcf = new FeedbackCombFilter(); - fbcf.delayTime.value = "4n"; - var quarterSeconds = fbcf.toSeconds("4n"); - expect(fbcf.delayTime.value).to.equal(quarterSeconds); - fbcf.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var fbcf; - Test.passesAudio(function(input, output){ - fbcf = new FeedbackCombFilter(); - input.connect(fbcf); - fbcf.connect(output); - }, function(){ - fbcf.dispose(); - done(); - }); - }); - }); - - describe("Tone.Mono", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var mono = new Mono(); - mono.dispose(); - Test.wasDisposed(mono); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var mono = new Mono(); - Test.acceptsInputAndOutput(mono); - mono.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var mono; - Test.passesAudio(function(input, output){ - mono = new FeedbackCombFilter(); - input.connect(mono); - mono.connect(output); - }, function(){ - mono.dispose(); - done(); - }); - }); - }); - - describe("Tone.MultibandSplit", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var mband = new MultibandSplit(); - mband.dispose(); - Test.wasDisposed(mband); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var mband = new MultibandSplit(); - Test.acceptsInput(mband); - Test.acceptsOutput(mband.low); - Test.acceptsOutput(mband, 1); - Test.acceptsOutput(mband, 2); - mband.dispose(); - }); - }); - - describe("Tone.Compressor", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var comp = new Compressor(); - comp.dispose(); - Test.wasDisposed(comp); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var comp = new Compressor(); - Test.acceptsInputAndOutput(comp); - comp.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var comp; - Test.passesAudio(function(input, output){ - comp = new Compressor(); - input.connect(comp); - comp.connect(output); - }, function(){ - comp.dispose(); - done(); - }); - }); - - it("can be get and set through object", function(){ - var comp = new Compressor(); - var values = { - "ratio" : 22, - "threshold" : -30, - "release" : 0.5, - "attack" : 0.03, - "knee" : 20 - }; - comp.set(values); - expect(comp.get()).to.have.keys(["ratio", "threshold", "release", "attack", "ratio"]); - comp.dispose(); - }); - - it("can get/set all interfaces", function(){ - var comp = new Compressor(); - var values = { - "ratio" : 22, - "threshold" : -30, - "release" : 0.5, - "attack" : 0.03, - "knee" : 20 - }; - comp.ratio.value = values.ratio; - comp.threshold.value = values.threshold; - comp.release.value = values.release; - comp.attack.value = values.attack; - comp.knee.value = values.knee; - expect(comp.ratio.value).to.equal(values.ratio); - expect(comp.threshold.value).to.equal(values.threshold); - expect(comp.release.value).to.equal(values.release); - expect(comp.attack.value).to.be.closeTo(values.attack, 0.01); - expect(comp.knee.value).to.equal(values.knee); - comp.dispose(); - }); - }); - - describe("Tone.PanVol", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var panvol = new PanVol(); - panvol.dispose(); - Test.wasDisposed(panvol); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var panvol = new PanVol(); - Test.acceptsInputAndOutput(panvol); - panvol.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var panvol; - Test.passesAudio(function(input, output){ - panvol = new PanVol(); - input.connect(panvol); - panvol.connect(output); - }, function(){ - panvol.dispose(); - done(); - }); - }); - - it("can set the pan and volume", function(){ - var panvol = new PanVol(); - panvol.volume.value = -12; - panvol.pan.value = 0; - expect(panvol.volume.value).to.be.closeTo(-12, 0.1); - expect(panvol.pan.value).to.be.equal(0); - }); - }); - - describe("Tone.MultibandCompressor", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var comp = new MultibandCompressor(); - comp.dispose(); - Test.wasDisposed(comp); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var comp = new MultibandCompressor(); - Test.acceptsInputAndOutput(comp); - comp.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var comp; - Test.passesAudio(function(input, output){ - comp = new MultibandCompressor(); - input.connect(comp); - comp.connect(output); - }, function(){ - comp.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - Test.onlineContext(); - var comp = new MultibandCompressor(); - var values = { - "low" : { - "attack" : 0.3 - }, - "mid" : { - "threshold" : -12 - } - }; - comp.set(values); - expect(comp.get()).to.have.deep.property("low.attack"); - expect(comp.get()).to.have.deep.property("mid.threshold"); - expect(comp.low.attack.value).to.be.closeTo(0.3, 0.05); - expect(comp.mid.threshold.value).to.be.closeTo(-12, 0.05); - comp.dispose(); - }); - }); - - describe("Tone.ScaledEnvelope", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var e = new ScaledEnvelope(); - e.dispose(); - Test.wasDisposed(e); - }); - - it("handles output connections", function(){ - Test.onlineContext(); - var e = new ScaledEnvelope(); - Test.acceptsOutput(e); - e.dispose(); - }); - - it ("can take parameters as an object", function(){ - var e0 = new ScaledEnvelope({ - "attack" : 0, - "decay" : 0.5, - "sustain" : 1, - "min" : 10, - "max": 5 - }); - expect(e0.attack).to.equal(0); - expect(e0.decay).to.equal(0.5); - expect(e0.sustain).to.equal(1); - e0.dispose(); - }); - - it ("can schedule an ADSR envelope", function(done){ - var env; - Test.offlineTest(0.7, function(dest){ - env = new ScaledEnvelope({ - "attack" : 0.1, - "decay" : 0.2, - "sustain" : 0.5, - "release" : 0.1, - "min" : 0, - "max": 100 - }); - env.connect(dest); - env.triggerAttack(0); - env.triggerRelease(0.4); - }, function(sample, time){ - if (time < 0.1){ - expect(sample).to.be.within(0, 100); - } else if (time < 0.3){ - expect(sample).to.be.within(0.5, 100); - } else if (time < 0.4){ - expect(sample).to.be.within(0.5, 51); - } else if (time < 0.5){ - expect(sample).to.be.within(0, 51); - } else { - expect(sample).to.be.below(1); - } - }, function(){ - env.dispose(); - done(); - }); - }); - - it ("can scale the range", function(done){ - var env; - Test.offlineTest(0.7, function(dest){ - env = new ScaledEnvelope(0.1, 0.2, 0.5, 0.1); - env.connect(dest); - env.min = 5; - env.max = 10; - env.triggerAttack(0.1); - }, function(sample, time){ - if (time < 0.1){ - expect(sample).to.be.closeTo(5, 0.1); - } else if (time < 0.2){ - expect(sample).to.be.within(5, 10); - } - }, function(){ - env.dispose(); - done(); - }); - }); - }); - - describe("Tone.Limiter", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var lim = new Limiter(); - lim.dispose(); - Test.wasDisposed(lim); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var lim = new Limiter(); - Test.acceptsInputAndOutput(lim); - lim.dispose(); - }); - - it("can get and set values", function(){ - Test.onlineContext(); - var lim = new Limiter(); - lim.threshold.value = -12; - expect(lim.threshold.value).to.be.closeTo(-12, 0.05); - lim.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var lim; - Test.passesAudio(function(input, output){ - lim = new Limiter(); - input.connect(lim); - lim.connect(output); - }, function(){ - lim.dispose(); - done(); - }); - }); - }); - - describe("Tone.Volume", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var vol = new Volume(); - vol.dispose(); - Test.wasDisposed(vol); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var vol = new Volume(); - Test.acceptsInputAndOutput(vol); - vol.dispose(); - }); - - it("can get and set values", function(){ - Test.onlineContext(); - var vol = new Volume(); - vol.volume.value = -12; - expect(vol.volume.value).to.be.closeTo(-12, 0.05); - vol.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var vol; - Test.passesAudio(function(input, output){ - vol = new Volume(); - input.connect(vol); - vol.connect(output); - }, function(){ - vol.dispose(); - done(); - }); - }); - }); - - describe("Tone.MidSideSplit", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var split = new MidSideSplit(); - split.dispose(); - Test.wasDisposed(split); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var split = new MidSideSplit(); - Test.acceptsInput(split); - Test.acceptsOutput(split.mid); - Test.acceptsOutput(split.side); - split.dispose(); - }); - }); - - describe("Tone.MidSideMerge", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var merge = new MidSideMerge(); - merge.dispose(); - Test.wasDisposed(merge); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var merge = new MidSideMerge(); - Test.acceptsInput(merge.side); - Test.acceptsInput(merge.mid); - Test.acceptsOutput(merge); - merge.dispose(); - }); - - it("passes the mid signal through", function(done){ - var merge; - Test.passesAudio(function(input, output){ - merge = new MidSideMerge(); - input.connect(merge.mid); - merge.connect(output); - }, function(){ - merge.dispose(); - done(); - }); - }); - - it("passes the side signal through", function(done){ - var merge; - Test.passesAudio(function(input, output){ - merge = new MidSideMerge(); - input.connect(merge.side); - merge.connect(output); - }, function(){ - merge.dispose(); - done(); - }); - }); - }); - - describe("Tone.MidSideCompressor", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var comp = new MidSideCompressor(); - comp.dispose(); - Test.wasDisposed(comp); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var comp = new MidSideCompressor(); - Test.acceptsInput(comp); - Test.acceptsOutput(comp); - comp.dispose(); - }); - - it("passes signal through", function(done){ - var comp; - Test.passesAudio(function(input, output){ - comp = new MidSideCompressor(); - input.connect(comp); - comp.connect(output); - }, function(){ - comp.dispose(); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/Core.js b/test/tests/Core.js deleted file mode 100644 index dca128db8..000000000 --- a/test/tests/Core.js +++ /dev/null @@ -1,394 +0,0 @@ -/* global it, describe, after */ - -define(["chai", "Tone/core/Tone", "Tone/core/Master", "Tone/core/Bus", - "Tone/core/Note", "tests/Common", "Tone/core/Buffer", "Tone/source/Oscillator", "Tone/instrument/SimpleSynth"], -function(chai, Tone, Master, Bus, Note, Test, Buffer, Oscillator, SimpleSynth){ - var expect = chai.expect; - - describe("AudioContext", function(){ - this.timeout(3000); - - it ("was created", function(){ - expect(Tone.context).to.be.instanceof(AudioContext); - }); - - it ("has OscillatorNode", function(){ - expect(Tone.context.createOscillator).to.be.instanceof(Function); - }); - - it ("clock running", function(done){ - var interval = setInterval(function(){ - if (Tone.context.currentTime > 0){ - clearInterval(interval); - done(); - } - }, 20); - }); - - it ("has current API", function(){ - expect(OscillatorNode.prototype.start).to.be.instanceof(Function); - expect(AudioBufferSourceNode.prototype.start).to.be.instanceof(Function); - expect(AudioContext.prototype.createGain).to.be.instanceof(Function); - }); - - }); - - describe("Tone", function(){ - - var tone = new Tone(); - - after(function(){ - tone.dispose(); - }); - - it("can be created and disposed", function(){ - var t = new Tone(); - t.dispose(); - Test.wasDisposed(t); - }); - - it("correctly calculates samples to seconds", function(){ - var sampleRate = tone.context.sampleRate; - expect(tone.samplesToSeconds(100)).to.equal(100/sampleRate); - expect(tone.samplesToSeconds(800)).to.equal(800/sampleRate); - }); - - it("can convert gain to db", function(){ - expect(tone.gainToDb(0)).to.equal(-Infinity); - expect(tone.gainToDb(1)).is.closeTo(0, 0.1); - expect(tone.gainToDb(0.5)).is.closeTo(-6, 0.1); - }); - - it("can convert db to gain", function(){ - expect(tone.dbToGain(0)).is.closeTo(1, 0.1); - expect(tone.dbToGain(-12)).is.closeTo(0.25, 0.1); - expect(tone.dbToGain(-24)).is.closeTo(0.125, 0.1); - }); - - it("can convert back and forth between db and gain representations", function(){ - expect(tone.dbToGain(tone.gainToDb(0))).is.closeTo(0, 0.01); - expect(tone.dbToGain(tone.gainToDb(0.5))).is.closeTo(0.5, 0.01); - expect(tone.gainToDb(tone.dbToGain(1))).is.closeTo(1, 0.01); - }); - - it("returns a default argument when the given is not defined", function(){ - expect(tone.defaultArg(undefined, 0)).is.equal(0); - expect(tone.defaultArg(undefined, "also")).is.equal("also"); - expect(tone.defaultArg("hihi", 100)).is.equal("hihi"); - }); - - it("handles default arguments on an object", function(){ - expect(tone.defaultArg({"b" : 10}, {"a" : 4, "b" : 10})).has.property("a", 4); - expect(tone.defaultArg({"b" : 10}, {"a" : 4, "b" : 10})).has.property("b", 10); - expect(tone.defaultArg({"b" : {"c" : 10}}, {"b" : {"c" : 20}})).has.deep.property("b.c", 10); - expect(tone.defaultArg({"a" : 10}, {"b" : {"c" : 20}})).has.deep.property("b.c", 20); - }); - - it("can connect and disconnect", function(){ - var node = Tone.context.createGain(); - tone.connect(node, 0, 0); - tone.disconnect(); - }); - - it("can chain connections", function(done){ - var node0, node1, node2; - Test.passesAudio(function(input, output){ - node0 = new Tone(1, 1); - //internal connection - node0.input.connect(node0.output); - //two other nodes to pass audio through - node1 = Tone.context.createGain(); - node2 = Tone.context.createGain(); - input.connect(node0); - node0.chain(node1, node2, output); - }, function(){ - node0.dispose(); - node1.disconnect(); - node2.disconnect(); - done(); - }); - }); - - it("can fan connections", function(done){ - var node0, node1, node2; - Test.passesAudio(function(input, output){ - node0 = new Tone(1, 1); - //internal connection - node0.input.connect(node0.output); - //two other nodes to pass audio through - node1 = Tone.context.createGain(); - node2 = Tone.context.createGain(); - input.connect(node0); - node0.fan(node1, node2, output); - }, function(){ - node0.dispose(); - node1.disconnect(); - node2.disconnect(); - done(); - }); - }); - }); - - describe("Tone.prototype.set / get", function(){ - - it("sets a value given an object", function(){ - var osc = new Oscillator(0); - osc.set({ - "frequency" : 30 - }); - expect(osc.frequency.value).to.be.closeTo(30, 0.001); - osc.dispose(); - }); - - it("sets a value given a string and a value", function(){ - var osc = new Oscillator(0); - osc.set("frequency", 2); - expect(osc.frequency.value).to.be.closeTo(2, 0.001); - osc.dispose(); - }); - - it("ramps to a value given an object and ramp time", function(done){ - var osc; - var setValue = 30; - Test.offlineTest(0.6, function(dest){ - osc = new Oscillator(0); - osc.frequency.connect(dest); - osc.set({ - "frequency" : setValue - }, 0.5); - expect(osc.frequency.value).to.not.be.closeTo(setValue, 0.001); - }, function(sample, time){ - if (time > 0.5){ - expect(sample).to.closeTo(setValue, 0.01); - } - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("ramps to a value given a string and a value and a ramp time", function(done){ - var osc; - var setValue = 30; - Test.offlineTest(0.6, function(dest){ - osc = new Oscillator(0); - osc.frequency.connect(dest); - osc.set("frequency", setValue, 0.5); - expect(osc.frequency.value).to.not.be.closeTo(setValue, 0.001); - }, function(sample, time){ - if (time > 0.5){ - expect(sample).to.closeTo(setValue, 0.01); - } - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("gets all defaults of the object with no arguments", function(){ - var osc = new Oscillator(0); - expect(osc.get()).to.contain.keys(Object.keys(Oscillator.defaults)); - osc.dispose(); - }); - - it("can 'get' only the given keys", function(){ - var osc = new Oscillator(0); - var keys = ["frequency", "type"]; - expect(Object.keys(osc.get(keys))).to.deep.equal(keys); - osc.dispose(); - }); - - it("can 'set' a nested object", function(){ - var synth = new SimpleSynth(); - synth.set({ - "oscillator" : { - "type" : "square2" - } - }); - expect(synth.oscillator.type).to.equal("square2"); - synth.dispose(); - }); - - it("can 'set' a value with dot notation", function(){ - var synth = new SimpleSynth(); - synth.set("oscillator.type", "triangle"); - expect(synth.oscillator.type).to.equal("triangle"); - synth.dispose(); - }); - - it("can 'get' a value with dot notation", function(){ - var synth = new SimpleSynth(); - synth.set({ - "oscillator" : { - "type" : "sine10", - "phase" : 20, - } - }); - expect(synth.get("oscillator.type").oscillator.type).to.equal("sine10"); - //get multiple values - expect(synth.get(["oscillator.type", "oscillator.phase"])).to.deep.equal({ - "oscillator" : { - "type" : "sine10", - "phase" : 20, - } - }); - synth.dispose(); - }); - - }); - - describe("Tone.Note", function(){ - - var tone = new Tone(); - - after(function(){ - tone.dispose(); - }); - - it("can convert notes into frequencies", function(){ - expect(tone.noteToFrequency("A4")).to.be.closeTo(440, 0.0001); - expect(tone.noteToFrequency("Bb4")).to.be.closeTo(466.163761, 0.0001); - }); - - it("can convert frequencies into notes", function(){ - expect(tone.frequencyToNote(440)).to.equal("A4"); - expect(tone.frequencyToNote(4978.031739553295)).to.equal("D#8"); - }); - - it("can convert note to midi values", function(){ - expect(tone.midiToNote(60)).to.equal("C3"); - expect(tone.midiToNote(61)).to.equal("C#3"); - }); - - it("can convert midi values to note names", function(){ - expect(tone.noteToMidi("C3")).to.equal(60); - expect(tone.noteToMidi("Bb2")).to.equal(58); - expect(tone.noteToMidi("A#2")).to.equal(58); - }); - - it("can convert semitone intervals to frequency ratios", function(){ - expect(tone.intervalToFrequencyRatio(0)).to.equal(1); - expect(tone.intervalToFrequencyRatio(12)).to.equal(2); - expect(tone.intervalToFrequencyRatio(7)).to.be.closeTo(1.5, 0.01); - }); - - it("can convert different representations into frequencies", function(){ - expect(tone.toFrequency("4n")).to.equal(2); - expect(tone.toFrequency("4hz")).to.equal(4); - expect(tone.toFrequency("A4")).to.be.closeTo(440, 0.001); - expect(tone.toFrequency(990)).to.equal(990); - }); - }); - - describe("Tone.Master", function(){ - it ("exists", function(){ - expect(Tone.Master).to.exist; - }); - - it ("provides a toMaster method", function(){ - expect(Tone.prototype.toMaster).is.a("function"); - }); - }); - - describe("Tone.Bus", function(){ - it ("provides a send and receive method", function(){ - expect(Tone.prototype.send).is.a("function"); - expect(Tone.prototype.receive).is.a("function"); - }); - - it ("passes audio from a send to a receive with the same name", function(done){ - var send, recv; - Test.passesAudio(function(input, output){ - //make them pass through nodes - send = new Tone(); - recv = new Tone(); - send.input.connect(send.output); - recv.input.connect(recv.output); - input.connect(send); - recv.connect(output); - send.send("test"); - recv.receive("test"); - }, function(){ - send.dispose(); - recv.dispose(); - done(); - }); - }); - }); - - describe("Tone.Buffer", function(){ - it ("can be created and disposed", function(){ - var buff = new Tone.Buffer("./testAudio/kick.mp3"); - buff.dispose(); - Test.wasDisposed(buff); - }); - - it("loads a file from a url string", function(done){ - var buffer = new Buffer("./testAudio/kick.mp3", function(buff){ - expect(buff).to.be.instanceof(Buffer); - buffer.dispose(); - done(); - }); - }); - - it("has a duration", function(done){ - var buffer = new Buffer("./testAudio/kick.mp3", function(){ - expect(buffer.duration).to.be.closeTo(0.23, 0.01); - buffer.dispose(); - done(); - }); - }); - - it("the static onload method is invoked", function(done){ - var buffer = new Buffer("./testAudio/hh.mp3"); - Buffer.onload = function(){ - buffer.dispose(); - done(); - //reset this method for the next one - Buffer.onload = function(){}; - }; - }); - - it("the static onprogress method is invoked", function(done){ - var progressWasInvoked = false; - var buffer = new Buffer("./testAudio/hh.mp3", function(){ - buffer.dispose(); - expect(progressWasInvoked).to.be.true; - done(); - }); - Buffer.onprogress = function(){ - progressWasInvoked = true; - //reset this method for the next one - Buffer.onprogress = function(){}; - }; - }); - - it("can reverse a buffer", function(done){ - var buffer = new Buffer("./testAudio/kick.mp3", function(){ - var buffArray = buffer.get(); - var lastSample = buffArray[buffArray.length - 1]; - buffer.reverse = true; - expect(buffer.get()[0]).to.equal(lastSample); - buffer.dispose(); - done(); - }); - }); - }); - - describe("Tone.setContext", function(){ - it ("can set a new context", function(){ - var origCtx = Tone.context; - var ctx = new OfflineAudioContext(2, 44100, 44100); - Tone.setContext(ctx); - expect(Tone.context).to.equal(ctx); - expect(Tone.prototype.context).to.equal(ctx); - //then set it back - Tone.setContext(origCtx); - expect(Tone.context).to.equal(origCtx); - expect(Tone.prototype.context).to.equal(origCtx); - //and a saftey check - expect(ctx).to.not.equal(origCtx); - }); - }); - -}); \ No newline at end of file diff --git a/test/tests/Effect.js b/test/tests/Effect.js deleted file mode 100644 index e464938f9..000000000 --- a/test/tests/Effect.js +++ /dev/null @@ -1,935 +0,0 @@ -/* global it, describe */ - -define(["tests/Core", "chai", "Recorder", "Tone/core/Master", "Tone/effect/Effect", "Tone/component/CrossFade", - "Tone/effect/FeedbackEffect", "Tone/signal/Signal", "Tone/effect/AutoPanner", "Tone/effect/AutoWah", "Tone/effect/BitCrusher", - "Tone/effect/FeedbackDelay", "Tone/effect/PingPongDelay", "Tone/effect/Chorus", "tests/Common", "Tone/effect/Freeverb", - "Tone/effect/JCReverb", "Tone/effect/StereoEffect", "Tone/effect/StereoFeedbackEffect", - "Tone/effect/StereoXFeedbackEffect", "Tone/effect/Phaser", "Tone/effect/Distortion", "Tone/effect/Chebyshev", - "Tone/effect/Convolver", "Tone/effect/MidSideEffect", "Tone/effect/StereoWidener", - "Tone/effect/AutoFilter", "Tone/effect/Tremolo"], -function(Tone, chai, Recorder, Master, Effect, CrossFade, FeedbackEffect, Signal, AutoPanner, AutoWah, BitCrusher, - FeedbackDelay, PingPongDelay, Chorus, Test, Freeverb, JCReverb, StereoEffect, StereoFeedbackEffect, - StereoXFeedbackEffect, Phaser, Distortion, Chebyshev, Convolver, MidSide, StereoWidener, AutoFilter, Tremolo){ - - var expect = chai.expect; - - Test.onlineContext(); - - Master.mute = true; - - describe("Tone.Effect", function(){ - - it("can be created and disposed", function(){ - var e = new Effect(); - e.dispose(); - Test.wasDisposed(e); - }); - - it("can by bypassed", function(){ - var e = new Effect(); - e.bypass(); - expect(e.wet.value).to.equal(0); - e.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var e = new Effect(); - Test.acceptsInputAndOutput(e); - e.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var effect; - Test.passesAudio(function(input, output){ - effect = new Effect({ - "wet" : 0.5 - }); - input.connect(effect); - effect.connect(output); - }, function(){ - effect.dispose(); - done(); - }); - }); - - it("has a dry/wet control", function(){ - var e = new Effect(); - expect(e.wet).is.instanceof(Signal); - e.dispose(); - }); - - it("can be set with options object", function(){ - var e = new Effect(); - e.set({"wet" : 0.22}); - expect(e.wet.value).is.closeTo(0.22, 0.01); - e.dispose(); - }); - }); - - describe("Tone.StereoEffect", function(){ - - it("can be created and disposed", function(){ - var stereoEffect = new StereoEffect(); - stereoEffect.dispose(); - Test.wasDisposed(stereoEffect); - }); - - it("extends Tone.Effect", function(){ - var stereoEffect = new StereoEffect(); - expect(stereoEffect).is.instanceof(Effect); - stereoEffect.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var e = new StereoEffect(); - Test.acceptsInputAndOutput(e); - e.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var effect; - Test.passesAudio(function(input, output){ - effect = new StereoEffect({ - "wet" : 0.5 - }); - input.connect(effect); - effect.connect(output); - }, function(){ - effect.dispose(); - done(); - }); - }); - }); - - describe("Tone.StereoFeedbackEffect", function(){ - - it("can be created and disposed", function(){ - var stereoEffect = new StereoFeedbackEffect(); - stereoEffect.dispose(); - Test.wasDisposed(stereoEffect); - }); - - it("extends Tone.Effect", function(){ - var stereoEffect = new StereoFeedbackEffect(); - expect(stereoEffect).is.instanceof(Effect); - stereoEffect.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var e = new StereoFeedbackEffect(); - Test.acceptsInputAndOutput(e); - e.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var effect; - Test.passesAudio(function(input, output){ - effect = new StereoFeedbackEffect({ - "wet" : 0.5 - }); - input.connect(effect); - effect.connect(output); - }, function(){ - effect.dispose(); - done(); - }); - }); - - it("can be set with options object", function(){ - var e = new StereoFeedbackEffect(); - var values = { - "feedback" : 0.22 - }; - e.set(values); - expect(e.get()).to.contain.keys(Object.keys(values)); - expect(e.feedback.value).is.closeTo(values.feedback, 0.05); - e.dispose(); - }); - }); - - describe("Tone.StereoXFeedbackEffect", function(){ - - it("can be created and disposed", function(){ - var stereoEffect = new StereoXFeedbackEffect(); - stereoEffect.dispose(); - Test.wasDisposed(stereoEffect); - }); - - it("extends Tone.Effect", function(){ - var stereoEffect = new StereoXFeedbackEffect(); - expect(stereoEffect).is.instanceof(Effect); - stereoEffect.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var e = new StereoXFeedbackEffect(); - Test.acceptsInputAndOutput(e); - e.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var effect; - Test.passesAudio(function(input, output){ - effect = new StereoXFeedbackEffect({ - "wet" : 0.5 - }); - input.connect(effect); - effect.connect(output); - }, function(){ - effect.dispose(); - done(); - }); - }); - }); - - describe("Tone.FeedbackEffect", function(){ - - it("can be created and disposed", function(){ - var fe = new FeedbackEffect(); - fe.dispose(); - Test.wasDisposed(fe); - }); - - it("has a dry/wet control", function(){ - var e = new FeedbackEffect(); - expect(e.wet).is.instanceof(Signal); - e.dispose(); - }); - - it("has a feedback control", function(){ - var e = new FeedbackEffect(); - expect(e.feedback).is.instanceof(Signal); - e.dispose(); - }); - - it("can be set with options object", function(){ - var e = new FeedbackEffect(); - e.set({"feedback" : 0.22}); - expect(e.feedback.value).is.closeTo(0.22, 0.01); - e.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var e = new FeedbackEffect(); - Test.acceptsInputAndOutput(e); - e.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var effect; - Test.passesAudio(function(input, output){ - effect = new FeedbackEffect({ - "wet" : 0.5 - }); - input.connect(effect); - effect.connect(output); - }, function(){ - effect.dispose(); - done(); - }); - }); - }); - - describe("Tone.AutoPanner", function(){ - - it("can be created and disposed", function(){ - var ap = new AutoPanner(); - ap.dispose(); - Test.wasDisposed(ap); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var ap = new AutoPanner(); - Test.acceptsInputAndOutput(ap); - ap.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var ap; - Test.passesAudio(function(input, output){ - ap = new AutoPanner(); - input.connect(ap); - ap.connect(output); - }, function(){ - ap.dispose(); - done(); - }); - }); - - it("extends Tone.Effect", function(){ - var ap = new AutoPanner(); - expect(ap).is.instanceof(Effect); - ap.dispose(); - }); - - it("can be set with options object", function(){ - var ap = new AutoPanner(); - ap.set({"wet" : 0.22}); - expect(ap.wet.value).is.closeTo(0.22, 0.01); - ap.dispose(); - }); - }); - - describe("Tone.AutoWah", function(){ - - it("can be created and disposed", function(){ - var aw = new AutoWah(); - aw.dispose(); - Test.wasDisposed(aw); - }); - - it("extends Tone.Effect", function(){ - var aw = new AutoWah(); - expect(aw).is.instanceof(Effect); - aw.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var wah = new AutoWah(); - Test.acceptsInputAndOutput(wah); - wah.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var aw; - Test.passesAudio(function(input, output){ - aw = new AutoWah(); - input.connect(aw); - aw.connect(output); - }, function(){ - aw.dispose(); - done(); - }); - }); - - it("handles getter/setter", function(){ - Test.onlineContext(); - var wah = new AutoWah(); - var values = { - "baseFrequency" : 200, - "octaves" : 4, - "sensitivity" : 1, - "Q" : 3, - "gain" : 0, - "follower" : { - "attack" : 0.4, - "release" : 0.6 - } - }; - wah.set(values); - expect(wah.get()).to.contain.keys(Object.keys(values)); - expect(wah.baseFrequency).to.equal(values.baseFrequency); - expect(wah.octaves).to.equal(values.octaves); - expect(wah.Q.value).to.be.closeTo(values.Q, 0.05); - expect(wah.gain.value).to.be.closeTo(values.gain, 0.05); - wah.dispose(); - }); - }); - - describe("Tone.BitCrusher", function(){ - - it("can be created and disposed", function(){ - var bc = new BitCrusher(); - bc.dispose(); - Test.wasDisposed(bc); - }); - - it("extends Tone.Effect", function(){ - var bc = new BitCrusher(); - expect(bc).is.instanceof(Effect); - bc.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var bc = new BitCrusher(); - Test.acceptsInputAndOutput(bc); - bc.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var bc; - Test.passesAudio(function(input, output){ - bc = new BitCrusher(); - input.connect(bc); - bc.connect(output); - }, function(){ - bc.dispose(); - done(); - }); - }); - - }); - - describe("Tone.FeedbackDelay", function(){ - - it("can be created and disposed", function(){ - var fd = new FeedbackDelay(); - fd.dispose(); - Test.wasDisposed(fd); - }); - - it("extends Tone.FeedbackEffect", function(){ - var fd = new FeedbackDelay(); - expect(fd).is.instanceof(FeedbackEffect); - fd.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var delay = new FeedbackDelay(); - Test.acceptsInputAndOutput(delay); - delay.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var delay; - Test.passesAudio(function(input, output){ - delay = new FeedbackDelay(0.01); - input.connect(delay); - delay.connect(output); - }, function(){ - delay.dispose(); - done(); - }); - }); - - it("can be created and disposed", function(){ - var fd = new FeedbackDelay(); - var values = { - "delayTime" : 0.5 - }; - fd.set(values); - expect(fd.get()).to.contain.keys(Object.keys(values)); - expect(fd.delayTime.value).to.be.closeTo(values.delayTime, 0.05); - fd.dispose(); - }); - }); - - describe("Tone.PingPongDelay", function(){ - - it("can be created and disposed", function(){ - var ppd = new PingPongDelay(); - ppd.dispose(); - Test.wasDisposed(ppd); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var delay = new PingPongDelay(); - Test.acceptsInputAndOutput(delay); - delay.dispose(); - }); - - it("extends Tone.StereoXFeedbackEffect", function(){ - var ppd = new PingPongDelay(); - expect(ppd).is.instanceof(StereoXFeedbackEffect); - ppd.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var delay; - Test.passesAudio(function(input, output){ - delay = new PingPongDelay(0.05); - input.connect(delay); - delay.connect(output); - }, function(){ - delay.dispose(); - done(); - }); - }); - - it("handles getter/setters", function(){ - var ppd = new PingPongDelay(); - var values = { - "delayTime" : 0.5 - }; - ppd.set(values); - expect(ppd.get()).to.contain.keys(Object.keys(values)); - expect(ppd.delayTime.value).to.be.closeTo(values.delayTime, 0.05); - ppd.dispose(); - }); - }); - - describe("Tone.Chorus", function(){ - - it("can be created and disposed", function(){ - var chorus = new Chorus(); - chorus.dispose(); - Test.wasDisposed(chorus); - }); - - it("extends Tone.Effect", function(){ - var chorus = new Chorus(); - expect(chorus).is.instanceof(Effect); - chorus.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var chorus = new Chorus(); - Test.acceptsInputAndOutput(chorus); - chorus.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var chorus; - Test.passesAudio(function(input, output){ - chorus = new Chorus(); - input.connect(chorus); - chorus.connect(output); - }, function(){ - chorus.dispose(); - done(); - }); - }); - - it("has getter/setters", function(){ - var chorus = new Chorus(); - var values = { - "frequency" : 1.5, - "delayTime" : 3.5, - "depth" : 0.7, - "feedback" : 0.1, - "type" : "sine" - }; - chorus.set(values); - expect(chorus.get()).to.contain.keys(Object.keys(values)); - expect(chorus.frequency.value).to.be.closeTo(values.frequency, 0.05); - expect(chorus.delayTime).to.equal(values.delayTime); - expect(chorus.depth).to.equal(values.depth); - expect(chorus.feedback.value).to.be.closeTo(values.feedback, 0.05); - expect(chorus.type).to.equal(values.type); - chorus.dispose(); - }); - }); - - describe("Tone.Phaser", function(){ - - it("can be created and disposed", function(){ - var phaser = new Phaser(); - phaser.dispose(); - Test.wasDisposed(phaser); - }); - - it("extends Tone.Effect", function(){ - var phaser = new Phaser(); - expect(phaser).is.instanceof(Effect); - phaser.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var phaser = new Phaser(); - Test.acceptsInputAndOutput(phaser); - phaser.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var phaser; - Test.passesAudio(function(input, output){ - phaser = new Phaser(); - input.connect(phaser); - phaser.connect(output); - }, function(){ - phaser.dispose(); - done(); - }); - }); - - it("can get/set values as an object", function(){ - var phaser = new Phaser(); - var values = { - "frequency" : 0.5, - "depth" : 10, - "baseFrequency" : 400, - }; - phaser.set(values); - expect(phaser.get()).to.contain.keys(Object.keys(values)); - expect(phaser.frequency.value).to.be.closeTo(values.frequency, 0.05); - expect(phaser.depth).to.be.closeTo(values.depth, 0.05); - phaser.dispose(); - }); - }); - - describe("Tone.Freeverb", function(){ - it("can be created and disposed", function(){ - var fv = new Freeverb(); - fv.dispose(); - Test.wasDisposed(fv); - }); - - it("extends Tone.Effect", function(){ - var fv = new Freeverb(); - expect(fv).is.instanceof(Effect); - fv.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var fv = new Freeverb(); - Test.acceptsInputAndOutput(fv); - fv.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var fv; - Test.passesAudio(function(input, output){ - fv = new Freeverb(); - input.connect(fv); - fv.connect(output); - }, function(){ - fv.dispose(); - done(); - }); - }); - - it("can get/set values as an object", function(){ - var fv = new Freeverb(); - var values = { - "roomSize" : 0.6, - "dampening" : 0.4 - }; - fv.set(values); - expect(fv.get()).to.contain.keys(Object.keys(values)); - expect(fv.roomSize.value).to.be.closeTo(values.roomSize, 0.05); - expect(fv.dampening.value).to.be.closeTo(values.dampening, 0.05); - fv.dispose(); - }); - }); - - describe("Tone.JCReverb", function(){ - it("can be created and disposed", function(){ - var rev = new JCReverb(); - rev.dispose(); - Test.wasDisposed(rev); - }); - - it("extends Tone.Effect", function(){ - var rev = new JCReverb(); - expect(rev).is.instanceof(Effect); - rev.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var rev = new JCReverb(); - Test.acceptsInputAndOutput(rev); - rev.dispose(); - }); - - it("can get/set values as an object", function(){ - var rev = new JCReverb(); - var values = { - "roomSize" : 0.6, - }; - rev.set(values); - expect(rev.get()).to.contain.keys(Object.keys(values)); - expect(rev.roomSize.value).to.be.closeTo(values.roomSize, 0.05); - rev.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var rev; - Test.passesAudio(function(input, output){ - rev = new JCReverb(); - input.connect(rev); - rev.connect(output); - }, function(){ - rev.dispose(); - done(); - }); - }); - }); - - describe("Tone.Distortion", function(){ - it("can be created and disposed", function(){ - var dist = new Distortion(); - dist.dispose(); - Test.wasDisposed(dist); - }); - - it("extends Tone.Effect", function(){ - var dist = new Distortion(); - expect(dist).is.instanceof(Effect); - dist.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var dist = new Distortion(); - Test.acceptsInputAndOutput(dist); - dist.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var dist; - Test.passesAudio(function(input, output){ - dist = new Distortion(); - input.connect(dist); - dist.connect(output); - }, function(){ - dist.dispose(); - done(); - }); - }); - - it("has getter/setters", function(){ - var dist = new Distortion(); - var values = { - "distortion" : 0.5, - "oversample" : "2x" - }; - dist.set(values); - expect(dist.get()).to.contain.keys(Object.keys(values)); - expect(dist.oversample).to.equal(values.oversample); - expect(dist.distortion).to.equal(values.distortion); - dist.dispose(); - }); - }); - - describe("Tone.Chebyshev", function(){ - it("can be created and disposed", function(){ - var cheb = new Chebyshev(); - cheb.dispose(); - Test.wasDisposed(cheb); - }); - - it("extends Tone.Effect", function(){ - var cheb = new Chebyshev(); - expect(cheb).is.instanceof(Effect); - cheb.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var cheb = new Chebyshev(); - Test.acceptsInputAndOutput(cheb); - cheb.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var cheb; - Test.passesAudio(function(input, output){ - cheb = new Chebyshev(1); - input.connect(cheb); - cheb.connect(output); - }, function(){ - cheb.dispose(); - done(); - }); - }); - - it("handles getter/setters", function(){ - var cheb = new Chebyshev(); - var values = { - "order" : 2, - "oversample" : "2x" - }; - cheb.set(values); - expect(cheb.get()).to.contain.keys(Object.keys(values)); - expect(cheb.order).to.equal(values.order); - expect(cheb.oversample).to.equal(values.oversample); - cheb.dispose(); - }); - }); - - describe("Tone.Convolver", function(){ - it("can be created and disposed", function(){ - var conv = new Convolver(); - conv.dispose(); - Test.wasDisposed(conv); - }); - - it("extends Tone.Effect", function(){ - var conv = new Convolver(); - expect(conv).is.instanceof(Effect); - conv.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var conv = new Convolver(); - Test.acceptsInputAndOutput(conv); - conv.dispose(); - }); - - /*it("passes the incoming signal through to the output", function(done){ - var conv; - Test.passesAudio(function(input, output){ - conv = new Convolver(1); - input.connect(conv); - conv.connect(output); - }, function(){ - conv.dispose(); - done(); - }); - });*/ - }); - - describe("Tone.MidSide", function(){ - it("can be created and disposed", function(){ - var midside = new MidSide(); - midside.dispose(); - Test.wasDisposed(midside); - }); - - it("extends Tone.StereoEffect", function(){ - var midside = new MidSide(); - expect(midside).is.instanceof(Effect); - midside.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var midside = new MidSide(); - Test.acceptsInputAndOutput(midside); - midside.dispose(); - }); - }); - - describe("Tone.StereoWidener", function(){ - it("can be created and disposed", function(){ - var widen = new StereoWidener(); - widen.dispose(); - Test.wasDisposed(widen); - }); - - it("extends Tone.MidSide", function(){ - var widen = new StereoWidener(); - expect(widen).is.instanceof(MidSide); - widen.dispose(); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var widen = new StereoWidener(); - Test.acceptsInputAndOutput(widen); - widen.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var widen; - Test.passesAudio(function(input, output){ - widen = new StereoWidener(1); - input.connect(widen); - widen.connect(output); - }, function(){ - widen.dispose(); - done(); - }); - }); - - it("handles getter/setters", function(){ - var widen = new StereoWidener(); - var values = { - "width" : 0.75, - }; - widen.set(values); - expect(widen.get()).to.contain.keys(Object.keys(values)); - expect(widen.width.value).to.equal(values.width); - widen.dispose(); - }); - }); - - describe("Tone.AutoFilter", function(){ - - it("can be created and disposed", function(){ - var af = new AutoFilter(); - af.dispose(); - Test.wasDisposed(af); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var af = new AutoFilter(); - Test.acceptsInputAndOutput(af); - af.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var af; - Test.passesAudio(function(input, output){ - af = new AutoFilter(); - input.connect(af); - af.connect(output); - }, function(){ - af.dispose(); - done(); - }); - }); - - it("extends Tone.Effect", function(){ - var af = new AutoFilter(); - expect(af).is.instanceof(Effect); - af.dispose(); - }); - - it("can be set with options object", function(){ - var af = new AutoFilter(); - af.set({ - "wet" : 0.22, - "frequency" : 2, - "depth" : 0.6, - }); - expect(af.wet.value).is.closeTo(0.22, 0.01); - expect(af.frequency.value).is.closeTo(2, 0.01); - expect(af.depth.value).is.closeTo(0.6, 0.01); - af.dispose(); - }); - }); - - describe("Tone.Tremolo", function(){ - - it("can be created and disposed", function(){ - var trem = new Tremolo(); - trem.dispose(); - Test.wasDisposed(trem); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var trem = new Tremolo(); - Test.acceptsInputAndOutput(trem); - trem.dispose(); - }); - - it("passes the incoming signal through to the output", function(done){ - var trem; - Test.passesAudio(function(input, output){ - trem = new Tremolo().start(); - input.connect(trem); - trem.connect(output); - }, function(){ - trem.dispose(); - done(); - }); - }); - - it("extends Tone.Effect", function(){ - var trem = new Tremolo(); - expect(trem).is.instanceof(Effect); - trem.dispose(); - }); - - it("can be set with options object", function(){ - var trem = new Tremolo(); - trem.set({ - "wet" : 0.22, - "frequency" : 12, - "depth" : 0.6, - }); - expect(trem.wet.value).is.closeTo(0.22, 0.01); - expect(trem.frequency.value).is.closeTo(12, 0.01); - expect(trem.depth.value).is.closeTo(0.6, 0.01); - trem.dispose(); - }); - }); -}); diff --git a/test/tests/EffectPresets.js b/test/tests/EffectPresets.js deleted file mode 100644 index 467d10bf5..000000000 --- a/test/tests/EffectPresets.js +++ /dev/null @@ -1,50 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "tests/Common", "Tone/effect/AutoWah", "Tone/effect/preset/AutoWah.preset", - "Tone/effect/Chorus", "Tone/effect/preset/Chorus.preset", "Tone/effect/Freeverb", "Tone/effect/preset/Freeverb.preset", - "Tone/effect/Phaser", "Tone/effect/preset/Phaser.preset"], -function(Tone, chai, Test, AutoWah, AutoWahPresets, Chorus, ChorusPresets, Freeverb, FreeverbPresets, - Phaser, PhaserPresets){ - - var expect = chai.expect; - - describe("Tone.Autowah Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var aw = new AutoWah(); - Test.validatePresets(aw); - aw.dispose(); - }); - }); - - describe("Tone.Chorus Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var chorus = new Chorus(); - Test.validatePresets(chorus); - chorus.dispose(); - }); - }); - - describe("Tone.Freeverb Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var fv = new Freeverb(); - Test.validatePresets(fv); - fv.dispose(); - }); - }); - - describe("Tone.Phaser Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var phase = new Phaser(); - Test.validatePresets(phase); - phase.dispose(); - }); - }); -}); diff --git a/test/tests/Expr.js b/test/tests/Expr.js deleted file mode 100644 index 83da80007..000000000 --- a/test/tests/Expr.js +++ /dev/null @@ -1,654 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "Tone/signal/Signal", "Tone/signal/Expr", "tests/Common"], -function(core, chai, Signal, Expr, Test){ - - var expect = chai.expect; - - describe("Tone.Expr - Basic", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var exp = new Expr("2"); - exp.dispose(); - // Test.wasDisposed(exp); - }); - - it("can create inputs", function(){ - var exp = new Expr("$0 + $1"); - Test.acceptsInput(exp, 0); - Test.acceptsInput(exp, 1); - exp.dispose(); - }); - - it("has an output", function(){ - var exp = new Expr("0 + 0"); - Test.acceptsOutput(exp); - exp.dispose(); - }); - - it("has output", function(done){ - var exp; - Test.outputsAudio(function(out){ - exp = new Expr("1.1"); - exp.connect(out); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("passes input", function(done){ - var exp; - Test.passesAudio(function(input, output){ - exp = new Expr("$0"); - input.connect(exp); - exp.connect(output); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("can do string replacements", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("% + %", 0.2, 0.8); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(1, 0.001); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("can do string replacements with strings", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("%", "1 + 2"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(3, 0.001); - }, function(){ - exp.dispose(); - done(); - }); - }); - }); - - describe("Tone.Expr - Signal Math", function(){ - this.timeout(maxTimeout); - - it("does signal addition", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1 + 3"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(4); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("does signal multiplication", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1.5 * 6"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(9); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("does signal subtraction", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("8 - 16"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(-8); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles precendence", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("8 + 16 * 4 * (2 - 1)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(72, 0.01); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles complex precendence", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("2 * 2 + 1 > 0 == 0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("tolerates inconsistent spacing", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("2 * 3-2 *4 "); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(-2); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles parens", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("(8 + 16) * (4 - 1)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(72); - }, function(){ - exp.dispose(); - done(); - }); - }); - }); - - describe("Tone.Expr - Signal Logic", function(){ - this.timeout(maxTimeout); - - it("correctly outputs 1 for 1 && 1", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1 && 1"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 0 for 0 && 1", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("0 && 1"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 0 for 0 || 0", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("0||0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 0 || 1", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("0 || 1"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 1 || 0", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1 || 0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 1 > 0", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1 > 0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 100 > 99", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1 > 0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 0 for -10 > -9", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("-10 > -9"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 1.001 < 1.002", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("1.001 < 1.002"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 0 for 11 < 1.002", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("11 < 1.002"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 for 11.001 == 11.001", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("11.001 == 11.001"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 0 for 11.002 == 11.001", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("11.002 == 11.001"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - }); - - describe("Tone.Expr - Unary Operators", function(){ - this.timeout(maxTimeout); - - it("correctly outputs negative", function(done){ - var exp, sig; - Test.offlineTest(0.1, function(dest){ - sig = new Signal(1); - exp = new Expr("-$0"); - sig.connect(exp); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(-1); - }, function(){ - exp.dispose(); - sig.dispose(); - done(); - }); - }); - - it("correctly handles NOT (!)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("!0"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - }); - - describe("Tone.Expr - Functions", function(){ - this.timeout(maxTimeout); - - it("handles if(false)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("if(0, 2, 11)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(11); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles if(true)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("if(1, 2, 11)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(2); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles abs(-10)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("abs(-10)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles abs(11)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("abs(11)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(11); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles min(10, 11)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("min(10, 11)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles min(7, -100)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("min(7, -100)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(-100); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles max(10, 11)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("max(10, 11)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(11); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles max(7, -100)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("max(7, -100)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(7); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles mod(0.1, 0.9)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("mod(0.1, 0.9)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.1, 0.0001); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("handles mod(0.5, 0.25)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("mod(0.6, 0.25)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.1, 0.0001); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("outputs 1 for gt0(9)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("gt0(9)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("outputs 0 for gt0(-9)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("gt0(-9)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("outputs 1 for eq0(0)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("eq0(0)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("outputs 0 for eq0(-10)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("eq0(-10)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("computes pow(0.2, 3)", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("pow(0.2, 3)"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.008, 0.001); - }, function(){ - exp.dispose(); - done(); - }); - }); - - }); - - describe("Tone.Expr - Nested Operators", function(){ - this.timeout(maxTimeout); - - it("correctly outputs 1 in if(2 * 4 > 8, max(1, 2), min(1, 2))", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("if(2 * 4 > 8, max(1, 2), min(1, 2))"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 1 in if(2 * 4 > 8, max(1, 2), min(1, 2))", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("if(2 * 4 > 8, max(1, 2), min(1, 2))"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 29 for abs(if(0 < -2, -10, -29))", function(done){ - var exp; - Test.offlineTest(0.1, function(dest){ - exp = new Expr("abs(if(0 < -2, -10, -29))"); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(29); - }, function(){ - exp.dispose(); - done(); - }); - }); - - }); - - describe("Tone.Expr - Signal Inputs", function(){ - this.timeout(maxTimeout); - - it("correctly outputs 1 in if($0 * $1 > 8, $1, $0) with inputs 1 and 7", function(done){ - var exp, sig0, sig1; - Test.offlineTest(0.1, function(dest){ - sig0 = new Signal(1); - sig1 = new Signal(7); - exp = new Expr("if($0 * $1 > 8, $1, $0)"); - sig0.connect(exp, 0, 0); - sig1.connect(exp, 0, 1); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - sig0.dispose(); - sig1.dispose(); - exp.dispose(); - done(); - }); - }); - - it("correctly outputs 3 in max($0 * $1, $0 + $1) with inputs 1 and 2", function(done){ - var exp, sig0, sig1; - Test.offlineTest(0.1, function(dest){ - sig0 = new Signal(1); - sig1 = new Signal(2); - exp = new Expr("max($0 * $1, $0 + $1)"); - sig0.connect(exp, 0, 0); - sig1.connect(exp, 0, 1); - exp.connect(dest); - }, function(sample){ - expect(sample).to.equal(3); - }, function(){ - sig0.dispose(); - sig1.dispose(); - exp.dispose(); - done(); - }); - }); - }); - -}); \ No newline at end of file diff --git a/test/tests/InstrumentPresets.js b/test/tests/InstrumentPresets.js deleted file mode 100644 index 91d64f113..000000000 --- a/test/tests/InstrumentPresets.js +++ /dev/null @@ -1,39 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "tests/Common", "Tone/instrument/MonoSynth", "Tone/instrument/preset/MonoSynth.preset", - "Tone/instrument/DuoSynth", "Tone/instrument/preset/DuoSynth.preset", - "Tone/instrument/FMSynth", "Tone/instrument/preset/FMSynth.preset"], -function(Tone, chai, Test, MonoSynth, MonoSynthPresets, DuoSynth, DuoSynthPresets, FMSynth, FMSynthPresets){ - - var expect = chai.expect; - - describe("Tone.MonoSynth Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var mono = new MonoSynth(); - Test.validatePresets(mono); - mono.dispose(); - }); - }); - - describe("Tone.DuoSynth Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var synth = new DuoSynth(); - Test.validatePresets(synth); - synth.dispose(); - }); - }); - - describe("Tone.FMSynth Presets", function(){ - this.timeout(maxTimeout); - - it ("has valid presets", function(){ - var synth = new FMSynth(); - Test.validatePresets(synth); - synth.dispose(); - }); - }); -}); diff --git a/test/tests/Instruments.js b/test/tests/Instruments.js deleted file mode 100644 index 58cd6e099..000000000 --- a/test/tests/Instruments.js +++ /dev/null @@ -1,568 +0,0 @@ -/* global it, describe */ - -define(["tests/Core", "chai", "Tone/instrument/DuoSynth", "Tone/instrument/MonoSynth", "Tone/instrument/FMSynth", - "Tone/instrument/PolySynth", "Tone/instrument/Sampler", - "tests/Common", "Tone/instrument/Instrument", "Tone/instrument/PluckSynth", "Tone/instrument/AMSynth", - "Tone/instrument/NoiseSynth", "Tone/core/Buffer", "Tone/instrument/DrumSynth", "Tone/instrument/SimpleSynth", - "Tone/instrument/SimpleAM", "Tone/instrument/SimpleFM"], -function(Tone, chai, DuoSynth, MonoSynth, FMSynth, PolySynth, Sampler, Test, Instrument, - PluckSynth, AMSynth, NoiseSynth, Buffer, DrumSynth, SimpleSynth, SimpleAM, SimpleFM){ - - var expect = chai.expect; - - Test.onlineContext(); - - function extendsInstrument(InstrumentFactory){ - var inst = new InstrumentFactory(); - expect(inst).to.be.instanceOf(Instrument); - inst.dispose(); - } - - describe("Tone.MonoSynth", function(){ - it("can be created and disposed", function(){ - var ms = new MonoSynth(); - ms.dispose(); - Test.wasDisposed(ms); - }); - - it("extends Instrument", function(){ - extendsInstrument(MonoSynth); - }); - - it("handles output connections", function(){ - var ms = new MonoSynth(); - Test.acceptsOutput(ms); - ms.dispose(); - }); - - it("outputs a sound", function(done){ - var ms; - Test.outputsAudio(function(dest){ - ms = new MonoSynth(); - ms.connect(dest); - ms.triggerAttack("C4"); - }, function(){ - ms.dispose(); - done(); - }); - }); - - it("can be get/set", function(){ - var ms = new MonoSynth(); - var values = { - "oscillator" : { - "type" : "triangle" - }, - "filter" : { - "Q" : 8, - }, - }; - ms.set(values); - expect(ms.get()).to.contain.keys(Object.keys(values)); - expect(ms.oscillator.type).to.equal(values.oscillator.type); - expect(ms.filter.Q.value).to.equal(values.filter.Q); - ms.dispose(); - }); - }); - - describe("Tone.DuoSynth", function(){ - it("can be created and disposed", function(){ - var ds = new DuoSynth(); - ds.dispose(); - Test.wasDisposed(ds); - }); - - it("extends Instrument", function(){ - extendsInstrument(DuoSynth); - }); - - it("handles output connections", function(){ - var ds = new DuoSynth(); - Test.acceptsOutput(ds); - ds.dispose(); - }); - - it("outputs a sound", function(done){ - var ds; - Test.outputsAudio(function(dest){ - ds = new DuoSynth(); - ds.connect(dest); - ds.triggerAttack("C4"); - }, function(){ - ds.dispose(); - done(); - }); - }); - it("can be get/set", function(){ - var ds = new DuoSynth(); - var values = { - "voice0" : { - "oscillator" : { - "type" : "triangle" - }, - }, - "voice1" : { - "oscillator" : { - "type" : "sine" - }, - } - }; - ds.set(values); - expect(ds.get()).to.contain.keys(Object.keys(values)); - expect(ds.voice0.oscillator.type).to.equal(values.voice0.oscillator.type); - expect(ds.voice1.oscillator.type).to.equal(values.voice1.oscillator.type); - ds.dispose(); - }); - }); - - describe("Tone.FMSynth", function(){ - it("can be created and disposed", function(){ - var fms = new FMSynth(); - fms.dispose(); - Test.wasDisposed(fms); - }); - - it("extends Instrument", function(){ - extendsInstrument(FMSynth); - }); - - it("handles output connections", function(){ - var fms = new FMSynth(); - Test.acceptsOutput(fms); - fms.dispose(); - }); - - it("outputs a sound", function(done){ - var fms; - Test.outputsAudio(function(dest){ - fms = new FMSynth(); - fms.connect(dest); - fms.triggerAttack("C4"); - }, function(){ - fms.dispose(); - done(); - }); - }); - - it("can be get/set", function(){ - var fms = new FMSynth(); - var values = { - "carrier" : { - "oscillator" : { - "type" : "triangle" - }, - }, - "modulator" : { - "oscillator" : { - "type" : "sine" - }, - } - }; - fms.set(values); - expect(fms.get()).to.contain.keys(Object.keys(values)); - expect(fms.carrier.oscillator.type).to.equal(values.carrier.oscillator.type); - expect(fms.modulator.oscillator.type).to.equal(values.modulator.oscillator.type); - fms.dispose(); - }); - }); - - describe("Tone.PolySynth", function(){ - it("can be created and disposed", function(){ - var ps = new PolySynth(); - ps.dispose(); - Test.wasDisposed(ps); - }); - - it("extends Instrument", function(){ - extendsInstrument(PolySynth); - }); - - it("handles output connections", function(){ - var psynth = new PolySynth(); - Test.acceptsOutput(psynth); - psynth.dispose(); - }); - - it("outputs a sound", function(done){ - var psynth; - Test.outputsAudio(function(dest){ - psynth = new PolySynth(); - psynth.connect(dest); - psynth.triggerAttack("C4"); - }, function(){ - psynth.dispose(); - done(); - }); - }); - - it("accepts a chord", function(done){ - var psynth; - Test.outputsAudio(function(dest){ - psynth = new PolySynth(4, DuoSynth); - psynth.connect(dest); - psynth.triggerAttackRelease(["C4", "E4", "G4"], "8n"); - }, function(){ - psynth.dispose(); - done(); - }); - }); - }); - - describe("Tone.Sampler", function(){ - it("can be created and disposed", function(){ - var samp = new Sampler(); - samp.dispose(); - Test.wasDisposed(samp); - }); - - it("extends Instrument", function(){ - extendsInstrument(Sampler); - }); - - it("handles output connections", function(){ - var samp = new Sampler(); - Test.acceptsOutput(samp); - samp.dispose(); - }); - - it("flattens a nested object of samples", function(done){ - var samp = new Sampler({ - "A" : { - "1" : "./testAudio/kick.mp3" - }, - "B" : "./testAudio/hh.mp3" - }); - Buffer.onload = function(){ - samp.sample = "A.1"; - samp.dispose(); - done(); - }; - }); - }); - - describe("Tone.PluckSynth", function(){ - it("can be created and disposed", function(){ - var pluck = new PluckSynth(); - pluck.dispose(); - Test.wasDisposed(pluck); - }); - - it("extends Instrument", function(){ - extendsInstrument(PluckSynth); - }); - - it("handles output connections", function(){ - var psynth = new PluckSynth(); - Test.acceptsOutput(psynth); - psynth.dispose(); - }); - - it("outputs a sound", function(done){ - var psynth; - Test.outputsAudio(function(dest){ - psynth = new PluckSynth(); - psynth.connect(dest); - psynth.triggerAttack("C4"); - }, function(){ - psynth.dispose(); - done(); - }); - }); - - it("handles getter/setter", function(){ - var psynth = new PluckSynth(); - var values = { - "attackNoise" : 3, - "dampening" : 5000, - "resonance" : 0.3 - }; - psynth.set(values); - expect(psynth.get()).to.contain.keys(Object.keys(values)); - expect(psynth.attackNoise).to.equal(values.attackNoise); - expect(psynth.dampening.value).to.equal(values.dampening); - expect(psynth.resonance.value).to.be.closeTo(values.resonance, 0.05); - psynth.dispose(); - }); - }); - - describe("Tone.AMSynth", function(){ - it("can be created and disposed", function(){ - var ams = new AMSynth(); - ams.dispose(); - Test.wasDisposed(ams); - }); - - it("extends Instrument", function(){ - extendsInstrument(AMSynth); - }); - - it("handles output connections", function(){ - var ams = new AMSynth(); - Test.acceptsOutput(ams); - ams.dispose(); - }); - - it("outputs a sound", function(done){ - var ams; - Test.outputsAudio(function(dest){ - ams = new AMSynth(); - ams.connect(dest); - ams.triggerAttack("C4"); - }, function(){ - ams.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - var ams = new AMSynth(); - var values = { - "harmonicity" : 3, - "carrier" : { - "filterEnvelope" : { - "min" : 20, - } - }, - "modulator" : { - "filterEnvelope" : { - "min" : 400, - } - } - }; - ams.set(values); - expect(ams.get()).to.contain.keys(Object.keys(values)); - expect(ams.harmonicity.value).to.be.closeTo(values.harmonicity, 0.05); - expect(ams.carrier.filterEnvelope.min).to.be.closeTo(values.carrier.filterEnvelope.min, 0.05); - expect(ams.modulator.filterEnvelope.min).to.be.closeTo(values.modulator.filterEnvelope.min, 0.05); - ams.dispose(); - }); - }); - - describe("Tone.NoiseSynth", function(){ - it("can be created and disposed", function(){ - var noiseSynth = new NoiseSynth(); - noiseSynth.dispose(); - Test.wasDisposed(noiseSynth); - }); - - it("extends Instrument", function(){ - extendsInstrument(NoiseSynth); - }); - - it("handles output connections", function(){ - var noiseSynth = new NoiseSynth(); - Test.acceptsOutput(noiseSynth); - noiseSynth.dispose(); - }); - - it("outputs a sound", function(done){ - var noiseSynth; - Test.outputsAudio(function(dest){ - noiseSynth = new NoiseSynth(); - noiseSynth.connect(dest); - noiseSynth.triggerAttack(); - }, function(){ - noiseSynth.dispose(); - done(); - }); - }); - }); - - describe("Tone.DrumSynth", function(){ - it("can be created and disposed", function(){ - var drumSynth = new DrumSynth(); - drumSynth.dispose(); - Test.wasDisposed(drumSynth); - }); - - it("extends Instrument", function(){ - extendsInstrument(DrumSynth); - }); - - it("handles output connections", function(){ - var drumSynth = new DrumSynth(); - Test.acceptsOutput(drumSynth); - drumSynth.dispose(); - }); - - it("outputs a sound", function(done){ - var drumSynth; - Test.outputsAudio(function(dest){ - drumSynth = new DrumSynth(); - drumSynth.connect(dest); - drumSynth.triggerAttack("C2"); - }, function(){ - drumSynth.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - var drumSynth = new DrumSynth(); - var values = { - "pitchDecay" : 0.06, - "octaves" : 9, - "oscillator" : { - "type" : "triangle", - }, - "envelope" : { - "attack" : 0.01, - } - }; - drumSynth.set(values); - expect(drumSynth.get()).to.contain.keys(Object.keys(values)); - expect(drumSynth.pitchDecay).to.be.closeTo(values.pitchDecay, 0.001); - expect(drumSynth.octaves).to.be.closeTo(values.octaves, 0.001); - expect(drumSynth.envelope.attack).to.be.closeTo(values.envelope.attack, 0.001); - drumSynth.dispose(); - }); - }); - - describe("Tone.SimpleSynth", function(){ - it("can be created and disposed", function(){ - var simpleSynth = new SimpleSynth(); - simpleSynth.dispose(); - Test.wasDisposed(simpleSynth); - }); - - it("extends Instrument", function(){ - extendsInstrument(SimpleSynth); - }); - - it("handles output connections", function(){ - var simpleSynth = new SimpleSynth(); - Test.acceptsOutput(simpleSynth); - simpleSynth.dispose(); - }); - - it("outputs a sound", function(done){ - var simpleSynth; - Test.outputsAudio(function(dest){ - simpleSynth = new SimpleSynth(); - simpleSynth.connect(dest); - simpleSynth.triggerAttack("C2"); - }, function(){ - simpleSynth.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - var simpleSynth = new SimpleSynth(); - var values = { - "oscillator" : { - "type" : "triangle", - }, - "envelope" : { - "attack" : 0.01, - } - }; - simpleSynth.set(values); - expect(simpleSynth.get()).to.contain.keys(Object.keys(values)); - expect(simpleSynth.envelope.attack).to.be.closeTo(values.envelope.attack, 0.001); - simpleSynth.dispose(); - }); - }); - - describe("Tone.SimpleAM", function(){ - it("can be created and disposed", function(){ - var simpleAM = new SimpleAM(); - simpleAM.dispose(); - Test.wasDisposed(simpleAM); - }); - - it("extends Instrument", function(){ - extendsInstrument(SimpleAM); - }); - - it("handles output connections", function(){ - var simpleAM = new SimpleAM(); - Test.acceptsOutput(simpleAM); - simpleAM.dispose(); - }); - - it("outputs a sound", function(done){ - var simpleAM; - Test.outputsAudio(function(dest){ - simpleAM = new SimpleAM(); - simpleAM.connect(dest); - simpleAM.triggerAttack("C2"); - }, function(){ - simpleAM.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - var simpleAM = new SimpleAM(); - var values = { - "carrier" : { - "envelope" : { - "attack" : 0.01, - } - }, - "modulator" : { - "oscillator" : { - "type" : "triangle", - }, - } - }; - simpleAM.set(values); - expect(simpleAM.get()).to.contain.keys(Object.keys(values)); - expect(simpleAM.carrier.envelope.attack).to.be.closeTo(values.carrier.envelope.attack, 0.001); - expect(simpleAM.modulator.oscillator.type).to.equal(values.modulator.oscillator.type); - simpleAM.dispose(); - }); - }); - - describe("Tone.SimpleFM", function(){ - it("can be created and disposed", function(){ - var simpleFM = new SimpleFM(); - simpleFM.dispose(); - Test.wasDisposed(simpleFM); - }); - - it("extends Instrument", function(){ - extendsInstrument(SimpleFM); - }); - - it("handles output connections", function(){ - var simpleFM = new SimpleFM(); - Test.acceptsOutput(simpleFM); - simpleFM.dispose(); - }); - - it("outputs a sound", function(done){ - var simpleFM; - Test.outputsAudio(function(dest){ - simpleFM = new SimpleFM(); - simpleFM.connect(dest); - simpleFM.triggerAttack("C2"); - }, function(){ - simpleFM.dispose(); - done(); - }); - }); - - it("handles getters/setters", function(){ - var simpleFM = new SimpleFM(); - var values = { - "carrier" : { - "envelope" : { - "attack" : 0.01, - } - }, - "modulator" : { - "oscillator" : { - "type" : "triangle", - }, - } - }; - simpleFM.set(values); - expect(simpleFM.get()).to.contain.keys(Object.keys(values)); - expect(simpleFM.carrier.envelope.attack).to.be.closeTo(values.carrier.envelope.attack, 0.001); - expect(simpleFM.modulator.oscillator.type).to.equal(values.modulator.oscillator.type); - simpleFM.dispose(); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/Signal.js b/test/tests/Signal.js deleted file mode 100644 index 45e851b90..000000000 --- a/test/tests/Signal.js +++ /dev/null @@ -1,551 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "Tone/signal/Signal", "Tone/source/Oscillator", - "Tone/signal/Switch", "Tone/signal/Route", "Tone/signal/Select", "tests/Common", - "Tone/signal/NOT", "Tone/signal/AND", "Tone/signal/OR", "Tone/signal/IfThenElse", "Tone/signal/WaveShaper"], -function(core, chai, Signal, Oscillator, Switch, Route, Select, Test, NOT, AND, OR, IfThenElse, WaveShaper){ - - var expect = chai.expect; - - describe("Tone.Signal", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var s = new Signal(); - s.dispose(); - Test.wasDisposed(s); - }); - - it("can start with a value initially", function(){ - var signal = new Signal(0); - expect(signal.value).to.equal(0); - signal.dispose(); - }); - - it("can set a value", function(){ - var signal = new Signal(0); - signal.value = 10; - expect(signal.value).to.equal(10); - signal.dispose(); - }); - - it("can set a value in the future with sample accurate timing", function(done){ - var sig; - Test.offlineTest(0.1, function(dest){ - sig = new Signal(10); - sig.setValueAtTime(100, "+0.1"); - expect(sig.value).to.equal(10); - sig.connect(dest); - }, function(sample, time){ - if (sample === 100){ - expect(time).is.closeTo(0.1, 0.001); - } - }, function(){ - sig.dispose(); - done(); - }); - }); - - it("can ramp from the current value", function(done){ - var sig; - Test.offlineTest(0.1, function(dest){ - sig = new Signal(0); - sig.value = -10; - sig.linearRampToValueNow(1, "+0.1"); - expect(sig.value).to.equal(-10); - sig.connect(dest); - }, function(sample, time){ - if (sample === 1){ - expect(time).is.closeTo(0.1, 0.001); - } - }, function(){ - sig.dispose(); - done(); - }); - }); - it("can ramp exponentially from the current value now", function(){ - var sig = new Signal(1); - sig.exponentialRampToValueNow(10, 0.5); - sig.dispose(); - }); - - it("can ramp exponentially from the current value in the future", function(){ - var sig = new Signal(1); - sig.exponentialRampToValueAtTime(10, 0.5); - sig.dispose(); - }); - }); - - describe("Tone.Switch", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var sw = new Switch(); - sw.dispose(); - Test.wasDisposed(sw); - }); - - it("can stop a signal from passing through", function(done){ - var signal, gate; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - gate = new Switch(); - signal.connect(gate); - gate.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - gate.dispose(); - done(); - }); - }); - - it("can allow a signal to pass through", function(done){ - var signal, gate; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - gate = new Switch(); - signal.connect(gate); - gate.open(); - gate.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal.dispose(); - gate.dispose(); - done(); - }); - }); - }); - - describe("Tone.Route", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var r = new Route(); - r.dispose(); - Test.wasDisposed(r); - }); - - it("can route a signal to first output", function(done){ - var signal, route; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - route = new Route(); - signal.connect(route); - route.select(0); - route.connect(dest, 0, 0); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal.dispose(); - route.dispose(); - done(); - }); - }); - - it("can route a signal to first output and not the second one", function(done){ - var signal, route; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - route = new Route(); - signal.connect(route); - route.select(0); - route.connect(dest, 1, 0); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - route.dispose(); - done(); - }); - }); - - it("can route a signal to second output", function(done){ - var signal, route; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(20); - route = new Route(); - signal.connect(route); - route.select(1); - route.connect(dest, 1, 0); - }, function(sample){ - expect(sample).to.equal(20); - }, function(){ - signal.dispose(); - route.dispose(); - done(); - }); - }); - - it("can route a signal to second output and not the first one", function(done){ - var signal, route; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(20); - route = new Route(); - signal.connect(route); - route.select(1); - route.connect(dest, 0, 0); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - route.dispose(); - done(); - }); - }); - }); - - describe("Tone.Select", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var s = new Select(); - s.dispose(); - Test.wasDisposed(s); - }); - - it("can select the first signal", function(done){ - var signal0, signal1, select; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(10); - signal1 = new Signal(20); - select = new Select(); - signal0.connect(select, 0, 0); - signal1.connect(select, 0, 1); - select.select(0); - select.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal0.dispose(); - signal1.dispose(); - select.dispose(); - done(); - }); - }); - - it("can select the second signal", function(done){ - var signal0, signal1, select; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(11); - signal1 = new Signal(21); - select = new Select(); - signal0.connect(select, 0, 0); - signal1.connect(select, 0, 1); - select.select(1); - select.connect(dest); - }, function(sample){ - expect(sample).to.equal(21); - }, function(){ - signal0.dispose(); - signal1.dispose(); - select.dispose(); - done(); - }); - }); - }); - - describe("Tone.IfThenElse", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var ite = new IfThenElse(); - ite.dispose(); - Test.wasDisposed(ite); - }); - - it("selects the second input (then) when input 0 (if) is 1", function(done){ - var signal0, signal1, signal2, ite; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(1); - signal1 = new Signal(10); - signal2 = new Signal(20); - ite = new IfThenElse(); - signal0.connect(ite, 0, 0); - signal1.connect(ite, 0, 1); - signal2.connect(ite, 0, 2); - ite.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal0.dispose(); - signal1.dispose(); - signal2.dispose(); - ite.dispose(); - done(); - }); - }); - - it("selects the third input (else) when input 0 (if) is not 1", function(done){ - var signal0, signal1, signal2, ite; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(0); - signal1 = new Signal(11); - signal2 = new Signal(21); - ite = new IfThenElse(); - signal0.connect(ite.if); - signal1.connect(ite.then); - signal2.connect(ite.else); - ite.connect(dest); - }, function(sample){ - expect(sample).to.equal(21); - }, function(){ - signal0.dispose(); - signal1.dispose(); - signal2.dispose(); - ite.dispose(); - done(); - }); - }); - }); - - describe("Tone.NOT", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var n = new NOT(); - n.dispose(); - Test.wasDisposed(n); - }); - - it("outputs 0 when the input is 1", function(done){ - var signal, not; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1); - not = new NOT(); - signal.connect(not); - not.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - not.dispose(); - done(); - }); - }); - - it("outputs 1 when the input is 0", function(done){ - var signal, not; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0); - not = new NOT(); - signal.connect(not); - not.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - not.dispose(); - done(); - }); - }); - - it("outputs 0 when the input is not 0", function(done){ - var signal, not; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.3); - not = new NOT(); - signal.connect(not); - not.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - not.dispose(); - done(); - }); - }); - }); - - describe("Tone.AND", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var a = new AND(); - a.dispose(); - Test.wasDisposed(a); - }); - - it("outputs 1 when both inputs are 1", function(done){ - var signal0, signal1, and; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(1); - signal1 = new Signal(1); - and = new AND(2); - signal0.connect(and); - signal1.connect(and); - and.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal0.dispose(); - signal1.dispose(); - and.dispose(); - done(); - }); - }); - - it("outputs 0 when only one input is 1", function(done){ - var signal0, signal1, and; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(1); - signal1 = new Signal(0); - and = new AND(2); - signal0.connect(and); - signal1.connect(and); - and.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal0.dispose(); - signal1.dispose(); - and.dispose(); - done(); - }); - }); - - it("outputs 0 when only the inputs are 0", function(done){ - var signal0, signal1, and; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(0); - signal1 = new Signal(0); - and = new AND(2); - signal0.connect(and); - signal1.connect(and); - and.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal0.dispose(); - signal1.dispose(); - and.dispose(); - done(); - }); - }); - - it("works with three signals", function(done){ - var signal0, signal1, signal2, and; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(1); - signal1 = new Signal(1); - signal2 = new Signal(1); - and = new AND(3); - signal0.connect(and); - signal1.connect(and); - signal2.connect(and); - and.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal0.dispose(); - signal1.dispose(); - signal2.dispose(); - and.dispose(); - done(); - }); - }); - }); - - describe("Tone.OR", function(){ - this.timeout(maxTimeout); - - it("can be created or disposed", function(){ - var a = new OR(); - a.dispose(); - Test.wasDisposed(a); - }); - - it("outputs 1 when at least one input is 1", function(done){ - var signal0, signal1, or; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(1); - signal1 = new Signal(1); - or = new OR(2); - signal0.connect(or, 0, 0); - signal1.connect(or, 0, 1); - or.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal0.dispose(); - signal1.dispose(); - or.dispose(); - done(); - }); - }); - - it("outputs 1 when only one input is 1", function(done){ - var signal0, signal1, or; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(0); - signal1 = new Signal(1); - or = new OR(2); - signal0.connect(or, 0, 0); - signal1.connect(or, 0, 1); - or.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal0.dispose(); - signal1.dispose(); - or.dispose(); - done(); - }); - }); - - it("outputs 0 when all the inputs are 0", function(done){ - var signal0, signal1, or; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(0); - signal1 = new Signal(0); - or = new OR(2); - signal0.connect(or, 0, 0); - signal1.connect(or, 0, 1); - or.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal0.dispose(); - signal1.dispose(); - or.dispose(); - done(); - }); - }); - - it("works with three signals", function(done){ - var signal0, signal1, signal2, or; - Test.offlineTest(0.2, function(dest){ - signal0 = new Signal(0); - signal1 = new Signal(0); - signal2 = new Signal(1); - or = new OR(3); - signal0.connect(or, 0, 0); - signal1.connect(or, 0, 1); - signal2.connect(or, 0, 2); - or.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal0.dispose(); - signal1.dispose(); - signal2.dispose(); - or.dispose(); - done(); - }); - }); - }); - - describe("Tone.WaveShaper", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var ws = new WaveShaper(function(val){ - return val; - }); - ws.dispose(); - Test.wasDisposed(ws); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/SignalComparison.js b/test/tests/SignalComparison.js deleted file mode 100644 index e0b33d032..000000000 --- a/test/tests/SignalComparison.js +++ /dev/null @@ -1,406 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "Tone/signal/Signal", "Tone/signal/EqualZero", - "Tone/signal/Equal", "Tone/signal/GreaterThan", "Tone/signal/LessThan", - "tests/Common", "Tone/signal/GreaterThanZero"], -function(core, chai, Signal, EqualZero, Equal, GreaterThan, LessThan, Test, GreaterThanZero){ - - var expect = chai.expect; - - describe("Tone.GreaterThanZero", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var gtz = new GreaterThanZero(2); - gtz.dispose(); - Test.wasDisposed(gtz); - }); - - it("outputs 0 when the incoming signal is equal to zero", function(done){ - var signal, gtz; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0); - gtz = new GreaterThanZero(); - signal.connect(gtz); - gtz.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - gtz.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is less than zero", function(done){ - var signal, gtz; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-1); - gtz = new GreaterThanZero(); - signal.connect(gtz); - gtz.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - gtz.dispose(); - done(); - }); - }); - - it("outputs 1 when the incoming signal is greater than zero", function(done){ - var signal, gtz; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.5); - gtz = new GreaterThanZero(); - signal.connect(gtz); - gtz.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - gtz.dispose(); - done(); - }); - }); - - it("can handle values very close to 0", function(done){ - var signal, gtz; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.00001); - gtz = new GreaterThanZero(); - signal.connect(gtz); - gtz.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - gtz.dispose(); - done(); - }); - }); - }); - - describe("Tone.EqualZero", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var ez = new EqualZero(); - ez.dispose(); - Test.wasDisposed(ez); - }); - - it("outputs 1 when the incoming signal is 0", function(done){ - var signal, ez; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0); - ez = new EqualZero(); - signal.connect(ez); - ez.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - ez.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is not 0", function(done){ - var signal, ez; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - ez = new EqualZero(); - signal.connect(ez); - ez.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - ez.dispose(); - done(); - }); - }); - - it("is not fooled by values very close to 0", function(done){ - var signal, ez; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.00001); - ez = new EqualZero(); - signal.connect(ez); - ez.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - ez.dispose(); - done(); - }); - }); - }); - - describe("Tone.Equal", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var eq = new Equal(3); - eq.dispose(); - Test.wasDisposed(eq); - }); - - it("outputs 1 when the incoming signal is equal to the value", function(done){ - var signal, eq; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(12.22); - eq = new Equal(12.22); - signal.connect(eq); - eq.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - eq.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is not equal", function(done){ - var signal, eq; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(12.23); - eq = new Equal(12.22); - signal.connect(eq); - eq.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - eq.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is very close but not equal", function(done){ - var signal, eq; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1.22001); - eq = new Equal(1.22); - signal.connect(eq); - eq.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - eq.dispose(); - done(); - }); - }); - - it("can compare two signals", function(done){ - var sigA, sigB, eq; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(5); - sigB = new Signal(5); - eq = new Equal(); - sigA.connect(eq, 0, 0); - sigB.connect(eq, 0, 1); - eq.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - sigA.dispose(); - sigB.dispose(); - eq.dispose(); - done(); - }); - }); - }); - - describe("Tone.GreaterThan", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var gt = new GreaterThan(0); - gt.dispose(); - Test.wasDisposed(gt); - }); - - it("outputs 1 when the incoming signal is greater than the value", function(done){ - var signal, gt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(3); - gt = new GreaterThan(2); - signal.connect(gt); - gt.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - gt.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal less than the value", function(done){ - var signal, gt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-101); - gt = new GreaterThan(-100); - signal.connect(gt); - gt.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - gt.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is equal to the value", function(done){ - var signal, gt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(100); - gt = new GreaterThan(100); - signal.connect(gt); - gt.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - gt.dispose(); - done(); - }); - }); - - it("can be set to a new value", function(done){ - var signal, gt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(100); - gt = new GreaterThan(200); - signal.connect(gt); - gt.connect(dest); - gt.value = 50; - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - gt.dispose(); - done(); - }); - }); - - it("can compare two signals", function(done){ - var sigA, sigB, gt; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(3); - sigB = new Signal(5); - gt = new GreaterThan(); - sigA.connect(gt, 0, 0); - sigB.connect(gt, 0, 1); - gt.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - sigA.dispose(); - sigB.dispose(); - gt.dispose(); - done(); - }); - }); - }); - - describe("Tone.LessThan", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var lt = new LessThan(2); - lt.dispose(); - Test.wasDisposed(lt); - }); - - it("outputs 1 when the incoming signal is less than the value", function(done){ - var signal, lt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(100); - lt = new LessThan(200); - signal.connect(lt); - lt.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - signal.dispose(); - lt.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal greater than the value", function(done){ - var signal, lt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1.01); - lt = new LessThan(1); - signal.connect(lt); - lt.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - lt.dispose(); - done(); - }); - }); - - it("outputs 0 when the incoming signal is equal the value", function(done){ - var signal, lt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1.01); - lt = new LessThan(1.01); - signal.connect(lt); - lt.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - lt.dispose(); - done(); - }); - }); - - it("can be set to a new value", function(done){ - var signal, lt; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(100); - lt = new LessThan(200); - signal.connect(lt); - lt.connect(dest); - lt.value = 50; - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - lt.dispose(); - done(); - }); - }); - - it("can compare two signals", function(done){ - var sigA, sigB, lt; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(-3); - sigB = new Signal(5); - lt = new LessThan(); - sigA.connect(lt, 0, 0); - sigB.connect(lt, 0, 1); - lt.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - sigA.dispose(); - sigB.dispose(); - lt.dispose(); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/SignalMath.js b/test/tests/SignalMath.js deleted file mode 100644 index 62675352f..000000000 --- a/test/tests/SignalMath.js +++ /dev/null @@ -1,921 +0,0 @@ -/* global it, describe, maxTimeout */ - -define(["tests/Core", "chai", "Tone/signal/Signal", "Tone/signal/Add", "Tone/signal/Multiply", - "Tone/signal/Scale", "Tone/source/Oscillator", "Tone/core/Master", "Tone/signal/Abs", "Tone/signal/Negate", - "Tone/signal/Max", "Tone/signal/Min", "Tone/signal/Clip", "Tone/signal/ScaleExp", - "Tone/signal/Modulo", "tests/Common", "Tone/signal/Subtract", - "Tone/signal/Pow", "Tone/signal/Normalize", "Tone/signal/AudioToGain", "Tone/signal/EqualPowerGain", - "Tone/signal/GainToAudio"], -function(core, chai, Signal, Add, Multiply, Scale, Oscillator, Master, Abs, Negate, Max, - Min, Clip, ScaleExp, Modulo, Test, Subtract, Pow, Normalize, AudioToGain, EqualPowerGain, GainToAudio){ - - var expect = chai.expect; - - Master.mute = true; - - //ADD - describe("Tone.Add", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var a = new Add(1); - a.dispose(); - Test.wasDisposed(a); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var a = new Add(); - Test.acceptsInput(a, 0); - Test.acceptsInput(a, 1); - Test.acceptsOutput(a); - a.dispose(); - }); - - it("correctly sums a signal and a number", function(done){ - var signal, adder; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0); - adder = new Add(3); - signal.connect(adder); - adder.connect(dest); - }, function(sample){ - expect(sample).to.equal(3); - }, function(){ - signal.dispose(); - adder.dispose(); - done(); - }); - }); - - it("can handle negative values", function(done){ - var signal, adder; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - adder = new Add(-1); - signal.connect(adder); - adder.connect(dest); - }, function(sample){ - expect(sample).to.equal(9); - }, function(){ - signal.dispose(); - adder.dispose(); - done(); - }); - }); - - it("can sum two signals", function(done){ - var sigA, sigB, adder; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(1); - sigB = new Signal(4); - adder = new Add(); - sigA.connect(adder, 0, 0); - sigB.connect(adder, 0, 1); - adder.connect(dest); - }, function(sample){ - expect(sample).to.equal(5); - }, function(){ - sigA.dispose(); - sigB.dispose(); - adder.dispose(); - done(); - }); - }); - }); - - describe("Tone.Subtract", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var a = new Subtract(); - a.dispose(); - Test.wasDisposed(a); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var sub = new Subtract(); - Test.acceptsInput(sub, 0); - Test.acceptsInput(sub, 1); - Test.acceptsOutput(sub); - sub.dispose(); - }); - - it("correctly subtracts a signal and a number", function(done){ - var signal, sub; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0); - sub = new Subtract(3); - signal.connect(sub); - sub.connect(dest); - }, function(sample){ - expect(sample).to.equal(-3); - }, function(){ - signal.dispose(); - sub.dispose(); - done(); - }); - }); - - it("can handle negative values", function(done){ - var signal, sub; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(4); - sub = new Subtract(-2); - signal.connect(sub); - sub.connect(dest); - }, function(sample){ - expect(sample).to.equal(6); - }, function(){ - signal.dispose(); - sub.dispose(); - done(); - }); - }); - - it("can subtract two signals", function(done){ - var sigA, sigB, sub; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(1); - sigB = new Signal(4); - sub = new Subtract(); - sigA.connect(sub, 0, 0); - sigB.connect(sub, 0, 1); - sub.connect(dest); - }, function(sample){ - expect(sample).to.equal(-3); - }, function(){ - sigA.dispose(); - sigB.dispose(); - sub.dispose(); - done(); - }); - }); - }); - - - //MULTIPLY - describe("Tone.Multiply", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var m = new Multiply(1); - m.dispose(); - Test.wasDisposed(m); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var mult = new Multiply(); - Test.acceptsInput(mult, 0); - Test.acceptsInput(mult, 1); - Test.acceptsOutput(mult); - mult.dispose(); - }); - - it("correctly multiplys a signal and a scalar", function(done){ - var signal, mult; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(2); - mult = new Multiply(10); - signal.connect(mult); - mult.connect(dest); - }, function(sample){ - expect(sample).to.equal(20); - }, function(){ - signal.dispose(); - mult.dispose(); - done(); - }); - }); - - it("can multiply two signals", function(done){ - var sigA, sigB, mult; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(3); - sigB = new Signal(5); - mult = new Multiply(); - sigA.connect(mult, 0, 0); - sigB.connect(mult, 0, 1); - mult.connect(dest); - }, function(sample){ - expect(sample).to.equal(15); - }, function(){ - sigA.dispose(); - sigB.dispose(); - mult.dispose(); - done(); - }); - }); - }); - - describe("Tone.Scale", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var s = new Scale(0, 10); - s.dispose(); - Test.wasDisposed(s); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var scale = new Scale(0, 100); - Test.acceptsInputAndOutput(scale); - scale.dispose(); - }); - - it("scales an input range to an output range", function(done){ - //make an oscillator to drive the signal - var osc, scale; - Test.offlineTest(0.2, function(dest){ - osc = new Signal(0.5); - scale = new Scale(10, 20); - osc.connect(scale); - scale.connect(dest); - }, function(sample){ - expect(sample).to.equal(15); - }, function(){ - osc.dispose(); - scale.dispose(); - done(); - }); - }); - }); - - //SCALE - describe("Tone.ScaleExp", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var s = new ScaleExp(0, 10, 2); - s.dispose(); - Test.wasDisposed(s); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var scale = new ScaleExp(0, 100); - Test.acceptsInputAndOutput(scale); - scale.dispose(); - }); - - it("scales a signal exponentially", function(done){ - var signal, scale; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.5); - scale = new ScaleExp(0, 1, 2); - signal.connect(scale); - scale.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.25, 0.01); - }, function(){ - signal.dispose(); - scale.dispose(); - done(); - }); - }); - }); - - describe("Tone.Abs", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var ab = new Abs(); - ab.dispose(); - Test.wasDisposed(ab); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var abs = new Abs(); - Test.acceptsInputAndOutput(abs); - abs.dispose(); - }); - - it("outputs the same value for positive values", function(done){ - var signal, abs; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(100); - abs = new Abs(); - signal.connect(abs); - abs.connect(dest); - }, function(sample){ - expect(sample).to.equal(100); - }, function(){ - signal.dispose(); - abs.dispose(); - done(); - }); - }); - - it("outputs the absolute value for negative numbers", function(done){ - var signal, abs; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-10); - abs = new Abs(); - signal.connect(abs); - abs.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal.dispose(); - abs.dispose(); - done(); - }); - }); - }); - - describe("Tone.Negate", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var neg = new Negate(); - neg.dispose(); - Test.wasDisposed(neg); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var neg = new Negate(); - Test.acceptsInputAndOutput(neg); - neg.dispose(); - }); - - - it("negates a positive value", function(done){ - var signal, neg; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1); - neg = new Negate(); - signal.connect(neg); - neg.connect(dest); - }, function(sample){ - expect(sample).to.equal(-1); - }, function(){ - signal.dispose(); - neg.dispose(); - done(); - }); - }); - - it("makes a negative value positive", function(done){ - var signal, neg; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-10); - neg = new Negate(); - signal.connect(neg); - neg.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal.dispose(); - neg.dispose(); - done(); - }); - }); - }); - - - //Max - describe("Tone.Max", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var max = new Max(); - max.dispose(); - Test.wasDisposed(max); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var max = new Max(); - Test.acceptsInput(max, 0); - Test.acceptsInput(max, 1); - Test.acceptsOutput(max); - max.dispose(); - }); - - it("outputs the set value when less than the incoming signal", function(done){ - var signal, max; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(1); - max = new Max(2); - signal.connect(max); - max.connect(dest); - }, function(sample){ - expect(sample).to.equal(2); - }, function(){ - signal.dispose(); - max.dispose(); - done(); - }); - }); - - it("outputs the incoming signal when greater than the max", function(done){ - var signal, max; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - max = new Max(-1); - signal.connect(max); - max.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signal.dispose(); - max.dispose(); - done(); - }); - }); - - it("can be set to a new value", function(done){ - var signal, max; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(10); - max = new Max(-1); - signal.connect(max); - max.value = 12; - max.connect(dest); - }, function(sample){ - expect(sample).to.equal(12); - }, function(){ - signal.dispose(); - max.dispose(); - done(); - }); - }); - - it("can use two signals", function(done){ - var sigA, sigB, max; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(3); - sigB = new Signal(50); - max = new Max(); - sigA.connect(max, 0, 0); - sigB.connect(max, 0, 1); - max.connect(dest); - }, function(sample){ - expect(sample).to.equal(50); - }, function(){ - sigA.dispose(); - sigB.dispose(); - max.dispose(); - done(); - }); - }); - }); - - - //Max - describe("Tone.Min", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var min = new Min(); - min.dispose(); - Test.wasDisposed(min); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var min = new Min(); - Test.acceptsInput(min, 0); - Test.acceptsInput(min, 1); - Test.acceptsOutput(min); - min.dispose(); - }); - - it("outputs the set value when greater than the incoming signal", function(done){ - var signal, min; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(4); - min = new Min(2); - signal.connect(min); - min.connect(dest); - }, function(sample){ - expect(sample).to.equal(2); - }, function(){ - signal.dispose(); - min.dispose(); - done(); - }); - }); - - it("outputs the incoming signal when less than the min", function(done){ - var signal, min; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-12); - min = new Min(-4); - signal.connect(min); - min.connect(dest); - }, function(sample){ - expect(sample).to.equal(-12); - }, function(){ - signal.dispose(); - min.dispose(); - done(); - }); - }); - - it("can be set to a new value", function(done){ - var signal, min; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(3); - min = new Min(-4); - signal.connect(min); - min.value = 4; - min.connect(dest); - }, function(sample){ - expect(sample).to.equal(3); - }, function(){ - signal.dispose(); - min.dispose(); - done(); - }); - }); - - it("can use two signals", function(done){ - var sigA, sigB, min; - Test.offlineTest(0.2, function(dest){ - sigA = new Signal(3); - sigB = new Signal(5); - min = new Min(); - sigA.connect(min, 0, 0); - sigB.connect(min, 0, 1); - min.connect(dest); - }, function(sample){ - expect(sample).to.equal(3); - }, function(){ - sigA.dispose(); - sigB.dispose(); - min.dispose(); - done(); - }); - }); - }); - - //Clip - describe("Tone.Clip", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var clip = new Clip(0, 1); - clip.dispose(); - Test.wasDisposed(clip); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var clip = new Clip(0, 1); - Test.acceptsInputAndOutput(clip); - clip.dispose(); - }); - - it("output the upper limit when signal is greater than clip", function(done){ - var signal, clip; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(4); - clip = new Clip(2, 3); - signal.connect(clip); - clip.connect(dest); - }, function(sample){ - expect(sample).to.equal(3); - }, function(){ - signal.dispose(); - clip.dispose(); - done(); - }); - }); - - it("outputs the incoming signal when in between upper and lower limit", function(done){ - var signal, clip; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-12); - clip = new Clip(-14, 14); - signal.connect(clip); - clip.connect(dest); - }, function(sample){ - expect(sample).to.equal(-12); - }, function(){ - signal.dispose(); - clip.dispose(); - done(); - }); - }); - - it("outputs the lower limit when incoming signal is less than the lower limit", function(done){ - var signal, clip; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-12); - clip = new Clip(0, 8); - signal.connect(clip); - clip.connect(dest); - }, function(sample){ - expect(sample).to.equal(0); - }, function(){ - signal.dispose(); - clip.dispose(); - done(); - }); - }); - }); - - describe("Tone.Modulo", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var mod = new Modulo(0.1); - mod.dispose(); - Test.wasDisposed(mod); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var mod = new Modulo(); - Test.acceptsInputAndOutput(mod); - mod.dispose(); - }); - - it("can evaluate 0.45 % 0.3", function(done){ - var signal, mod; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.45); - mod = new Modulo(0.3); - signal.connect(mod); - mod.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.15, 0.0001); - }, function(){ - signal.dispose(); - mod.dispose(); - done(); - }); - }); - - it("can evaluate 0.1 % 0.2", function(done){ - var signal, mod; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.1); - mod = new Modulo(0.2); - signal.connect(mod); - mod.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.1, 0.0001); - }, function(){ - signal.dispose(); - mod.dispose(); - done(); - }); - }); - }); - - describe("Tone.Pow", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var pow = new Pow(); - pow.dispose(); - Test.wasDisposed(pow); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var pow = new Pow(); - Test.acceptsInputAndOutput(pow); - pow.dispose(); - }); - - it("can do powers of 2", function(done){ - var signal, pow; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(0.3); - pow = new Pow(2); - signal.connect(pow); - pow.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.09, 0.01); - }, function(){ - signal.dispose(); - pow.dispose(); - done(); - }); - }); - - it("can compute negative values and powers less than 1", function(done){ - var signal, pow; - Test.offlineTest(0.2, function(dest){ - signal = new Signal(-0.49); - pow = new Pow(0.5); - signal.connect(pow); - pow.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.7, 0.01); - }, function(){ - signal.dispose(); - pow.dispose(); - done(); - }); - }); - }); - - describe("Tone.Normalize", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var s = new Normalize(); - s.dispose(); - Test.wasDisposed(s); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var norm = new Normalize(); - Test.acceptsInputAndOutput(norm); - norm.dispose(); - }); - - it("normalizes an oscillator to 0,1", function(done){ - //make an oscillator to drive the signal - var osc, norm; - Test.offlineTest(0.2, function(dest){ - osc = new Oscillator(1000); - norm = new Normalize(-1, 1); - osc.connect(norm); - norm.connect(dest); - }, function(sample){ - expect(sample).to.be.within(0, 1); - }, function(){ - osc.dispose(); - norm.dispose(); - done(); - }); - }); - - it("normalizes an input", function(done){ - //make an oscillator to drive the signal - var sig, norm; - Test.offlineTest(0.2, function(dest){ - sig = new Signal(1000); - norm = new Normalize(0, 1000); - sig.connect(norm); - norm.connect(dest); - }, function(sample){ - expect(sample).to.equal(1); - }, function(){ - sig.dispose(); - norm.dispose(); - done(); - }); - }); - }); - - describe("Tone.AudioToGain", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var a2g = new AudioToGain(); - a2g.dispose(); - Test.wasDisposed(a2g); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var a2g = new AudioToGain(); - Test.acceptsInputAndOutput(a2g); - a2g.dispose(); - }); - - it("normalizes an oscillator to 0,1", function(done){ - //make an oscillator to drive the signal - var osc, a2g; - Test.offlineTest(0.2, function(dest){ - osc = new Oscillator(1000); - a2g = new AudioToGain(); - osc.connect(a2g); - a2g.connect(dest); - }, function(sample){ - expect(sample).to.be.within(0, 1); - }, function(){ - osc.dispose(); - a2g.dispose(); - done(); - }); - }); - - it("outputs 0.5 for an input value of 0", function(done){ - //make an oscillator to drive the signal - var sig, a2g; - Test.offlineTest(0.2, function(dest){ - sig = new Signal(0); - a2g = new AudioToGain(); - sig.connect(a2g); - a2g.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0.5, 0.01); - }, function(){ - sig.dispose(); - a2g.dispose(); - done(); - }); - }); - }); - - describe("Tone.GainToAudio", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var g2a = new GainToAudio(); - g2a.dispose(); - Test.wasDisposed(g2a); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var g2a = new GainToAudio(); - Test.acceptsInputAndOutput(g2a); - g2a.dispose(); - }); - - it("outputs 0 for an input value of 0.5", function(done){ - //make an oscillator to drive the signal - var sig, g2a; - Test.offlineTest(0.2, function(dest){ - sig = new Signal(0.5); - g2a = new GainToAudio(); - sig.connect(g2a); - g2a.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(0, 0.01); - }, function(){ - sig.dispose(); - g2a.dispose(); - done(); - }); - }); - - it("outputs 1 for an input value of 1", function(done){ - //make an oscillator to drive the signal - var sig, g2a; - Test.offlineTest(0.2, function(dest){ - sig = new Signal(1); - g2a = new GainToAudio(); - sig.connect(g2a); - g2a.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(1, 0.01); - }, function(){ - sig.dispose(); - g2a.dispose(); - done(); - }); - }); - - it("outputs -1 for an input value of 0", function(done){ - //make an oscillator to drive the signal - var sig, g2a; - Test.offlineTest(0.2, function(dest){ - sig = new Signal(0); - g2a = new GainToAudio(); - sig.connect(g2a); - g2a.connect(dest); - }, function(sample){ - expect(sample).to.be.closeTo(-1, 0.01); - }, function(){ - sig.dispose(); - g2a.dispose(); - done(); - }); - }); - }); - - describe("Tone.EqualPowerGain", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var eqpg = new EqualPowerGain(); - eqpg.dispose(); - Test.wasDisposed(eqpg); - }); - - it("handles input and output connections", function(){ - Test.onlineContext(); - var eqpg = new EqualPowerGain(); - Test.acceptsInputAndOutput(eqpg); - eqpg.dispose(); - }); - - it("passes the incoming signal through", function(done){ - var eqpg; - Test.passesAudio(function(input, output){ - eqpg = new EqualPowerGain(); - input.connect(eqpg); - eqpg.connect(output); - }, function(){ - eqpg.dispose(); - done(); - }); - }); - }); - -}); \ No newline at end of file diff --git a/test/tests/Sound.js b/test/tests/Sound.js deleted file mode 100644 index ff3a8a04b..000000000 --- a/test/tests/Sound.js +++ /dev/null @@ -1,330 +0,0 @@ -/* global it, describe, mocha*/ - -require.config({ - baseUrl:"./", - paths : { - "Tone" : "../Tone", - "chai" : "./testDeps/chai", - } -}); - - -require(["Tone/core/Master", "Tone/instrument/MonoSynth", "Tone/instrument/DuoSynth", - "Tone/instrument/FMSynth", "Tone/instrument/AMSynth", "Tone/instrument/NoiseSynth", - "Tone/source/Oscillator", "Tone/source/PulseOscillator", "Tone/source/PWMOscillator", - "Tone/source/Noise", "Tone/instrument/PluckSynth", "Tone/effect/AutoPanner", - "Tone/effect/AutoWah", "Tone/effect/BitCrusher", "Tone/effect/Chebyshev", - "Tone/effect/Chorus", "Tone/effect/Distortion", "Tone/effect/FeedbackDelay", - "Tone/effect/Freeverb", "Tone/effect/JCReverb", "Tone/effect/Phaser", - "Tone/effect/PingPongDelay"], -function(Master, MonoSynth, DuoSynth, FMSynth, AMSynth, NoiseSynth, Oscillator, - PulseOscillator, PWMOscillator, Noise, PluckSynth, AutoPanner, AutoWah, BitCrusher, - Chebyshev, Chorus, Distortion, FeedbackDelay, Freeverb, JCReverb, Phaser, PingPongDelay){ - - var noteDuration = 1; - - describe("Tone.Oscillator", function(){ - it("makes sound", function(done){ - var source = new Oscillator(); - source.toMaster(); - source.start(); - source.stop("+"+ (noteDuration * 0.5)); - setTimeout(function(){ - source.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.PulseOscillator", function(){ - it("makes sound", function(done){ - var source = new PulseOscillator(); - source.toMaster(); - source.start(); - source.stop("+"+ (noteDuration * 0.5)); - setTimeout(function(){ - source.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.PWMOscillator", function(){ - it("makes sound", function(done){ - var source = new PWMOscillator(); - source.toMaster(); - source.start(); - source.stop("+"+ (noteDuration * 0.5)); - setTimeout(function(){ - source.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Noise", function(){ - it("makes sound", function(done){ - var source = new Noise(); - source.toMaster(); - source.start(); - source.stop("+"+ (noteDuration * 0.5)); - setTimeout(function(){ - source.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.MonoSynth", function(){ - it("makes sound", function(done){ - var synth = new MonoSynth(); - synth.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.5); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.DuoSynth", function(){ - it("makes sound", function(done){ - var synth = new DuoSynth(); - synth.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.5); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.FMSynth", function(){ - it("makes sound", function(done){ - var synth = new FMSynth(); - synth.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.5); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.AMSynth", function(){ - it("makes sound", function(done){ - var synth = new AMSynth(); - synth.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.5); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.NoiseSynth", function(){ - it("makes sound", function(done){ - var synth = new NoiseSynth(); - synth.toMaster(); - synth.triggerAttack(); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.PluckSynth", function(){ - it("makes sound", function(done){ - var synth = new PluckSynth(); - synth.volume.value = 12; - synth.toMaster(); - synth.triggerAttack("C3"); - setTimeout(function(){ - synth.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.AutoPanner", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new AutoPanner(10); - effect.start(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.AutoWah", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new AutoWah({ - "baseFrequency" : 100, - "octaves" : 4, - "sensitivity" : 0, - "Q" : 2, - "gain" : 10, - "rolloff" : -12, - "follower" : { - "attack" : 0.05, - "release" : 0.2 - } - }); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.BitCrusher", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new BitCrusher(4); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Chebyshev", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new Chebyshev(50); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Chorus", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new Chorus(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Distortion", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new Distortion(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.FeedbackDelay", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new FeedbackDelay(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.1); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Freeverb", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new Freeverb(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.1); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.JCReverb", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new JCReverb(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.1); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.Phaser", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new Phaser(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.75); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - describe("Tone.PingPongDelay", function(){ - it("effects sound", function(done){ - var synth = new MonoSynth(); - var effect = new PingPongDelay(); - synth.connect(effect); - effect.toMaster(); - synth.triggerAttackRelease("C4", noteDuration * 0.1); - setTimeout(function(){ - synth.dispose(); - effect.dispose(); - done(); - }, noteDuration * 1000); - }); - }); - - //run the tests - mocha.run(); -}); \ No newline at end of file diff --git a/test/tests/Sources.js b/test/tests/Sources.js deleted file mode 100644 index 02b54be65..000000000 --- a/test/tests/Sources.js +++ /dev/null @@ -1,684 +0,0 @@ -/* global it, describe, after, maxTimeout, beforeEach */ - -define(["chai", "Tone/source/Player", "Tone/core/Master", "Tone/source/Oscillator", - "Recorder", "Tone/source/Noise", "tests/Core", "Tone/source/PulseOscillator", "tests/Common", - "Tone/source/PWMOscillator", "Tone/source/OmniOscillator", "Tone/source/Microphone", "Tone/core/Buffer", - "Tone/core/Transport"], -function(chai, Player, Master, Oscillator, Recorder, Noise, core, PulseOscillator, Test, - PWMOscillator, OmniOscillator, Microphone, Buffer, Transport){ - - var expect = chai.expect; - - describe("Tone.Player", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var p = new Player(); - p.dispose(); - Test.wasDisposed(p); - }); - - it("handles output connections", function(){ - var p = new Player(); - Test.acceptsOutput(p); - p.dispose(); - }); - - it("loads a file", function(done){ - var player = new Player("./testAudio/kick.mp3", function(){ - player.dispose(); - done(); - }); - }); - - it("invokes a callback onend", function(done){ - var player = new Player("./testAudio/kick.mp3", function(){ - player.start(); - expect(player.state).to.equal("started"); - }); - player.onended = function(){ - setTimeout(function(){ - expect(player.state).to.equal("stopped"); - player.dispose(); - done(); - }, 100); - }; - player.toMaster(); - }); - - it("can handle multiple restarts", function(done){ - var player = new Player("./testAudio/kick.mp3"); - Buffer.onload = function(){ - expect(player.state).to.equal("stopped"); - player.start(); - player.start(); - player.stop(); - player.stop(); - expect(player.state).to.equal("stopped"); - player.dispose(); - done(); - }; - }); - - it("can be created with an options object", function(done){ - var player = new Player({ - "url" : "./testAudio/kick.mp3", - "loop" : true - }); - Buffer.onload = function(){ - expect(player.loop).to.be.true; - player.dispose(); - done(); - }; - }); - - it("can be set with an options object", function(){ - var player = new Player(); - expect(player.loop).to.be.false; - expect(player.loopStart).to.equal(0); - player.set({ - "loop" : true, - "loopStart" : 0.4 - }); - expect(player.loop).to.be.true; - expect(player.loopStart).to.equal(0.4); - player.dispose(); - }); - - it("can sync to the Transport", function(done){ - var player = new Player("./testAudio/kick.mp3"); - player.sync(); - Buffer.onload = function(){ - expect(player.state).to.equal("stopped"); - Transport.start(); - setTimeout(function(){ - expect(player.state).to.equal("started"); - Transport.stop(); - setTimeout(function(){ - expect(player.state).to.equal("stopped"); - player.dispose(); - player = null; - done(); - }, 100); - }, 100); - }; - }); - }); - - describe("Tone.Oscillator", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var o = new Oscillator(); - o.dispose(); - Test.wasDisposed(o); - }); - - it("outputs a sound", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new Oscillator(); - osc.connect(dest); - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("handles output connections", function(){ - var osc = new Oscillator(); - Test.acceptsOutput(osc); - osc.dispose(); - }); - - it("starts and stops", function(done){ - var oscillator = new Oscillator(); - expect(oscillator.state).to.equal("stopped"); - oscillator.start(); - expect(oscillator.state).to.equal("started"); - setTimeout(function(){ - oscillator.stop(); - oscillator.dispose(); - done(); - }, 100); - }); - - it("can be scheduled to stop", function(done){ - var oscillator = new Oscillator(); - expect(oscillator.state).to.equal("stopped"); - oscillator.start(); - oscillator.stop("+0.05"); - setTimeout(function(){ - // expect(oscillator.state).to.equal("stopped"); - oscillator.dispose(); - done(); - }, 200); - }); - - it("won't start again before stopping", function(){ - var oscillator = new Oscillator(); - expect(oscillator.state).to.equal("stopped"); - oscillator.start(); - oscillator.start(); - oscillator.stop(); - oscillator.stop(); - expect(oscillator.state).to.equal("stopped"); - oscillator.dispose(); - }); - - it("invokes the onended callback on stop", function(done){ - var oscillator = new Oscillator(); - oscillator.toMaster(); - oscillator.onended = function(){ - oscillator.dispose(); - done(); - }; - oscillator.start(); - oscillator.stop("+0.2"); - }); - - it("be scheduled to start in the future", function(done){ - var osc; - Test.offlineTest(1, function(dest){ - osc = new Oscillator(440); - osc.connect(dest); - osc.start("+0.1"); - }, function(sample, time){ - if (sample !== 0){ - expect(time).to.be.at.least(0.1); - } - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("can set the frequency", function(){ - var oscillator = new Oscillator(); - oscillator.frequency.value = 110; - expect(oscillator.frequency.value).to.equal(110); - oscillator.start(); - oscillator.frequency.value = 220; - expect(oscillator.frequency.value).to.equal(220); - oscillator.dispose(); - }); - - it("can be created with an options object", function(){ - var osc = new Oscillator({ - "frequency" : 200, - "detune" : -20 - }); - expect(osc.frequency.value).to.equal(200); - expect(osc.detune.value).to.equal(-20); - osc.dispose(); - }); - - it("can be set with an options object", function(){ - var osc = new Oscillator(); - osc.set({ - "frequency" : 231, - "detune" : -21, - "type" : "square" - }); - expect(osc.frequency.value).to.equal(231); - expect(osc.detune.value).to.equal(-21); - expect(osc.type).to.equal("square"); - osc.dispose(); - }); - - it("can sync the frequency to Transport", function(done){ - var osc; - Test.offlineTest(0.1, function(dest){ - Transport.bpm.value = 120; - osc = new Oscillator(2); - osc.frequency.connect(dest); - osc.syncFrequency(); - Transport.bpm.value = 240; - }, function(freq){ - expect(freq).to.be.closeTo(4, 0.001); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("can unsync the frequency to Transport", function(done){ - var osc; - Test.offlineTest(0.1, function(dest){ - Transport.bpm.value = 120; - osc = new Oscillator(2); - osc.frequency.connect(dest); - osc.syncFrequency(); - Transport.bpm.value = 240; - osc.unsyncFrequency(); - }, function(freq){ - expect(freq).to.be.closeTo(2, 0.001); - }, function(){ - osc.dispose(); - done(); - }); - }); - }); - - describe("Tone.Noise", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var n = new Noise(); - n.dispose(); - Test.wasDisposed(n); - }); - - it("handles output connections", function(){ - var n = new Noise(); - Test.acceptsOutput(n); - n.dispose(); - }); - - it("outputs a sound", function(done){ - var noise; - Test.outputsAudio(function(dest){ - noise = new Noise(); - noise.connect(dest); - noise.start(); - }, function(){ - noise.dispose(); - done(); - }); - }); - - it("starts and stops", function(done){ - var noise = new Noise(); - expect(noise.state).to.equal("stopped"); - noise.start(); - expect(noise.state).to.equal("started"); - setTimeout(function(){ - noise.stop(); - noise.dispose(); - done(); - }, 100); - }); - - it("invokes the onended callback on stop", function(done){ - var noise = new Noise(); - noise.toMaster(); - noise.onended = function(){ - noise.dispose(); - done(); - }; - noise.start(); - noise.stop("+0.2"); - }); - - it("can be scheduled to stop", function(done){ - var noise = new Noise(); - noise.toMaster(); - expect(noise.state).to.equal("stopped"); - noise.start(); - expect(noise.state).to.equal("started"); - noise.stop("+0.05"); - setTimeout(function(){ - expect(noise.state).to.equal("stopped"); - noise.dispose(); - done(); - }, 100); - }); - - it("won't start again before stopping", function(){ - var noise = new Noise(); - expect(noise.state).to.equal("stopped"); - noise.start(); - noise.start(); - noise.stop(); - noise.stop(); - expect(noise.state).to.equal("stopped"); - noise.dispose(); - }); - - it("be scheduled to start in the future", function(done){ - var noi; - Test.offlineTest(1, function(dest){ - noi = new Noise(); - noi.connect(dest); - noi.start("+0.1"); - }, function(sample, time){ - if (sample !== 0){ - expect(time).to.be.at.least(0.1); - } - }, function(){ - noi.dispose(); - done(); - }); - }); - - it("can set the noise types", function(){ - var noise = new Noise(); - noise.type = "brown"; - noise.type = "white"; - noise.type = "pink"; - //even after started - noise.start(); - noise.type = "brown"; - noise.type = "white"; - noise.type = "pink"; - noise.stop(); - noise.dispose(); - }); - - it("can be created with an options object", function(){ - var noise = new Noise({ - "type" : "brown" - }); - expect(noise.type).to.equal("brown"); - noise.dispose(); - }); - - it("can be set with an options object", function(){ - var noise = new Noise(); - noise.set({ - "type" : "pink" - }); - expect(noise.type).to.equal("pink"); - noise.dispose(); - }); - }); - - describe("Tone.PulseOscillator", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var o = new PulseOscillator(); - o.dispose(); - Test.wasDisposed(o); - }); - - it("handles output connections", function(){ - var o = new PulseOscillator(); - Test.acceptsOutput(o); - o.dispose(); - }); - - it("outputs a sound", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new PulseOscillator(); - osc.connect(dest); - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("can set the width", function(){ - var pulse = new PulseOscillator(); - pulse.width.value = 0.2; - pulse.dispose(); - }); - - it("can set the frequency", function(){ - var pulse = new PulseOscillator(); - pulse.frequency.value = 220; - pulse.dispose(); - }); - - it("invokes the onended callback on stop", function(done){ - var oscillator = new PulseOscillator(); - oscillator.toMaster(); - oscillator.onended = function(){ - oscillator.dispose(); - done(); - }; - oscillator.start(); - oscillator.stop("+0.2"); - }); - - it("can be created with an options object", function(){ - var osc = new PulseOscillator({ - "frequency" : 200, - "detune" : -20, - }); - expect(osc.frequency.value).to.equal(200); - expect(osc.detune.value).to.equal(-20); - osc.dispose(); - }); - - it("can be set with an options object", function(){ - var osc = new PulseOscillator(); - osc.set({ - "frequency" : 231, - "detune" : -21, - "width" : 0.2 - }); - expect(osc.frequency.value).to.equal(231); - expect(osc.detune.value).to.equal(-21); - expect(osc.width.value).to.be.closeTo(0.2, 0.001); - osc.dispose(); - }); - }); - - describe("Tone.PWMOscillator", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var pwm = new PWMOscillator(); - pwm.dispose(); - Test.wasDisposed(pwm); - }); - - it("handles output connections", function(){ - var pwm = new PWMOscillator(); - Test.acceptsOutput(pwm); - pwm.dispose(); - }); - - it("outputs a sound", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new PWMOscillator(); - osc.connect(dest); - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("can set the modulation frequency", function(){ - var pwm = new PWMOscillator(); - pwm.modulationFrequency.value = 0.2; - pwm.dispose(); - }); - - it("can set the frequency", function(){ - var pwm = new PWMOscillator(); - pwm.frequency.value = 220; - pwm.dispose(); - }); - - it("invokes the onended callback on stop", function(done){ - var oscillator = new PWMOscillator(); - oscillator.toMaster(); - oscillator.onended = function(){ - oscillator.dispose(); - done(); - }; - oscillator.start(); - oscillator.stop("+0.2"); - }); - - it("can be created with an options object", function(){ - var osc = new PWMOscillator({ - "frequency" : 200, - "detune" : -20, - }); - expect(osc.frequency.value).to.equal(200); - expect(osc.detune.value).to.equal(-20); - osc.dispose(); - }); - - it("can be set with an options object", function(){ - var osc = new PWMOscillator(); - osc.set({ - "frequency" : 231, - "detune" : -21, - "modulationFrequency" : 0.2 - }); - expect(osc.frequency.value).to.equal(231); - expect(osc.detune.value).to.equal(-21); - expect(osc.modulationFrequency.value).to.be.closeTo(0.2, 0.001); - osc.dispose(); - }); - }); - - describe("Tone.OmniOscillator", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Test.onlineContext(); - }); - - it("can be created and disposed", function(){ - var omni = new OmniOscillator(); - omni.dispose(); - Test.wasDisposed(omni); - }); - - it("outputs a sound", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new OmniOscillator(); - osc.connect(dest); - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("outputs a sound when set to a square wave", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new OmniOscillator(); - osc.connect(dest); - osc.type = "square"; - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("outputs a sound when set to a pwm wave", function(done){ - var osc; - Test.outputsAudio(function(dest){ - osc = new OmniOscillator(); - osc.connect(dest); - osc.type = "pwm"; - osc.start(); - }, function(){ - osc.dispose(); - done(); - }); - }); - - it("handles output connections", function(){ - var omni = new OmniOscillator(); - Test.acceptsOutput(omni); - omni.dispose(); - }); - - it("invokes the onended callback on stop", function(done){ - var omni = new OmniOscillator(); - omni.toMaster(); - omni.onended = function(){ - omni.dispose(); - done(); - }; - omni.start(); - omni.stop("+0.2"); - }); - - it("can set the modulation frequency only when type is pwm", function(){ - var omni = new OmniOscillator(); - omni.type = "pwm"; - expect(function(){ - omni.modulationFrequency.value = 0.2; - }).to.not.throw(Error); - omni.type = "pulse"; - expect(function(){ - omni.modulationFrequency.value = 0.2; - }).to.throw(Error); - omni.dispose(); - }); - - it("can set the modulation width only when type is pulse", function(){ - var omni = new OmniOscillator(); - omni.type = "pulse"; - expect(function(){ - omni.width.value = 0.2; - }).to.not.throw(Error); - omni.type = "sine"; - expect(function(){ - omni.width.value = 0.2; - }).to.throw(Error); - omni.dispose(); - }); - - it("can be created with an options object", function(){ - var osc = new OmniOscillator({ - "frequency" : 210, - "detune" : -30, - "type" : "square" - }); - expect(osc.frequency.value).to.equal(210); - expect(osc.detune.value).to.equal(-30); - expect(osc.type).to.equal("square"); - osc.dispose(); - }); - - it("can be set with an options object", function(){ - var osc = new OmniOscillator(); - osc.set({ - "type" : "pwm", - "frequency" : 231, - "detune" : -21, - }); - expect(osc.frequency.value).to.equal(231); - expect(osc.detune.value).to.equal(-21); - expect(osc.type).to.equal("pwm"); - osc.dispose(); - }); - }); - - describe("Tone.Microphone", function(){ - this.timeout(maxTimeout); - - it("can be created and disposed", function(){ - var mic = new Microphone(); - mic.dispose(); - Test.wasDisposed(mic); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/Timing.js b/test/tests/Timing.js deleted file mode 100644 index 932072ad6..000000000 --- a/test/tests/Timing.js +++ /dev/null @@ -1,136 +0,0 @@ -/* global it, describe*/ - -define(["chai", "Tone/core/Tone", "Tone/core/Transport", "tests/Core", "tests/Common", "Tone/core/Note"], -function(chai, Tone, Transport, Core, Test, Note){ - var expect = chai.expect; - var tone = new Tone(); - - describe("Tone.notationToSeconds", function(){ - - - it("handles measures, measure subdivision and triplets", function(){ - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.notationToSeconds("1m")).to.equal(2); - expect(tone.notationToSeconds("4n")).to.equal(0.5); - expect(tone.notationToSeconds("8n")).to.equal(0.25); - expect(tone.notationToSeconds("16n")).to.equal(0.125); - expect(tone.notationToSeconds("2t")).to.equal(2/3); - expect(tone.notationToSeconds("8t")).to.equal(1/6); - }); - - it("handles setting different BPM", function(){ - Transport.bpm.value = 240; - Transport.timeSignature = 4; - expect(tone.notationToSeconds("4n")).to.equal(0.25); - expect(tone.notationToSeconds("1")).to.equal(0); - expect(tone.notationToSeconds("8t")).to.equal(0.25/3); - expect(tone.notationToSeconds("8m")).to.equal(8); - }); - }); - - describe("Tone.transportTimeToSeconds", function(){ - - it("converts transport time in multiple forms to seconds", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.transportTimeToSeconds("1:2:3")).to.equal(3.375); - expect(tone.transportTimeToSeconds("1:2:0")).to.equal(3); - expect(tone.transportTimeToSeconds("1:2")).to.equal(3); - expect(tone.transportTimeToSeconds("0:2")).to.equal(1); - expect(tone.transportTimeToSeconds("2")).to.equal(1); - expect(tone.transportTimeToSeconds("0:0:2")).to.equal(0.25); - }); - - it("handles time signature changes", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 6; - expect(tone.transportTimeToSeconds("4:0")).to.equal(12); - expect(tone.transportTimeToSeconds("6")).to.equal(3); - }); - }); - - describe("Tone.toTransportTime", function(){ - - it("converts seconds to transport time", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.toTransportTime(3.375)).to.equal("1:2:3"); - expect(tone.toTransportTime(3)).to.equal("1:2:0"); - }); - - it("handles time signature changes", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 6; - expect(tone.toTransportTime(12)).to.equal("4:0:0"); - expect(tone.toTransportTime(3)).to.equal("1:0:0"); - }); - }); - - describe("Tone.frequencyToSeconds", function(){ - - it("converts frequencies as a string or number", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.frequencyToSeconds("1hz")).to.equal(1); - expect(tone.frequencyToSeconds(".5")).to.equal(2); - expect(tone.secondsToFrequency("5")).to.equal(0.2); - }); - }); - - describe("Tone.toFrequency", function(){ - - it("infers type correctly", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.toFrequency("1hz")).to.equal(1); - expect(tone.toFrequency("4n")).to.equal(2); - expect(tone.toFrequency(500)).to.equal(500); - }); - }); - - describe("Tone.toSeconds", function(){ - - it("correctly infers type", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.toSeconds("5")).to.equal(5); - expect(tone.toSeconds("1m")).to.equal(2); - expect(tone.toSeconds("1")).to.equal(1); - expect(tone.toSeconds("1:0:0")).to.equal(2); - expect(tone.toSeconds("2hz")).to.equal(0.5); - }); - - it("handles 'now' relative values", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - var now = tone.now(); - expect(tone.toSeconds("+5")).to.be.closeTo(now + 5, 0.01); - now = tone.now(); - expect(tone.toSeconds("+4n")).to.be.closeTo(now + 0.5, 0.01); - now = tone.now(); - expect(tone.toSeconds("+1:0")).to.be.closeTo(now + 2, 0.01); - }); - - it("with no arguments returns 'now'", function(){ - var now = tone.now(); - expect(tone.toSeconds()).to.be.closeTo(now, 0.01); - }); - - it("can evaluate mathematical expressions of time", function(){ - Transport.stop(); - Transport.bpm.value = 120; - Transport.timeSignature = 4; - expect(tone.toSeconds("1+2+3")).to.equal(6); - expect(tone.toSeconds("2*1:2 - 1m")).to.equal(4); - }); - }); -}); \ No newline at end of file diff --git a/test/tests/Transport.js b/test/tests/Transport.js deleted file mode 100644 index 434d7d874..000000000 --- a/test/tests/Transport.js +++ /dev/null @@ -1,235 +0,0 @@ -/* global it, describe, beforeEach, maxTimeout */ - -define(["chai", "Tone/core/Transport", "tests/Core", "tests/Common", "Tone/core/Clock", "Tone/signal/Signal"], - function(chai, Transport, Core, Test, Clock, Signal){ - var expect = chai.expect; - - describe("Tone.Clock", function(){ - this.timeout(maxTimeout); - - it("can be created and destroyed", function(){ - var c = new Clock(); - c.dispose(); - Test.wasDisposed(c); - }); - - it("invokes a callback at regular intervals", function(done){ - var tickCount = 0; - var clock; - Test.offlineTest(11, function(){ - clock = new Clock(1, function(){ - tickCount++; - }); - clock.start(); - }, function(){ - }, function(){ - expect(tickCount).to.be.above(9); - done(); - }); - }); - - it("invokes the callback when stopped", function(done){ - Test.onlineContext(); - var clock = new Clock(0.5, function(){}); - clock.start(); - clock.onended = function(){ - done(); - }; - clock.stop("+0.5"); - }); - }); - - describe("Transport.setBpm / getBpm", function(){ - this.timeout(maxTimeout); - - it("is the right bpm after starting", function(done){ - var duration = 2; - Test.offlineTest(duration, function(){ - Transport.start(); - }, function(){ - }, function(){ - expect(Transport.bpm.value).to.equal(120); - done(); - }); - }); - - - it("is the right bpm after stopping", function(done){ - Test.offlineTest(0.2, function(){ - }, function(){ - }, function(){ - expect(Transport.bpm.value).to.equal(120); - done(); - }); - }); - - it("ramps to the right value", function(done){ - var duration = 2; - Test.offlineTest(duration, function(){ - expect(Transport.bpm.value).to.equal(120); - Transport.start(); - Transport.bpm.rampTo(200, 0.05); - }, function(){ - }, function(){ - expect(Transport.bpm.value).to.equal(200); - done(); - }); - }); - - it("can sync a signal to the bpm", function(done){ - var signalSync; - Test.offlineTest(0.1, function(dest){ - Transport.bpm.value = 120; - signalSync = new Signal(5); - Transport.syncSignal(signalSync); - Transport.bpm.value = 240; - signalSync.connect(dest); - }, function(sample){ - expect(sample).to.equal(10); - }, function(){ - signalSync.dispose(); - done(); - }); - }); - }); - - describe("Transport.setTimeout", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Transport.clearTimeouts(); - }); - - it("invokes a callback at the start", function(done){ - Transport.bpm.value = 240; - var wasCalled = false; - var duration = 2; - Test.offlineTest(duration, function(){ - Transport.setTimeout(function(){ - wasCalled = true; - }, 0); - Transport.start(); - }, undefined, function(){ - expect(wasCalled).to.be.true; - done(); - }); - }); - - it("invokes the callback in the future", function(done){ - Transport.bpm.value = 240; - var duration = 4; - var firstCallback = 0; - var callbackTime = 0; - Test.offlineTest(duration, function(){ - Transport.setTimeout(function(time){ - firstCallback = time; - }, 0); - Transport.setTimeout(function(time){ - callbackTime = time; - }, "4n"); - Transport.start(); - }, function(){}, function(){ - expect(callbackTime - firstCallback).to.be.closeTo(0.25, 0.1); - done(); - }); - }); - - it("invokes a callback at the right time even when the transport is looping at a smaller interval", function(done){ - Transport.loop = true; - Transport.setLoopPoints(0, 0.3); - var duration = 2; - Test.offlineTest(duration, function(){ - Transport.setTimeout(function(){ - Transport.loop = false; - done(); - }, 1); - Transport.start(); - }); - }); - - it("can clear a timeout", function(done){ - Transport.bpm.value = 120; - Test.offlineTest(2, function(){ - var timeoutId = Transport.setTimeout(function(){ - throw new Error("should not have called this"); - }, "4n"); - Transport.clearTimeout(timeoutId); - Transport.start(); - }, function(){}, function(){ - done(); - }); - }); - }); - - describe("Transport.setInterval", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Transport.clearIntervals(); - }); - - it("invokes a repeated event", function(done){ - Transport.bpm.value = 120; - var callbackCount = 0; - Test.offlineTest(4, function(){ - Transport.setInterval(function(){ - callbackCount++; - }, "4n"); - Transport.start(); - }, function(){}, function(){ - expect(callbackCount).to.be.greaterThan(7); - done(); - }); - }); - - it("can clear an interval", function(done){ - Transport.bpm.value = 120; - Test.offlineTest(2, function(){ - var timeoutId = Transport.setInterval(function(){ - throw new Error("should not have called this"); - }, "4n"); - Transport.clearInterval(timeoutId); - Transport.start(); - }, function(){}, function(){ - done(); - }); - }); - }); - - - describe("Transport.setTimeline", function(){ - this.timeout(maxTimeout); - - beforeEach(function(){ - Transport.clearTimelines(); - }); - - it("invokes the callback", function(done){ - Transport.bpm.value = 240; - var wasCalled = false; - Test.offlineTest(2, function(){ - Transport.setTimeline(function(){ - wasCalled = true; - }, "0:3:0"); - Transport.start(); - }, undefined, function(){ - expect(wasCalled).to.be.true; - done(); - }); - }); - - - it("can clear a timeline event", function(done){ - Transport.bpm.value = 120; - Test.offlineTest(2, function(){ - var timeoutId = Transport.setTimeline(function(){ - throw new Error("should not have called this"); - }, "4n"); - Transport.clearTimeline(timeoutId); - Transport.start(); - }, function(){}, function(){ - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/utils/MidiToScore.js b/utils/MidiToScore.js deleted file mode 100644 index 9222f82eb..000000000 --- a/utils/MidiToScore.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Pass in a MIDI File as the command line argument to this Node.js - * script and it will output a js file in the same directory. - * - * @example - * node MidiToScore.js mySong.mid - */ - -var midiFileParser = require("midi-file-parser"); -var fs = require("fs"); - -var file = fs.readFileSync(process.argv[2], "binary"); - -var midi = midiFileParser(file); - -var output = {}; - -var tempo = 120; -var ticksPerBeat = midi.header.ticksPerBeat; -var timeSignature = [4, 4]; -var microsecondsPerBeat = 60000000 / tempo; - -//parse the tracks -for (var i = 0; i < midi.tracks.length; i++) { - var data = midi.tracks[i]; - var trackName = "track"+i; - var trackNotes = []; - var currentTime = 0; - for (var j = 0; j < data.length; j++) { - //meta tag stuff - var evnt = data[j]; - if (evnt.type === "meta"){ - if (evnt.subtype === "timeSignature"){ - timeSignature[0] = evnt.numerator; - timeSignature[1] = evnt.denominator; - } else if (evnt.subtype === "setTempo"){ - tempo = 60000000 / evnt.microsecondsPerBeat; - microsecondsPerBeat = evnt.microsecondsPerBeat; - } else if (evnt.subtype === "trackName"){ - trackName = evnt.text; - } - } else { - if (evnt.subtype === "noteOn" || evnt.subtype === "noteOff"){ - currentTime += evnt.deltaTime; - var time = deltaTimeToMeter(currentTime); - var note = midiToNote(evnt.noteNumber); - var velocity = evnt.velocity / 127; - if (evnt.subtype === "noteOff"){ - velocity = 0; - } - trackNotes.push([time, note, velocity.toFixed(2)]); - } - } - } - output[trackName] = trackNotes; -} - -output.tempo = tempo; -output.timeSignature = timeSignature; - -//write it to the output file -fs.writeFileSync(process.argv[2]+".js", JSON.stringify(output)); - -function deltaTimeToMeter(deltaTime){ - var timeSigValue = timeSignature[0] / (timeSignature[1] / 4); - // return deltaTime; - var quarters = deltaTime / ticksPerBeat; - var measures = Math.floor(quarters / timeSigValue); - var sixteenths = (quarters % 1) * 4; - quarters = Math.floor(quarters) % timeSigValue; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); -} - - - -function midiToNote(noteNum){ - var noteIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; - var octave = Math.floor(noteNum / 12) - 2; - var note = noteNum % 12; - return noteIndexToNote[note] + octave; -} \ No newline at end of file diff --git a/utils/TypeScript/Tone.d.ts b/utils/TypeScript/Tone.d.ts deleted file mode 100644 index ce5c2cb55..000000000 --- a/utils/TypeScript/Tone.d.ts +++ /dev/null @@ -1,1109 +0,0 @@ -/// - -// Type definitions for TONE.JS -// Project: https://github.com/TONEnoTONE/Tone.js -// Definitions by: Luke Phillips -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -//var Tone: { -// new(inputs?: number, outputs?: number): Tone; -//} - -interface Tone { - new(inputs?: number, outputs?: number): Tone; - context: AudioContext; - input: GainNode; - output: GainNode; - chain(...nodes: any[]): Tone; - connect(unit: any, outputNum?:number, inputNum?:number): Tone; - connectSeries(...args: any[]): Tone; - connectParallel(...args: any[]): Tone; - dbToGain(db: number): number; - defaultArg(given: any, fallback: any): any; - disconnect(outputNum?:number): Tone; - dispose(): Tone; - equalPowerScale(percent:number): number; - expScale(gain: number): number; - extend(child: ()=>any, parent?: ()=>any): void; - fan(...nodes: any[]): Tone; - frequencyToNote(freq:number):string; - frequencyToSeconds(freq:number):number; - gainToDb(gain: number): number; - get(params?:any): any; - interpolate(input: number, outputMin: number, outputMax: number): number; - isFrequency(freq: number): boolean; - isFunction(arg: any): boolean; - isUndef(arg: any): boolean; - midiToNote(midiNumber: number): string; - noGC(): Tone; - normalize(input: number, inputMin: number, inputMax: number): number; - notationToSeconds(notation: string, bpm?: number, timeSignature?: number): number; - noteToFrequency(note: string): number; - noteToMidi(note: string): number; - now(): number; - optionsObject(values: Array, keys: Array, defaults?:Object): Object; - receive(channelName: string, input?: AudioNode): Tone; - samplesToSeconds(samples: number): number; - secondsToFrequency(seconds: number): number; - send(channelName: string, amount?: number): Tone; - set(params: any, value?: number, rampTime?: Tone.Time): Tone; - setContext(ctx: AudioContext): void; - setPreset(presetName: string): Tone; - startMobile(): void; - toFrequency(note: Tone.Frequency, now?: number): number; - toMaster(): Tone; - toSamples(time: Tone.Time): number; - toSeconds(time?: number, now?: number): number; -} - -declare module Tone { - - var Abs: { - new(): Tone.Abs; - }; - - interface Abs extends Tone.SignalBase { - dispose(): Tone.Abs; - } - - var Add: { - new(value?:number): Tone.Add; - }; - - interface Add extends Tone.Signal { - dispose(): Tone.Add; - } - - var AmplitudeEnvelope: { - new(attack?: any, decay?: Tone.Time, sustain?: number, release?:Tone.Time): Tone.AmplitudeEnvelope; //TODO: Change 'any' to 'Tone.Time | Object' - }; - - interface AmplitudeEnvelope extends Tone.Envelope { - dispose(): Tone.AmplitudeEnvelope; - } - - var AMSynth: { - new(options?: Object): Tone.AMSynth; - }; - - interface AMSynth extends Tone.Monophonic { - carrier: Tone.MonoSynth; - frequency: Tone.Signal; - harmonicity: number; - modulator: Tone.MonoSynth; - dispose(): Tone.AMSynth; - triggerEnvelopeAttack(time?: Tone.Time, velocity?: number): Tone.AMSynth; - triggerEnvelopeRelease(time?: Tone.Time): Tone.AMSynth; - } - - var AND: { - new(inputCount?:number): Tone.AND; - }; - - interface AND extends Tone.SignalBase { - dispose(): Tone.AND; - } - - var AudioToGain: { - new(): Tone.AudioToGain; - }; - - interface AudioToGain extends Tone.SignalBase { - dispose(): Tone.AudioToGain; - } - - var AutoPanner: { - new(frequency?: any): Tone.AutoPanner; //TODO: Number || Object - }; - - interface AutoPanner extends Effect { - amount: Tone.Signal; - frequency: Tone.Signal; - type: string; - dispose(): Tone.AutoPanner; - start(Time?: Tone.Time): Tone.AutoPanner; - stop(Time?: Tone.Time): Tone.AutoPanner; - sync(): Tone.AutoPanner; - unsync(): Tone.AutoPanner; - } - - var AutoWah: { - new(baseFrequency?: any, octaves?: number, sensitivity?:number): Tone.AutoWah; //Todo number | Object - }; - - interface AutoWah extends Tone.Effect { - baseFrequency: Tone.Frequency; - gain: Tone.Signal; - octaves: number; - Q: Tone.Signal; - sensitivity: number; - dispose(): Tone.AutoWah; - } - - var BitCrusher: { - new(bits: any): Tone.BitCrusher; //TODO: Number || Object - }; - - interface BitCrusher extends Tone.Effect { - bits: number; - dispose(): Tone.BitCrusher; - } - - var Buffer: { - new(url: any): Tone.Buffer; //TODO: Change 'any' to 'AudioBuffer | string' when available - }; - - interface Buffer extends Tone { - MAX_SIMULTANEOUS_DOWNLOADS: number; - duration: number; // Readonly - loaded: boolean; // Readonly - onload: (e: any)=>any; - url: string; // Readonly - load(url:string, callback?: (e: any)=>any): Tone.Buffer; - onerror(); - onprogress(); - dispose(): Tone.Buffer; - get(): AudioBuffer; - set(buffer: any): Tone.Buffer; //TODO: change any to AudioBuffer | Tone.Buffer - } - - var Chebyshev: { - new(order: any): Tone.Chebyshev; //TODO: Number || Object - }; - - interface Chebyshev extends Tone.Effect { - order: number; - oversample: string; - dispose(): Tone.Chebyshev; - } - - var Chorus: { - new(rate?: any, delayTime?: number, depth?: number): Tone.Chorus; - }; - - interface Chorus extends Tone.StereoXFeedbackEffect { - delayTime: number - depth: number; - frequency: Tone.Signal; - type: string; - dispose(): Tone.Chorus; - } - - var Clip: { - new(min: number, max: number): Tone.Clip; - }; - - interface Clip extends Tone.SignalBase { - max: Tone.Signal; - min: Tone.Signal; - dispose(): Tone.Clip; - } - - var Compressor: { - new(threshold?: any, ratio?: number): Tone.Compressor; //TODO: Number || Object - }; - - interface Compressor extends Tone { - attack: Tone.Signal; - knee: AudioParam; - ratio: AudioParam; - release: Tone.Signal; - threshold: AudioParam; - dispose(): Tone.Compressor; - } - - var Convolver: { - new(url: any): Tone.Convolver; //TODO: Change any to 'string | AudioBuffer' when available - }; - - interface Convolver extends Tone.Effect { - buffer: AudioBuffer; - load(url: string, callback?: (e: any)=>any): Tone.Convolver; - dispose(): Tone.Convolver; - } - - var CrossFade: { - new(initialFade?: number): Tone.CrossFade; - }; - - interface CrossFade extends Tone { - a: GainNode; - b: GainNode; - fade: Tone.Signal; - dispose(): Tone.CrossFade; - } - - var Distortion: { - new(distortion: any): Tone.Distortion; //TODO: Number || Object - }; - - interface Distortion extends Tone.Effect { - distortion: number; - oversample: string; - dispose(): Tone.Distortion; - } - - var DuoSynth: { - new(options?: any): Tone.DuoSynth; - }; - - interface DuoSynth extends Tone.Monophonic { - frequency: Tone.Signal; - harmonicity: number; - vibratoAmount: Tone.Signal; - vibratoRate: Tone.Signal; - voice0: Tone.MonoSynth; - voice1: Tone.MonoSynth; - triggerEnvelopeAttack(time?: Tone.Time, velocity?: number): Tone.DuoSynth; - triggerEnvelopeRelease(time?: Tone.Time): Tone.DuoSynth; - } - - var Effect: { - new(initialWet?: number): Tone.Effect; - }; - - interface Effect extends Tone { - wet: Tone.Signal; - bypass(): Tone.Effect; - dispose(): Tone.Effect; - } - - var Envelope: { - new(attack: any, decay?: Tone.Time, sustain?: number, release?: Tone.Time): Tone.Envelope; //TODO: Change 'any' to 'Tone.Time | Object' - }; - - interface Envelope extends Tone { - attack: Tone.Time; - decay: Tone.Time; - release: Tone.Time; - sustain: number; - dispose(): Tone.Envelope; - triggerAttack(time?: Tone.Time, velocity?: number): Tone.Envelope; - triggerAttackRelease(duration: Tone.Time, time?: Tone.Time, velocity?: number): Tone.Envelope; - triggerRelease(time?: Tone.Time): Tone.Envelope; - } - - var EQ3: { - new(lowLevel?: any, midLevel?: number, highLevel?: number): Tone.EQ3; //TODO: Change 'any' to 'number | Object' - }; - - interface EQ3 extends Tone { - highFrequency: Tone.Signal; - high: GainNode; - lowFrequency: Tone.Signal; - low: GainNode; - mid: GainNode; - dispose(): Tone.EQ3; - } - - var Equal: { - new(value: number): Tone.Equal; - }; - - interface Equal extends Tone.SignalBase { - value: number; - dispose(): Tone.Equal; - } - - var EqualPowerGain: { - new(): Tone.EqualPowerGain; - }; - - interface EqualPowerGain extends Tone.SignalBase { - dispose(): Tone.EqualPowerGain; - } - - var EqualZero: { - new(): Tone.EqualZero; - }; - - interface EqualZero extends Tone.SignalBase { - dispose(): Tone.EqualZero; - } - - var Expr: { - new(expr: string): Tone.Expr; - }; - - interface Expr extends Tone.SignalBase { - input: any; //todo: any[] - output: any; // todo: Tone - dispose(): Tone.Expr; - } - - var FeedbackCombFilter: { - new(minDelay?: number, maxDelay?: number): Tone.FeedbackCombFilter; - }; - - interface FeedbackCombFilter extends Tone { - delayTime: Tone.Time; - resonance: Tone.Signal; - dispose(): Tone.FeedbackCombFilter; - } - - var FeedbackDelay: { - new(delayTime: any): Tone.FeedbackDelay; - }; - - interface FeedbackDelay extends Tone.FeedbackEffect { - delayTime: Tone.Signal; - dispose(): Tone.FeedbackDelay; - } - - var FeedbackEffect: { - new(initialFeedback?: any): Tone.FeedbackEffect; - }; - - interface FeedbackEffect extends Tone.Effect { - feedback: Tone.Signal; - dispose(): Tone.FeedbackEffect; - } - - var Filter: { - new(freq?: any, type?: string, rolloff?: number): Tone.Filter; //TODO: Number || Object - }; - - interface Filter extends Tone { - detune: Tone.Signal; - frequency: Tone.Signal; - gain: AudioParam; - Q: Tone.Signal; - rolloff: number; - type: string; - dispose(): Tone.Filter; - } - - var FMSynth: { - new(options?: any): Tone.FMSynth; - }; - - interface FMSynth extends Tone.Monophonic { - carrier: Tone.MonoSynth; - frequency: Tone.Signal; - harmonicity: number; - modulationIndex: number; - modulator: Tone.MonoSynth; - dispose(): Tone.FMSynth; - triggerEnvelopeAttack(time?: Tone.Time, velocity?: number): Tone.FMSynth; - triggerEnvelopeRelease(time?: Tone.Time): Tone.FMSynth; - } - - var Follower: { - new(attack?: Tone.Time, release?: Tone.Time): Tone.Follower; - }; - - interface Follower extends Tone { - attack: Tone.Time; - release: Tone.Time; - dispose(): Tone.Follower; - } - - var Freeverb: { - new(roomSize?: any, dampening?: number): Tone.Freeverb; - }; - - interface Freeverb extends Tone.Effect { - dampening: Tone.Signal; - roomSize: Tone.Signal; - dispose(): Tone.Freeverb; - } - - interface Frequency{} - - var Gate: { - new(thresh?: number, attackTime?: Tone.Time, releaseTime?: Tone.Time): Tone.Gate; - }; - - interface Gate extends Tone { - attack: Tone.Time; - release: Tone.Time; - threshold: Tone.Time; - dispose(): Tone.Gate; - } - - var GreaterThan: { - new(value?: number): Tone.GreaterThan; - }; - - interface GreaterThan extends Tone.Signal { - dispose(): Tone.GreaterThan; - } - - var GreaterThanZero: { - new(): Tone.GreaterThanZero; - }; - - interface GreaterThanZero extends Tone.SignalBase { - dispose(): Tone.GreaterThanZero; - } - - var IfThenElse: { - new(): Tone.IfThenElse; - }; - - interface IfThenElse extends Tone.SignalBase { - dispose(): Tone.IfThenElse; - } - - var Instrument: { - new(): Tone.Instrument; - }; - - interface Instrument extends Tone { - volume: Tone.Signal; - triggerAttack(note: any, time?: Tone.Time, velocity?: number): Tone.Instrument; //Todo: string | number - triggerAttackRelease(note: any, duration: Tone.Time, time?: Tone.Time, velocity?: number): Tone.Instrument; //Todo: string | number - triggerRelease(time?: Tone.Time): Tone.Instrument; - dispose(): Tone.Instrument; - } - - var JCReverb: { - new(roomSize: number): Tone.JCReverb; //TODO: Number || Object - }; - - interface JCReverb extends Tone.Effect { - roomSize: Tone.Signal; - dispose(): Tone.JCReverb; - } - - var LessThan: { - new(value?: number): Tone.LessThan; - }; - - interface LessThan extends Tone.Signal { - dispose(): Tone.LessThan; - } - - var LFO: { - new(frequency?: Tone.Time, outputMin?: number, outputMax?: number): Tone.LFO; //TODO: Number || Object - }; - - interface LFO extends Tone.Oscillator { - amplitude: Tone.Signal; - frequency: Tone.Signal; - max: number; - min: number; - oscillator: Tone.Oscillator; - phase: number; - type: string; - dispose(): Tone.LFO; - start(time?: Tone.Time): Tone.LFO; - stop(time?: Tone.Time): Tone.LFO; - sync(delay?: Tone.Time): Tone.LFO; - unsync(): Tone.LFO; - } - - var Limiter: { - new(threshold: AudioParam): Tone.Limiter; - }; - - interface Limiter extends Tone { - dispose(): Tone.Limiter; - } - - var LowpassCombFilter: { - new(minDelay?: number, maxDelay?: number): Tone.LowpassCombFilter; - }; - - interface LowpassCombFilter extends Tone { - dampening: Tone.Signal; - delayTime: Tone.Time; - resonance: Tone.Signal; - dispose(): Tone.LowpassCombFilter; - setDelayTimeAtTime(delayAmount: Tone.Time, time?: Tone.Time): Tone.LowpassCombFilter; - } - - var Master: Tone.Master; - - interface Master extends Tone { - volume: Tone.Signal; - mute(): Tone.Master; - unmute(): Tone.Master; - receive(node:any): Tone.Master; //todo: AudioNode | Tone - send(node:any): Tone.Master; //todo: AudioNode | Tone - } - - var Max: { - new(max?: number): Tone.Max; - }; - - interface Max extends Tone.Signal { - dispose(): Tone.Max; - } - - var Merge: { - new(): Tone.Merge; - }; - - interface Merge extends Tone { - left: GainNode; - right: GainNode; - dispose(): Tone.Merge; - } - - var Meter: { - new(channels?: number, smoothing?: number, clipMemory?:number): Tone.Meter; - }; - - interface Meter extends Tone { - dispose(): Tone.Meter; - getDb(channel?:number): number; - getLevel(channel?:number): number; - getValue(channel?:number): number; - isClipped(): boolean; - } - - var Microphone: { - new(inputNum?: number): Tone.Microphone; - }; - - interface Microphone extends Tone.Source { - dispose(): Tone.Microphone; - } - - var MidSideEffect : { - new(): Tone.MidSideEffect; - }; - - interface MidSideEffect extends Tone.StereoEffect { - midReturn: GainNode; - midSend: Tone.Expr; - sideReturn: GainNode; - sideSend: Tone.Expr; - dispose(): Tone.MidSideEffect; - } - - var Min: { - new(min: number): Tone.Min; - }; - - interface Min extends Tone.Signal { - dispose(): Tone.Min; - } - - var Modulo: { - new(modulus: number, bits?:number): Tone.Modulo; - }; - - interface Modulo extends Tone.SignalBase { - value: number; - dispose(): Tone.Modulo; - } - - var Mono: { - new(): Tone.Mono; - }; - - interface Mono extends Tone { - dispose(): Tone.Mono; - } - - var Monophonic: { - new(): Tone.Monophonic; - }; - - interface Monophonic extends Tone.Instrument { - portamento: Tone.Time; - setNote(note: any):Tone.Monophonic; //Todo: number | string - } - - var MonoSynth: { - new(options?: any): Tone.MonoSynth; - }; - - interface MonoSynth extends Tone.Monophonic { - detune: Tone.Signal; - envelope: Tone.Envelope; - filter: Tone.Filter; - filterEnvelope: Tone.Envelope; - frequency: Tone.Signal; - oscillator: Tone.OmniOscillator; - dispose(): Tone.MonoSynth; - triggerEnvelopeAttack(time?: Tone.Time, velocity?: number): Tone.MonoSynth; - triggerEnvelopeRelease(time?: Tone.Time): Tone.MonoSynth; - } - - var MultibandCompressor: { - new(options: Object): Tone.MultibandCompressor; - }; - - interface MultibandCompressor extends Tone { - high: Tone.Compressor; - highFrequency: Tone.Signal; - low: Tone.Compressor; - lowFrequency: Tone.Signal; - mid: Tone.Compressor; - dispose(): Tone.MultibandCompressor; - } - - var MultibandEQ: { - new(options?: any): Tone.MultibandEQ; - }; - - interface MultibandEQ extends Tone { - //set(params: Object): void; - setType(type: string, band: number): void; - getType(band: number): string; - setFrequency(freq: number, band: number): void; - getFrequency(band: number): number; - setQ(Q: number, band: number): void; - getQ(band: number): number; - getGain(band: number): number; - setGain(gain: number, band: number): void; - } - - var MultibandSplit: { - new(lowFrequency: number, highFrequency: number): Tone.MultibandSplit; - }; - - interface MultibandSplit extends Tone { - high: Tone.Filter; - highFrequency: Tone.Signal; - low: Tone.Filter; - lowFrequency: Tone.Signal; - mid: Tone.Filter; - dispose(): Tone.MultibandSplit; - } - - var Multiply: { - new(value?: number): Tone.Multiply; - }; - - interface Multiply extends Tone.Signal { - dispose(): Tone.Multiply; - } - - var Negate: { - new(): Tone.Negate; - }; - - interface Negate extends Tone.SignalBase { - dispose(): Tone.Negate; - } - - var Noise: { - new(type: string): Tone.Noise; - }; - - interface Noise extends Tone.Source { - type: string; - dispose(): Tone.Noise; - } - - var NoiseSynth: { - new(options?: Object): Tone.NoiseSynth; - }; - - interface NoiseSynth extends Tone.Instrument { - envelope: Tone.Envelope; - filter: Tone.Filter; - filterEnvelope: Tone.Envelope; - noise: Tone.Noise; - dispose(): Tone.NoiseSynth; - triggerAttack(time?: Tone.Time, velocity?: number): Tone.NoiseSynth; - triggerAttackRelease(duration: Tone.Time, time?: Tone.Time, velocity?: number): Tone.NoiseSynth; - triggerRelease(time?: Tone.Time): Tone.NoiseSynth; - } - - var Normalize: { - new(min?: number, max?: number): Tone.Normalize; - }; - - interface Normalize extends Tone.SignalBase { - max: number; - min: number; - dispose(): Tone.Normalize; - } - - var Note: { - new(channel: any, time:Tone.Time, value: any): Tone.Note; //todo: channel: number|string, value: string|number|Object|Array - }; - - interface Note { - value: any; //todo: string | number | Object - parseScore(score: Object): Tone.Note[]; - route(channel:any, callback?: (e: any)=>any): void; //todo: string | number - unroute(channel: any, callback?: (e: any)=>any): void; //todo: string | number; - dispose(): Tone.Note; - } - - var OmniOscillator: { - new(frequency?: Tone.Frequency, type?: string): Tone.OmniOscillator; //TODO: Number || Object - }; - - interface OmniOscillator extends Tone.Source { - detune: Tone.Signal; - frequency: Tone.Signal; - modulationFrequency: Tone.Signal; - phase: number; - type: string; - width: Tone.Signal; - dispose(): Tone.OmniOscillator; - } - - var OR: { - new(inputCount?:number): Tone.OR; - }; - - interface OR extends Tone.SignalBase { - dispose(): Tone.OR; - } - - var Oscillator: { - new(frequency?: any, type?: string): Tone.Oscillator; //todo: number | string - }; - - interface Oscillator extends Tone.Source { - detune: Tone.Signal; - frequency: Tone.Signal; - phase: number; - type: string; - dispose(): Tone.Oscillator; - syncFrequency(): Tone.Oscillator; - unsyncFrequency(): Tone.Oscillator; - } - - var Panner: { - new(initialPan?: number): Tone.Panner; - }; - - interface Panner extends Tone { - pan: Tone.Signal; - dispose(): Tone.Panner; - } - - var PanVol: { - new(pan: number, volume: number): Tone.PanVol; - }; - - interface PanVol extends Tone { - output: GainNode; - volume: Tone.Signal; - dispose(): Tone.PanVol; - } - - var Phaser: { - new(rate?: any, depth?: number, baseFrequency?: number): Tone.Phaser; //TODO: change 'any' to 'number | Object' - }; - - interface Phaser extends Tone.StereoEffect { - baseFrequency: number; - depth: number; - frequency: Tone.Signal; - dispose(): Tone.Phaser; - } - - var PingPongDelay: { - new(delayTime?: any, feedback?: number): Tone.PingPongDelay; //TODO: Tone.Time || Object - }; - - interface PingPongDelay extends Tone.StereoXFeedbackEffect { - delayTime: Tone.Signal; - dispose(): Tone.PingPongDelay; - } - - var Player: { - new(url?: string, onload?: (e: any)=>any): Tone.Player; //todo: string | AudioBuffer - }; - - interface Player extends Tone.Source { - buffer: AudioBuffer; - duration: number; - loop: boolean; - loopEnd: Tone.Time; - loopStart: Tone.Time; - playbackRate: number; - retrigger: boolean; - dispose(): Tone.Player; - load(url:string, callback?:(e: any)=>any): Tone.Player; - setLoopPoints(loopStart:Tone.Time, loopEnd:Tone.Time): Tone.Player; - } - - var PluckSynth : { - new(options?: Object): Tone.PluckSynth; - }; - - interface PluckSynth extends Tone.Instrument { - attackNoise: number; - dampening: Tone.Signal; - resonance: Tone.Signal; - dispose(): Tone.PluckSynth; - triggerAttack(note: any, time?: Tone.Time): Tone.PluckSynth; //todo: string | number - } - - var PolySynth : { - new(voicesAmount?: any, voice?: ()=>any): Tone.PolySynth; // number | Object - }; - - interface PolySynth extends Tone.Instrument { - voices: any[]; - dispose(): Tone.PolySynth; - get(params?: any[]); - set(params: Object); - setPreset(presetName: string): Tone.PolySynth; - triggerAttack(value: any, time?: Tone.Time, velocity?: number): Tone.PolySynth; //todo: string | number | Object| string[] | number[] - triggerAttackRelease(value: any, duration: Tone.Time, time?: Tone.Time, velocity?: number): Tone.PolySynth; //todo: string | number | Object | string[] | number[] - triggerRelease(value: any, time?: Tone.Time): Tone.PolySynth; //todo: string | number | Object | string[] | number[] - } - - var Pow: { - new(exp: number): Tone.Pow; - }; - - interface Pow extends Tone.SignalBase { - value: number; - dispose(): Tone.Pow; - } - - var PulseOscillator: { - new(frequency?: number, width?:number): Tone.PulseOscillator; - }; - - interface PulseOscillator extends Tone.Oscillator { - detune: Tone.Signal; - frequency: Tone.Signal; - phase: number; - width: Tone.Signal; - dispose(): Tone.PulseOscillator; - } - - var PWMOscillator: { - new(frequency?: Tone.Frequency, modulationFrequency?: number): Tone.PWMOscillator; - }; - - interface PWMOscillator extends Tone.Oscillator { - detune: Tone.Signal; - frequency: Tone.Signal; - modulationFrequency :Tone.Signal; - phase: number; - width: Tone.Signal; - dispose(): Tone.PWMOscillator; - } - - var Route: { - new(outputCount?: number): Tone.Route; - }; - - interface Route extends Tone.SignalBase { - gate: Tone.Signal; - dispose(): Tone.Route; - select(which?: number, time?: Tone.Time): Tone.Route; - } - - var Sampler: { - new(urls: any, options?: Object): Tone.Sampler; //todo: Object | string - }; - - interface Sampler extends Tone.Instrument { - envelope: Tone.Envelope; - filter: BiquadFilterNode; - filterEnvelope: Tone.Envelope; - pitch: number; - player: Tone.Player; - sample: any; //todo: number | string - dispose(): Tone.Sampler; - triggerAttack(sample?: string, time?: Tone.Time, velocity?: number): Tone.Sampler; - triggerRelease(time?: Tone.Time): Tone.Sampler; - } - - var Scale: { - new(outputMin?: number, outputMax?: number): Tone.Scale; - }; - - interface Scale extends Tone.SignalBase { - max: number; - min: number; - dispose(): Tone.Scale; - } - - var ScaledEnvelope: { - new(attack?: any, decay?: Tone.Time, sustain?: number, release?:Tone.Time): Tone.ScaledEnvelope; //TODO: Change 'any' to 'Tone.Time | Object' - }; - - interface ScaledEnvelope extends Tone.Envelope { - exponent: number; - max: number; - min: number; - dispose(): Tone.ScaledEnvelope; - } - - var ScaleExp: { - new(outputMin?: number, outputMax?: number, exponent?: number): Tone.ScaleExp; - }; - - interface ScaleExp extends Tone.SignalBase { - exponent: number; - max: number; - min: number; - dispose(): Tone.ScaleExp; - } - - var Select: { - new(sourceCount?: number): Tone.Select; - }; - - interface Select extends Tone.SignalBase { - gate: Tone.Signal; - dispose(): Tone.Select; - select(which: number, time?: Tone.Time): Tone.Select; - } - - module Signal { - interface Unit{} - interface Type{} - } - - var Signal: { - new(value?: any, units?: Tone.Signal.Unit): Tone.Signal; //todo: number | AudioParam - }; - - interface Signal extends Tone.SignalBase { - units: Tone.Signal.Type; - value: any; //TODO: Tone.Time | Tone.Frequency | number - cancelScheduledValues(startTime: Tone.Time): Tone.Signal; - dispose(): Tone.Signal; - exponentialRampToValueAtTime(value: number, endTime: Tone.Time): Tone.Signal; - exponentialRampToValueNow(value: number, rampTime: Tone.Time): Tone.Signal; - linearRampToValueAtTime(value: number, endTime: Tone.Time): Tone.Signal; - linearRampToValueNow(value: number, rampTime: Tone.Time): Tone.Signal; - rampTo(value: number, rampTime: Tone.Time): Tone.Signal; - setCurrentValueNow(now?: number): Tone.Signal; - setTargetAtTime(value: number, startTime: Tone.Time, timeConstant: number): Tone.Signal; - setValueAtTime(value: number, time: Tone.Time): Tone.Signal; - setValueCurveAtTime(values: number[], startTime: Tone.Time, duration: Tone.Time): Tone.Signal; - } - - var SignalBase: { - new(): Tone.SignalBase; - }; - - interface SignalBase extends Tone { - connect(node: any, outputNumber?: number, inputNumber?: number): Tone.SignalBase; //TODO: Change 'any' to 'AudioParam | AudioNode | Tone.Signal | Tone' when available - } - - var Source: { - new(): Tone.Source; - }; - - interface Source extends Tone { - State: string; - onended: ()=>any; - state: Tone.Source.State; - volume: Tone.Signal; - dispose(): Tone.Source; - start(time?: Tone.Time): Tone.Source; - stop(time?: Tone.Time): Tone.Source; - sync(delay?: Tone.Time): Tone.Source; - unsync(): Tone.Source; - } - - module Source { - interface State{} - } - - var Split: { - new(): Tone.Split; - }; - - interface Split extends Tone { - left: GainNode; - right: GainNode; - dispose(): Tone.Split; - } - - var StereoEffect: { - new(): Tone.StereoEffect; - }; - - interface StereoEffect extends Tone.Effect { - effectReturnL: GainNode; - effectReturnR: GainNode; - dispose(): Tone.StereoEffect; - } - - var StereoFeedbackEffect: { - new(): Tone.StereoFeedbackEffect; - }; - - interface StereoFeedbackEffect extends Tone.FeedbackEffect { - feedback: Tone.Signal; - dispose(): Tone.StereoFeedbackEffect; - } - - var StereoWidener: { - new(width?: any): Tone.StereoWidener; //TODO change 'any' to 'number | Object' - }; - - interface StereoWidener extends Tone.MidSideEffect { - width: Tone.Signal; - dispose(): Tone.StereoWidener; - } - - var StereoXFeedbackEffect: { - new(): Tone.StereoXFeedbackEffect; - }; - - interface StereoXFeedbackEffect extends Tone.FeedbackEffect { - feedback: Tone.Signal; - dispose(): Tone.StereoXFeedbackEffect; - } - - var Switch: { - new(): Tone.Switch; - }; - - interface Switch extends Tone.SignalBase { - gate: Tone.Signal; - close(time: Tone.Time): Tone.Switch; - dispose(): Tone.Switch; - open(time: Tone.Time): Tone.Switch - } - - interface Time{} - - var Transport: { - new(): Tone.Transport; - }; - - interface Transport extends Tone { - bpm: Tone.Signal; - loop: boolean; - loopEnd: Tone.Time; - loopStart: Tone.Time; - position: string; - state: TransportState; - swing: number; - swingSubdivision: Tone.Time; - timeSignature: number; - clearInterval(rmInterval: number): boolean; - clearIntervals(): void; - clearTimeline(timelineID: number): boolean; - clearTimelines(): void; - clearTimeout(timeoutID: number): boolean; - clearTimeouts(): void; - dispose(): Tone.Transport; - nextBeat(subdivision?: string): number; - pause(time: Tone.Time): Tone.Transport; - setInterval(callback: (e: any)=>any, interval: Tone.Time): number; - setLoopPoints(startPosition: Tone.Time, endPosition: Tone.Time): Tone.Transport; - setTimeline(callback: (e: any)=>any, timeout: Tone.Time): number; - setTimeout(callback: (e: any)=>any, time: Tone.Time): number; - start(time: Tone.Time, offset?: Tone.Time): Tone.Transport; - stop(time: Tone.Time): Tone.Transport; - syncSignal(signal: Tone.Signal, ratio?: number): Tone.Transport; - syncSource(source: Tone.Source, delay: Tone.Time): Tone.Transport; - unsyncSignal(signal: Tone.Signal): Tone.Transport; - unsyncSource(source: Tone.Source): Tone.Transport; - } - - interface TransportState {} - - var WaveShaper: { - new(mapping: any, bufferLen?: number): Tone.WaveShaper; //TODO: change 'any' to 'Function | Array | number' - }; - - interface WaveShaper extends Tone.SignalBase { - curve: number[]; - oversample: string; - } -} \ No newline at end of file diff --git a/utils/TypeScript/WebAudio.d.ts b/utils/TypeScript/WebAudio.d.ts deleted file mode 100644 index 9c4ece6ee..000000000 --- a/utils/TypeScript/WebAudio.d.ts +++ /dev/null @@ -1,1144 +0,0 @@ -// Type definitions for Web Audio API -// Project: http://www.w3.org/TR/webaudio/ -// Definitions by: Baruch Berger , Kon -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -/** - * This interface represents a set of AudioNode objects and their connections. It allows for arbitrary routing of signals to the AudioDestinationNode (what the user ultimately hears). Nodes are created from the context and are then connected together. In most use cases, only a single AudioContext is used per document. An AudioContext is constructed as follows: - * - * var context = new AudioContext(); - */ -interface AudioContext { - /** - * An AudioDestinationNode with a single input representing the final destination for all audio (to be rendered to the audio hardware). All AudioNodes actively rendering audio will directly or indirectly connect to destination. - */ - destination: AudioDestinationNode; - - /** - * The sample rate (in sample-frames per second) at which the AudioContext handles audio. It is assumed that all AudioNodes in the context run at this rate. In making this assumption, sample-rate converters or "varispeed" processors are not supported in real-time processing. - */ - sampleRate: number; - - /** - * This is a time in seconds which starts at zero when the context is created and increases in real-time. All scheduled times are relative to it. This is not a "transport" time which can be started, paused, and re-positioned. It is always moving forward. A GarageBand-like timeline transport system can be very easily built on top of this (in JavaScript). This time corresponds to an ever-increasing hardware timestamp. - */ - currentTime: number; - - /** - * An AudioListener which is used for 3D spatialization. - */ - listener: AudioListener; - - /** - * The number of AudioBufferSourceNodes that are currently playing. - */ - activeSourceCount: number; - - /** - * Creates an AudioBuffer of the given size. The audio data in the buffer will be zero-initialized (silent). An exception will be thrown if the numberOfChannels or sampleRate are out-of-bounds. - * @param numberOfChannels how many channels the buffer will have. An implementation must support at least 32 channels. - * @param length the size of the buffer in sample-frames. - * @param sampleRate the sample-rate of the linear PCM audio data in the buffer in sample-frames per second. An implementation must support sample-rates in at least the range 22050 to 96000. - */ - createBuffer(numberOfChannels: number, length: number, sampleRate: number): AudioBuffer; - - /** - * Creates an AudioBuffer given the audio file data contained in the ArrayBuffer. The ArrayBuffer can, for example, be loaded from an XMLHttpRequest's response attribute after setting the responseType to "arraybuffer". Audio file data can be in any of the formats supported by the audio element. - * The following steps must be performed: - * 1. Decode the encoded buffer from the AudioBuffer into linear PCM. If a decoding error is encountered due to the audio format not being recognized or supported, or because of corrupted/unexpected/inconsistent data then return NULL (and these steps will be terminated). - * 2. If mixToMono is true, then mixdown the decoded linear PCM data to mono. - * 3. Take the decoded (possibly mixed-down) linear PCM audio data, and resample it to the sample-rate of the AudioContext if it is different from the sample-rate of buffer. The final result will be stored in an AudioBuffer and returned as the result of this method. - * @param buffer the audio file data (for example from a .wav file). - * @param mixToMono if a mixdown to mono will be performed. Normally, this would not be set. - */ - createBuffer(buffer: ArrayBuffer, mixToMono: boolean): AudioBuffer; - - /** - * Asynchronously decodes the audio file data contained in the ArrayBuffer. The ArrayBuffer can, for example, be loaded from an XMLHttpRequest's response attribute after setting the responseType to "arraybuffer". Audio file data can be in any of the formats supported by the audio element. - * The decodeAudioData() method is preferred over the createBuffer() from ArrayBuffer method because it is asynchronous and does not block the main JavaScript thread. - * - * The following steps must be performed: - * 1. Temporarily neuter the audioData ArrayBuffer in such a way that JavaScript code may not access or modify the data. - * 2. Queue a decoding operation to be performed on another thread. - * 3. The decoding thread will attempt to decode the encoded audioData into linear PCM. If a decoding error is encountered due to the audio format not being recognized or supported, or because of corrupted/unexpected/inconsistent data then the audioData neutered state will be restored to normal and the errorCallback will be scheduled to run on the main thread's event loop and these steps will be terminated. - * 4. The decoding thread will take the result, representing the decoded linear PCM audio data, and resample it to the sample-rate of the AudioContext if it is different from the sample-rate of audioData. The final result (after possibly sample-rate converting) will be stored in an AudioBuffer. - * 5. The audioData neutered state will be restored to normal - * 6. The successCallback function will be scheduled to run on the main thread's event loop given the AudioBuffer from step (4) as an argument. - * - * @param ArrayBuffer containing audio file data. - * @param callback function which will be invoked when the decoding is finished. The single argument to this callback is an AudioBuffer representing the decoded PCM audio data. - * @param callback function which will be invoked if there is an error decoding the audio file data. - */ - decodeAudioData(audioData: ArrayBuffer, successCallback: any, errorCallback?: any): void; - - /** - * Creates an AudioBufferSourceNode. - */ - createBufferSource(): AudioBufferSourceNode; - - /** - * Creates a MediaElementAudioSourceNode given an HTMLMediaElement. As a consequence of calling this method, audio playback from the HTMLMediaElement will be re-routed into the processing graph of the AudioContext. - */ - createMediaElementSource(mediaElement: HTMLMediaElement): MediaElementAudioSourceNode; - - /** - * Creates a MediaStreamAudioSourceNode given a MediaStream. As a consequence of calling this method, audio playback from the MediaStream will be re-routed into the processing graph of the AudioContext. - */ - createMediaStreamSource(mediaStream: any): MediaStreamAudioSourceNode; - - /** - * Creates a ScriptProcessorNode for direct audio processing using JavaScript. An exception will be thrown if bufferSize or numberOfInputChannels or numberOfOutputChannels are outside the valid range. - * It is invalid for both numberOfInputChannels and numberOfOutputChannels to be zero. - * @param bufferSize the buffer size in units of sample-frames. It must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384. This value controls how frequently the onaudioprocess event handler is called and how many sample-frames need to be processed each call. Lower values for bufferSize will result in a lower (better) latency. Higher values will be necessary to avoid audio breakup and glitches. The value chosen must carefully balance between latency and audio quality. - * @param numberOfInputChannels (defaults to 2) the number of channels for this node's input. Values of up to 32 must be supported. - * @param numberOfOutputChannels (defaults to 2) the number of channels for this node's output. Values of up to 32 must be supported. - */ - createScriptProcessor(bufferSize: number, numberOfInputChannels?: number, numberOfOutputChannels?: number): ScriptProcessorNode; - - /** - * Creates a AnalyserNode. - */ - createAnalyser(): AnalyserNode; - - /** - * Creates a GainNode. - */ - createGain(): GainNode; - - /** - * Creates a DelayNode representing a variable delay line. The initial default delay time will be 0 seconds. - * @param maxDelayTime the maximum delay time in seconds allowed for the delay line. If specified, this value must be greater than zero and less than three minutes or a NOT_SUPPORTED_ERR exception will be thrown. - */ - createDelay(maxDelayTime?: number): DelayNode; - //createDelayNode(maxDelayTime?: number): DelayNode; - - /** - * Creates a BiquadFilterNode representing a second order filter which can be configured as one of several common filter types. - */ - createBiquadFilter(): BiquadFilterNode; - - /** - * Creates a WaveShaperNode representing a non-linear distortion. - */ - createWaveShaper(): WaveShaperNode; - - /** - * Creates an PannerNode. - */ - createPanner(): PannerNode; - - /** - * Creates a ConvolverNode. - */ - createConvolver(): ConvolverNode; - - /** - * Creates an ChannelSplitterNode representing a channel splitter. An exception will be thrown for invalid parameter values. - * @param numberOfOutputs the number of outputs. Values of up to 32 must be supported. If not specified, then 6 will be used. - */ - createChannelSplitter(numberOfOutputs?: number): ChannelSplitterNode; - - /** - * Creates an ChannelMergerNode representing a channel merger. An exception will be thrown for invalid parameter values. - * @param numberOfInputs the number of inputs. Values of up to 32 must be supported. If not specified, then 6 will be used. - */ - createChannelMerger(numberOfInputs?: number): ChannelMergerNode; - - /** - * Creates a DynamicsCompressorNode. - */ - createDynamicsCompressor(): DynamicsCompressorNode; - - /** - * Creates an OscillatorNode. - */ - createOscillator(): OscillatorNode; - - /** - * Creates a WaveTable representing a waveform containing arbitrary harmonic content. The real and imag parameters must be of type Float32Array of equal lengths greater than zero and less than or equal to 4096 or an exception will be thrown. These parameters specify the Fourier coefficients of a Fourier series representing the partials of a periodic waveform. The created WaveTable will be used with an OscillatorNode and will represent a normalized time-domain waveform having maximum absolute peak value of 1. Another way of saying this is that the generated waveform of an OscillatorNode will have maximum peak value at 0dBFS. Conveniently, this corresponds to the full-range of the signal values used by the Web Audio API. Because the WaveTable will be normalized on creation, the real and imag parameters represent relative values. - * @param real an array of cosine terms (traditionally the A terms). In audio terminology, the first element (index 0) is the DC-offset of the periodic waveform and is usually set to zero. The second element (index 1) represents the fundamental frequency. The third element represents the first overtone, and so on. - * @param imag an array of sine terms (traditionally the B terms). The first element (index 0) should be set to zero (and will be ignored) since this term does not exist in the Fourier series. The second element (index 1) represents the fundamental frequency. The third element represents the first overtone, and so on. - */ - createWaveTable(real: any,imag: any): WaveTable; -} - -declare var AudioContext: { - new (): AudioContext; -} - -declare var webkitAudioContext: { - new (): AudioContext; -} - -interface OfflineRenderSuccessCallback{ - (renderedData: AudioBuffer): void; -} - -/** - * OfflineAudioContext is a particular type of AudioContext for rendering/mixing-down (potentially) faster than real-time. It does not render to the audio hardware, but instead renders as quickly as possible, calling a render callback function upon completion with the result provided as an AudioBuffer. It is constructed by specifying the numberOfChannels, length, and sampleRate as follows: - * - * var offlineContext = new OfflineAudioContext(unsigned long numberOfChannels, unsigned long length, float sampleRate); - */ -interface OfflineAudioContext extends AudioContext{ - startRendering(): void; - oncomplete: OfflineRenderSuccessCallback; -} - -declare var webkitOfflineAudioContext: { - new (numberOfChannels: number, length: number, sampleRate: number): OfflineAudioContext; -} - -/** - * AudioNodes are the building blocks of an AudioContext. This interface represents audio sources, the audio destination, and intermediate processing modules. These modules can be connected together to form processing graphs for rendering audio to the audio hardware. Each node can have inputs and/or outputs. An AudioSourceNode has no inputs and a single output. An AudioDestinationNode has one input and no outputs and represents the final destination to the audio hardware. Most processing nodes such as filters will have one input and one output. Each type of AudioNode differs in the details of how it processes or synthesizes audio. But, in general, AudioNodes will process its inputs (if it has any), and generate audio for its outputs (if it has any). - * - * An output may connect to one or more AudioNode inputs, thus fanout is supported. An input may be connected from one or more AudioNode outputs, thus fanin is supported. - * - * In order to handle this fanin, any AudioNode with inputs performs an up-mixing of all connections for each input: - * - * 1. Calculate N: the maximum number of channels of all the connections to the input. For example, if an input has a mono connection and a stereo connection then this number will be 2. - * 2. For each connection to the input, up-mix to N channels. - * 3. Mix together all the up-mixed streams from (2). This is a straight-forward mixing together of each of the corresponding channels from each connection. - * - * Please see Mixer Gain Structure for more informative details. - * - * For performance reasons, practical implementations will need to use block processing, with each AudioNode processing a fixed number of sample-frames of size block-size. In order to get uniform behavior across implementations, we will define this value explicitly. block-size is defined to be 128 sample-frames which corresponds to roughly 3ms at a sample-rate of 44.1KHz. - */ -interface AudioNode { - /** - * Connects the AudioNode to another AudioNode. - * - * It is possible to connect an AudioNode output to more than one input with multiple calls to connect(). Thus, "fanout" is supported. - * - * It is possible to connect an AudioNode to another AudioNode which creates a cycle. In other words, an AudioNode may connect to another AudioNode, which in turn connects back to the first AudioNode. This is allowed only if there is at least one DelayNode in the cycle or an exception will be thrown. - * - * There can only be one connection between a given output of one specific node and a given input of another specific node. Multiple connections with the same termini are ignored. For example: - * - * nodeA.connect(nodeB); - * nodeA.connect(nodeB); - * - * will have the same effect as - * - * nodeA.connect(nodeB); - * - * @param destination the AudioNode to connect to. - * @param output an index describing which output of the AudioNode from which to connect. An out-of-bound value throws an exception. - * @param input an index describing which input of the destination AudioNode to connect to. An out-of-bound value throws an exception. - */ - connect(destination: AudioNode, output?: number, input?: number): void; - - /** - * Connects the AudioNode to an AudioParam, controlling the parameter value with an audio-rate signal. - * - * It is possible to connect an AudioNode output to more than one AudioParam with multiple calls to connect(). Thus, "fanout" is supported. - * - * It is possible to connect more than one AudioNode output to a single AudioParam with multiple calls to connect(). Thus, "fanin" is supported. - * - * An AudioParam will take the rendered audio data from any AudioNode output connected to it and convert it to mono by down-mixing if it is not already mono, then mix it together with other such outputs and finally will mix with the intrinsic parameter value (the value the AudioParam would normally have without any audio connections), including any timeline changes scheduled for the parameter. - * - * There can only be one connection between a given output of one specific node and a specific AudioParam. Multiple connections with the same termini are ignored. For example: - * - * nodeA.connect(param); - * nodeA.connect(param); - * - * will have the same effect as - * - * nodeA.connect(param); - * - * @param destination the AudioParam to connect to. - * @param output an index describing which output of the AudioNode from which to connect. An out-of-bound value throws an exception. - */ - connect(destination: AudioParam, output?: number): void; - - /** - * Disconnects an AudioNode's output. - * @param output an index describing which output of the AudioNode to disconnect. An out-of-bound value throws an exception. - */ - disconnect(output?: number): void; - - /** - * The AudioContext which owns this AudioNode. - */ - context: AudioContext; - - /** - * The number of inputs feeding into the AudioNode. This will be 0 for an AudioSourceNode. - */ - numberOfInputs: number; - - /** - * The number of outputs coming out of the AudioNode. This will be 0 for an AudioDestinationNode. - */ - numberOfOutputs: number; -} - - -/** - * This is an abstract interface representing an audio source, an AudioNode which has no inputs and a single output: - * - * numberOfInputs : 0 - * numberOfOutputs : 1 - * - * Subclasses of AudioSourceNode will implement specific types of audio sources. - */ -interface AudioSourceNode extends AudioNode { - -} - -/** - * This is an AudioNode representing the final audio destination and is what the user will ultimately hear. It can be considered as an audio output device which is connected to speakers. All rendered audio to be heard will be routed to this node, a "terminal" node in the AudioContext's routing graph. There is only a single AudioDestinationNode per AudioContext, provided through the destination attribute of AudioContext. - * - * numberOfInputs : 1 - * numberOfOutputs : 0 - */ -interface AudioDestinationNode extends AudioNode { - /** - * The maximum number of channels that the numberOfChannels attribute can be set to. An AudioDestinationNode representing the audio hardware end-point (the normal case) can potentially output more than 2 channels of audio if the audio hardware is multi-channel. maxNumberOfChannels is the maximum number of channels that this hardware is capable of supporting. If this value is 0, then this indicates that maxNumberOfChannels may not be changed. This will be the case for an AudioDestinationNode in an OfflineAudioContext. - * @readonly - */ - maxNumberOfChannels: number; - - /** - * The number of channels of the destination's input. This value will default to 2, and may be set to any non-zero value less than or equal to maxNumberOfChannels. An exception will be thrown if this value is not within the valid range. Giving a concrete example, if the audio hardware supports 8-channel output, then we may set numberOfChannels to 8, and render 8-channels of output. - */ - numberOfChannels: number; -} - -/** - * AudioParam controls an individual aspect of an AudioNode's functioning, such as volume. The parameter can be set immediately to a particular value using the "value" attribute. Or, value changes can be scheduled to happen at very precise times (in the coordinate system of AudioContext.currentTime), for envelopes, volume fades, LFOs, filter sweeps, grain windows, etc. In this way, arbitrary timeline-based automation curves can be set on any AudioParam. Additionally, audio signals from the outputs of AudioNodes can be connected to an AudioParam, summing with the intrinsic parameter value. - * - * Some synthesis and processing AudioNodes have AudioParams as attributes whose values must be taken into account on a per-audio-sample basis. For other AudioParams, sample-accuracy is not important and the value changes can be sampled more coarsely. Each individual AudioParam will specify that it is either an a-rate parameter which means that its values must be taken into account on a per-audio-sample basis, or it is a k-rate parameter. - * - * Implementations must use block processing, with each AudioNode processing 128 sample-frames in each block. - * - * For each 128 sample-frame block, the value of a k-rate parameter must be sampled at the time of the very first sample-frame, and that value must be used for the entire block. a-rate parameters must be sampled for each sample-frame of the block. - */ -interface AudioParam { - /** - * The parameter's floating-point value. This attribute is initialized to the defaultValue. If a value is set outside the allowable range described by minValue and maxValue no exception is thrown, because these limits are just nominal and may be exceeded. If a value is set during a time when there are any automation events scheduled then it will be ignored and no exception will be thrown. - */ - value: number; - - /** - * Nominal minimum value. This attribute is informational and value may be set lower than this value. - */ - minValue: number; - - /** - * Nominal maximum value. This attribute is informational and value may be set higher than this value. - */ - maxValue: number; - - /** - * Initial value for the value attribute - */ - defaultValue: number; - - /** - * Schedules a parameter value change at the given time. - * - * If there are no more events after this SetValue event, then for t >= startTime, v(t) = value. In other words, the value will remain constant. - * - * If the next event (having time T1) after this SetValue event is not of type LinearRampToValue or ExponentialRampToValue, then, for t: startTime <= t < T1, v(t) = value. In other words, the value will remain constant during this time interval, allowing the creation of "step" functions. - * - * If the next event after this SetValue event is of type LinearRampToValue or ExponentialRampToValue then please see details below. - * - * @param value the value the parameter will change to at the given time - * @param startTime parameter is the time in the same time coordinate system as AudioContext.currentTime. - */ - setValueAtTime(value: number, startTime: number): void; - - /** - * Schedules a linear continuous change in parameter value from the previous scheduled parameter value to the given value. - * - * The value during the time interval T0 <= t < T1 (where T0 is the time of the previous event and T1 is the endTime parameter passed into this method) will be calculated as: - * - * v(t) = V0 + (V1 - V0) * ((t - T0) / (T1 - T0)) - * - * Where V0 is the value at the time T0 and V1 is the value parameter passed into this method. - * - * If there are no more events after this LinearRampToValue event then for t >= T1, v(t) = V1 - * - * @param value the value the parameter will linearly ramp to at the given time. - * @param endTime the time in the same time coordinate system as AudioContext.currentTime. - */ - linearRampToValueAtTime(value: number, time: number): void; - - /** - * Schedules an exponential continuous change in parameter value from the previous scheduled parameter value to the given value. Parameters representing filter frequencies and playback rate are best changed exponentially because of the way humans perceive sound. - * - * The value during the time interval T0 <= t < T1 (where T0 is the time of the previous event and T1 is the endTime parameter passed into this method) will be calculated as: - * - * v(t) = V0 * (V1 / V0) ^ ((t - T0) / (T1 - T0)) - * - * Where V0 is the value at the time T0 and V1 is the value parameter passed into this method. - * - * If there are no more events after this ExponentialRampToValue event then for t >= T1, v(t) = V1 - * - * @param value the value the parameter will exponentially ramp to at the given time. An exception will be thrown if this value is less than or equal to 0, or if the value at the time of the previous event is less than or equal to 0. - * @param endTime the time in the same time coordinate system as AudioContext.currentTime. - */ - exponentialRampToValueAtTime(value: number, endTime: number): void; - - /** - * Start exponentially approaching the target value at the given time with a rate having the given time constant. Among other uses, this is useful for implementing the "decay" and "release" portions of an ADSR envelope. Please note that the parameter value does not immediately change to the target value at the given time, but instead gradually changes to the target value. - * - * More precisely, timeConstant is the time it takes a first-order linear continuous time-invariant system to reach the value 1 - 1/e (around 63.2%) given a step input response (transition from 0 to 1 value). - * - * During the time interval: T0 <= t < T1, where T0 is the startTime parameter and T1 represents the time of the event following this event (or infinity if there are no following events): - * - * v(t) = V1 + (V0 - V1) * exp(-(t - T0) / timeConstant) - * - * Where V0 is the initial value (the .value attribute) at T0 (the startTime parameter) and V1 is equal to the target parameter. - * - * @param target the value the parameter will start changing to at the given time. - * @param startTime the time in the same time coordinate system as AudioContext.currentTime. - * @param timeConstant the time-constant value of first-order filter (exponential) approach to the target value. The larger this value is, the slower the transition will be. - */ - setTargetValueAtTime(target: number, startTime: number, timeConstant: number): void; - - /** - * Sets an array of arbitrary parameter values starting at the given time for the given duration. The number of values will be scaled to fit into the desired duration. - * - * During the time interval: startTime <= t < startTime + duration, values will be calculated: - * - * v(t) = values[N * (t - startTime) / duration], where N is the length of the values array. - * - * After the end of the curve time interval (t >= startTime + duration), the value will remain constant at the final curve value, until there is another automation event (if any). - * - * @param values a Float32Array representing a parameter value curve. These values will apply starting at the given time and lasting for the given duration. - * @param startTime the time in the same time coordinate system as AudioContext.currentTime. - * @param duration the amount of time in seconds (after the time parameter) where values will be calculated according to the values parameter.. - * - */ - setValueCurveAtTime(values: Float32Array, time: number, duration: number): void; - - /** - * Cancels all scheduled parameter changes with times greater than or equal to startTime. - * - * @param startTime the starting time at and after which any previously scheduled parameter changes will be cancelled. It is a time in the same time coordinate system as AudioContext.currentTime. - */ - cancelScheduledValues(startTime: number): void; -} - -/** - * Changing the gain of an audio signal is a fundamental operation in audio applications. The GainNode is one of the building blocks for creating mixers. This interface is an AudioNode with a single input and single output: - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - * - * which multiplies the input audio signal by the (possibly time-varying) gain attribute, copying the result to the output. By default, it will take the input and pass it through to the output unchanged, which represents a constant gain change of 1. - * - * As with other AudioParams, the gain parameter represents a mapping from time (in the coordinate system of AudioContext.currentTime) to floating-point value. Every PCM audio sample in the input is multiplied by the gain parameter's value for the specific time corresponding to that audio sample. This multiplied value represents the PCM audio sample for the output. - * - * The number of channels of the output will always equal the number of channels of the input, with each channel of the input being multiplied by the gain values and being copied into the corresponding channel of the output. - * - * The implementation must make gain changes to the audio stream smoothly, without introducing noticeable clicks or glitches. This process is called "de-zippering". - */ -interface GainNode extends AudioNode { - /** - * Represents the amount of gain to apply. Its default value is 1 (no gain change). The nominal minValue is 0, but may be set negative for phase inversion. The nominal maxValue is 1, but higher values are allowed (no exception thrown).This parameter is a-rate - */ - gain: AudioParam; -} - -/** - * A delay-line is a fundamental building block in audio applications. This interface is an AudioNode with a single input and single output: - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - * - * which delays the incoming audio signal by a certain amount. The default amount is 0 seconds (no delay). When the delay time is changed, the implementation must make the transition smoothly, without introducing noticeable clicks or glitches to the audio stream. - */ -interface DelayNode extends AudioNode { - /** - * An AudioParam object representing the amount of delay (in seconds) to apply. The default value (delayTime.value) is 0 (no delay). The minimum value is 0 and the maximum value is determined by the maxDelayTime argument to the AudioContext method createDelay. This parameter is k-rate - */ - delayTime: AudioParam; -} - -/** - * This interface represents a memory-resident audio asset (for one-shot sounds and other short audio clips). Its format is non-interleaved IEEE 32-bit linear PCM with a nominal range of -1 -> +1. It can contain one or more channels. It is analogous to a WebGL texture. Typically, it would be expected that the length of the PCM data would be fairly short (usually somewhat less than a minute). For longer sounds, such as music soundtracks, streaming should be used with the audio element and MediaElementAudioSourceNode. - * - * An AudioBuffer may be used by one or more AudioContexts. - */ -interface AudioBuffer { - /** - * The sample-rate for the PCM audio data in samples per second. - * @readonly - */ - sampleRate: number; - - /** - * Length of the PCM audio data in sample-frames. - * @readonly - */ - length: number; - - /** - * Duration of the PCM audio data in seconds. - * @readonly - */ - duration: number; - - /** - * The number of discrete audio channels. - * @readonly - */ - numberOfChannels: number; - - /** - * Returns the Float32Array representing the PCM audio data for the specific channel. - * - * The channel parameter is an index representing the particular channel to get data for. An index value of 0 represents the first channel. This index value MUST be less than numberOfChannels or an exception will be thrown. - */ - getChannelData(channel: number): Float32Array; - -} - -/** - * This interface represents an audio source from an in-memory audio asset in an AudioBuffer. It generally will be used for short audio assets which require a high degree of scheduling flexibility (can playback in rhythmically perfect ways). The playback state of an AudioBufferSourceNode goes through distinct stages during its lifetime in this order: UNSCHEDULED_STATE, SCHEDULED_STATE, PLAYING_STATE, FINISHED_STATE. The start() method causes a transition from the UNSCHEDULED_STATE to SCHEDULED_STATE. Depending on the time argument passed to start(), a transition is made from the SCHEDULED_STATE to PLAYING_STATE, at which time sound is first generated. Following this, a transition from the PLAYING_STATE to FINISHED_STATE happens when either the buffer's audio data has been completely played (if the loop attribute is false), or when the stop() method has been called and the specified time has been reached. Please see more details in the start() and stop() description. Once an AudioBufferSourceNode has reached the FINISHED state it will no longer emit any sound. Thus start() and stop() may not be issued multiple times for a given AudioBufferSourceNode. - * - * numberOfInputs : 0 - * numberOfOutputs : 1 - */ -interface AudioBufferSourceNode extends AudioSourceNode { - - /** - * The playback state, initialized to UNSCHEDULED_STATE. - */ - playbackState: number; - - /** - * Represents the audio asset to be played. - */ - buffer: AudioBuffer; - - /** - * The speed at which to render the audio stream. The default playbackRate.value is 1. This parameter is a-rate - */ - playbackRate: AudioParam; - - /** - * Indicates if the audio data should play in a loop. The default value is false. - */ - loop: boolean; - - /** - * An optional value in seconds where looping should begin if the loop attribute is true. Its default value is 0, and it may usefully be set to any value between 0 and the duration of the buffer. - */ - loopStart: number; - - /** - * An optional value in seconds where looping should end if the loop attribute is true. Its default value is 0, and it may usefully be set to any value between 0 and the duration of the buffer. - */ - loopEnd: number; - - /** - * A property used to set the EventHandler for the ended event that is dispatched to AudioBufferSourceNode node types. When the playback of the buffer for an AudioBufferSourceNode is finished, an event of type Event will be dispatched to the event handler. - */ - onended: EventListener; - - /** - * Schedules a sound to playback at an exact time. - * - * @param when time (in seconds) the sound should start playing. It is in the same time coordinate system as AudioContext.currentTime. If 0 is passed in for this value or if the value is less than currentTime, then the sound will start playing immediately. start may only be called one time and must be called before stop is called or an exception will be thrown. - * @param offset the offset time in the buffer (in seconds) where playback will begin. This parameter is optional with a default value of 0 (playing back from the beginning of the buffer). - * @param duration the duration of the portion (in seconds) to be played. This parameter is optional, with the default value equal to the total duration of the AudioBuffer minus the offset parameter. Thus if neither offset nor duration are specified then the implied duration is the total duration of the AudioBuffer. - */ - start(when: number, offset?: number, duration?: number): void; - - /** - * Schedules a sound to stop playback at an exact time. Please see deprecation section for the old method name. - * - * The when parameter describes at what time (in seconds) the sound should stop playing. It is in the same time coordinate system as AudioContext.currentTime. If 0 is passed in for this value or if the value is less than currentTime, then the sound will stop playing immediately. stop must only be called one time and only after a call to start or stop, or an exception will be thrown. - */ - stop(when: number): void; -} - -/* - * This interface represents an audio source from an audio or video element. - * - * numberOfInputs : 0 - * numberOfOutputs : 1 - */ -interface MediaElementAudioSourceNode extends AudioSourceNode { -} - -/** - * This interface is an AudioNode which can generate, process, or analyse audio directly using JavaScript. - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - * - * The ScriptProcessorNode is constructed with a bufferSize which must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384. This value controls how frequently the onaudioprocess event handler is called and how many sample-frames need to be processed each call. Lower numbers for bufferSize will result in a lower (better) latency. Higher numbers will be necessary to avoid audio breakup and glitches. The value chosen must carefully balance between latency and audio quality. - * - * numberOfInputChannels and numberOfOutputChannels determine the number of input and output channels. It is invalid for both numberOfInputChannels and numberOfOutputChannels to be zero. - * - * var node = context.createScriptProcessor(bufferSize, numberOfInputChannels, numberOfOutputChannels); - */ -interface ScriptProcessorNode extends AudioNode { - /** - * An event listener which is called periodically for audio processing. An event of type AudioProcessingEvent will be passed to the event handler. - */ - onaudioprocess: EventListener; - - /** - * The size of the buffer (in sample-frames) which needs to be processed each time onprocessaudio is called. Legal values are (256, 512, 1024, 2048, 4096, 8192, 16384). - */ - bufferSize: number; -} - -/** - * This interface is a type of Event which is passed to the onaudioprocess event handler used by ScriptProcessorNode. - * - * The event handler processes audio from the input (if any) by accessing the audio data from the inputBuffer attribute. The audio data which is the result of the processing (or the synthesized data if there are no inputs) is then placed into the outputBuffer. - */ -interface AudioProcessingEvent extends Event { - /** - * The ScriptProcessorNode associated with this processing event. - */ - node: ScriptProcessorNode; - - /** - * The time when the audio will be played in the same time coordinate system as AudioContext.currentTime. playbackTime allows for very tight synchronization between processing directly in JavaScript with the other events in the context's rendering graph. - */ - playbackTime: number; - - /** - * An AudioBuffer containing the input audio data. It will have a number of channels equal to the numberOfInputChannels parameter of the createScriptProcessor() method. This AudioBuffer is only valid while in the scope of the onaudioprocess function. Its values will be meaningless outside of this scope. - */ - inputBuffer: AudioBuffer; - - /** - * An AudioBuffer where the output audio data should be written. It will have a number of channels equal to the numberOfOutputChannels parameter of the createScriptProcessor() method. Script code within the scope of the onaudioprocess function is expected to modify the Float32Array arrays representing channel data in this AudioBuffer. Any script modifications to this AudioBuffer outside of this scope will not produce any audible effects. - */ - outputBuffer: AudioBuffer; -} - -declare enum PanningModelType { - /** - * A simple and efficient spatialization algorithm using equal-power panning. - */ - equalpower, - - /** - * A higher quality spatialization algorithm using a convolution with measured impulse responses from human subjects. This panning method renders stereo output. - */ - HRTF, - - /** - * An algorithm which spatializes multi-channel audio using sound field algorithms. - */ - soundfield -} - -declare enum DistanceModelType { - /** - * A linear distance model which calculates distanceGain according to: - * 1 - rolloffFactor * (distance - refDistance) / (maxDistance - refDistance) - */ - linear, - - /** - * An inverse distance model which calculates distanceGain according to: - * refDistance / (refDistance + rolloffFactor * (distance - refDistance)) - */ - inverse, - - /** - * An exponential distance model which calculates distanceGain according to: - * pow(distance / refDistance, -rolloffFactor) - */ - exponential -} - -/** - * This interface represents a processing node which positions / spatializes an incoming audio stream in three-dimensional space. The spatialization is in relation to the AudioContext's AudioListener (listener attribute). - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - * - * The audio stream from the input will be either mono or stereo, depending on the connection(s) to the input. - * - * The output of this node is hard-coded to stereo (2 channels) and currently cannot be configured. - */ -interface PannerNode extends AudioNode { - /** - * Determines which spatialization algorithm will be used to position the audio in 3D space. The default is "HRTF". - */ - panningModel: PanningModelType; - - /** - * Sets the position of the audio source relative to the listener attribute. A 3D cartesian coordinate system is used. - * - * The default value is (0,0,0) - * - * @param x the x coordinates in 3D space. - * @param y the y coordinates in 3D space. - * @param z the z coordinates in 3D space. - */ - setPosition(x: number, y: number, z: number): void; - - /** - * Describes which direction the audio source is pointing in the 3D cartesian coordinate space. Depending on how directional the sound is (controlled by the cone attributes), a sound pointing away from the listener can be very quiet or completely silent. - * - * The default value is (1,0,0) - * - * @param x - * @param y - * @param z - */ - setOrientation(x: number, y: number, z: number): void; - - /** - * Sets the velocity vector of the audio source. This vector controls both the direction of travel and the speed in 3D space. This velocity relative to the listener's velocity is used to determine how much doppler shift (pitch change) to apply. The units used for this vector is meters / second and is independent of the units used for position and orientation vectors. - * - * The default value is (0,0,0) - * - * @param x a direction vector indicating direction of travel and intensity. - * @param y - * @param z - */ - setVelocity(x: number, y: number, z: number): void; - - /** - * Determines which algorithm will be used to reduce the volume of an audio source as it moves away from the listener. The default is "inverse". - */ - distanceModel: DistanceModelType; - - /** - * A reference distance for reducing volume as source move further from the listener. The default value is 1. - */ - refDistance: number; - - /** - * The maximum distance between source and listener, after which the volume will not be reduced any further. The default value is 10000. - */ - maxDistance: number; - - /** - * Describes how quickly the volume is reduced as source moves away from listener. The default value is 1. - */ - rolloffFactor: number; - - /** - * A parameter for directional audio sources, this is an angle, inside of which there will be no volume reduction. The default value is 360. - */ - coneInnerAngle: number; - - /** - * A parameter for directional audio sources, this is an angle, outside of which the volume will be reduced to a constant value of coneOuterGain. The default value is 360. - */ - coneOuterAngle: number; - - /** - * A parameter for directional audio sources, this is the amount of volume reduction outside of the coneOuterAngle. The default value is 0. - */ - coneOuterGain: number; -} - -/** - * This interface represents the position and orientation of the person listening to the audio scene. All PannerNode objects spatialize in relation to the AudioContext's listener. See this section for more details about spatialization. - */ -interface AudioListener { - /** - * A constant used to determine the amount of pitch shift to use when rendering a doppler effect. The default value is 1. - */ - dopplerFactor: number; - - /** - * The speed of sound used for calculating doppler shift. The default value is 343.3 meters / second. - */ - speedOfSound: number; - - /** - * Sets the position of the listener in a 3D cartesian coordinate space. PannerNode objects use this position relative to individual audio sources for spatialization. - * - * The default value is (0,0,0) - * - * @param x - * @param y - * @param z - */ - setPosition(x: number, y: number, z: number): void; - - /** - * Describes which direction the listener is pointing in the 3D cartesian coordinate space. Both a front vector and an up vector are provided. In simple human terms, the front vector represents which direction the person's nose is pointing. The up vector represents the direction the top of a person's head is pointing. These values are expected to be linearly independent (at right angles to each other). For normative requirements of how these values are to be interpreted, see the spatialization section. - * - * @param x x coordinate of a front direction vector in 3D space, with the default value being 0 - * @param y y coordinate of a front direction vector in 3D space, with the default value being 0 - * @param z z coordinate of a front direction vector in 3D space, with the default value being -1 - * @param xUp x coodinate of an up direction vector in 3D space, with the default value being 0 - * @param yUp y coodinate of an up direction vector in 3D space, with the default value being 1 - * @param zUp z coodinate of an up direction vector in 3D space, with the default value being 0 - */ - setOrientation(x: number, y: number, z: number, xUp: number, yUp: number, zUp: number): void; - - /** - * Sets the velocity vector of the listener. This vector controls both the direction of travel and the speed in 3D space. This velocity relative to an audio source's velocity is used to determine how much doppler shift (pitch change) to apply. The units used for this vector is meters / second and is independent of the units used for position and orientation vectors. - * - * @param x x coordinate of a direction vector indicating direction of travel and intensity. The default value is 0 - * @param y y coordinate of a direction vector indicating direction of travel and intensity. The default value is 0 - * @param z z coordinate of a direction vector indicating direction of travel and intensity. The default value is 0 - */ - setVelocity(x: number, y: number, z: number): void; -} - - -/** - * This interface represents a processing node which applies a linear convolution effect given an impulse response. Normative requirements for multi-channel convolution matrixing are described [here](http://www.w3.org/TR/2012/WD-webaudio-20121213/#Convolution-reverb-effect). - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - */ -interface ConvolverNode extends AudioNode { - /** - * A mono, stereo, or 4-channel AudioBuffer containing the (possibly multi-channel) impulse response used by the ConvolverNode. At the time when this attribute is set, the buffer and the state of the normalize attribute will be used to configure the ConvolverNode with this impulse response having the given normalization. - */ - buffer: AudioBuffer; - - /** - * Controls whether the impulse response from the buffer will be scaled by an equal-power normalization when the buffer atttribute is set. Its default value is true in order to achieve a more uniform output level from the convolver when loaded with diverse impulse responses. If normalize is set to false, then the convolution will be rendered with no pre-processing/scaling of the impulse response. Changes to this value do not take effect until the next time the buffer attribute is set. - */ - normalize: boolean; -} - -/** - * This interface represents a node which is able to provide real-time frequency and time-domain analysis information. The audio stream will be passed un-processed from input to output. - * - * numberOfInputs : 1 - * numberOfOutputs : 1 Note that this output may be left unconnected. - */ -interface AnalyserNode extends AudioNode { - /** - * Copies the current frequency data into the passed floating-point array. If the array has fewer elements than the frequencyBinCount, the excess elements will be dropped. - * @param array where frequency-domain analysis data will be copied. - */ - getFloatFrequencyData(array: any): void; - - /** - * Copies the current frequency data into the passed unsigned byte array. If the array has fewer elements than the frequencyBinCount, the excess elements will be dropped. - * @param Tarray where frequency-domain analysis data will be copied. - */ - getByteFrequencyData(array: any): void; - - /** - * Copies the current time-domain (waveform) data into the passed unsigned byte array. If the array has fewer elements than the frequencyBinCount, the excess elements will be dropped. - * @param array where time-domain analysis data will be copied. - */ - getByteTimeDomainData(array: any): void; - - /** - * The size of the FFT used for frequency-domain analysis. This must be a power of two. - */ - fftSize: number; - - /** - * Half the FFT size. - */ - frequencyBinCount: number; - - /** - * The minimum power value in the scaling range for the FFT analysis data for conversion to unsigned byte values. - */ - minDecibels: number; - - /** - * The maximum power value in the scaling range for the FFT analysis data for conversion to unsigned byte values. - */ - maxDecibels: number; - - /** - * A value from 0 -> 1 where 0 represents no time averaging with the last analysis frame. - */ - smoothingTimeConstant: number; -} - -/** - * The ChannelSplitterNode is for use in more advanced applications and would often be used in conjunction with ChannelMergerNode. - * - * numberOfInputs : 1 - * numberOfOutputs : Variable N (defaults to 6) // number of "active" (non-silent) outputs is determined by number of channels in the input - */ -interface ChannelSplitterNode extends AudioNode { -} - -/** - * The ChannelMergerNode is for use in more advanced applications and would often be used in conjunction with ChannelSplitterNode. - * - * numberOfInputs : Variable N (default to 6) // number of connected inputs may be less than this - * numberOfOutputs : 1 - */ -interface ChannelMergerNode extends AudioNode { -} - -/** - * DynamicsCompressorNode is an AudioNode processor implementing a dynamics compression effect. - * - * Dynamics compression is very commonly used in musical production and game audio. It lowers the volume of the loudest parts of the signal and raises the volume of the softest parts. Overall, a louder, richer, and fuller sound can be achieved. It is especially important in games and musical applications where large numbers of individual sounds are played simultaneous to control the overall signal level and help avoid clipping (distorting) the audio output to the speakers. - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - */ -interface DynamicsCompressorNode extends AudioNode { - /** - * The decibel value above which the compression will start taking effect. Its default value is -24, with a nominal range of -100 to 0. - */ - threshold: AudioParam; - - /** - * A decibel value representing the range above the threshold where the curve smoothly transitions to the "ratio" portion. Its default value is 30, with a nominal range of 0 to 40. - */ - knee: AudioParam; - - /** - * The amount of dB change in input for a 1 dB change in output. Its default value is 12, with a nominal range of 1 to 20. - */ - ratio: AudioParam; - - /** - * A read-only decibel value for metering purposes, representing the current amount of gain reduction that the compressor is applying to the signal. If fed no signal the value will be 0 (no gain reduction). The nominal range is -20 to 0. - */ - reduction: AudioParam; - - /** - * The amount of time (in seconds) to reduce the gain by 10dB. Its default value is 0.003, with a nominal range of 0 to 1. - */ - attack: AudioParam; - - /** - * The amount of time (in seconds) to increase the gain by 10dB. Its default value is 0.250, with a nominal range of 0 to 1. - */ - release: AudioParam; - -} - -declare enum BiquadFilterType { - /** - * A lowpass filter allows frequencies below the cutoff frequency to pass through and attenuates frequencies above the cutoff. It implements a standard second-order resonant lowpass filter with 12dB/octave rolloff. - * - * ## frequency - * The cutoff frequency - * ## Q - * Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked. Please note that for this filter type, this value is not a traditional Q, but is a resonance value in decibels. - * ## gain - * Not used in this filter type - */ - lowpass, - - /** - * A highpass filter is the opposite of a lowpass filter. Frequencies above the cutoff frequency are passed through, but frequencies below the cutoff are attenuated. It implements a standard second-order resonant highpass filter with 12dB/octave rolloff. - * - * ## frequency - * The cutoff frequency below which the frequencies are attenuated - * ## Q - * Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked. Please note that for this filter type, this value is not a traditional Q, but is a resonance value in decibels. - * ## gain - * Not used in this filter type - */ - highpass, - - /** - * A bandpass filter allows a range of frequencies to pass through and attenuates the frequencies below and above this frequency range. It implements a second-order bandpass filter. - * - * ## frequency - * The center of the frequency band - * ## Q - * Controls the width of the band. The width becomes narrower as the Q value increases. - * ## gain - * Not used in this filter type - */ - bandpass, - - /** - * The lowshelf filter allows all frequencies through, but adds a boost (or attenuation) to the lower frequencies. It implements a second-order lowshelf filter. - * - * ## frequency - * The upper limit of the frequences where the boost (or attenuation) is applied. - * ## Q - * Not used in this filter type. - * ## gain - * The boost, in dB, to be applied. If the value is negative, the frequencies are attenuated. - */ - lowshelf, - - /** - * The highshelf filter is the opposite of the lowshelf filter and allows all frequencies through, but adds a boost to the higher frequencies. It implements a second-order highshelf filter - * - * ## frequency - * The lower limit of the frequences where the boost (or attenuation) is applied. - * ## Q - * Not used in this filter type. - * ## gain - * The boost, in dB, to be applied. If the value is negative, the frequencies are attenuated. - */ - highshelf, - - /** - * The peaking filter allows all frequencies through, but adds a boost (or attenuation) to a range of frequencies. - * - * ## frequency - * The center frequency of where the boost is applied. - * ## Q - * Controls the width of the band of frequencies that are boosted. A large value implies a narrow width. - * ## gain - * The boost, in dB, to be applied. If the value is negative, the frequencies are attenuated. - */ - peaking, - - /** - * The notch filter (also known as a band-stop or band-rejection filter) is the opposite of a bandpass filter. It allows all frequencies through, except for a set of frequencies. - * - * ## frequency - * The center frequency of where the notch is applied. - * ## Q - * Controls the width of the band of frequencies that are attenuated. A large value implies a narrow width. - * ## gain - * Not used in this filter type. - */ - notch, - - /** - * An allpass filter allows all frequencies through, but changes the phase relationship between the various frequencies. It implements a second-order allpass filter - * - * ## frequency - * The frequency where the center of the phase transition occurs. Viewed another way, this is the frequency with maximal group delay. - * ## Q - * Controls how sharp the phase transition is at the center frequency. A larger value implies a sharper transition and a larger group delay. - * ## gain - * Not used in this filter type. - */ - allpass -} - -/** - * BiquadFilterNode is an AudioNode processor implementing very common low-order filters. - * - * Low-order filters are the building blocks of basic tone controls (bass, mid, treble), graphic equalizers, and more advanced filters. Multiple BiquadFilterNode filters can be combined to form more complex filters. The filter parameters such as "frequency" can be changed over time for filter sweeps, etc. Each BiquadFilterNode can be configured as one of a number of common filter types as shown in the IDL below. The default filter type is "lowpass" - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - * - * The filter types are briefly described below. We note that all of these filters are very commonly used in audio processing. In terms of implementation, they have all been derived from standard analog filter prototypes. For more technical details, we refer the reader to the excellent reference by Robert Bristow-Johnson. - * - * All parameters are k-rate with the following default parameter values: - * - * ## frequency - * 350Hz, with a nominal range of 10 to the Nyquist frequency (half the sample-rate). - * ## Q - * 1, with a nominal range of 0.0001 to 1000. - * ## gain - * 0, with a nominal range of -40 to 40. - */ -interface BiquadFilterNode extends AudioNode { - - type: BiquadFilterType; - frequency: AudioParam; - Q: AudioParam; - gain: AudioParam; - - /** - * Given the current filter parameter settings, calculates the frequency response for the specified frequencies. - * @param frequencyHz an array of frequencies at which the response values will be calculated. - * @param magResponse an output array receiving the linear magnitude response values. - * @param phaseResponse an output array receiving the phase response values in radians. - */ - getFrequencyResponse(frequencyHz: any, magResponse: any, phaseResponse: any): void; -} - -/** - * WaveShaperNode is an AudioNode processor implementing non-linear distortion effects. - * - * Non-linear waveshaping distortion is commonly used for both subtle non-linear warming, or more obvious distortion effects. Arbitrary non-linear shaping curves may be specified. - * - * numberOfInputs : 1 - * numberOfOutputs : 1 - */ -interface WaveShaperNode extends AudioNode { - /** - * The shaping curve used for the waveshaping effect. The input signal is nominally within the range -1 -> +1. Each input sample within this range will index into the shaping curve with a signal level of zero corresponding to the center value of the curve array. Any sample value less than -1 will correspond to the first value in the curve array. Any sample value less greater than +1 will correspond to the last value in the curve array. - */ - curve: Float32Array; -} - -declare enum OscillatorType { - sine, - square, - sawtooth, - triangle, - custom -} - -/** - * OscillatorNode represents an audio source generating a periodic waveform. It can be set to a few commonly used waveforms. Additionally, it can be set to an arbitrary periodic waveform through the use of a WaveTable object. - * - * Oscillators are common foundational building blocks in audio synthesis. An OscillatorNode will start emitting sound at the time specified by the start() method. - * - * Mathematically speaking, a continuous-time periodic waveform can have very high (or infinitely high) frequency information when considered in the frequency domain. When this waveform is sampled as a discrete-time digital audio signal at a particular sample-rate, then care must be taken to discard (filter out) the high-frequency information higher than the Nyquist frequency (half the sample-rate) before converting the waveform to a digital form. If this is not done, then aliasing of higher frequencies (than the Nyquist frequency) will fold back as mirror images into frequencies lower than the Nyquist frequency. In many cases this will cause audibly objectionable artifacts. This is a basic and well understood principle of audio DSP. - * - * There are several practical approaches that an implementation may take to avoid this aliasing. But regardless of approach, the idealized discrete-time digital audio signal is well defined mathematically. The trade-off for the implementation is a matter of implementation cost (in terms of CPU usage) versus fidelity to achieving this ideal. - * - * It is expected that an implementation will take some care in achieving this ideal, but it is reasonable to consider lower-quality, less-costly approaches on lower-end hardware. - * - * Both .frequency and .detune are a-rate parameters and are used together to determine a computedFrequency value: - * - * computedFrequency(t) = frequency(t) * pow(2, detune(t) / 1200) - * - * The OscillatorNode's instantaneous phase at each time is the time integral of computedFrequency. - * - * numberOfInputs : 0 - * numberOfOutputs : 1 (mono output) - */ -interface OscillatorNode extends AudioSourceNode { - /** - * The shape of the periodic waveform. It may directly be set to any of the type constant values except for "custom". The setWaveTable() method can be used to set a custom waveform, which results in this attribute being set to "custom". The default value is "sine". - */ - type: OscillatorType; - - /** - * defined as in AudioBufferSourceNode. - * @readonly - */ - playbackState: number; - - /** - * The frequency (in Hertz) of the periodic waveform. This parameter is a-rate - * @readonly - */ - frequency: AudioParam; - - /** - * A detuning value (in Cents) which will offset the frequency by the given amount. This parameter is a-rate - */ - detune: AudioParam; // in Cents - - /** - * defined as in AudioBufferSourceNode. - */ - start(when: number): void; - - /** - * defined as in AudioBufferSourceNode. - */ - stop(when: number): void; - - /** - * Sets an arbitrary custom periodic waveform given a WaveTable. - */ - setWaveTable(waveTable: WaveTable): void; -} - -/** - * WaveTable represents an arbitrary periodic waveform to be used with an OscillatorNode. Please see createWaveTable() and setWaveTable() and for more details. - */ -interface WaveTable { -} - -/** - * This interface represents an audio source from a MediaStream. The first AudioMediaStreamTrack from the MediaStream will be used as a source of audio. - * - * numberOfInputs : 0 - * numberOfOutputs : 1 - */ -interface MediaStreamAudioSourceNode extends AudioSourceNode { -}