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. + *
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.
+ * 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.
+ * 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. 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. 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. 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.
+ * 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.
+ * 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.
- * 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.
+ * 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.
- * 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: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.
- *
+ * 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:
- * 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;hreturnType
is "byte" which returns values
+ in the range 0-255.
+