diff --git a/.gitignore b/.gitignore index 828b7af5a..328e066d9 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,5 @@ test/Main.js build/p5.Tone.min.js build/p5.Tone.js -.DS_Store \ No newline at end of file +.DS_Store +examples/graph.html diff --git a/.jshintrc b/.jshintrc index 7561cf848..dc346d474 100644 --- a/.jshintrc +++ b/.jshintrc @@ -18,6 +18,7 @@ "GainNode" : false, "AudioNode" : false, "AudioParam" : false, + "AnalyserNode" : false, "WaveShaperNode" : false, "DynamicsCompressorNode" : false, "MediaStreamTrack" : false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..f42e14104 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: node_js +node_js: + - "4.1" +before_install: + - export CHROME_BIN=chromium-browser + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +before_script: + - cd gulp + - npm install -g karma + - npm install -g gulp + - npm install +script: gulp karma-test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 18aa1be61..cb79073e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +### r7 + +* MetalSynth creates metalic, cymbal sounds +* DrumSynth -> MembraneSynth +* FMOscillator, AMOscillator types +* FatOscillator creates multiple oscillators and detunes them slightly +* FM, AM, Fat Oscillators incorporated into OmniOscillator +* Simplified FM and AM Synths and APIs +* Panner.pan is between -1,1 like the StereoPannerNode +* Pruned away unused (or little used) Signal classes. + * All this functionality will be available when the AudioWorkerNode is introduced. +* Clock uses Web Workers instead of requestAnimationFrame which allows it to run in the background. +* Removed `startMobile`. Using [StartAudioContext](https://github.com/tambien/StartAudioContext) in examples. +* Automated test runner using [Travis CI](https://travis-ci.org/Tonejs/Tone.js/) +* Simplified NoiseSynth by removing filter and filter envelope. +* Added new timing primitive types: Time, Frequency, TransportTime. +* Switching parameter position of type and size in Tone.Analyser +* Tone.Meter uses Tone.Analyser instead of ScriptProcessorNode. +* Tone.Envelope has 5 new attack/release curves: "sine", "cosine", "bounce", "ripple", "step" +* Renamed Tone.SimpleSynth -> Tone.Synth +* Tone.Buffers combines multiple buffers +* Tone.BufferSource a low-level wrapper, and Tone.MultiPlayer which is good for multisampled instruments. +* Tone.GrainPlayer: granular synthesis buffer player. +* Simplified Sampler + +DEPRECATED: +* Removed SimpleFM and SimpleAM + ### r6 * Added PitchShift and Vibrato Effect. diff --git a/README.md b/README.md index 90f9b9b3d..3b6040808 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,43 @@ Tone.js ========= -Tone.js is a Web Audio framework for creating interactive music in the browser. The architecture of Tone.js aims to be familiar to both musicians and audio programmers looking to create web-based audio applications. On the high-level, Tone offers common DAW (digital audio workstation) features like a global transport for scheduling and timing events and prebuilt synths and effects. For signal-processing programmers (coming from languages like Max/MSP), Tone provides a wealth of high performance, low latency building blocks and DSP modules to build your own synthesizers, effects, and complex control signals. +Tone.js is a Web Audio framework for creating interactive music in the browser. The architecture of Tone.js aims to be familiar to both musicians and audio programmers looking to create web-based audio applications. On the high-level, Tone offers common DAW (digital audio workstation) features like a global transport for scheduling events and prebuilt synths and effects. For signal-processing programmers (coming from languages like Max/MSP), Tone provides a wealth of high performance, low latency building blocks and DSP modules to build your own synthesizers, effects, and complex control signals. -[API](http://tonejs.org/docs/) +[API](https://tonejs.github.io/docs/) -[Examples](http://tonejs.org/examples/) +[Examples](https://tonejs.github.io/examples/) # Demos +* [Chrome Music Lab - Google Creative Lab](https://musiclab.chromeexperiments.com) +* [Groove Pizza - NYU Music Experience Design Lab](https://apps.musedlab.org/groovepizza/) * [Jazz.Computer - Yotam Mann](http://jazz.computer/) * [motionEmotion - Karen Peng, Jason Sigal](http://motionemotion.herokuapp.com/) * [p5.sound - build with Tone.js](https://github.com/processing/p5.js-sound) * [Hypercube - @eddietree](http://eddietree.github.io/hypercube/) -* [Random Commander - Jake Albaugh](http://randomcommander.io/) +* [Musical Chord Progression Arpeggiator - Jake Albaugh](http://codepen.io/jakealbaugh/full/qNrZyw/) * [Tone.js + NexusUI - Ben Taylor](http://taylorbf.github.io/Tone-Rack/) * [Solarbeat - Luke Twyman](http://www.whitevinyldesign.com/solarbeat/) -* [Wind - João Costa](http://wind.joaocosta.co) * [Block Chords - Abe Rubenstein](http://dev.abe.sh/block-chords/) * [This is Not a Machine Learning - David Karam](http://posttool.github.io/) -* [Airjam - Seth Kranzler, Abe Rubenstein, and Teresa Lamb](http://airjam.band/) * [Calculaural - Matthew Hasbach](https://github.com/mjhasbach/calculaural) * [Scratch + Tone.js - Eric Rosenbaum](http://ericrosenbaum.github.io/tone-synth-extension/) * [Game of Reich - Ben Taylor](http://nexusosc.com/gameofreich/) * [Yume - Helios + Luke Twyman](http://www.unseen-music.com/yume/) +* [TR-808 - Gregor Adams](http://codepen.io/pixelass/full/adyLPR) +* [Tweet FM - Mike Mitchell](https://tweet-fm.herokuapp.com/) +* [TextXoX - Damon Holzborn](http://rustleworks.com/textxox/) +* [Stepping - John Hussey](http://stepping.audio/) +* [Limp Body Beat](http://www.adultswim.com/etcetera/limp-body-beat/) +* [MsCompose 95 - Autotel](http://autotel.co/mscompose95/) +* [Pedalboard - Micha Hanselmann](https://deermichel.github.io/pedalboard/) +* [Keyboard Boogie - Douglas Tarr](http://douglastarr.com/keyboard-boogie) -Using Tone.js? I'd love to hear it: yotammann@gmail.com +Using Tone.js? I'd love to hear it: yotam@tonejs.org # Installation -* CDN - [full](http://cdn.tonejs.org/latest/Tone.js) | [min](http://cdn.tonejs.org/latest/Tone.min.js) +* CDN - [full](https://tonejs.github.io/CDN/latest/Tone.js) | [min](https://tonejs.github.io/CDN/latest/Tone.min.js) * [bower](http://bower.io/) - `bower install tone` * [npm](https://www.npmjs.org/) - `npm install tone` @@ -38,75 +46,73 @@ Using Tone.js? I'd love to hear it: yotammann@gmail.com # Hello Tone ```javascript -//create one of Tone's built-in synthesizers and connect it to the master output -var synth = new Tone.SimpleSynth().toMaster(); +//create a synth and connect it to the master output (your speakers) +var synth = new Tone.Synth().toMaster(); -//play a middle c for the duration of an 8th note +//play a middle 'C' for the duration of an 8th note synth.triggerAttackRelease("C4", "8n"); ``` -[SimpleSynth](http://tonejs.org/docs/#SimpleSynth) is a single oscillator, single envelope synthesizer. It's [ADSR envelope](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope) has two phases: the attack and the release. These can be triggered by calling `triggerAttack` and `triggerRelease` separately, or combined as shown above. The first argument of `triggerAttackRelease` is the frequency, which can be given either a number (like `440`) or as "pitch-octave" notation (like `"D#2"`). The second argument is the duration of the envelope's sustain (i.e. how long the note is held for). The third (optional) argument of `triggerAttackRelease` is the time the attack should start. With no argument, the time will evaluate to "now" and play immediately. Passing in a time value let's you schedule the event in the future. +#### Tone.Synth -### Time +[Tone.Synth](https://tonejs.github.io/docs/#Synth) is a basic synthesizer with a single [oscillator](https://tonejs.github.io/docs/#OmniOscillator) and an [ADSR envelope](https://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope). -Any method which takes a time as a parameter will accept either a number or a string. Numbers will be taken literally as the time in seconds and strings can encode time expressions in terms of the current tempo. For example `"4n"` is a quarter-note, `"8t"` is an eighth-note triplet, and `"1m"` is one measure. Any value prefixed with `"+"` will be added to the current time. To trigger the same note one measure from now: +#### triggerAttackRelease -```javascript -synth.triggerAttackRelease("C4", "8n", "+1m"); -``` +The "attack" of an envelope is the period when the amplitude is rising, and the "release" is when it is falling back to 0. These two methods can be invoked separately as `triggerAttack` and `triggerRelease`, or combined as shown above. The first argument is the frequency which can either be a number (like `440`) or as "pitch-octave" notation (like `"D#2"`). The second argument is how long the note should be held before triggering the release phases. An optional third argument schedules the event for some time in the future. With no third argument, the note will play immediately. + +#### Time + +In the examples above, instead of using the time in seconds (for an 8th note at 120 BPM it would be 0.25 seconds), any method which takes time as an argument can accept a number or a string. Numbers will be taken literally as the time in seconds and strings can encode time expressions in terms of the current tempo. For example `"4n"` is a quarter-note, `"8t"` is an eighth-note triplet, and `"1m"` is one measure. [Read about Time encodings.](https://github.com/Tonejs/Tone.js/wiki/Time) +# Scheduling + ### Transport -Time expressions are evaluated against the Transport's BPM. [Tone.Transport](http://tonejs.org/docs/#Transport) is the master timekeeper, allowing for application-wide synchronization of sources, signals and events along a shared timeline. Callbacks scheduled with Tone.Transport will be invoked right before the scheduled time with the exact time of the event is passed in as the first parameter to the callback. - -```javascript -//schedule a callback on the second beat of the first measure -Tone.Transport.schedule(function(time){ - //schedule the synth's attackRelease using the passed-in time - synth.triggerAttackRelease("C4", "8n", time); -}, "1:2:0"); - -//start the transport -Tone.Transport.start(); -``` -[Read more about scheduling events with the Transport.](https://github.com/Tonejs/Tone.js/wiki/Transport) +[Tone.Transport](https://tonejs.github.io/docs/#Transport) is the master timekeeper, allowing for application-wide synchronization of sources, signals and events along a shared timeline. Time expressions (like the ones above) are evaluated against the Transport's BPM which can be set like this: `Tone.Transport.bpm.value = 120`. ### Loops -Instead of scheduling events directly on the Transport, Tone.js provides a few higher-level classes for working with events. [Tone.Loop](http://tonejs.org/docs/#Loop) is a simple way to create a looped callback that can be scheduled to start and stop. +Tone.js provides higher-level abstractions for scheduling events. [Tone.Loop](https://tonejs.github.io/docs/#Loop) is a simple way to create a looped callback that can be scheduled to start and stop. ```javascript //play a note every quarter-note var loop = new Tone.Loop(function(time){ synth.triggerAttackRelease("C2", "8n", time); }, "4n"); +``` + +Since Javascript timing is not sample-accurate, the precise time of the event is passed into the callback function. This time should be used to schedule events within the loop. +You can then start and stop the loop along the Transport's timeline. + +```javascript //loop between the first and fourth measures of the Transport's timeline loop.start("1m").stop("4m"); ``` -Start the Transport to hear the looped notes: +Then start the Transport to hear the loop: ```javascript Transport.start(); ``` -[Read about Tone.js' Event classes.](https://github.com/Tonejs/Tone.js/wiki/Events) +[Read about Tone.js' Event classes](https://github.com/Tonejs/Tone.js/wiki/Events) and [scheduling events with the Transport.](https://github.com/Tonejs/Tone.js/wiki/Transport) # Instruments -Tone has a number of instruments which all inherit from the same [Instrument base class](http://tonejs.org/docs/#Instrument), giving them a common API for playing notes. [Tone.MonoSynth](http://tonejs.org/docs/#MonoSynth) is composed of one oscillator, one filter, and two envelopes connected to the amplitude and the filter frequency. +Tone has a number of instruments which all inherit from the same [Instrument base class](https://tonejs.github.io/docs/#Instrument), giving them a common API for playing notes. [Tone.Synth](https://tonejs.github.io/docs/#Synth) is composed of one oscillator and an amplitude envelope. ```javascript //pass in some initial values for the filter and filter envelope -var monoSynth = new Tone.MonoSynth({ - "filter" : { - "type" : "lowpass", - "Q" : 7 +var synth = new Tone.Synth({ + "oscillator" : { + "type" : "pwm", + "modulationFrequency" : 0.2 }, - "filterEnvelope" : { + "envelope" : { "attack" : 0.02, "decay" : 0.1, "sustain" : 0.2, @@ -115,14 +121,14 @@ var monoSynth = new Tone.MonoSynth({ }).toMaster(); //start the note "D3" one second from now -monoSynth.triggerAttack("D3", "+1"); +synth.triggerAttack("D3", "+1"); ``` -All instruments are monophonic (one voice) but can be made polyphonic when the constructor is passed in as the second argument to [Tone.PolySynth](http://tonejs.org/docs/#PolySynth). +All instruments are monophonic (one voice) but can be made polyphonic when the constructor is passed in as the second argument to [Tone.PolySynth](https://tonejs.github.io/docs/#PolySynth). ```javascript -//a 4 voice MonoSynth -var polySynth = new Tone.PolySynth(4, Tone.MonoSynth).toMaster(); +//a 4 voice Synth +var polySynth = new Tone.PolySynth(4, Tone.Synth).toMaster(); //play a chord polySynth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "2n"); ``` @@ -131,7 +137,7 @@ polySynth.triggerAttackRelease(["C4", "E4", "G4", "B4"], "2n"); # Effects -In the above examples, the synthesizer was always connected directly to the [master output](http://tonejs.org/docs/#Master), but the output of the synth could also be routed through one (or more) effects before going to the speakers. +In the above examples, the synthesizer was always connected directly to the [master output](https://tonejs.github.io/docs/#Master), but the output of the synth could also be routed through one (or more) effects before going to the speakers. ```javascript //create a distortion effect @@ -144,7 +150,7 @@ synth.connect(distortion); # Sources -Tone has a few basic audio sources like [Tone.Oscillator](http://tonejs.org/docs/#Oscillator) which has sine, square, triangle, and sawtooth waveforms, a buffer player ([Tone.Player](http://tonejs.org/docs/#Player)), a noise generator ([Tone.Noise]((http://tonejs.org/docs/#Noise))), two additional oscillator types ([pwm](http://tonejs.org/docs/#PWMOscillator), [pulse](http://tonejs.org/docs/#PulseOscillator)) and [external audio input](http://tonejs.org/docs/#Microphone) (when [WebRTC is supported](http://caniuse.com/#feat=stream)). +Tone has a few basic audio sources like [Tone.Oscillator](https://tonejs.github.io/docs/#Oscillator) which has sine, square, triangle, and sawtooth waveforms, a buffer player ([Tone.Player](https://tonejs.github.io/docs/#Player)), a noise generator ([Tone.Noise]((https://tonejs.github.io/docs/#Noise))), two additional oscillator types ([pwm](https://tonejs.github.io/docs/#PWMOscillator), [pulse](https://tonejs.github.io/docs/#PulseOscillator)) and [external audio input](https://tonejs.github.io/docs/#Microphone) (when [WebRTC is supported](http://caniuse.com/#feat=stream)). ```javascript //a pwm oscillator which is connected to the speaker and started right away @@ -165,16 +171,18 @@ Tone.js creates an AudioContext when it loads and shims it for maximum browser c # MIDI -To use MIDI files, you'll first need to convert them into a JSON format which Tone.js can understand using [MidiConvert](http://tonejs.github.io/MidiConvert/). +To use MIDI files, you'll first need to convert them into a JSON format which Tone.js can understand using [MidiConvert](https://tonejs.github.io/MidiConvert/). # Performance -Tone.js uses only one ScriptProcessorNode (in Tone.Meter). The rest of Tone's modules find a native Web Audio component workaround, making extensive use of the GainNode and WaveShaperNode especially, which enables Tone.js to work well on both desktop and mobile browsers. While the ScriptProcessorNode is extremely powerful, it introduces a lot of latency and the potential for glitches more than any other node. +Tone.js makes extensive use of the native Web Audio Nodes such as the GainNode and WaveShaperNode for all signal processing, which enables Tone.js to work well on both desktop and mobile browsers. It uses no ScriptProcessorNodes. # Contributing There are many ways to contribute to Tone.js. Check out [this wiki](https://github.com/Tonejs/Tone.js/wiki/Contributing) if you're interested. +If you have questions (or answers) that are not necessarily bugs/issues, please post them to the [forum](https://groups.google.com/forum/#!forum/tonejs). + # References and Inspiration * [Tuna.js](https://github.com/Dinahmoe/tuna) diff --git a/Tone/component/Analyser.js b/Tone/component/Analyser.js index 38eb08e32..dda49d35d 100644 --- a/Tone/component/Analyser.js +++ b/Tone/component/Analyser.js @@ -7,20 +7,20 @@ define(["Tone/core/Tone"], function (Tone) { * [AnalyserNode](http://webaudio.github.io/web-audio-api/#idl-def-AnalyserNode). * Extracts FFT or Waveform data from the incoming signal. * @extends {Tone} + * @param {String=} type The return type of the analysis, either "fft", or "waveform". * @param {Number=} size The size of the FFT. Value must be a power of * two in the range 32 to 32768. - * @param {String=} type The return type of the analysis, either "fft", or "waveform". */ Tone.Analyser = function(){ - var options = this.optionsObject(arguments, ["size", "type"], Tone.Analyser.defaults); + var options = this.optionsObject(arguments, ["type", "size"], Tone.Analyser.defaults); /** * The analyser node. * @private * @type {AnalyserNode} */ - this._analyser = this.input = this.context.createAnalyser(); + this._analyser = this.input = this.output = this.context.createAnalyser(); /** * The analysis type @@ -59,7 +59,7 @@ define(["Tone/core/Tone"], function (Tone) { * @const */ Tone.Analyser.defaults = { - "size" : 2048, + "size" : 1024, "returnType" : "byte", "type" : "fft", "smoothing" : 0.8, @@ -68,7 +68,7 @@ define(["Tone/core/Tone"], function (Tone) { }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse() * @enum {String} */ Tone.Analyser.Type = { @@ -77,7 +77,10 @@ define(["Tone/core/Tone"], function (Tone) { }; /** - * Possible return types of Tone.Analyser.value + * Possible return types of Tone.Analyser.analyse(). + * byte values are between [0,255]. float values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @enum {String} */ Tone.Analyser.ReturnType = { @@ -101,7 +104,17 @@ define(["Tone/core/Tone"], function (Tone) { if (this._returnType === Tone.Analyser.ReturnType.Byte){ this._analyser.getByteTimeDomainData(this._buffer); } else { - this._analyser.getFloatTimeDomainData(this._buffer); + if (this.isFunction(AnalyserNode.prototype.getFloatTimeDomainData)){ + this._analyser.getFloatTimeDomainData(this._buffer); + } else { + var uint8 = new Uint8Array(this._buffer.length); + this._analyser.getByteTimeDomainData(uint8); + //referenced https://github.com/mohayonao/get-float-time-domain-data + // POLYFILL + for (var i = 0; i < uint8.length; i++){ + this._buffer[i] = (uint8[i] - 128) * 0.0078125; + } + } } } return this._buffer; @@ -124,9 +137,11 @@ define(["Tone/core/Tone"], function (Tone) { }); /** - * The return type of Tone.Analyser.value, either "byte" or "float". + * The return type of Tone.Analyser.analyse(), either "byte" or "float". * When the type is set to "byte" the range of values returned in the array - * are between 0-255, when set to "float" the values are between 0-1. + * are between 0-255. "float" values are between + * [-1, 1] when the type is set to "waveform" and between + * [minDecibels,maxDecibels] when the type is "fft". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -141,14 +156,14 @@ define(["Tone/core/Tone"], function (Tone) { } else if (type === Tone.Analyser.ReturnType.Float){ this._buffer = new Float32Array(this._analyser.frequencyBinCount); } else { - throw new Error("Invalid Return Type: "+type); + throw new TypeError("Tone.Analayser: invalid return type: "+type); } this._returnType = type; } }); /** - * The analysis function returned by Tone.Analyser.value, either "fft" or "waveform". + * The analysis function returned by Tone.Analyser.analyse(), either "fft" or "waveform". * @memberOf Tone.Analyser# * @type {String} * @name type @@ -159,7 +174,7 @@ define(["Tone/core/Tone"], function (Tone) { }, set : function(type){ if (type !== Tone.Analyser.Type.Waveform && type !== Tone.Analyser.Type.FFT){ - throw new Error("Invalid Type: "+type); + throw new TypeError("Tone.Analyser: invalid type: "+type); } this._type = type; } diff --git a/Tone/component/Envelope.js b/Tone/component/Envelope.js index 21726456f..88c12eb0c 100644 --- a/Tone/component/Envelope.js +++ b/Tone/component/Envelope.js @@ -1,5 +1,5 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", - "Tone/signal/Pow", "Tone/core/Type"], function(Tone){ + "Tone/signal/Pow", "Tone/type/Type"], function(Tone){ "use strict"; @@ -69,21 +69,14 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", * @type {number} * @private */ - this._attackCurve = Tone.Envelope.Type.Linear; + this._attackCurve = "linear"; /** * the next time the envelope is at standby * @type {number} * @private */ - this._releaseCurve = Tone.Envelope.Type.Exponential; - - /** - * the minimum output value - * @type {number} - * @private - */ - this._minOutput = 0.00001; + this._releaseCurve = "exponential"; /** * the signal @@ -91,7 +84,7 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", * @private */ this._sig = this.output = new Tone.TimelineSignal(); - this._sig.setValueAtTime(this._minOutput, 0); + this._sig.setValueAtTime(0, 0); //set the attackCurve initially this.attackCurve = options.attackCurve; @@ -114,13 +107,6 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", "releaseCurve" : "exponential", }; - /** - * the envelope time multipler - * @type {number} - * @private - */ - Tone.Envelope.prototype._timeMult = 0.25; - /** * Read the current value of the envelope. Useful for * syncronizing visual output to the envelope. @@ -131,50 +117,102 @@ define(["Tone/core/Tone", "Tone/signal/TimelineSignal", */ Object.defineProperty(Tone.Envelope.prototype, "value", { get : function(){ - return this._sig.value; + return this.getValueAtTime(this.now()); } }); /** - * The slope of the attack. Either "linear" or "exponential". + * The shape of the attack. + * Can be any of these strings: + *
note * .octaves
and ramps to
* note
over the duration of .pitchDecay
.
*
@@ -18,12 +18,12 @@ function(Tone){
* @param {Object} [options] the options available for the synth
* see defaults below
* @example
- * var synth = new Tone.DrumSynth().toMaster();
+ * var synth = new Tone.MembraneSynth().toMaster();
* synth.triggerAttackRelease("C2", "8n");
*/
- Tone.DrumSynth = function(options){
+ Tone.MembraneSynth = function(options){
- options = this.defaultArg(options, Tone.DrumSynth.defaults);
+ options = this.defaultArg(options, Tone.MembraneSynth.defaults);
Tone.Instrument.call(this, options);
/**
@@ -54,13 +54,13 @@ function(Tone){
this._readOnly(["oscillator", "envelope"]);
};
- Tone.extend(Tone.DrumSynth, Tone.Instrument);
+ Tone.extend(Tone.MembraneSynth, Tone.Instrument);
/**
* @static
* @type {Object}
*/
- Tone.DrumSynth.defaults = {
+ Tone.MembraneSynth.defaults = {
"pitchDecay" : 0.05,
"octaves" : 10,
"oscillator" : {
@@ -81,11 +81,11 @@ function(Tone){
* @param {Frequency} note the note
* @param {Time} [time=now] the time, if not given is now
* @param {number} [velocity=1] velocity defaults to 1
- * @returns {Tone.DrumSynth} this
+ * @returns {Tone.MembraneSynth} this
* @example
* kick.triggerAttack(60);
*/
- Tone.DrumSynth.prototype.triggerAttack = function(note, time, velocity) {
+ Tone.MembraneSynth.prototype.triggerAttack = function(note, time, velocity) {
time = this.toSeconds(time);
note = this.toFrequency(note);
var maxNote = note * this.octaves;
@@ -99,18 +99,18 @@ function(Tone){
* Trigger the release portion of the note.
*
* @param {Time} [time=now] the time the note will release
- * @returns {Tone.DrumSynth} this
+ * @returns {Tone.MembraneSynth} this
*/
- Tone.DrumSynth.prototype.triggerRelease = function(time){
+ Tone.MembraneSynth.prototype.triggerRelease = function(time){
this.envelope.triggerRelease(time);
return this;
};
/**
* Clean up.
- * @returns {Tone.DrumSynth} this
+ * @returns {Tone.MembraneSynth} this
*/
- Tone.DrumSynth.prototype.dispose = function(){
+ Tone.MembraneSynth.prototype.dispose = function(){
Tone.Instrument.prototype.dispose.call(this);
this._writable(["oscillator", "envelope"]);
this.oscillator.dispose();
@@ -120,5 +120,5 @@ function(Tone){
return this;
};
- return Tone.DrumSynth;
+ return Tone.MembraneSynth;
});
\ No newline at end of file
diff --git a/Tone/instrument/MetalSynth.js b/Tone/instrument/MetalSynth.js
new file mode 100644
index 000000000..4266da59c
--- /dev/null
+++ b/Tone/instrument/MetalSynth.js
@@ -0,0 +1,274 @@
+define(["Tone/core/Tone", "Tone/instrument/Instrument", "Tone/source/FMOscillator", "Tone/component/Filter",
+ "Tone/component/FrequencyEnvelope", "Tone/component/AmplitudeEnvelope", "Tone/core/Gain", "Tone/signal/Scale", "Tone/signal/Multiply"],
+ function (Tone) {
+
+ /**
+ * Inharmonic ratio of frequencies based on the Roland TR-808
+ * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model
+ * @private
+ * @static
+ * @type {Array}
+ */
+ var inharmRatios = [1.0, 1.483, 1.932, 2.546, 2.630, 3.897];
+
+ /**
+ * @class A highly inharmonic and spectrally complex source with a highpass filter
+ * and amplitude envelope which is good for making metalophone sounds. Based
+ * on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic).
+ * Inspiration from [Sound on Sound](http://www.soundonsound.com/sos/jul02/articles/synthsecrets0702.asp).
+ *
+ * @constructor
+ * @extends {Tone.Instrument}
+ * @param {Object} [options] The options availble for the synth
+ * see defaults below
+ */
+ Tone.MetalSynth = function(options){
+
+ options = this.defaultArg(options, Tone.MetalSynth.defaults);
+ Tone.Instrument.call(this, options);
+
+ /**
+ * The frequency of the cymbal
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
+
+ /**
+ * The array of FMOscillators
+ * @type {Array}
+ * @private
+ */
+ this._oscillators = [];
+
+ /**
+ * The frequency multipliers
+ * @type {Array}
+ * @private
+ */
+ this._freqMultipliers = [];
+
+ /**
+ * The amplitude for the body
+ * @type {Tone.Gain}
+ * @private
+ */
+ this._amplitue = new Tone.Gain(0).connect(this.output);
+
+ /**
+ * highpass the output
+ * @type {Tone.Filter}
+ * @private
+ */
+ this._highpass = new Tone.Filter({
+ "type" : "highpass",
+ "Q" : 0
+ }).connect(this._amplitue);
+
+ /**
+ * The number of octaves the highpass
+ * filter frequency ramps
+ * @type {Number}
+ * @private
+ */
+ this._octaves = options.octaves;
+
+ /**
+ * Scale the body envelope
+ * for the bandpass
+ * @type {Tone.Scale}
+ * @private
+ */
+ this._filterFreqScaler = new Tone.Scale(options.resonance, 7000);
+
+ /**
+ * The envelope which is connected both to the
+ * amplitude and highpass filter's cutoff frequency
+ * @type {Tone.Envelope}
+ */
+ this.envelope = new Tone.Envelope({
+ "attack" : options.envelope.attack,
+ "attackCurve" : "exponential",
+ "decay" : options.envelope.decay,
+ "sustain" : 0,
+ "release" : options.envelope.release,
+ }).chain(this._filterFreqScaler, this._highpass.frequency);
+ this.envelope.connect(this._amplitue.gain);
+
+ for (var i = 0; i < inharmRatios.length; i++){
+ var osc = new Tone.FMOscillator({
+ "type" : "square",
+ "modulationType" : "square",
+ "harmonicity" : options.harmonicity,
+ "modulationIndex" : options.modulationIndex
+ });
+ osc.connect(this._highpass).start(0);
+ this._oscillators[i] = osc;
+
+ var mult = new Tone.Multiply(inharmRatios[i]);
+ this._freqMultipliers[i] = mult;
+ this.frequency.chain(mult, osc.frequency);
+ }
+
+ //set the octaves
+ this.octaves = options.octaves;
+
+ };
+
+ Tone.extend(Tone.MetalSynth, Tone.Instrument);
+
+ /**
+ * default values
+ * @static
+ * @const
+ * @type {Object}
+ */
+ Tone.MetalSynth.defaults = {
+ "frequency" : 200,
+ "envelope" : {
+ "attack" : 0.0015,
+ "decay" : 1.4,
+ "release" : 0.2
+ },
+ "harmonicity" : 5.1,
+ "modulationIndex" : 32,
+ "resonance" : 4000,
+ "octaves" : 1.5
+ };
+
+ /**
+ * Trigger the attack.
+ * @param {Time} time When the attack should be triggered.
+ * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at.
+ * @return {Tone.MetalSynth} this
+ */
+ Tone.MetalSynth.prototype.triggerAttack = function(time, vel) {
+ time = this.toSeconds(time);
+ vel = this.defaultArg(vel, 1);
+ this.envelope.triggerAttack(time, vel);
+ return this;
+ };
+
+ /**
+ * Trigger the release of the envelope.
+ * @param {Time} time When the release should be triggered.
+ * @return {Tone.MetalSynth} this
+ */
+ Tone.MetalSynth.prototype.triggerRelease = function(time) {
+ time = this.toSeconds(time);
+ this.envelope.triggerRelease(time);
+ return this;
+ };
+
+ /**
+ * Trigger the attack and release of the envelope after the given
+ * duration.
+ * @param {Time} duration The duration before triggering the release
+ * @param {Time} time When the attack should be triggered.
+ * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at.
+ * @return {Tone.MetalSynth} this
+ */
+ Tone.MetalSynth.prototype.triggerAttackRelease = function(duration, time, velocity) {
+ var now = this.now();
+ time = this.toSeconds(time, now);
+ duration = this.toSeconds(duration, now);
+ this.triggerAttack(time, velocity);
+ this.triggerRelease(time + duration);
+ return this;
+ };
+
+ /**
+ * The modulationIndex of the oscillators which make up the source.
+ * see Tone.FMOscillator.modulationIndex
+ * @memberOf Tone.MetalSynth#
+ * @type {Positive}
+ * @name modulationIndex
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, "modulationIndex", {
+ get : function(){
+ return this._oscillators[0].modulationIndex.value;
+ },
+ set : function(val){
+ for (var i = 0; i < this._oscillators.length; i++){
+ this._oscillators[i].modulationIndex.value = val;
+ }
+ }
+ });
+
+ /**
+ * The harmonicity of the oscillators which make up the source.
+ * see Tone.FMOscillator.harmonicity
+ * @memberOf Tone.MetalSynth#
+ * @type {Positive}
+ * @name harmonicity
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, "harmonicity", {
+ get : function(){
+ return this._oscillators[0].harmonicity.value;
+ },
+ set : function(val){
+ for (var i = 0; i < this._oscillators.length; i++){
+ this._oscillators[i].harmonicity.value = val;
+ }
+ }
+ });
+
+ /**
+ * The frequency of the highpass filter attached to the envelope
+ * @memberOf Tone.MetalSynth#
+ * @type {Frequency}
+ * @name resonance
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, "resonance", {
+ get : function(){
+ return this._filterFreqScaler.min;
+ },
+ set : function(val){
+ this._filterFreqScaler.min = val;
+ this.octaves = this._octaves;
+ }
+ });
+
+ /**
+ * The number of octaves above the "resonance" frequency
+ * that the filter ramps during the attack/decay envelope
+ * @memberOf Tone.MetalSynth#
+ * @type {Number}
+ * @name octaves
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, "octaves", {
+ get : function(){
+ return this._octaves;
+ },
+ set : function(octs){
+ this._octaves = octs;
+ this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, octs);
+ }
+ });
+
+ /**
+ * Clean up
+ * @returns {Tone.MetalSynth} this
+ */
+ Tone.MetalSynth.prototype.dispose = function(){
+ Tone.Instrument.prototype.dispose.call(this);
+ for (var i = 0; i < this._oscillators.length; i++){
+ this._oscillators[i].dispose();
+ this._freqMultipliers[i].dispose();
+ }
+ this._oscillators = null;
+ this._freqMultipliers = null;
+ this.frequency.dispose();
+ this.frequency = null;
+ this._filterFreqScaler.dispose();
+ this._filterFreqScaler = null;
+ this._amplitue.dispose();
+ this._amplitue = null;
+ this.envelope.dispose();
+ this.envelope = null;
+ this._highpass.dispose();
+ this._highpass = null;
+ };
+
+ return Tone.MetalSynth;
+});
\ No newline at end of file
diff --git a/Tone/instrument/NoiseSynth.js b/Tone/instrument/NoiseSynth.js
index 785a2d438..dbb30b50d 100644
--- a/Tone/instrument/NoiseSynth.js
+++ b/Tone/instrument/NoiseSynth.js
@@ -32,18 +32,6 @@ function(Tone){
*/
this.noise = new Tone.Noise();
- /**
- * The filter.
- * @type {Tone.Filter}
- */
- this.filter = new Tone.Filter(options.filter);
-
- /**
- * The filter envelope.
- * @type {Tone.FrequencyEnvelope}
- */
- this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope);
-
/**
* The amplitude envelope.
* @type {Tone.AmplitudeEnvelope}
@@ -51,12 +39,10 @@ function(Tone){
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
//connect the noise to the output
- this.noise.chain(this.filter, this.envelope, this.output);
+ this.noise.chain(this.envelope, this.output);
//start the noise
this.noise.start();
- //connect the filter envelope
- this.filterEnvelope.connect(this.filter.frequency);
- this._readOnly(["noise", "filter", "filterEnvelope", "envelope"]);
+ this._readOnly(["noise", "envelope"]);
};
Tone.extend(Tone.NoiseSynth, Tone.Instrument);
@@ -70,23 +56,10 @@ function(Tone){
"noise" : {
"type" : "white"
},
- "filter" : {
- "Q" : 6,
- "type" : "highpass",
- "rolloff" : -24
- },
"envelope" : {
"attack" : 0.005,
"decay" : 0.1,
"sustain" : 0.0,
- },
- "filterEnvelope" : {
- "attack" : 0.06,
- "decay" : 0.2,
- "sustain" : 0,
- "release" : 2,
- "baseFrequency" : 20,
- "octaves" : 5,
}
};
@@ -102,7 +75,6 @@ function(Tone){
Tone.NoiseSynth.prototype.triggerAttack = function(time, velocity){
//the envelopes
this.envelope.triggerAttack(time, velocity);
- this.filterEnvelope.triggerAttack(time);
return this;
};
@@ -113,7 +85,6 @@ function(Tone){
*/
Tone.NoiseSynth.prototype.triggerRelease = function(time){
this.envelope.triggerRelease(time);
- this.filterEnvelope.triggerRelease(time);
return this;
};
@@ -138,15 +109,11 @@ function(Tone){
*/
Tone.NoiseSynth.prototype.dispose = function(){
Tone.Instrument.prototype.dispose.call(this);
- this._writable(["noise", "filter", "filterEnvelope", "envelope"]);
+ this._writable(["noise", "envelope"]);
this.noise.dispose();
this.noise = null;
this.envelope.dispose();
this.envelope = null;
- this.filterEnvelope.dispose();
- this.filterEnvelope = null;
- this.filter.dispose();
- this.filter = null;
return this;
};
diff --git a/Tone/instrument/PolySynth.js b/Tone/instrument/PolySynth.js
index 5a30eee55..75e4928aa 100644
--- a/Tone/instrument/PolySynth.js
+++ b/Tone/instrument/PolySynth.js
@@ -1,4 +1,4 @@
-define(["Tone/core/Tone", "Tone/instrument/MonoSynth", "Tone/source/Source"],
+define(["Tone/core/Tone", "Tone/instrument/Synth", "Tone/source/Source"],
function(Tone){
"use strict";
@@ -13,11 +13,11 @@ function(Tone){
* @constructor
* @extends {Tone.Instrument}
* @param {number|Object} [polyphony=4] The number of voices to create
- * @param {function} [voice=Tone.MonoSynth] The constructor of the voices
- * uses Tone.MonoSynth by default.
+ * @param {function} [voice=Tone.Synth] The constructor of the voices
+ * uses Tone.Synth by default.
* @example
- * //a polysynth composed of 6 Voices of MonoSynth
- * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster();
+ * //a polysynth composed of 6 Voices of Synth
+ * var synth = new Tone.PolySynth(6, Tone.Synth).toMaster();
* //set the attributes using the set interface
* synth.set("detune", -1200);
* //play a chord
@@ -28,6 +28,10 @@ function(Tone){
Tone.Instrument.call(this);
var options = this.optionsObject(arguments, ["polyphony", "voice"], Tone.PolySynth.defaults);
+ options = this.defaultArg(options, Tone.Instrument.defaults);
+
+ //max polyphony
+ options.polyphony = Math.min(Tone.PolySynth.MAX_POLYPHONY, options.polyphony);
/**
* the array of voices
@@ -36,36 +40,38 @@ function(Tone){
this.voices = new Array(options.polyphony);
/**
- * If there are no more voices available,
- * should an active voice be stolen to play the new note?
- * @type {Boolean}
- */
- this.stealVoices = true;
-
- /**
- * the queue of free voices
+ * The queue of voices with data about last trigger
+ * and the triggered note
* @private
* @type {Array}
*/
- this._freeVoices = [];
+ this._triggers = new Array(options.polyphony);
/**
- * keeps track of which notes are down
- * @private
- * @type {Object}
+ * The detune in cents
+ * @type {Cents}
+ * @signal
*/
- this._activeVoices = {};
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
+ this._readOnly("detune");
//create the voices
for (var i = 0; i < options.polyphony; i++){
var v = new options.voice(arguments[2], arguments[3]);
this.voices[i] = v;
v.connect(this.output);
+ if (v.hasOwnProperty("detune")){
+ this.detune.connect(v.detune);
+ }
+ this._triggers[i] = {
+ release : -1,
+ note : null,
+ voice : v
+ };
}
- //make a copy of the voices
- this._freeVoices = this.voices.slice(0);
- //get the prototypes and properties
+ //set the volume initially
+ this.volume.value = options.volume;
};
Tone.extend(Tone.PolySynth, Tone.Instrument);
@@ -78,7 +84,9 @@ function(Tone){
*/
Tone.PolySynth.defaults = {
"polyphony" : 4,
- "voice" : Tone.MonoSynth
+ "volume" : 0,
+ "detune" : 0,
+ "voice" : Tone.Synth
};
/**
@@ -96,23 +104,21 @@ function(Tone){
if (!Array.isArray(notes)){
notes = [notes];
}
+ time = this.toSeconds(time);
for (var i = 0; i < notes.length; i++){
var val = notes[i];
- var stringified = JSON.stringify(val);
- //retrigger the same note if possible
- if (this._activeVoices.hasOwnProperty(stringified)){
- this._activeVoices[stringified].triggerAttack(val, time, velocity);
- } else if (this._freeVoices.length > 0){
- var voice = this._freeVoices.shift();
- voice.triggerAttack(val, time, velocity);
- this._activeVoices[stringified] = voice;
- } else if (this.stealVoices){ //steal a voice
- //take the first voice
- for (var voiceName in this._activeVoices){
- this._activeVoices[voiceName].triggerAttack(val, time, velocity);
- break;
+ //trigger the oldest voice
+ var oldest = this._triggers[0];
+ var oldestIndex = 0;
+ for (var j = 1; j < this._triggers.length; j++){
+ if (this._triggers[j].release < oldest.release){
+ oldest = this._triggers[j];
+ oldestIndex = j;
}
}
+ oldest.release = Infinity;
+ oldest.note = JSON.stringify(val);
+ oldest.voice.triggerAttack(val, time, velocity);
}
return this;
};
@@ -129,11 +135,21 @@ function(Tone){
* @example
* //trigger a chord for a duration of a half note
* poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n");
+ * @example
+ * //can pass in an array of durations as well
+ * poly.triggerAttackRelease(["Eb3", "G4", "C5"], ["2n", "4n", "4n"]);
*/
Tone.PolySynth.prototype.triggerAttackRelease = function(notes, duration, time, velocity){
time = this.toSeconds(time);
this.triggerAttack(notes, time, velocity);
- this.triggerRelease(notes, time + this.toSeconds(duration));
+ if (this.isArray(duration) && this.isArray(notes)){
+ for (var i = 0; i < notes.length; i++){
+ var d = duration[Math.min(i, duration.length - 1)];
+ this.triggerRelease(notes[i], time + this.toSeconds(d));
+ }
+ } else {
+ this.triggerRelease(notes, time + this.toSeconds(duration));
+ }
return this;
};
@@ -151,15 +167,16 @@ function(Tone){
if (!Array.isArray(notes)){
notes = [notes];
}
+ time = this.toSeconds(time);
for (var i = 0; i < notes.length; i++){
//get the voice
var stringified = JSON.stringify(notes[i]);
- var voice = this._activeVoices[stringified];
- if (voice){
- voice.triggerRelease(time);
- this._freeVoices.push(voice);
- delete this._activeVoices[stringified];
- voice = null;
+ for (var v = 0; v < this._triggers.length; v++){
+ var desc = this._triggers[v];
+ if (desc.note === stringified && desc.release > time){
+ desc.voice.triggerRelease(time);
+ desc.release = time;
+ }
}
}
return this;
@@ -207,8 +224,13 @@ function(Tone){
* @return {Tone.PolySynth} this
*/
Tone.PolySynth.prototype.releaseAll = function(time){
- for (var i = 0; i < this.voices.length; i++){
- this.voices[i].triggerRelease(time);
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._triggers.length; i++){
+ var desc = this._triggers[i];
+ if (desc.release > time){
+ desc.release = time;
+ desc.voice.triggerRelease(time);
+ }
}
return this;
};
@@ -223,11 +245,21 @@ function(Tone){
this.voices[i].dispose();
this.voices[i] = null;
}
+ this._writable("detune");
+ this.detune.dispose();
+ this.detune = null;
this.voices = null;
- this._activeVoices = null;
- this._freeVoices = null;
+ this._triggers = null;
return this;
};
+ /**
+ * The maximum number of notes that can be allocated
+ * to a polysynth.
+ * @type {Number}
+ * @static
+ */
+ Tone.PolySynth.MAX_POLYPHONY = 20;
+
return Tone.PolySynth;
});
\ No newline at end of file
diff --git a/Tone/instrument/Sampler.js b/Tone/instrument/Sampler.js
index 964d2de01..48298d741 100644
--- a/Tone/instrument/Sampler.js
+++ b/Tone/instrument/Sampler.js
@@ -1,92 +1,43 @@
-define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/component/FrequencyEnvelope",
- "Tone/component/Filter", "Tone/instrument/Instrument"],
+define(["Tone/core/Tone", "Tone/source/Player", "Tone/component/AmplitudeEnvelope", "Tone/instrument/Instrument"],
function(Tone){
"use strict";
/**
- * @class A sampler instrument which plays an audio buffer
- * through an amplitude envelope and a filter envelope. The sampler takes
- * an Object in the constructor which maps a sample name to the URL
- * of the sample. Nested Objects will be flattened and can be accessed using
- * a dot notation (see the example).
- *
+ * @class Sampler wraps Tone.Player in an AmplitudeEnvelope.
*
* @constructor
* @extends {Tone.Instrument}
- * @param {Object|string} urls the urls of the audio file
- * @param {Object} [options] the options object for the synth
+ * @param {String} url the url of the audio file
+ * @param {Function=} onload The callback to invoke when the sample is loaded.
* @example
- * var sampler = new Sampler({
- * A : {
- * 1 : "./audio/casio/A1.mp3",
- * 2 : "./audio/casio/A2.mp3",
- * },
- * "B.1" : "./audio/casio/B1.mp3",
+ * var sampler = new Sampler("./audio/casio/A1.mp3", function(){
+ * //repitch the sample down a half step
+ * sampler.triggerAttack(-1);
* }).toMaster();
- *
- * //listen for when all the samples have loaded
- * Tone.Buffer.onload = function(){
- * sampler.triggerAttack("A.1", time, velocity);
- * };
*/
- Tone.Sampler = function(urls, options){
+ Tone.Sampler = function(){
- options = this.defaultArg(options, Tone.Sampler.defaults);
+ var options = this.optionsObject(arguments, ["url", "onload"], Tone.Sampler.defaults);
Tone.Instrument.call(this, options);
/**
* The sample player.
* @type {Tone.Player}
*/
- this.player = new Tone.Player(options.player);
+ this.player = new Tone.Player(options.url, options.onload);
this.player.retrigger = true;
- /**
- * the buffers
- * @type {Object}
- * @private
- */
- this._buffers = {};
-
/**
* The amplitude envelope.
* @type {Tone.AmplitudeEnvelope}
*/
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
- /**
- * The filter envelope.
- * @type {Tone.FrequencyEnvelope}
- */
- this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope);
-
- /**
- * The name of the current sample.
- * @type {string}
- * @private
- */
- this._sample = options.sample;
-
- /**
- * the private reference to the pitch
- * @type {number}
- * @private
- */
- this._pitch = options.pitch;
-
- /**
- * The filter.
- * @type {Tone.Filter}
- */
- this.filter = new Tone.Filter(options.filter);
-
- //connections / setup
- this._loadBuffers(urls);
- this.pitch = options.pitch;
- this.player.chain(this.filter, this.envelope, this.output);
- this.filterEnvelope.connect(this.filter.frequency);
- this._readOnly(["player", "filterEnvelope", "envelope", "filter"]);
+ this.player.chain(this.envelope, this.output);
+ this._readOnly(["player", "envelope"]);
+ this.loop = options.loop;
+ this.reverse = options.reverse;
};
Tone.extend(Tone.Sampler, Tone.Instrument);
@@ -96,92 +47,33 @@ function(Tone){
* @static
*/
Tone.Sampler.defaults = {
- "sample" : 0,
- "pitch" : 0,
- "player" : {
- "loop" : false,
- },
+ "onload" : Tone.noOp,
+ "loop" : false,
+ "reverse" : false,
"envelope" : {
"attack" : 0.001,
"decay" : 0,
"sustain" : 1,
"release" : 0.1
- },
- "filterEnvelope" : {
- "attack" : 0.001,
- "decay" : 0.001,
- "sustain" : 1,
- "release" : 0.5,
- "baseFrequency" : 20,
- "octaves" : 10,
- },
- "filter" : {
- "type" : "lowpass"
- }
- };
-
- /**
- * load the buffers
- * @param {Object} urls the urls
- * @private
- */
- Tone.Sampler.prototype._loadBuffers = function(urls){
- if (this.isString(urls)){
- this._buffers["0"] = new Tone.Buffer(urls, function(){
- this.sample = "0";
- }.bind(this));
- } else {
- urls = this._flattenUrls(urls);
- for (var buffName in urls){
- this._sample = buffName;
- var urlString = urls[buffName];
- this._buffers[buffName] = new Tone.Buffer(urlString);
- }
}
};
/**
- * Flatten an object into a single depth object.
- * thanks to https://gist.github.com/penguinboy/762197
- * @param {Object} ob
- * @return {Object}
- * @private
- */
- Tone.Sampler.prototype._flattenUrls = function(ob) {
- var toReturn = {};
- for (var i in ob) {
- if (!ob.hasOwnProperty(i)) continue;
- if (this.isObject(ob[i])) {
- var flatObject = this._flattenUrls(ob[i]);
- for (var x in flatObject) {
- if (!flatObject.hasOwnProperty(x)) continue;
- toReturn[i + "." + x] = flatObject[x];
- }
- } else {
- toReturn[i] = ob[i];
- }
- }
- return toReturn;
- };
-
- /**
- * Start the sample and simultaneously trigger the envelopes.
- * @param {string=} sample The name of the sample to trigger, defaults to
- * the last sample used.
+ * Trigger the start of the sample.
+ * @param {Interval} [pitch=0] The amount the sample should
+ * be repitched.
* @param {Time} [time=now] The time when the sample should start
- * @param {number} [velocity=1] The velocity of the note
+ * @param {NormalRange} [velocity=1] The velocity of the note
* @returns {Tone.Sampler} this
* @example
- * sampler.triggerAttack("B.1");
+ * sampler.triggerAttack(0, "+0.1", 0.5);
*/
- Tone.Sampler.prototype.triggerAttack = function(name, time, velocity){
+ Tone.Sampler.prototype.triggerAttack = function(pitch, time, velocity){
time = this.toSeconds(time);
- if (name){
- this.sample = name;
- }
+ pitch = this.defaultArg(pitch, 0);
+ this.player.playbackRate = this.intervalToFrequencyRatio(pitch);
this.player.start(time);
this.envelope.triggerAttack(time, velocity);
- this.filterEnvelope.triggerAttack(time);
return this;
};
@@ -196,32 +88,23 @@ function(Tone){
*/
Tone.Sampler.prototype.triggerRelease = function(time){
time = this.toSeconds(time);
- this.filterEnvelope.triggerRelease(time);
this.envelope.triggerRelease(time);
this.player.stop(this.toSeconds(this.envelope.release) + time);
return this;
};
/**
- * The name of the sample to trigger.
+ * If the output sample should loop or not.
* @memberOf Tone.Sampler#
* @type {number|string}
- * @name sample
- * @example
- * //set the sample to "A.2" for next time the sample is triggered
- * sampler.sample = "A.2";
+ * @name loop
*/
- Object.defineProperty(Tone.Sampler.prototype, "sample", {
+ Object.defineProperty(Tone.Sampler.prototype, "loop", {
get : function(){
- return this._sample;
+ return this.player.loop;
},
- set : function(name){
- if (this._buffers.hasOwnProperty(name)){
- this._sample = name;
- this.player.buffer = this._buffers[name];
- } else {
- throw new Error("Sampler does not have a sample named "+name);
- }
+ set : function(loop){
+ this.player.loop = loop;
}
});
@@ -233,34 +116,10 @@ function(Tone){
*/
Object.defineProperty(Tone.Sampler.prototype, "reverse", {
get : function(){
- for (var i in this._buffers){
- return this._buffers[i].reverse;
- }
+ return this.player.reverse;
},
set : function(rev){
- for (var i in this._buffers){
- this._buffers[i].reverse = rev;
- }
- }
- });
-
- /**
- * Repitch the sampled note by some interval (measured
- * in semi-tones).
- * @memberOf Tone.Sampler#
- * @type {Interval}
- * @name pitch
- * @example
- * sampler.pitch = -12; //down one octave
- * sampler.pitch = 7; //up a fifth
- */
- Object.defineProperty(Tone.Sampler.prototype, "pitch", {
- get : function(){
- return this._pitch;
- },
- set : function(interval){
- this._pitch = interval;
- this.player.playbackRate = this.intervalToFrequencyRatio(interval);
+ this.player.reverse = rev;
}
});
@@ -270,22 +129,13 @@ function(Tone){
*/
Tone.Sampler.prototype.dispose = function(){
Tone.Instrument.prototype.dispose.call(this);
- this._writable(["player", "filterEnvelope", "envelope", "filter"]);
+ this._writable(["player", "envelope"]);
this.player.dispose();
- this.filterEnvelope.dispose();
- this.envelope.dispose();
- this.filter.dispose();
this.player = null;
- this.filterEnvelope = null;
+ this.envelope.dispose();
this.envelope = null;
- this.filter = null;
- for (var sample in this._buffers){
- this._buffers[sample].dispose();
- this._buffers[sample] = null;
- }
- this._buffers = null;
return this;
};
return Tone.Sampler;
-});
+});
\ No newline at end of file
diff --git a/Tone/instrument/SimpleAM.js b/Tone/instrument/SimpleAM.js
deleted file mode 100644
index 68d50d7e9..000000000
--- a/Tone/instrument/SimpleAM.js
+++ /dev/null
@@ -1,170 +0,0 @@
-define(["Tone/core/Tone", "Tone/instrument/SimpleSynth", "Tone/signal/Signal", "Tone/signal/Multiply",
- "Tone/instrument/Monophonic", "Tone/signal/AudioToGain"],
-function(Tone){
-
- "use strict";
-
- /**
- * @class AMSynth uses the output of one Tone.SimpleSynth to modulate the
- * amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between
- * the two signals) affects the timbre of the output signal the most.
- * Read more about Amplitude Modulation Synthesis on [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm).
- *
- *
- * @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var synth = new Tone.SimpleAM().toMaster();
- * synth.triggerAttackRelease("C4", "8n");
- */
- Tone.SimpleAM = function(options){
-
- options = this.defaultArg(options, Tone.SimpleAM.defaults);
- Tone.Monophonic.call(this, options);
-
- /**
- * The carrier voice.
- * @type {Tone.SimpleSynth}
- */
- this.carrier = new Tone.SimpleSynth(options.carrier);
-
- /**
- * The modulator voice.
- * @type {Tone.SimpleSynth}
- */
- this.modulator = new Tone.SimpleSynth(options.modulator);
-
- /**
- * the frequency control
- * @type {Frequency}
- * @signal
- */
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
-
- /**
- * The ratio between the carrier and the modulator frequencies. A value of 1
- * makes both voices in unison, a value of 0.5 puts the modulator an octave below
- * the carrier.
- * @type {Positive}
- * @signal
- * @example
- * //set the modulator an octave above the carrier frequency
- * simpleAM.harmonicity.value = 2;
- */
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
-
- /**
- * convert the -1,1 output to 0,1
- * @type {Tone.AudioToGain}
- * @private
- */
- this._modulationScale = new Tone.AudioToGain();
-
- /**
- * the node where the modulation happens
- * @type {GainNode}
- * @private
- */
- this._modulationNode = this.context.createGain();
-
- //control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.modulator.chain(this._modulationScale, this._modulationNode.gain);
- this.carrier.chain(this._modulationNode, this.output);
- this._readOnly(["carrier", "modulator", "frequency", "harmonicity"]);
- };
-
- Tone.extend(Tone.SimpleAM, Tone.Monophonic);
-
- /**
- * @static
- * @type {Object}
- */
- Tone.SimpleAM.defaults = {
- "harmonicity" : 3,
- "carrier" : {
- "volume" : -10,
- "portamento" : 0,
- "oscillator" : {
- "type" : "sine"
- },
- "envelope" : {
- "attack" : 0.01,
- "decay" : 0.01,
- "sustain" : 1,
- "release" : 0.5
- },
- },
- "modulator" : {
- "volume" : -10,
- "portamento" : 0,
- "oscillator" : {
- "type" : "sine"
- },
- "envelope" : {
- "attack" : 0.5,
- "decay" : 0.1,
- "sustain" : 1,
- "release" : 0.5
- }
- }
- };
-
- /**
- * trigger the attack portion of the note
- *
- * @param {Time} [time=now] the time the note will occur
- * @param {number} [velocity=1] the velocity of the note
- * @returns {Tone.SimpleAM} this
- * @private
- */
- Tone.SimpleAM.prototype._triggerEnvelopeAttack = function(time, velocity){
- //the port glide
- time = this.toSeconds(time);
- //the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
- return this;
- };
-
- /**
- * trigger the release portion of the note
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.SimpleAM} this
- * @private
- */
- Tone.SimpleAM.prototype._triggerEnvelopeRelease = function(time){
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
- return this;
- };
-
- /**
- * clean up
- * @returns {Tone.SimpleAM} this
- */
- Tone.SimpleAM.prototype.dispose = function(){
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable(["carrier", "modulator", "frequency", "harmonicity"]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
- this.frequency.dispose();
- this.frequency = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this._modulationScale.dispose();
- this._modulationScale = null;
- this._modulationNode.disconnect();
- this._modulationNode = null;
- return this;
- };
-
- return Tone.SimpleAM;
-});
\ No newline at end of file
diff --git a/Tone/instrument/SimpleFM.js b/Tone/instrument/SimpleFM.js
deleted file mode 100644
index 3b02d6893..000000000
--- a/Tone/instrument/SimpleFM.js
+++ /dev/null
@@ -1,177 +0,0 @@
-define(["Tone/core/Tone", "Tone/instrument/SimpleSynth", "Tone/signal/Signal", "Tone/signal/Multiply", "Tone/instrument/Monophonic"],
-function(Tone){
-
- "use strict";
-
- /**
- * @class SimpleFM is composed of two Tone.SimpleSynths where one Tone.SimpleSynth modulates
- * the frequency of a second Tone.SimpleSynth. A lot of spectral content
- * can be explored using the Tone.FMSynth.modulationIndex parameter. Read more about
- * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm).
- *
- *
- * @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var fmSynth = new Tone.SimpleFM().toMaster();
- * fmSynth.triggerAttackRelease("C4", "8n");
- */
- Tone.SimpleFM = function(options){
-
- options = this.defaultArg(options, Tone.SimpleFM.defaults);
- Tone.Monophonic.call(this, options);
-
- /**
- * The carrier voice.
- * @type {Tone.SimpleSynth}
- */
- this.carrier = new Tone.SimpleSynth(options.carrier);
- this.carrier.volume.value = -10;
-
- /**
- * The modulator voice.
- * @type {Tone.SimpleSynth}
- */
- this.modulator = new Tone.SimpleSynth(options.modulator);
- this.modulator.volume.value = -10;
-
- /**
- * the frequency control
- * @type {Frequency}
- * @signal
- */
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
-
- /**
- * Harmonicity is the ratio between the two voices. A harmonicity of
- * 1 is no change. Harmonicity = 2 means a change of an octave.
- * @type {Positive}
- * @signal
- * @example
- * //pitch voice1 an octave below voice0
- * synth.harmonicity.value = 0.5;
- */
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
-
- /**
- * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
- * ratio of the frequency of the modulating signal (mf) to the amplitude of the
- * modulating signal (ma) -- as in ma/mf.
- * @type {Positive}
- * @signal
- */
- this.modulationIndex = new Tone.Multiply(options.modulationIndex);
- this.modulationIndex.units = Tone.Type.Positive;
-
- /**
- * the node where the modulation happens
- * @type {GainNode}
- * @private
- */
- this._modulationNode = this.context.createGain();
-
- //control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.frequency.chain(this.modulationIndex, this._modulationNode);
- this.modulator.connect(this._modulationNode.gain);
- this._modulationNode.gain.value = 0;
- this._modulationNode.connect(this.carrier.frequency);
- this.carrier.connect(this.output);
- this._readOnly(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]);;
- };
-
- Tone.extend(Tone.SimpleFM, Tone.Monophonic);
-
- /**
- * @static
- * @type {Object}
- */
- Tone.SimpleFM.defaults = {
- "harmonicity" : 3,
- "modulationIndex" : 10,
- "carrier" : {
- "volume" : -10,
- "portamento" : 0,
- "oscillator" : {
- "type" : "sine"
- },
- "envelope" : {
- "attack" : 0.01,
- "decay" : 0.0,
- "sustain" : 1,
- "release" : 0.5
- }
- },
- "modulator" : {
- "volume" : -10,
- "portamento" : 0,
- "oscillator" : {
- "type" : "triangle"
- },
- "envelope" : {
- "attack" : 0.01,
- "decay" : 0.0,
- "sustain" : 1,
- "release" : 0.5
- }
- }
- };
-
- /**
- * trigger the attack portion of the note
- *
- * @param {Time} [time=now] the time the note will occur
- * @param {number} [velocity=1] the velocity of the note
- * @returns {Tone.SimpleFM} this
- * @private
- */
- Tone.SimpleFM.prototype._triggerEnvelopeAttack = function(time, velocity){
- //the port glide
- time = this.toSeconds(time);
- //the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
- return this;
- };
-
- /**
- * trigger the release portion of the note
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.SimpleFM} this
- * @private
- */
- Tone.SimpleFM.prototype._triggerEnvelopeRelease = function(time){
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
- return this;
- };
-
- /**
- * clean up
- * @returns {Tone.SimpleFM} this
- */
- Tone.SimpleFM.prototype.dispose = function(){
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable(["carrier", "modulator", "frequency", "harmonicity", "modulationIndex"]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
- this.frequency.dispose();
- this.frequency = null;
- this.modulationIndex.dispose();
- this.modulationIndex = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this._modulationNode.disconnect();
- this._modulationNode = null;
- return this;
- };
-
- return Tone.SimpleFM;
-});
\ No newline at end of file
diff --git a/Tone/instrument/SimpleSynth.js b/Tone/instrument/SimpleSynth.js
index 364732292..69eb85f94 100644
--- a/Tone/instrument/SimpleSynth.js
+++ b/Tone/instrument/SimpleSynth.js
@@ -1,119 +1,19 @@
-define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/source/OmniOscillator", "Tone/signal/Signal", "Tone/instrument/Monophonic"],
+define(["Tone/core/Tone", "Tone/instrument/Synth"],
function(Tone){
"use strict";
/**
- * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator
- * routed through a Tone.AmplitudeEnvelope.
- *
- *
+ * @class Now called Tone.Synth
* @constructor
* @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var synth = new Tone.SimpleSynth().toMaster();
- * synth.triggerAttackRelease("C4", "8n");
*/
Tone.SimpleSynth = function(options){
-
- //get the defaults
- options = this.defaultArg(options, Tone.SimpleSynth.defaults);
- Tone.Monophonic.call(this, options);
-
- /**
- * The oscillator.
- * @type {Tone.OmniOscillator}
- */
- this.oscillator = new Tone.OmniOscillator(options.oscillator);
-
- /**
- * The frequency control.
- * @type {Frequency}
- * @signal
- */
- this.frequency = this.oscillator.frequency;
-
- /**
- * The detune control.
- * @type {Cents}
- * @signal
- */
- this.detune = this.oscillator.detune;
-
- /**
- * The amplitude envelope.
- * @type {Tone.AmplitudeEnvelope}
- */
- this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
-
- //connect the oscillators to the output
- this.oscillator.chain(this.envelope, this.output);
- //start the oscillators
- this.oscillator.start();
- this._readOnly(["oscillator", "frequency", "detune", "envelope"]);
+ console.warn("Tone.SimpleSynth is now called Tone.Synth");
+ Tone.Synth.call(this, options);
};
- Tone.extend(Tone.SimpleSynth, Tone.Monophonic);
-
- /**
- * @const
- * @static
- * @type {Object}
- */
- Tone.SimpleSynth.defaults = {
- "oscillator" : {
- "type" : "triangle"
- },
- "envelope" : {
- "attack" : 0.005,
- "decay" : 0.1,
- "sustain" : 0.3,
- "release" : 1
- }
- };
-
- /**
- * start the attack portion of the envelope
- * @param {Time} [time=now] the time the attack should start
- * @param {number} [velocity=1] the velocity of the note (0-1)
- * @returns {Tone.SimpleSynth} this
- * @private
- */
- Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function(time, velocity){
- //the envelopes
- this.envelope.triggerAttack(time, velocity);
- return this;
- };
-
- /**
- * start the release portion of the envelope
- * @param {Time} [time=now] the time the release should start
- * @returns {Tone.SimpleSynth} this
- * @private
- */
- Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function(time){
- this.envelope.triggerRelease(time);
- return this;
- };
-
-
- /**
- * clean up
- * @returns {Tone.SimpleSynth} this
- */
- Tone.SimpleSynth.prototype.dispose = function(){
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable(["oscillator", "frequency", "detune", "envelope"]);
- this.oscillator.dispose();
- this.oscillator = null;
- this.envelope.dispose();
- this.envelope = null;
- this.frequency = null;
- this.detune = null;
- return this;
- };
+ Tone.extend(Tone.SimpleSynth, Tone.Synth);
return Tone.SimpleSynth;
});
\ No newline at end of file
diff --git a/Tone/instrument/Synth.js b/Tone/instrument/Synth.js
new file mode 100644
index 000000000..e325d2d55
--- /dev/null
+++ b/Tone/instrument/Synth.js
@@ -0,0 +1,119 @@
+define(["Tone/core/Tone", "Tone/component/AmplitudeEnvelope", "Tone/source/OmniOscillator", "Tone/signal/Signal", "Tone/instrument/Monophonic"],
+function(Tone){
+
+ "use strict";
+
+ /**
+ * @class Tone.Synth is composed simply of a Tone.OmniOscillator
+ * routed through a Tone.AmplitudeEnvelope.
+ *
+ *
+ * @constructor
+ * @extends {Tone.Monophonic}
+ * @param {Object} [options] the options available for the synth
+ * see defaults below
+ * @example
+ * var synth = new Tone.Synth().toMaster();
+ * synth.triggerAttackRelease("C4", "8n");
+ */
+ Tone.Synth = function(options){
+
+ //get the defaults
+ options = this.defaultArg(options, Tone.Synth.defaults);
+ Tone.Monophonic.call(this, options);
+
+ /**
+ * The oscillator.
+ * @type {Tone.OmniOscillator}
+ */
+ this.oscillator = new Tone.OmniOscillator(options.oscillator);
+
+ /**
+ * The frequency control.
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = this.oscillator.frequency;
+
+ /**
+ * The detune control.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this.oscillator.detune;
+
+ /**
+ * The amplitude envelope.
+ * @type {Tone.AmplitudeEnvelope}
+ */
+ this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
+
+ //connect the oscillators to the output
+ this.oscillator.chain(this.envelope, this.output);
+ //start the oscillators
+ this.oscillator.start();
+ this._readOnly(["oscillator", "frequency", "detune", "envelope"]);
+ };
+
+ Tone.extend(Tone.Synth, Tone.Monophonic);
+
+ /**
+ * @const
+ * @static
+ * @type {Object}
+ */
+ Tone.Synth.defaults = {
+ "oscillator" : {
+ "type" : "triangle"
+ },
+ "envelope" : {
+ "attack" : 0.005,
+ "decay" : 0.1,
+ "sustain" : 0.3,
+ "release" : 1
+ }
+ };
+
+ /**
+ * start the attack portion of the envelope
+ * @param {Time} [time=now] the time the attack should start
+ * @param {number} [velocity=1] the velocity of the note (0-1)
+ * @returns {Tone.Synth} this
+ * @private
+ */
+ Tone.Synth.prototype._triggerEnvelopeAttack = function(time, velocity){
+ //the envelopes
+ this.envelope.triggerAttack(time, velocity);
+ return this;
+ };
+
+ /**
+ * start the release portion of the envelope
+ * @param {Time} [time=now] the time the release should start
+ * @returns {Tone.Synth} this
+ * @private
+ */
+ Tone.Synth.prototype._triggerEnvelopeRelease = function(time){
+ this.envelope.triggerRelease(time);
+ return this;
+ };
+
+
+ /**
+ * clean up
+ * @returns {Tone.Synth} this
+ */
+ Tone.Synth.prototype.dispose = function(){
+ Tone.Monophonic.prototype.dispose.call(this);
+ this._writable(["oscillator", "frequency", "detune", "envelope"]);
+ this.oscillator.dispose();
+ this.oscillator = null;
+ this.envelope.dispose();
+ this.envelope = null;
+ this.frequency = null;
+ this.detune = null;
+ return this;
+ };
+
+ return Tone.Synth;
+});
\ No newline at end of file
diff --git a/Tone/signal/AND.js b/Tone/signal/AND.js
deleted file mode 100644
index f19adb934..000000000
--- a/Tone/signal/AND.js
+++ /dev/null
@@ -1,51 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Equal"], function(Tone){
-
- "use strict";
-
- /**
- * @class [AND](https://en.wikipedia.org/wiki/Logical_conjunction)
- * returns 1 when all the inputs are equal to 1 and returns 0 otherwise.
- *
- * @extends {Tone.SignalBase}
- * @constructor
- * @param {number} [inputCount=2] the number of inputs. NOTE: all inputs are
- * connected to the single AND input node
- * @example
- * var and = new Tone.AND(2);
- * var sigA = new Tone.Signal(0).connect(and, 0, 0);
- * var sigB = new Tone.Signal(1).connect(and, 0, 1);
- * //the output of and is 0.
- */
- Tone.AND = function(inputCount){
-
- inputCount = this.defaultArg(inputCount, 2);
-
- Tone.call(this, inputCount, 0);
-
- /**
- * @type {Tone.Equal}
- * @private
- */
- this._equals = this.output = new Tone.Equal(inputCount);
-
- //make each of the inputs an alias
- for (var i = 0; i < inputCount; i++){
- this.input[i] = this._equals;
- }
- };
-
- Tone.extend(Tone.AND, Tone.SignalBase);
-
- /**
- * clean up
- * @returns {Tone.AND} this
- */
- Tone.AND.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._equals.dispose();
- this._equals = null;
- return this;
- };
-
- return Tone.AND;
-});
\ No newline at end of file
diff --git a/Tone/signal/Abs.js b/Tone/signal/Abs.js
index 46e57e780..198545b3e 100644
--- a/Tone/signal/Abs.js
+++ b/Tone/signal/Abs.js
@@ -1,4 +1,4 @@
-define(["Tone/core/Tone", "Tone/signal/Select", "Tone/signal/Negate", "Tone/signal/LessThan", "Tone/signal/Signal"],
+define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/signal/SignalBase"],
function(Tone){
"use strict";
@@ -21,27 +21,13 @@ function(Tone){
* @type {Tone.LessThan}
* @private
*/
- this._ltz = new Tone.LessThan(0);
-
- /**
- * @type {Tone.Select}
- * @private
- */
- this._switch = this.output = new Tone.Select(2);
-
- /**
- * @type {Tone.Negate}
- * @private
- */
- this._negate = new Tone.Negate();
-
- //two signal paths, positive and negative
- this.input.connect(this._switch, 0, 0);
- this.input.connect(this._negate);
- this._negate.connect(this._switch, 0, 1);
-
- //the control signal
- this.input.chain(this._ltz, this._switch.gate);
+ this._abs = this.input = this.output = new Tone.WaveShaper(function(val){
+ if (val === 0){
+ return 0;
+ } else {
+ return Math.abs(val);
+ }
+ }, 127);
};
Tone.extend(Tone.Abs, Tone.SignalBase);
@@ -52,12 +38,8 @@ function(Tone){
*/
Tone.Abs.prototype.dispose = function(){
Tone.prototype.dispose.call(this);
- this._switch.dispose();
- this._switch = null;
- this._ltz.dispose();
- this._ltz = null;
- this._negate.dispose();
- this._negate = null;
+ this._abs.dispose();
+ this._abs = null;
return this;
};
diff --git a/Tone/signal/Clip.js b/Tone/signal/Clip.js
deleted file mode 100644
index dd4c69a99..000000000
--- a/Tone/signal/Clip.js
+++ /dev/null
@@ -1,62 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Max", "Tone/signal/Min", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Clip the incoming signal so that the output is always between min and max.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number} min the minimum value of the outgoing signal
- * @param {number} max the maximum value of the outgoing signal
- * @example
- * var clip = new Tone.Clip(0.5, 1);
- * var osc = new Tone.Oscillator().connect(clip);
- * //clips the output of the oscillator to between 0.5 and 1.
- */
- Tone.Clip = function(min, max){
- //make sure the args are in the right order
- if (min > max){
- var tmp = min;
- min = max;
- max = tmp;
- }
-
- /**
- * The min clip value
- * @type {Number}
- * @signal
- */
- this.min = this.input = new Tone.Min(max);
- this._readOnly("min");
-
- /**
- * The max clip value
- * @type {Number}
- * @signal
- */
- this.max = this.output = new Tone.Max(min);
- this._readOnly("max");
-
- this.min.connect(this.max);
- };
-
- Tone.extend(Tone.Clip, Tone.SignalBase);
-
- /**
- * clean up
- * @returns {Tone.Clip} this
- */
- Tone.Clip.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._writable("min");
- this.min.dispose();
- this.min = null;
- this._writable("max");
- this.max.dispose();
- this.max = null;
- return this;
- };
-
- return Tone.Clip;
-});
\ No newline at end of file
diff --git a/Tone/signal/Equal.js b/Tone/signal/Equal.js
deleted file mode 100644
index 4f6e0a484..000000000
--- a/Tone/signal/Equal.js
+++ /dev/null
@@ -1,70 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/EqualZero", "Tone/signal/Subtract", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Output 1 if the signal is equal to the value, otherwise outputs 0.
- * Can accept two signals if connected to inputs 0 and 1.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number=} value The number to compare the incoming signal to
- * @example
- * var eq = new Tone.Equal(3);
- * var sig = new Tone.Signal(3).connect(eq);
- * //the output of eq is 1.
- */
- Tone.Equal = function(value){
-
- Tone.call(this, 2, 0);
-
- /**
- * subtract the value from the incoming signal
- *
- * @type {Tone.Add}
- * @private
- */
- this._sub = this.input[0] = new Tone.Subtract(value);
-
- /**
- * @type {Tone.EqualZero}
- * @private
- */
- this._equals = this.output = new Tone.EqualZero();
-
- this._sub.connect(this._equals);
- this.input[1] = this._sub.input[1];
- };
-
- Tone.extend(Tone.Equal, Tone.SignalBase);
-
- /**
- * The value to compare to the incoming signal.
- * @memberOf Tone.Equal#
- * @type {number}
- * @name value
- */
- Object.defineProperty(Tone.Equal.prototype, "value", {
- get : function(){
- return this._sub.value;
- },
- set : function(value){
- this._sub.value = value;
- }
- });
-
- /**
- * Clean up.
- * @returns {Tone.Equal} this
- */
- Tone.Equal.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._equals.dispose();
- this._equals = null;
- this._sub.dispose();
- this._sub = null;
- return this;
- };
-
- return Tone.Equal;
-});
\ No newline at end of file
diff --git a/Tone/signal/EqualZero.js b/Tone/signal/EqualZero.js
deleted file mode 100644
index 1f04c0014..000000000
--- a/Tone/signal/EqualZero.js
+++ /dev/null
@@ -1,67 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/GreaterThanZero", "Tone/signal/WaveShaper"],
-function(Tone){
-
- "use strict";
-
- /**
- * @class EqualZero outputs 1 when the input is equal to
- * 0 and outputs 0 otherwise.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @example
- * var eq0 = new Tone.EqualZero();
- * var sig = new Tone.Signal(0).connect(eq0);
- * //the output of eq0 is 1.
- */
- Tone.EqualZero = function(){
-
- /**
- * scale the incoming signal by a large factor
- * @private
- * @type {Tone.Multiply}
- */
- this._scale = this.input = new Tone.Multiply(10000);
-
- /**
- * @type {Tone.WaveShaper}
- * @private
- */
- this._thresh = new Tone.WaveShaper(function(val){
- if (val === 0){
- return 1;
- } else {
- return 0;
- }
- }, 128);
-
- /**
- * threshold the output so that it's 0 or 1
- * @type {Tone.GreaterThanZero}
- * @private
- */
- this._gtz = this.output = new Tone.GreaterThanZero();
-
- //connections
- this._scale.chain(this._thresh, this._gtz);
- };
-
- Tone.extend(Tone.EqualZero, Tone.SignalBase);
-
- /**
- * Clean up.
- * @returns {Tone.EqualZero} this
- */
- Tone.EqualZero.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._gtz.dispose();
- this._gtz = null;
- this._scale.dispose();
- this._scale = null;
- this._thresh.dispose();
- this._thresh = null;
- return this;
- };
-
- return Tone.EqualZero;
-});
\ No newline at end of file
diff --git a/Tone/signal/Expr.js b/Tone/signal/Expr.js
index d9323eb3e..992f938dd 100644
--- a/Tone/signal/Expr.js
+++ b/Tone/signal/Expr.js
@@ -1,8 +1,6 @@
define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signal/Multiply",
- "Tone/signal/IfThenElse", "Tone/signal/OR", "Tone/signal/AND", "Tone/signal/NOT",
- "Tone/signal/GreaterThan", "Tone/signal/LessThan", "Tone/signal/Equal", "Tone/signal/EqualZero",
- "Tone/signal/GreaterThanZero", "Tone/signal/Abs", "Tone/signal/Negate", "Tone/signal/Max",
- "Tone/signal/Min", "Tone/signal/Modulo", "Tone/signal/Pow", "Tone/signal/AudioToGain"],
+ "Tone/signal/GreaterThan", "Tone/signal/GreaterThanZero", "Tone/signal/Abs", "Tone/signal/Negate",
+ "Tone/signal/Modulo", "Tone/signal/Pow", "Tone/signal/AudioToGain"],
function(Tone){
"use strict";
@@ -50,7 +48,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
result = this._eval(tree);
} catch (e){
this._disposeNodes();
- throw new Error("Could evaluate expression: "+expr);
+ throw new Error("Tone.Expr: Could evaluate expression: "+expr);
}
/**
@@ -125,32 +123,6 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
regexp : /^abs/,
method : applyUnary.bind(this, Tone.Abs)
},
- "min" : {
- regexp : /^min/,
- method : applyBinary.bind(this, Tone.Min)
- },
- "max" : {
- regexp : /^max/,
- method : applyBinary.bind(this, Tone.Max)
- },
- "if" : {
- regexp : /^if/,
- method : function(args, self){
- var op = new Tone.IfThenElse();
- self._eval(args[0]).connect(op.if);
- self._eval(args[1]).connect(op.then);
- self._eval(args[2]).connect(op.else);
- return op;
- }
- },
- "gt0" : {
- regexp : /^gt0/,
- method : applyUnary.bind(this, Tone.GreaterThanZero)
- },
- "eq0" : {
- regexp : /^eq0/,
- method : applyUnary.bind(this, Tone.EqualZero)
- },
"mod" : {
regexp : /^mod/,
method : function(args, self){
@@ -201,32 +173,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
regexp : /^\*/,
precedence : 0,
method : applyBinary.bind(this, Tone.Multiply)
- },
- ">" : {
- regexp : /^\>/,
- precedence : 2,
- method : applyBinary.bind(this, Tone.GreaterThan)
- },
- "<" : {
- regexp : /^,
- precedence : 2,
- method : applyBinary.bind(this, Tone.LessThan)
- },
- "==" : {
- regexp : /^==/,
- precedence : 3,
- method : applyBinary.bind(this, Tone.Equal)
- },
- "&&" : {
- regexp : /^&&/,
- precedence : 4,
- method : applyBinary.bind(this, Tone.AND)
- },
- "||" : {
- regexp : /^\|\|/,
- precedence : 5,
- method : applyBinary.bind(this, Tone.OR)
- },
+ }
},
//unary expressions
"unary" : {
@@ -304,7 +251,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
}
}
}
- throw new SyntaxError("Unexpected token "+expr);
+ throw new SyntaxError("Tone.Expr: Unexpected token "+expr);
}
return {
@@ -372,7 +319,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
method : token.method,
args : [
expr,
- parseExpression(precedence)
+ parseExpression(precedence-1)
]
};
token = lexer.peek();
@@ -399,7 +346,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
var token, expr;
token = lexer.peek();
if (isUndef(token)) {
- throw new SyntaxError("Unexpected termination of expression");
+ throw new SyntaxError("Tone.Expr: Unexpected termination of expression");
}
if (token.type === "func") {
token = lexer.next();
@@ -421,14 +368,14 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
}
return expr;
}
- throw new SyntaxError("Parse error, cannot process token " + token.value);
+ throw new SyntaxError("Tone.Expr: Parse error, cannot process token " + token.value);
}
function parseFunctionCall(func) {
var token, args = [];
token = lexer.next();
if (!matchSyntax(token, "(")) {
- throw new SyntaxError("Expected ( in a function call \"" + func.value + "\"");
+ throw new SyntaxError("Tone.Expr: Expected ( in a function call \"" + func.value + "\"");
}
token = lexer.peek();
if (!matchSyntax(token, ")")) {
@@ -436,7 +383,7 @@ define(["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Subtract", "Tone/signa
}
token = lexer.next();
if (!matchSyntax(token, ")")) {
- throw new SyntaxError("Expected ) in a function call \"" + func.value + "\"");
+ throw new SyntaxError("Tone.Expr: Expected ) in a function call \"" + func.value + "\"");
}
return {
method : func.method,
diff --git a/Tone/signal/GreaterThanZero.js b/Tone/signal/GreaterThanZero.js
index 17f43ec5d..0515c0030 100644
--- a/Tone/signal/GreaterThanZero.js
+++ b/Tone/signal/GreaterThanZero.js
@@ -27,7 +27,7 @@ function(Tone){
} else {
return 1;
}
- });
+ }, 127);
/**
* scale the first thresholded signal by a large value.
diff --git a/Tone/signal/IfThenElse.js b/Tone/signal/IfThenElse.js
deleted file mode 100644
index 66978d83e..000000000
--- a/Tone/signal/IfThenElse.js
+++ /dev/null
@@ -1,56 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Select", "Tone/signal/Equal"], function(Tone){
-
- "use strict";
-
- /**
- * @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;
-});
\ No newline at end of file
diff --git a/Tone/signal/LessThan.js b/Tone/signal/LessThan.js
deleted file mode 100644
index ba21ede29..000000000
--- a/Tone/signal/LessThan.js
+++ /dev/null
@@ -1,78 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/Negate", "Tone/signal/Signal"],
-function(Tone){
-
- "use strict";
-
- /**
- * @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;
-});
\ No newline at end of file
diff --git a/Tone/signal/Max.js b/Tone/signal/Max.js
deleted file mode 100644
index 29470027d..000000000
--- a/Tone/signal/Max.js
+++ /dev/null
@@ -1,76 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/GreaterThan", "Tone/signal/IfThenElse", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Outputs the greater of two signals. If a number is provided in the constructor
- * it will use that instead of the signal.
- *
- * @constructor
- * @extends {Tone.Signal}
- * @param {number=} max Max value if provided. if not provided, it will use the
- * signal value from input 1.
- * @example
- * var max = new Tone.Max(2);
- * var sig = new Tone.Signal(3).connect(max);
- * //max outputs 3
- * sig.value = 1;
- * //max outputs 2
- * @example
- * var max = new Tone.Max();
- * var sigA = new Tone.Signal(3);
- * var sigB = new Tone.Signal(4);
- * sigA.connect(max, 0, 0);
- * sigB.connect(max, 0, 1);
- * //output of max is 4.
- */
- Tone.Max = function(max){
-
- Tone.call(this, 2, 0);
- this.input[0] = this.context.createGain();
-
- /**
- * the max signal
- * @type {Tone.Signal}
- * @private
- */
- this._param = this.input[1] = new Tone.Signal(max);
-
- /**
- * @type {Tone.Select}
- * @private
- */
- this._ifThenElse = this.output = new Tone.IfThenElse();
-
- /**
- * @type {Tone.Select}
- * @private
- */
- this._gt = new Tone.GreaterThan();
-
- //connections
- this.input[0].chain(this._gt, this._ifThenElse.if);
- this.input[0].connect(this._ifThenElse.then);
- this._param.connect(this._ifThenElse.else);
- this._param.connect(this._gt, 0, 1);
- };
-
- Tone.extend(Tone.Max, Tone.Signal);
-
- /**
- * Clean up.
- * @returns {Tone.Max} this
- */
- Tone.Max.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._param.dispose();
- this._ifThenElse.dispose();
- this._gt.dispose();
- this._param = null;
- this._ifThenElse = null;
- this._gt = null;
- return this;
- };
-
- return Tone.Max;
-});
\ No newline at end of file
diff --git a/Tone/signal/Min.js b/Tone/signal/Min.js
deleted file mode 100644
index e233086d5..000000000
--- a/Tone/signal/Min.js
+++ /dev/null
@@ -1,75 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/LessThan", "Tone/signal/IfThenElse", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Outputs the lesser of two signals. If a number is given
- * in the constructor, it will use a signal and a number.
- *
- * @constructor
- * @extends {Tone.Signal}
- * @param {number} min The minimum to compare to the incoming signal
- * @example
- * var min = new Tone.Min(2);
- * var sig = new Tone.Signal(3).connect(min);
- * //min outputs 2
- * sig.value = 1;
- * //min outputs 1
- * @example
- * var min = new Tone.Min();
- * var sigA = new Tone.Signal(3);
- * var sigB = new Tone.Signal(4);
- * sigA.connect(min, 0, 0);
- * sigB.connect(min, 0, 1);
- * //output of min is 3.
- */
- Tone.Min = function(min){
-
- Tone.call(this, 2, 0);
- this.input[0] = this.context.createGain();
-
- /**
- * @type {Tone.Select}
- * @private
- */
- this._ifThenElse = this.output = new Tone.IfThenElse();
-
- /**
- * @type {Tone.Select}
- * @private
- */
- this._lt = new Tone.LessThan();
-
- /**
- * the min signal
- * @type {Tone.Signal}
- * @private
- */
- this._param = this.input[1] = new Tone.Signal(min);
-
- //connections
- this.input[0].chain(this._lt, this._ifThenElse.if);
- this.input[0].connect(this._ifThenElse.then);
- this._param.connect(this._ifThenElse.else);
- this._param.connect(this._lt, 0, 1);
- };
-
- Tone.extend(Tone.Min, Tone.Signal);
-
- /**
- * clean up
- * @returns {Tone.Min} this
- */
- Tone.Min.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._param.dispose();
- this._ifThenElse.dispose();
- this._lt.dispose();
- this._param = null;
- this._ifThenElse = null;
- this._lt = null;
- return this;
- };
-
- return Tone.Min;
-});
\ No newline at end of file
diff --git a/Tone/signal/NOT.js b/Tone/signal/NOT.js
deleted file mode 100644
index 3d75f8c97..000000000
--- a/Tone/signal/NOT.js
+++ /dev/null
@@ -1,21 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/EqualZero"], function(Tone){
-
- "use strict";
-
- /**
- * @class Just an alias for Tone.EqualZero, but has the same effect as a NOT operator.
- * Outputs 1 when input equals 0.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @example
- * var not = new Tone.NOT();
- * var sig = new Tone.Signal(1).connect(not);
- * //output of not equals 0.
- * sig.value = 0;
- * //output of not equals 1.
- */
- Tone.NOT = Tone.EqualZero;
-
- return Tone.NOT;
-});
\ No newline at end of file
diff --git a/Tone/signal/OR.js b/Tone/signal/OR.js
deleted file mode 100644
index e26b3b9a2..000000000
--- a/Tone/signal/OR.js
+++ /dev/null
@@ -1,60 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/GreaterThanZero"], function(Tone){
-
- "use strict";
-
- /**
- * @class [OR](https://en.wikipedia.org/wiki/OR_gate)
- * the inputs together. True if at least one of the inputs is true.
- *
- * @extends {Tone.SignalBase}
- * @constructor
- * @param {number} [inputCount=2] the input count
- * @example
- * var or = new Tone.OR(2);
- * var sigA = new Tone.Signal(0)connect(or, 0, 0);
- * var sigB = new Tone.Signal(1)connect(or, 0, 1);
- * //output of or is 1 because at least
- * //one of the inputs is equal to 1.
- */
- Tone.OR = function(inputCount){
-
- inputCount = this.defaultArg(inputCount, 2);
- Tone.call(this, inputCount, 0);
-
- /**
- * a private summing node
- * @type {GainNode}
- * @private
- */
- this._sum = this.context.createGain();
-
- /**
- * @type {Tone.Equal}
- * @private
- */
- this._gtz = this.output = new Tone.GreaterThanZero();
-
- //make each of the inputs an alias
- for (var i = 0; i < inputCount; i++){
- this.input[i] = this._sum;
- }
- this._sum.connect(this._gtz);
- };
-
- Tone.extend(Tone.OR, Tone.SignalBase);
-
- /**
- * clean up
- * @returns {Tone.OR} this
- */
- Tone.OR.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._gtz.dispose();
- this._gtz = null;
- this._sum.disconnect();
- this._sum = null;
- return this;
- };
-
- return Tone.OR;
-});
\ No newline at end of file
diff --git a/Tone/signal/Route.js b/Tone/signal/Route.js
deleted file mode 100644
index 39e15e905..000000000
--- a/Tone/signal/Route.js
+++ /dev/null
@@ -1,116 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Route a single input to the specified output.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number} [outputCount=2] the number of inputs the switch accepts
- * @example
- * var route = new Tone.Route(4);
- * var signal = new Tone.Signal(3).connect(route);
- * route.select(0);
- * //signal is routed through output 0
- * route.select(3);
- * //signal is now routed through output 3
- */
- Tone.Route = function(outputCount){
-
- outputCount = this.defaultArg(outputCount, 2);
- Tone.call(this, 1, outputCount);
-
- /**
- * The control signal.
- * @type {Number}
- * @signal
- */
- this.gate = new Tone.Signal(0);
- this._readOnly("gate");
-
- //make all the inputs and connect them
- for (var i = 0; i < outputCount; i++){
- var routeGate = new RouteGate(i);
- this.output[i] = routeGate;
- this.gate.connect(routeGate.selecter);
- this.input.connect(routeGate);
- }
- };
-
- Tone.extend(Tone.Route, Tone.SignalBase);
-
- /**
- * Routes the signal to one of the outputs and close the others.
- * @param {number} [which=0] Open one of the gates (closes the other).
- * @param {Time} [time=now] The time when the switch will open.
- * @returns {Tone.Route} this
- */
- Tone.Route.prototype.select = function(which, time){
- //make sure it's an integer
- which = Math.floor(which);
- this.gate.setValueAtTime(which, this.toSeconds(time));
- return this;
- };
-
- /**
- * Clean up.
- * @returns {Tone.Route} this
- */
- Tone.Route.prototype.dispose = function(){
- this._writable("gate");
- this.gate.dispose();
- this.gate = null;
- for (var i = 0; i < this.output.length; i++){
- this.output[i].dispose();
- this.output[i] = null;
- }
- Tone.prototype.dispose.call(this);
- return this;
- };
-
- ////////////START HELPER////////////
-
- /**
- * helper class for Tone.Route representing a single gate
- * @constructor
- * @extends {Tone}
- * @private
- */
- var RouteGate = function(num){
-
- /**
- * the selector
- * @type {Tone.Equal}
- */
- this.selecter = new Tone.Equal(num);
-
- /**
- * the gate
- * @type {GainNode}
- */
- this.gate = this.input = this.output = this.context.createGain();
-
- //connect the selecter to the gate gain
- this.selecter.connect(this.gate.gain);
- };
-
- Tone.extend(RouteGate);
-
- /**
- * clean up
- * @private
- */
- RouteGate.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this.selecter.dispose();
- this.selecter = null;
- this.gate.disconnect();
- this.gate = null;
- };
-
- ////////////END HELPER////////////
-
- //return Tone.Route
- return Tone.Route;
-});
\ No newline at end of file
diff --git a/Tone/signal/Select.js b/Tone/signal/Select.js
deleted file mode 100644
index da671b8ce..000000000
--- a/Tone/signal/Select.js
+++ /dev/null
@@ -1,122 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/Equal", "Tone/signal/Signal"], function(Tone){
-
- "use strict";
-
- /**
- * @class Select between any number of inputs, sending the one
- * selected by the gate signal to the output
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number} [sourceCount=2] the number of inputs the switch accepts
- * @example
- * var sel = new Tone.Select(2);
- * var sigA = new Tone.Signal(10).connect(sel, 0, 0);
- * var sigB = new Tone.Signal(20).connect(sel, 0, 1);
- * sel.gate.value = 0;
- * //sel outputs 10 (the value of sigA);
- * sel.gate.value = 1;
- * //sel outputs 20 (the value of sigB);
- */
- Tone.Select = function(sourceCount){
-
- sourceCount = this.defaultArg(sourceCount, 2);
-
- Tone.call(this, sourceCount, 1);
-
- /**
- * the control signal
- * @type {Number}
- * @signal
- */
- this.gate = new Tone.Signal(0);
- this._readOnly("gate");
-
- //make all the inputs and connect them
- for (var i = 0; i < sourceCount; i++){
- var switchGate = new SelectGate(i);
- this.input[i] = switchGate;
- this.gate.connect(switchGate.selecter);
- switchGate.connect(this.output);
- }
- };
-
- Tone.extend(Tone.Select, Tone.SignalBase);
-
- /**
- * Open a specific input and close the others.
- * @param {number} which The gate to open.
- * @param {Time} [time=now] The time when the switch will open
- * @returns {Tone.Select} this
- * @example
- * //open input 1 in a half second from now
- * sel.select(1, "+0.5");
- */
- Tone.Select.prototype.select = function(which, time){
- //make sure it's an integer
- which = Math.floor(which);
- this.gate.setValueAtTime(which, this.toSeconds(time));
- return this;
- };
-
- /**
- * Clean up.
- * @returns {Tone.Select} this
- */
- Tone.Select.prototype.dispose = function(){
- this._writable("gate");
- this.gate.dispose();
- this.gate = null;
- for (var i = 0; i < this.input.length; i++){
- this.input[i].dispose();
- this.input[i] = null;
- }
- Tone.prototype.dispose.call(this);
- return this;
- };
-
- ////////////START HELPER////////////
-
- /**
- * helper class for Tone.Select representing a single gate
- * @constructor
- * @extends {Tone}
- * @private
- */
- var SelectGate = function(num){
-
- /**
- * the selector
- * @type {Tone.Equal}
- */
- this.selecter = new Tone.Equal(num);
-
- /**
- * the gate
- * @type {GainNode}
- */
- this.gate = this.input = this.output = this.context.createGain();
-
- //connect the selecter to the gate gain
- this.selecter.connect(this.gate.gain);
- };
-
- Tone.extend(SelectGate);
-
- /**
- * clean up
- * @private
- */
- SelectGate.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this.selecter.dispose();
- this.gate.disconnect();
- this.selecter = null;
- this.gate = null;
- };
-
- ////////////END HELPER////////////
-
- //return Tone.Select
- return Tone.Select;
-});
\ No newline at end of file
diff --git a/Tone/signal/Signal.js b/Tone/signal/Signal.js
index c0ab1e590..8d3681f81 100644
--- a/Tone/signal/Signal.js
+++ b/Tone/signal/Signal.js
@@ -1,4 +1,4 @@
-define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/core/Type", "Tone/core/Param", "Tone/core/Gain"], function(Tone){
+define(["Tone/core/Tone", "Tone/signal/WaveShaper", "Tone/type/Type", "Tone/core/Param", "Tone/core/Gain"], function(Tone){
"use strict";
diff --git a/Tone/signal/Switch.js b/Tone/signal/Switch.js
deleted file mode 100644
index b977ef23b..000000000
--- a/Tone/signal/Switch.js
+++ /dev/null
@@ -1,99 +0,0 @@
-define(["Tone/core/Tone", "Tone/signal/SignalBase", "Tone/signal/GreaterThan"], function(Tone){
-
- "use strict";
-
- /**
- * @class When the gate is set to 0, the input signal does not pass through to the output.
- * If the gate is set to 1, the input signal passes through.
- * the gate is initially closed.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {Boolean} [open=false] If the gate is initially open or closed.
- * @example
- * var sigSwitch = new Tone.Switch();
- * var signal = new Tone.Signal(2).connect(sigSwitch);
- * //initially no output from sigSwitch
- * sigSwitch.gate.value = 1;
- * //open the switch and allow the signal through
- * //the output of sigSwitch is now 2.
- */
- Tone.Switch = function(open){
-
- open = this.defaultArg(open, false);
-
- Tone.call(this);
-
- /**
- * The control signal for the switch.
- * When this value is 0, the input signal will NOT pass through,
- * when it is high (1), the input signal will pass through.
- *
- * @type {Number}
- * @signal
- */
- this.gate = new Tone.Signal(0);
- this._readOnly("gate");
-
- /**
- * thresh the control signal to either 0 or 1
- * @type {Tone.GreaterThan}
- * @private
- */
- this._thresh = new Tone.GreaterThan(0.5);
-
- this.input.connect(this.output);
- this.gate.chain(this._thresh, this.output.gain);
-
- //initially open
- if (open){
- this.open();
- }
- };
-
- Tone.extend(Tone.Switch, Tone.SignalBase);
-
- /**
- * Open the switch at a specific time.
- *
- * @param {Time} [time=now] The time when the switch will be open.
- * @returns {Tone.Switch} this
- * @example
- * //open the switch to let the signal through
- * sigSwitch.open();
- */
- Tone.Switch.prototype.open = function(time){
- this.gate.setValueAtTime(1, this.toSeconds(time));
- return this;
- };
-
- /**
- * Close the switch at a specific time.
- *
- * @param {Time} [time=now] The time when the switch will be closed.
- * @returns {Tone.Switch} this
- * @example
- * //close the switch a half second from now
- * sigSwitch.close("+0.5");
- */
- Tone.Switch.prototype.close = function(time){
- this.gate.setValueAtTime(0, this.toSeconds(time));
- return this;
- };
-
- /**
- * Clean up.
- * @returns {Tone.Switch} this
- */
- Tone.Switch.prototype.dispose = function(){
- Tone.prototype.dispose.call(this);
- this._writable("gate");
- this.gate.dispose();
- this.gate = null;
- this._thresh.dispose();
- this._thresh = null;
- return this;
- };
-
- return Tone.Switch;
-});
\ No newline at end of file
diff --git a/Tone/signal/TimelineSignal.js b/Tone/signal/TimelineSignal.js
index 593179dfd..7e5e373d3 100644
--- a/Tone/signal/TimelineSignal.js
+++ b/Tone/signal/TimelineSignal.js
@@ -12,12 +12,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
Tone.TimelineSignal = function(){
var options = this.optionsObject(arguments, ["value", "units"], Tone.Signal.defaults);
-
- //constructors
- Tone.Signal.apply(this, options);
- options.param = this._param;
- Tone.Param.call(this, options);
-
+
/**
* The scheduled events
* @type {Tone.Timeline}
@@ -25,6 +20,11 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
*/
this._events = new Tone.Timeline(10);
+ //constructors
+ Tone.Signal.apply(this, options);
+ options.param = this._param;
+ Tone.Param.call(this, options);
+
/**
* The initial scheduled value
* @type {Number}
@@ -38,11 +38,13 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
/**
* The event types of a schedulable signal.
* @enum {String}
+ * @private
*/
Tone.TimelineSignal.Type = {
Linear : "linear",
Exponential : "exponential",
Target : "target",
+ Curve : "curve",
Set : "set"
};
@@ -54,11 +56,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
*/
Object.defineProperty(Tone.TimelineSignal.prototype, "value", {
get : function(){
- return this._toUnits(this._param.value);
+ var now = this.now();
+ var val = this.getValueAtTime(now);
+ return this._toUnits(val);
},
set : function(value){
var convertedVal = this._fromUnits(value);
this._initial = convertedVal;
+ this.cancelScheduledValues();
this._param.value = convertedVal;
}
});
@@ -118,15 +123,27 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
* @returns {Tone.TimelineSignal} this
*/
Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
+ //get the previous event and make sure it's not starting from 0
+ var beforeEvent = this._searchBefore(endTime);
+ if (beforeEvent && beforeEvent.value === 0){
+ //reschedule that event
+ this.setValueAtTime(this._minOutput, beforeEvent.time);
+ }
value = this._fromUnits(value);
- value = Math.max(this._minOutput, value);
+ var setValue = Math.max(value, this._minOutput);
endTime = this.toSeconds(endTime);
this._events.addEvent({
"type" : Tone.TimelineSignal.Type.Exponential,
- "value" : value,
+ "value" : setValue,
"time" : endTime
});
- this._param.exponentialRampToValueAtTime(value, endTime);
+ //if the ramped to value is 0, make it go to the min output, and then set to 0.
+ if (value < this._minOutput){
+ this._param.exponentialRampToValueAtTime(this._minOutput, endTime - this.sampleTime);
+ this.setValueAtTime(0, endTime);
+ } else {
+ this._param.exponentialRampToValueAtTime(value, endTime);
+ }
return this;
};
@@ -153,6 +170,39 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
return this;
};
+ /**
+ * Set an array of arbitrary values starting at the given time for the given duration.
+ * @param {Float32Array} values
+ * @param {Time} startTime
+ * @param {Time} duration
+ * @param {NormalRange} [scaling=1] If the values in the curve should be scaled by some value
+ * @returns {Tone.TimelineSignal} this
+ */
+ Tone.TimelineSignal.prototype.setValueCurveAtTime = function (values, startTime, duration, scaling) {
+ scaling = this.defaultArg(scaling, 1);
+ //copy the array
+ var floats = new Array(values.length);
+ for (var i = 0; i < floats.length; i++){
+ floats[i] = this._fromUnits(values[i]) * scaling;
+ }
+ startTime = this.toSeconds(startTime);
+ duration = this.toSeconds(duration);
+ this._events.addEvent({
+ "type" : Tone.TimelineSignal.Type.Curve,
+ "value" : floats,
+ "time" : startTime,
+ "duration" : duration
+ });
+ //set the first value
+ this._param.setValueAtTime(floats[0], startTime);
+ //schedule a lienar ramp for each of the segments
+ for (var j = 1; j < floats.length; j++){
+ var segmentTime = startTime + (j / (floats.length - 1) * duration);
+ this._param.linearRampToValueAtTime(floats[j], segmentTime);
+ }
+ return this;
+ };
+
/**
* Cancels all scheduled parameter changes with times greater than or
* equal to startTime.
@@ -179,19 +229,34 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
Tone.TimelineSignal.prototype.setRampPoint = function (time) {
time = this.toSeconds(time);
//get the value at the given time
- var val = this.getValueAtTime(time);
- //reschedule the next event to end at the given time
- var after = this._searchAfter(time);
- if (after){
- //cancel the next event(s)
+ var val = this._toUnits(this.getValueAtTime(time));
+ //if there is an event at the given time
+ //and that even is not a "set"
+ var before = this._searchBefore(time);
+ if (before && before.time === time){
+ //remove everything after
+ this.cancelScheduledValues(time + this.sampleTime);
+ } else if (before &&
+ before.type === Tone.TimelineSignal.Type.Curve &&
+ before.time + before.duration > time){
+ //if the curve is still playing
+ //cancel the curve
this.cancelScheduledValues(time);
- if (after.type === Tone.TimelineSignal.Type.Linear){
- this.linearRampToValueAtTime(val, time);
- } else if (after.type === Tone.TimelineSignal.Type.Exponential){
- this.exponentialRampToValueAtTime(val, time);
- }
- }
- this.setValueAtTime(val, time);
+ this.linearRampToValueAtTime(val, time);
+ } else {
+ //reschedule the next event to end at the given time
+ var after = this._searchAfter(time);
+ if (after){
+ //cancel the next event(s)
+ this.cancelScheduledValues(time);
+ if (after.type === Tone.TimelineSignal.Type.Linear){
+ this.linearRampToValueAtTime(val, time);
+ } else if (after.type === Tone.TimelineSignal.Type.Exponential){
+ this.exponentialRampToValueAtTime(val, time);
+ }
+ }
+ this.setValueAtTime(val, time);
+ }
return this;
};
@@ -269,6 +334,8 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
previouVal = previous.value;
}
value = this._exponentialApproach(before.time, previouVal, before.value, before.constant, time);
+ } else if (before.type === Tone.TimelineSignal.Type.Curve){
+ value = this._curveInterpolate(before.time, before.value, before.duration, time);
} else if (after === null){
value = before.value;
} else if (after.type === Tone.TimelineSignal.Type.Linear){
@@ -326,6 +393,31 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
};
+ /**
+ * Calculates the the value along the curve produced by setValueCurveAtTime
+ * @private
+ */
+ Tone.TimelineSignal.prototype._curveInterpolate = function (start, curve, duration, time) {
+ var len = curve.length;
+ // If time is after duration, return the last curve value
+ if (time >= start + duration) {
+ return curve[len - 1];
+ } else if (time <= start){
+ return curve[0];
+ } else {
+ var progress = (time - start) / duration;
+ var lowerIndex = Math.floor((len - 1) * progress);
+ var upperIndex = Math.ceil((len - 1) * progress);
+ var lowerVal = curve[lowerIndex];
+ var upperVal = curve[upperIndex];
+ if (upperIndex === lowerIndex){
+ return lowerVal;
+ } else {
+ return this._linearInterpolate(lowerIndex, lowerVal, upperIndex, upperVal, progress * (len - 1));
+ }
+ }
+ };
+
/**
* Clean up.
* @return {Tone.TimelineSignal} this
diff --git a/Tone/signal/WaveShaper.js b/Tone/signal/WaveShaper.js
index 779faee75..7be05d14c 100644
--- a/Tone/signal/WaveShaper.js
+++ b/Tone/signal/WaveShaper.js
@@ -70,7 +70,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){
*/
Tone.WaveShaper.prototype.setMap = function(mapping){
for (var i = 0, len = this._curve.length; i < len; i++){
- var normalized = (i / (len)) * 2 - 1;
+ var normalized = (i / (len - 1)) * 2 - 1;
this._curve[i] = mapping(normalized, i);
}
this._shaper.curve = this._curve;
@@ -110,7 +110,7 @@ define(["Tone/core/Tone", "Tone/signal/SignalBase"], function(Tone){
if (["none", "2x", "4x"].indexOf(oversampling) !== -1){
this._shaper.oversample = oversampling;
} else {
- throw new Error("invalid oversampling: "+oversampling);
+ throw new RangeError("Tone.WaveShaper: oversampling must be either 'none', '2x', or '4x'");
}
}
});
diff --git a/Tone/signal/Zero.js b/Tone/signal/Zero.js
new file mode 100644
index 000000000..4fe41a545
--- /dev/null
+++ b/Tone/signal/Zero.js
@@ -0,0 +1,64 @@
+define(["Tone/core/Tone", "Tone/core/Gain"], function (Tone) {
+
+ /**
+ * @class Tone.Zero outputs 0's at audio-rate. The reason this has to be
+ * it's own class is that many browsers optimize out Tone.Signal
+ * with a value of 0 and will not process nodes further down the graph.
+ * @extends {Tone}
+ */
+ Tone.Zero = function(){
+
+ /**
+ * The gain node
+ * @type {Tone.Gain}
+ * @private
+ */
+ this._gain = this.input = this.output = new Tone.Gain();
+
+ Tone.Zero._zeros.connect(this._gain);
+ };
+
+ Tone.extend(Tone.Zero);
+
+ /**
+ * clean up
+ * @return {Tone.Zero} this
+ */
+ Tone.Zero.prototype.dispose = function(){
+ Tone.prototype.dispose.call(this);
+ this._gain.dispose();
+ this._gain = null;
+ return this;
+ };
+
+ /**
+ * Generates a constant output of 0. This is so
+ * the processing graph doesn't optimize out this
+ * segment of the graph.
+ * @static
+ * @private
+ * @const
+ * @type {AudioBufferSourceNode}
+ */
+ Tone.Zero._zeros = null;
+
+ /**
+ * initializer function
+ */
+ Tone._initAudioContext(function(audioContext){
+ var buffer = audioContext.createBuffer(1, 128, audioContext.sampleRate);
+ var arr = buffer.getChannelData(0);
+ for (var i = 0; i < arr.length; i++){
+ arr[i] = 0;
+ }
+ Tone.Zero._zeros = audioContext.createBufferSource();
+ Tone.Zero._zeros.channelCount = 1;
+ Tone.Zero._zeros.channelCountMode = "explicit";
+ Tone.Zero._zeros.buffer = buffer;
+ Tone.Zero._zeros.loop = true;
+ Tone.Zero._zeros.start(0);
+ Tone.Zero._zeros.noGC();
+ });
+
+ return Tone.Zero;
+});
\ No newline at end of file
diff --git a/Tone/source/AMOscillator.js b/Tone/source/AMOscillator.js
new file mode 100644
index 000000000..5caeff6a8
--- /dev/null
+++ b/Tone/source/AMOscillator.js
@@ -0,0 +1,220 @@
+define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply",
+ "Tone/core/Gain", "Tone/signal/AudioToGain"],
+function(Tone){
+
+ "use strict";
+
+ /**
+ * @class Tone.AMOscillator
+ *
+ * @extends {Tone.Oscillator}
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
+ * @example
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.AMOscillator("Ab3", "sine", "square").toMaster().start();
+ */
+ Tone.AMOscillator = function(){
+
+ var options = this.optionsObject(arguments, ["frequency", "type", "modulationType"], Tone.AMOscillator.defaults);
+ Tone.Source.call(this, options);
+
+ /**
+ * The carrier oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._carrier = new Tone.Oscillator(options.frequency, options.type);
+
+ /**
+ * The oscillator's frequency
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = this._carrier.frequency;
+
+ /**
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this._carrier.detune;
+ this.detune.value = options.detune;
+
+ /**
+ * The modulating oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._modulator = new Tone.Oscillator(options.frequency, options.modulationType);
+
+ /**
+ * convert the -1,1 output to 0,1
+ * @type {Tone.AudioToGain}
+ * @private
+ */
+ this._modulationScale = new Tone.AudioToGain();
+
+ /**
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
+ * @signal
+ * @example
+ * //pitch the modulator an octave below carrier
+ * synth.harmonicity.value = 0.5;
+ */
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
+
+ /**
+ * the node where the modulation happens
+ * @type {Tone.Gain}
+ * @private
+ */
+ this._modulationNode = new Tone.Gain(0);
+
+ //connections
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.detune.connect(this._modulator.detune);
+ this._modulator.chain(this._modulationScale, this._modulationNode.gain);
+ this._carrier.chain(this._modulationNode, this.output);
+
+ this.phase = options.phase;
+
+ this._readOnly(["frequency", "detune", "harmonicity"]);
+ };
+
+ Tone.extend(Tone.AMOscillator, Tone.Oscillator);
+
+ /**
+ * default values
+ * @static
+ * @type {Object}
+ * @const
+ */
+ Tone.AMOscillator.defaults = {
+ "frequency" : 440,
+ "detune" : 0,
+ "phase" : 0,
+ "modulationType" : "square",
+ "harmonicity" : 1
+ };
+
+ /**
+ * start the oscillator
+ * @param {Time} [time=now]
+ * @private
+ */
+ Tone.AMOscillator.prototype._start = function(time){
+ time = this.toSeconds(time);
+ this._modulator.start(time);
+ this._carrier.start(time);
+ };
+
+ /**
+ * stop the oscillator
+ * @param {Time} time (optional) timing parameter
+ * @private
+ */
+ Tone.AMOscillator.prototype._stop = function(time){
+ time = this.toSeconds(time);
+ this._modulator.stop(time);
+ this._carrier.stop(time);
+ };
+
+ /**
+ * The type of the carrier oscillator
+ * @memberOf Tone.AMOscillator#
+ * @type {string}
+ * @name type
+ */
+ Object.defineProperty(Tone.AMOscillator.prototype, "type", {
+ get : function(){
+ return this._carrier.type;
+ },
+ set : function(type){
+ this._carrier.type = type;
+ }
+ });
+
+ /**
+ * The type of the modulator oscillator
+ * @memberOf Tone.AMOscillator#
+ * @type {string}
+ * @name modulationType
+ */
+ Object.defineProperty(Tone.AMOscillator.prototype, "modulationType", {
+ get : function(){
+ return this._modulator.type;
+ },
+ set : function(type){
+ this._modulator.type = type;
+ }
+ });
+
+ /**
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.AMOscillator#
+ * @type {number}
+ * @name phase
+ */
+ Object.defineProperty(Tone.AMOscillator.prototype, "phase", {
+ get : function(){
+ return this._carrier.phase;
+ },
+ set : function(phase){
+ this._carrier.phase = phase;
+ this._modulator.phase = phase;
+ }
+ });
+
+ /**
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.AMOscillator#
+ * @type {Array}
+ * @name partials
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
+ */
+ Object.defineProperty(Tone.AMOscillator.prototype, "partials", {
+ get : function(){
+ return this._carrier.partials;
+ },
+ set : function(partials){
+ this._carrier.partials = partials;
+ }
+ });
+
+ /**
+ * Clean up.
+ * @return {Tone.AMOscillator} this
+ */
+ Tone.AMOscillator.prototype.dispose = function(){
+ Tone.Source.prototype.dispose.call(this);
+ this._writable(["frequency", "detune", "harmonicity"]);
+ this.frequency = null;
+ this.detune = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this._modulationNode.dispose();
+ this._modulationNode = null;
+ this._modulationScale.dispose();
+ this._modulationScale = null;
+ return this;
+ };
+
+ return Tone.AMOscillator;
+});
\ No newline at end of file
diff --git a/Tone/source/BufferSource.js b/Tone/source/BufferSource.js
new file mode 100644
index 000000000..71cf2ef21
--- /dev/null
+++ b/Tone/source/BufferSource.js
@@ -0,0 +1,303 @@
+define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function (Tone) {
+
+ /**
+ * @class Wrapper around the native BufferSourceNode.
+ * @param {AudioBuffer|Tone.Buffer} buffer The buffer to play
+ * @param {Function} onended The callback to invoke when the
+ * buffer is done playing.
+ */
+ Tone.BufferSource = function(){
+
+ var options = this.optionsObject(arguments, ["buffer", "onended"], Tone.BufferSource.defaults);
+
+ /**
+ * The callback to invoke after the
+ * buffer source is done playing.
+ * @type {Function}
+ */
+ this.onended = options.onended;
+
+ /**
+ * The time that the buffer was started.
+ * @type {Number}
+ * @private
+ */
+ this._startTime = -1;
+
+ /**
+ * The gain node which envelopes the BufferSource
+ * @type {GainNode}
+ * @private
+ */
+ this._gainNode = this.output = this.context.createGain();
+
+ /**
+ * The buffer source
+ * @type {AudioBufferSourceNode}
+ * @private
+ */
+ this._source = this.context.createBufferSource();
+ this._source.connect(this._gainNode);
+ this._source.onended = this._onended.bind(this);
+
+ /**
+ * The playbackRate of the buffer
+ * @type {AudioParam}
+ */
+ this.playbackRate = this._source.playbackRate;
+
+ /**
+ * The fadeIn time of the amplitude envelope.
+ * @type {Time}
+ */
+ this.fadeIn = options.fadeIn;
+
+ /**
+ * The fadeOut time of the amplitude envelope.
+ * @type {Time}
+ */
+ this.fadeOut = options.fadeOut;
+
+ /**
+ * The value that the buffer ramps to
+ * @type {Gain}
+ * @private
+ */
+ this._gain = 1;
+
+ //set the buffer initially
+ if (!this.isUndef(options.buffer)){
+ this.buffer = options.buffer;
+ }
+
+ this.loop = options.loop;
+ };
+
+ Tone.extend(Tone.BufferSource);
+
+ /**
+ * The defaults
+ * @const
+ * @type {Object}
+ */
+ Tone.BufferSource.defaults = {
+ "onended" : Tone.noOp,
+ "fadeIn" : 0,
+ "fadeOut" : 0
+ };
+
+ /**
+ * Returns the playback state of the source, either "started" or "stopped".
+ * @type {Tone.State}
+ * @readOnly
+ * @memberOf Tone.BufferSource#
+ * @name state
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, "state", {
+ get : function(){
+ var now = this.now();
+ if (this._startTime !== -1 && now > this._startTime){
+ return Tone.State.Started;
+ } else {
+ return Tone.State.Stopped;
+ }
+ }
+ });
+
+ /**
+ * Start the buffer
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
+ * to start at.
+ * @param {Time=} duration How long the sample should play. If no duration
+ * is given, it will default to the full length
+ * of the sample (minus any offset)
+ * @param {Gain} [gain=1] The gain to play the buffer back at.
+ * @param {Time=} fadeInTime The optional fadeIn ramp time.
+ * @return {Tone.BufferSource} this
+ */
+ Tone.BufferSource.prototype.start = function(time, offset, duration, gain, fadeInTime){
+ if (this._startTime !== -1){
+ throw new Error("Tone.BufferSource: can only be started once.");
+ }
+
+ if (!this.buffer){
+ throw new Error("Tone.BufferSource: no buffer set.");
+ }
+
+ time = this.toSeconds(time);
+ //if it's a loop the default offset is the loopstart point
+ if (this.loop){
+ offset = this.defaultArg(offset, this.loopStart);
+ } else {
+ //otherwise the default offset is 0
+ offset = this.defaultArg(offset, 0);
+ }
+ offset = this.toSeconds(offset);
+ //the values in seconds
+ time = this.toSeconds(time);
+
+ this._source.start(time, offset);
+
+ gain = this.defaultArg(gain, 1);
+ this._gain = gain;
+
+ //the fadeIn time
+ if (this.isUndef(fadeInTime)){
+ fadeInTime = this.toSeconds(this.fadeIn);
+ } else {
+ fadeInTime = this.toSeconds(fadeInTime);
+ }
+
+ if (fadeInTime > 0){
+ this._gainNode.gain.setValueAtTime(0, time);
+ this._gainNode.gain.linearRampToValueAtTime(this._gain, time + fadeInTime);
+ } else {
+ this._gainNode.gain.setValueAtTime(gain, time);
+ }
+
+ this._startTime = time + fadeInTime;
+
+ if (!this.isUndef(duration)){
+ duration = this.defaultArg(duration, this.buffer.duration - offset);
+ duration = this.toSeconds(duration);
+ this.stop(time + duration + fadeInTime, fadeInTime);
+ }
+
+ return this;
+ };
+
+ /**
+ * Stop the buffer. Optionally add a ramp time to fade the
+ * buffer out.
+ * @param {Time=} time The time the buffer should stop.
+ * @param {Time=} fadeOutTime How long the gain should fade out for
+ * @return {Tone.BufferSource} this
+ */
+ Tone.BufferSource.prototype.stop = function(time, fadeOutTime){
+ if (!this.buffer){
+ throw new Error("Tone.BufferSource: no buffer set.");
+ }
+
+ time = this.toSeconds(time);
+
+ //the fadeOut time
+ if (this.isUndef(fadeOutTime)){
+ fadeOutTime = this.toSeconds(this.fadeOut);
+ } else {
+ fadeOutTime = this.toSeconds(fadeOutTime);
+ }
+
+ //cancel the end curve
+ this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime);
+
+ //set a new one
+ if (fadeOutTime > 0){
+ this._gainNode.gain.setValueAtTime(this._gain, time);
+ this._gainNode.gain.linearRampToValueAtTime(0, time + fadeOutTime);
+ time += fadeOutTime;
+ } else {
+ this._gainNode.gain.setValueAtTime(0, time);
+ }
+ this._source.stop(time);
+
+ return this;
+ };
+
+ /**
+ * Internal callback when the buffer is ended.
+ * Invokes `onended` and disposes the node.
+ * @private
+ */
+ Tone.BufferSource.prototype._onended = function(){
+ this.onended(this);
+ this.dispose();
+ };
+
+ /**
+ * If loop is true, the loop will start at this position.
+ * @memberOf Tone.BufferSource#
+ * @type {Time}
+ * @name loopStart
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, "loopStart", {
+ get : function(){
+ return this._source.loopStart;
+ },
+ set : function(loopStart){
+ this._source.loopStart = this.toSeconds(loopStart);
+ }
+ });
+
+ /**
+ * If loop is true, the loop will end at this position.
+ * @memberOf Tone.BufferSource#
+ * @type {Time}
+ * @name loopEnd
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, "loopEnd", {
+ get : function(){
+ return this._source.loopEnd;
+ },
+ set : function(loopEnd){
+ this._source.loopEnd = this.toSeconds(loopEnd);
+ }
+ });
+
+ /**
+ * The audio buffer belonging to the player.
+ * @memberOf Tone.BufferSource#
+ * @type {AudioBuffer}
+ * @name buffer
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, "buffer", {
+ get : function(){
+ return this._source.buffer;
+ },
+ set : function(buffer){
+ if (buffer instanceof Tone.Buffer){
+ this._source.buffer = buffer.get();
+ } else {
+ this._source.buffer = buffer;
+ }
+ }
+ });
+
+ /**
+ * If the buffer should loop once it's over.
+ * @memberOf Tone.BufferSource#
+ * @type {boolean}
+ * @name loop
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, "loop", {
+ get : function(){
+ return this._source.loop;
+ },
+ set : function(loop){
+ this._source.loop = loop;
+ }
+ });
+
+ /**
+ * Clean up.
+ * @return {Tone.BufferSource} this
+ */
+ Tone.BufferSource.prototype.dispose = function(){
+ this.onended = null;
+ if (this._source){
+ this._source.onended = null;
+ this._source.disconnect();
+ this._source = null;
+ }
+ if (this._gainNode){
+ this._gainNode.disconnect();
+ this._gainNode = null;
+ }
+ this._startTime = -1;
+ this.playbackRate = null;
+ this.output = null;
+ return this;
+ };
+
+ return Tone.BufferSource;
+});
\ No newline at end of file
diff --git a/Tone/source/ExternalInput.js b/Tone/source/ExternalInput.js
index 36b27d92d..e331cae2d 100644
--- a/Tone/source/ExternalInput.js
+++ b/Tone/source/ExternalInput.js
@@ -88,11 +88,12 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone
/**
* wrapper for getUserMedia function
* @param {function} callback
+ * @param {function} error
* @private
*/
- Tone.ExternalInput.prototype._getUserMedia = function(callback){
+ Tone.ExternalInput.prototype._getUserMedia = function(callback, error){
if (!Tone.ExternalInput.supported){
- throw new Error("browser does not support 'getUserMedia'");
+ error("browser does not support 'getUserMedia'");
}
if (Tone.ExternalInput.sources[this._inputNum]){
this._constraints = {
@@ -105,7 +106,7 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone
this._onStream(stream);
callback();
}.bind(this), function(err){
- callback(err);
+ error(err);
});
};
@@ -116,7 +117,7 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone
*/
Tone.ExternalInput.prototype._onStream = function(stream){
if (!this.isFunction(this.context.createMediaStreamSource)){
- throw new Error("browser does not support the 'MediaStreamSourceNode'");
+ throw new Error("Tone.ExternalInput: browser does not support the 'MediaStreamSourceNode'");
}
//can only start a new source if the previous one is closed
if (!this._stream){
@@ -132,12 +133,18 @@ define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Gain"], function(Tone
* Open the media stream
* @param {function=} callback The callback function to
* execute when the stream is open
+ * @param {function=} error The callback function to execute
+ * when the media stream can't open.
+ * This is fired either because the browser
+ * doesn't support the media stream,
+ * or the user blocked opening the microphone.
* @return {Tone.ExternalInput} this
*/
- Tone.ExternalInput.prototype.open = function(callback){
+ Tone.ExternalInput.prototype.open = function(callback, error){
callback = this.defaultArg(callback, Tone.noOp);
+ error = this.defaultArg(error, Tone.noOp);
Tone.ExternalInput.getSources(function(){
- this._getUserMedia(callback);
+ this._getUserMedia(callback, error);
}.bind(this));
return this;
};
diff --git a/Tone/source/FMOscillator.js b/Tone/source/FMOscillator.js
new file mode 100644
index 000000000..ac9faed17
--- /dev/null
+++ b/Tone/source/FMOscillator.js
@@ -0,0 +1,227 @@
+define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply", "Tone/core/Gain"],
+function(Tone){
+
+ "use strict";
+
+ /**
+ * @class Tone.FMOscillator
+ *
+ * @extends {Tone.Oscillator}
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
+ * @example
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.FMOscillator("Ab3", "sine", "square").toMaster().start();
+ */
+ Tone.FMOscillator = function(){
+
+ var options = this.optionsObject(arguments, ["frequency", "type", "modulationType"], Tone.FMOscillator.defaults);
+ Tone.Source.call(this, options);
+
+ /**
+ * The carrier oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._carrier = new Tone.Oscillator(options.frequency, options.type);
+
+ /**
+ * The oscillator's frequency
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
+
+ /**
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this._carrier.detune;
+ this.detune.value = options.detune;
+
+ /**
+ * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
+ * ratio of the frequency of the modulating signal (mf) to the amplitude of the
+ * modulating signal (ma) -- as in ma/mf.
+ * @type {Positive}
+ * @signal
+ */
+ this.modulationIndex = new Tone.Multiply(options.modulationIndex);
+ this.modulationIndex.units = Tone.Type.Positive;
+
+ /**
+ * The modulating oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._modulator = new Tone.Oscillator(options.frequency, options.modulationType);
+
+ /**
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
+ * @signal
+ * @example
+ * //pitch the modulator an octave below carrier
+ * synth.harmonicity.value = 0.5;
+ */
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
+
+ /**
+ * the node where the modulation happens
+ * @type {Tone.Gain}
+ * @private
+ */
+ this._modulationNode = new Tone.Gain(0);
+
+ //connections
+ this.frequency.connect(this._carrier.frequency);
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.frequency.chain(this.modulationIndex, this._modulationNode);
+ this._modulator.connect(this._modulationNode.gain);
+ this._modulationNode.connect(this._carrier.frequency);
+ this._carrier.connect(this.output);
+ this.detune.connect(this._modulator.detune);
+
+ this.phase = options.phase;
+
+ this._readOnly(["modulationIndex", "frequency", "detune", "harmonicity"]);
+ };
+
+ Tone.extend(Tone.FMOscillator, Tone.Oscillator);
+
+ /**
+ * default values
+ * @static
+ * @type {Object}
+ * @const
+ */
+ Tone.FMOscillator.defaults = {
+ "frequency" : 440,
+ "detune" : 0,
+ "phase" : 0,
+ "modulationIndex" : 2,
+ "modulationType" : "square",
+ "harmonicity" : 1
+ };
+
+ /**
+ * start the oscillator
+ * @param {Time} [time=now]
+ * @private
+ */
+ Tone.FMOscillator.prototype._start = function(time){
+ time = this.toSeconds(time);
+ this._modulator.start(time);
+ this._carrier.start(time);
+ };
+
+ /**
+ * stop the oscillator
+ * @param {Time} time (optional) timing parameter
+ * @private
+ */
+ Tone.FMOscillator.prototype._stop = function(time){
+ time = this.toSeconds(time);
+ this._modulator.stop(time);
+ this._carrier.stop(time);
+ };
+
+ /**
+ * The type of the carrier oscillator
+ * @memberOf Tone.FMOscillator#
+ * @type {string}
+ * @name type
+ */
+ Object.defineProperty(Tone.FMOscillator.prototype, "type", {
+ get : function(){
+ return this._carrier.type;
+ },
+ set : function(type){
+ this._carrier.type = type;
+ }
+ });
+
+ /**
+ * The type of the modulator oscillator
+ * @memberOf Tone.FMOscillator#
+ * @type {String}
+ * @name modulationType
+ */
+ Object.defineProperty(Tone.FMOscillator.prototype, "modulationType", {
+ get : function(){
+ return this._modulator.type;
+ },
+ set : function(type){
+ this._modulator.type = type;
+ }
+ });
+
+ /**
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.FMOscillator#
+ * @type {number}
+ * @name phase
+ */
+ Object.defineProperty(Tone.FMOscillator.prototype, "phase", {
+ get : function(){
+ return this._carrier.phase;
+ },
+ set : function(phase){
+ this._carrier.phase = phase;
+ this._modulator.phase = phase;
+ }
+ });
+
+ /**
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.FMOscillator#
+ * @type {Array}
+ * @name partials
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
+ */
+ Object.defineProperty(Tone.FMOscillator.prototype, "partials", {
+ get : function(){
+ return this._carrier.partials;
+ },
+ set : function(partials){
+ this._carrier.partials = partials;
+ }
+ });
+
+ /**
+ * Clean up.
+ * @return {Tone.FMOscillator} this
+ */
+ Tone.FMOscillator.prototype.dispose = function(){
+ Tone.Source.prototype.dispose.call(this);
+ this._writable(["modulationIndex", "frequency", "detune", "harmonicity"]);
+ this.frequency.dispose();
+ this.frequency = null;
+ this.detune = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this._modulationNode.dispose();
+ this._modulationNode = null;
+ this.modulationIndex.dispose();
+ this.modulationIndex = null;
+ return this;
+ };
+
+ return Tone.FMOscillator;
+});
\ No newline at end of file
diff --git a/Tone/source/FatOscillator.js b/Tone/source/FatOscillator.js
new file mode 100644
index 000000000..2736479cd
--- /dev/null
+++ b/Tone/source/FatOscillator.js
@@ -0,0 +1,282 @@
+define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/signal/Multiply", "Tone/core/Gain"],
+function(Tone){
+
+ "use strict";
+
+ /**
+ * @class Tone.FatOscillator
+ *
+ * @extends {Tone.Oscillator}
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
+ * @example
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.FatOscillator("Ab3", "sine", "square").toMaster().start();
+ */
+ Tone.FatOscillator = function(){
+
+ var options = this.optionsObject(arguments, ["frequency", "type", "spread"], Tone.FatOscillator.defaults);
+ Tone.Source.call(this, options);
+
+ /**
+ * The oscillator's frequency
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
+
+ /**
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
+
+ /**
+ * The array of oscillators
+ * @type {Array}
+ * @private
+ */
+ this._oscillators = [];
+
+ /**
+ * The total spread of the oscillators
+ * @type {Cents}
+ * @private
+ */
+ this._spread = options.spread;
+
+ /**
+ * The type of the oscillator
+ * @type {String}
+ * @private
+ */
+ this._type = options.type;
+
+ /**
+ * The phase of the oscillators
+ * @type {Degrees}
+ * @private
+ */
+ this._phase = options.phase;
+
+ /**
+ * The partials array
+ * @type {Array}
+ * @private
+ */
+ this._partials = this.defaultArg(options.partials, []);
+
+ //set the count initially
+ this.count = options.count;
+
+ this._readOnly(["frequency", "detune"]);
+ };
+
+ Tone.extend(Tone.FatOscillator, Tone.Oscillator);
+
+ /**
+ * default values
+ * @static
+ * @type {Object}
+ * @const
+ */
+ Tone.FatOscillator.defaults = {
+ "frequency" : 440,
+ "detune" : 0,
+ "phase" : 0,
+ "spread" : 20,
+ "count" : 3,
+ "type" : "sawtooth"
+ };
+
+ /**
+ * start the oscillator
+ * @param {Time} [time=now]
+ * @private
+ */
+ Tone.FatOscillator.prototype._start = function(time){
+ time = this.toSeconds(time);
+ this._forEach(function(osc){
+ osc.start(time);
+ });
+ };
+
+ /**
+ * stop the oscillator
+ * @param {Time} time (optional) timing parameter
+ * @private
+ */
+ Tone.FatOscillator.prototype._stop = function(time){
+ time = this.toSeconds(time);
+ this._forEach(function(osc){
+ osc.stop(time);
+ });
+ };
+
+ /**
+ * Iterate over all of the oscillators
+ * @param {Function} iterator The iterator function
+ * @private
+ */
+ Tone.FatOscillator.prototype._forEach = function(iterator){
+ for (var i = 0; i < this._oscillators.length; i++){
+ iterator.call(this, this._oscillators[i], i);
+ }
+ };
+
+ /**
+ * The type of the carrier oscillator
+ * @memberOf Tone.FatOscillator#
+ * @type {string}
+ * @name type
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, "type", {
+ get : function(){
+ return this._type;
+ },
+ set : function(type){
+ this._type = type;
+ this._forEach(function(osc){
+ osc.type = type;
+ });
+ }
+ });
+
+ /**
+ * The detune spread between the oscillators. If "count" is
+ * set to 3 oscillators and the "spread" is set to 40,
+ * the three oscillators would be detuned like this: [-20, 0, 20]
+ * for a total detune spread of 40 cents.
+ * @memberOf Tone.FatOscillator#
+ * @type {Cents}
+ * @name spread
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, "spread", {
+ get : function(){
+ return this._spread;
+ },
+ set : function(spread){
+ this._spread = spread;
+ if (this._oscillators.length > 1){
+ var start = -spread/2;
+ var step = spread / (this._oscillators.length - 1);
+ this._forEach(function(osc, i){
+ osc.detune.value = start + step * i;
+ });
+ }
+ }
+ });
+
+ /**
+ * The number of detuned oscillators
+ * @memberOf Tone.FatOscillator#
+ * @type {Number}
+ * @name count
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, "count", {
+ get : function(){
+ return this._oscillators.length;
+ },
+ set : function(count){
+ count = Math.max(count, 1);
+ if (this._oscillators.length !== count){
+ // var partials = this.partials;
+ // var type = this.type;
+ //dispose the previous oscillators
+ this._forEach(function(osc){
+ osc.dispose();
+ });
+ this._oscillators = [];
+ for (var i = 0; i < count; i++){
+ var osc = new Tone.Oscillator();
+ if (this.type === Tone.Oscillator.Type.Custom){
+ osc.partials = this._partials;
+ } else {
+ osc.type = this._type;
+ }
+ osc.phase = this._phase;
+ osc.volume.value = -6 - count;
+ this.frequency.connect(osc.frequency);
+ this.detune.connect(osc.detune);
+ osc.connect(this.output);
+ this._oscillators[i] = osc;
+ }
+ //set the spread
+ this.spread = this._spread;
+ if (this.state === Tone.State.Started){
+ this._forEach(function(osc){
+ osc.start();
+ });
+ }
+ }
+ }
+ });
+
+ /**
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.FatOscillator#
+ * @type {Number}
+ * @name phase
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, "phase", {
+ get : function(){
+ return this._phase;
+ },
+ set : function(phase){
+ this._phase = phase;
+ this._forEach(function(osc){
+ osc.phase = phase;
+ });
+ }
+ });
+
+ /**
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.FatOscillator#
+ * @type {Array}
+ * @name partials
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, "partials", {
+ get : function(){
+ return this._partials;
+ },
+ set : function(partials){
+ this._partials = partials;
+ this._type = Tone.Oscillator.Type.Custom;
+ this._forEach(function(osc){
+ osc.partials = partials;
+ });
+ }
+ });
+
+ /**
+ * Clean up.
+ * @return {Tone.FatOscillator} this
+ */
+ Tone.FatOscillator.prototype.dispose = function(){
+ Tone.Source.prototype.dispose.call(this);
+ this._writable(["frequency", "detune"]);
+ this.frequency.dispose();
+ this.frequency = null;
+ this.detune.dispose();
+ this.detune = null;
+ this._forEach(function(osc){
+ osc.dispose();
+ });
+ this._oscillators = null;
+ this._partials = null;
+ return this;
+ };
+
+ return Tone.FatOscillator;
+});
\ No newline at end of file
diff --git a/Tone/source/GrainPlayer.js b/Tone/source/GrainPlayer.js
new file mode 100644
index 000000000..410c8fa42
--- /dev/null
+++ b/Tone/source/GrainPlayer.js
@@ -0,0 +1,325 @@
+define(["Tone/core/Tone", "Tone/source/Source", "Tone/core/Buffer", "Tone/source/MultiPlayer"],
+function (Tone) {
+
+ /**
+ * @class Tone.GrainPlayer implements [granular synthesis](https://en.wikipedia.org/wiki/Granular_synthesis).
+ * Granular Synthesis enables you to adjust pitch and playback rate independently. The grainSize is the
+ * amount of time each small chunk of audio is played for and the overlap is the
+ * amount of crossfading transition time between successive grains.
+ * @extends {Tone}
+ * @param {String|Tone.Buffer} url The url to load, or the Tone.Buffer to play.
+ * @param {Function=} callback The callback to invoke after the url is loaded.
+ */
+ Tone.GrainPlayer = function(){
+
+ var options = this.optionsObject(arguments, ["url", "onload"], Tone.GrainPlayer.defaults);
+
+ Tone.Source.call(this);
+
+ /**
+ * The audio buffer belonging to the player.
+ * @type {Tone.Buffer}
+ */
+ this.buffer = new Tone.Buffer(options.url, options.onload);
+
+ /**
+ * Plays the buffer with a small envelope
+ * @type {Tone.MultiPlayer}
+ * @private
+ */
+ this._player = this.output = new Tone.MultiPlayer();
+
+ /**
+ * Create a repeating tick to schedule
+ * the grains.
+ * @type {Tone.Clock}
+ * @private
+ */
+ this._clock = new Tone.Clock(this._tick.bind(this), 1);
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._loopStart = 0;
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._loopEnd = 0;
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._playbackRate = options.playbackRate;
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._grainSize = options.grainSize;
+
+ /**
+ * @private
+ * @type {Number}
+ */
+ this._overlap = options.overlap;
+
+ /**
+ * Adjust the pitch independently of the playbackRate.
+ * @type {Cents}
+ */
+ this.detune = options.detune;
+
+ /**
+ * The amount of time randomly added
+ * or subtracted from the grain's offset
+ * @type {Time}
+ */
+ this.drift = options.drift;
+
+ //setup
+ this.overlap = options.overlap;
+ this.loop = options.loop;
+ this.playbackRate = options.playbackRate;
+ this.grainSize = options.grainSize;
+ this.loopStart = options.loopStart;
+ this.loopEnd = options.loopEnd;
+ this.reverse = options.reverse;
+ };
+
+ Tone.extend(Tone.GrainPlayer, Tone.Source);
+
+ /**
+ * the default parameters
+ * @static
+ * @const
+ * @type {Object}
+ */
+ Tone.GrainPlayer.defaults = {
+ "onload" : Tone.noOp,
+ "overlap" : 0.1,
+ "grainSize" : 0.2,
+ "drift" : 0.0,
+ "playbackRate" : 1,
+ "detune" : 0,
+ "loop" : false,
+ "loopStart" : 0,
+ "loopEnd" : 0,
+ "reverse" : false
+ };
+
+ /**
+ * Play the buffer at the given startTime. Optionally add an offset
+ * from the start of the buffer to play from.
+ *
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
+ * to start at.
+ * @return {Tone.GrainPlayer} this
+ */
+
+ /**
+ * Internal start method
+ * @param {Time} time
+ * @param {Time} offset
+ * @private
+ */
+ Tone.GrainPlayer.prototype._start = function(time, offset){
+ offset = this.defaultArg(offset, 0);
+ offset = this.toSeconds(offset);
+ time = this.toSeconds(time);
+
+ this._offset = offset;
+ this._clock.start(time);
+ };
+
+ /**
+ * Internal start method
+ * @param {Time} time
+ * @private
+ */
+ Tone.GrainPlayer.prototype._stop = function(time){
+ this._clock.stop(time);
+ this._player.stop(this.buffer, time);
+ this._offset = 0;
+ };
+
+ /**
+ * Invoked on each clock tick. scheduled a new
+ * grain at this time.
+ * @param {Time} time
+ * @private
+ */
+ Tone.GrainPlayer.prototype._tick = function(time){
+
+ var bufferDuration = this.buffer.duration;
+ if (this.loop && this._loopEnd > 0){
+ bufferDuration = this._loopEnd;
+ }
+ var drift = (Math.random() * 2 - 1) * this.drift;
+ var offset = this._offset - this._overlap + drift;
+ var detune = this.detune / 100;
+
+
+ var originalFadeIn = this._player.fadeIn;
+ if (this.loop && this._offset > bufferDuration){
+ //play the end
+ var endSegmentDuration = this._offset - bufferDuration;
+ this._player.start(this.buffer, time, offset, endSegmentDuration + this._overlap, detune);
+
+ //and play the beginning
+ offset = this._offset % bufferDuration;
+ this._offset = this._loopStart;
+ this._player.fadeIn = 0;
+ this._player.start(this.buffer, time + endSegmentDuration, this._offset, offset + this._overlap, detune);
+ } else if (this._offset > bufferDuration){
+ //set the state to stopped.
+ this.stop(time);
+ } else {
+ if (offset < 0){
+ this._player.fadeIn = Math.max(this._player.fadeIn + offset, 0);
+ offset = 0;
+ }
+ this._player.start(this.buffer, time, offset, this.grainSize + this._overlap, detune);
+ }
+
+ this._player.fadeIn = originalFadeIn;
+ //increment the offset
+ var duration = this._clock._nextTick - time;
+ this._offset += duration * this._playbackRate;
+ };
+
+ /**
+ * Jump to a specific time and play it.
+ * @param {Time} offset The offset to jump to.
+ * @param {Time=} time When to make the jump.
+ * @return {[type]} [description]
+ */
+ Tone.GrainPlayer.prototype.scrub = function(offset, time){
+ this._offset = this.toSeconds(offset);
+ this._tick(this.toSeconds(time));
+ return this;
+ };
+
+ /**
+ * The playback rate of the sample
+ * @memberOf Tone.GrainPlayer#
+ * @type {Positive}
+ * @name playbackRate
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "playbackRate", {
+ get : function(){
+ return this._playbackRate;
+ },
+ set : function(rate){
+ this._playbackRate = rate;
+ this.grainSize = this._grainSize;
+ }
+ });
+
+ /**
+ * The loop start time.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name loopStart
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "loopStart", {
+ get : function(){
+ return this._loopStart;
+ },
+ set : function(time){
+ this._loopStart = this.toSeconds(time);
+ }
+ });
+
+ /**
+ * The loop end time.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name loopEnd
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "loopEnd", {
+ get : function(){
+ return this._loopEnd;
+ },
+ set : function(time){
+ this._loopEnd = this.toSeconds(time);
+ }
+ });
+
+ /**
+ * The direction the buffer should play in
+ * @memberOf Tone.GrainPlayer#
+ * @type {boolean}
+ * @name reverse
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "reverse", {
+ get : function(){
+ return this.buffer.reverse;
+ },
+ set : function(rev){
+ this.buffer.reverse = rev;
+ }
+ });
+
+ /**
+ * The size of each chunk of audio that the
+ * buffer is chopped into and played back at.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name grainSize
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "grainSize", {
+ get : function(){
+ return this._grainSize;
+ },
+ set : function(size){
+ this._grainSize = this.toSeconds(size);
+ this._clock.frequency.value = this._playbackRate / this._grainSize;
+ }
+ });
+
+ /**
+ * This is the duration of the cross-fade between
+ * sucessive grains.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name overlap
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, "overlap", {
+ get : function(){
+ return this._overlap;
+ },
+ set : function(time){
+ time = this.toSeconds(time);
+ this._overlap = time;
+ if (this._overlap < 0){
+ this._player.fadeIn = 0.01;
+ this._player.fadeOut = 0.01;
+ } else {
+ this._player.fadeIn = time;
+ this._player.fadeOut = time;
+ }
+ }
+ });
+
+ /**
+ * Clean up
+ * @return {Tone.GrainPlayer} this
+ */
+ Tone.GrainPlayer.prototype.dispose = function(){
+ Tone.Source.prototype.dispose.call(this);
+ this.buffer.dispose();
+ this.buffer = null;
+ this._player.dispose();
+ this._player = null;
+ this._clock.dispose();
+ this._clock = null;
+ return this;
+ };
+
+ return Tone.GrainPlayer;
+});
\ No newline at end of file
diff --git a/Tone/source/MultiPlayer.js b/Tone/source/MultiPlayer.js
new file mode 100644
index 000000000..01f451943
--- /dev/null
+++ b/Tone/source/MultiPlayer.js
@@ -0,0 +1,247 @@
+define(["Tone/core/Tone", "Tone/source/BufferSource", "Tone/core/Buffers",
+ "Tone/source/Source", "Tone/component/Volume"],
+function (Tone) {
+
+ /**
+ * @class Tone.MultiPlayer is well suited for one-shots, multi-sampled istruments
+ * or any time you need to play a bunch of audio buffers.
+ * @param {Object|Array|Tone.Buffers} buffers The buffers which are available
+ * to the MultiPlayer
+ * @param {Function} onload The callback to invoke when all of the buffers are loaded.
+ * @extends {Tone}
+ * @example
+ * var multiPlayer = new MultiPlayer({
+ * "kick" : "path/to/kick.mp3",
+ * "snare" : "path/to/snare.mp3",
+ * }, function(){
+ * multiPlayer.start("kick");
+ * });
+ * @example
+ * //can also store the values in an array
+ * var multiPlayer = new MultiPlayer(["path/to/kick.mp3", "path/to/snare.mp3"],
+ * function(){
+ * //if an array is passed in, the samples are referenced to by index
+ * multiPlayer.start(1);
+ * });
+ */
+ Tone.MultiPlayer = function(){
+
+ var options = this.optionsObject(arguments, ["urls", "onload"], Tone.MultiPlayer.defaults);
+
+ if (options.urls instanceof Tone.Buffers){
+ /**
+ * All the buffers belonging to the player.
+ * @type {Tone.Buffers}
+ */
+ this.buffers = options.urls;
+ } else {
+ this.buffers = new Tone.Buffers(options.urls, options.onload);
+ }
+
+ /**
+ * Keeps track of the currently playing sources.
+ * @type {Array}
+ * @private
+ */
+ this._activeSources = [];
+
+ /**
+ * The fade in envelope which is applied
+ * to the beginning of the BufferSource
+ * @type {Time}
+ */
+ this.fadeIn = options.fadeIn;
+
+ /**
+ * The fade out envelope which is applied
+ * to the end of the BufferSource
+ * @type {Time}
+ */
+ this.fadeOut = options.fadeOut;
+
+ /**
+ * The output volume node
+ * @type {Tone.Volume}
+ * @private
+ */
+ this._volume = this.output = new Tone.Volume(options.volume);
+
+ /**
+ * The volume of the output in decibels.
+ * @type {Decibels}
+ * @signal
+ * @example
+ * source.volume.value = -6;
+ */
+ this.volume = this._volume.volume;
+ this._readOnly("volume");
+
+ //make the output explicitly stereo
+ this._volume.output.output.channelCount = 2;
+ this._volume.output.output.channelCountMode = "explicit";
+ //mute initially
+ this.mute = options.mute;
+ };
+
+ Tone.extend(Tone.MultiPlayer, Tone.Source);
+
+ /**
+ * The defaults
+ * @type {Object}
+ */
+ Tone.MultiPlayer.defaults = {
+ "onload" : Tone.noOp,
+ "fadeIn" : 0,
+ "fadeOut" : 0
+ };
+
+ /**
+ * Get the given buffer.
+ * @param {String|Number|AudioBuffer|Tone.Buffer} buffer
+ * @return {AudioBuffer} The requested buffer.
+ * @private
+ */
+ Tone.MultiPlayer.prototype._getBuffer = function(buffer){
+ if (this.isNumber(buffer) || this.isString(buffer)){
+ return this.buffers.get(buffer).get();
+ } else if (buffer instanceof Tone.Buffer){
+ return buffer.get();
+ } else {
+ return buffer;
+ }
+ };
+
+ /**
+ * Start a buffer by name. The `start` method allows a number of options
+ * to be passed in such as offset, interval, and gain. This is good for multi-sampled
+ * instruments and sound sprites where samples are repitched played back at different velocities.
+ * @param {String|AudioBuffer} buffer The name of the buffer to start.
+ * Or pass in a buffer which will be started.
+ * @param {Time} time When to start the buffer.
+ * @param {Time} [offset=0] The offset into the buffer to play from.
+ * @param {Time=} duration How long to play the buffer for.
+ * @param {Interval} [interval=0] The interval to repitch the buffer.
+ * @param {Gain} [gain=1] The gain to play the sample at.
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.start = function(buffer, time, offset, duration, interval, gain){
+ buffer = this._getBuffer(buffer);
+ var source = new Tone.BufferSource(buffer).connect(this.output);
+ this._activeSources.push(source);
+ time = this.toSeconds(time);
+ source.start(time, offset, duration, this.defaultArg(gain, 1), this.fadeIn);
+ if (duration){
+ source.stop(time + this.toSeconds(duration), this.fadeOut);
+ }
+ source.onended = this._onended.bind(this);
+ interval = this.defaultArg(interval, 0);
+ source.playbackRate.value = this.intervalToFrequencyRatio(interval);
+ return this;
+ };
+
+ /**
+ * Internal callback when a buffer is done playing.
+ * @param {Tone.BufferSource} source The stopped source
+ * @private
+ */
+ Tone.MultiPlayer.prototype._onended = function(source){
+ var index = this._activeSources.indexOf(source);
+ this._activeSources.splice(index, 1);
+ };
+
+ /**
+ * Stop all instances of the currently playing buffer at the given time.
+ * @param {String|AudioBuffer} buffer The buffer to stop.
+ * @param {Time=} time When to stop the buffer
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.stop = function(buffer, time){
+ buffer = this._getBuffer(buffer);
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._activeSources.length; i++){
+ if (this._activeSources[i].buffer === buffer){
+ this._activeSources[i].stop(time, this.fadeOut);
+ }
+ }
+ return this;
+ };
+
+ /**
+ * Stop all currently playing buffers at the given time.
+ * @param {Time=} time When to stop the buffers.
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.stopAll = function(time){
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._activeSources.length; i++){
+ this._activeSources[i].stop(time, this.fadeOut);
+ }
+ return this;
+ };
+
+ /**
+ * Add another buffer to the available buffers.
+ * @param {String} name The name to that the buffer is refered
+ * to in start/stop methods.
+ * @param {String|Tone.Buffer} url The url of the buffer to load
+ * or the buffer.
+ * @param {Function} callback The function to invoke after the buffer is loaded.
+ */
+ Tone.MultiPlayer.prototype.add = function(name, url, callback){
+ this.buffers.add(name, url, callback);
+ return this;
+ };
+
+ /**
+ * Returns the playback state of the source. "started"
+ * if there are any buffers playing. "stopped" otherwise.
+ * @type {Tone.State}
+ * @readOnly
+ * @memberOf Tone.MultiPlayer#
+ * @name state
+ */
+ Object.defineProperty(Tone.MultiPlayer.prototype, "state", {
+ get : function(){
+ return this._activeSources.length > 0 ? Tone.State.Started : Tone.State.Stopped;
+ }
+ });
+
+ /**
+ * Mute the output.
+ * @memberOf Tone.MultiPlayer#
+ * @type {boolean}
+ * @name mute
+ * @example
+ * //mute the output
+ * source.mute = true;
+ */
+ Object.defineProperty(Tone.MultiPlayer.prototype, "mute", {
+ get : function(){
+ return this._volume.mute;
+ },
+ set : function(mute){
+ this._volume.mute = mute;
+ }
+ });
+
+ /**
+ * Clean up.
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.dispose = function(){
+ Tone.prototype.dispose.call(this);
+ this._volume.dispose();
+ this._volume = null;
+ this._writable("volume");
+ this.volume = null;
+ this.buffers.dispose();
+ this.buffers = null;
+ for (var i = 0; i < this._activeSources.length; i++){
+ this._activeSources[i].dispose();
+ }
+ this._activeSources = null;
+ return this;
+ };
+
+ return Tone.MultiPlayer;
+});
\ No newline at end of file
diff --git a/Tone/source/Noise.js b/Tone/source/Noise.js
index 94d308ad4..86bceec9b 100644
--- a/Tone/source/Noise.js
+++ b/Tone/source/Noise.js
@@ -100,7 +100,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){
this._buffer = _brownNoise;
break;
default :
- throw new Error("invalid noise type: "+type)
+ throw new TypeError("Tone.Noise: invalid type: "+type);
}
//if it's playing, stop and restart it
if (this.state === Tone.State.Started){
@@ -143,7 +143,7 @@ define(["Tone/core/Tone", "Tone/source/Source"], function(Tone){
this._source.loop = true;
this._source.playbackRate.value = this._playbackRate;
this._source.connect(this.output);
- this._source.start(this.toSeconds(time));
+ this._source.start(this.toSeconds(time), Math.random() * (this._buffer.duration - 0.001));
};
/**
diff --git a/Tone/source/OmniOscillator.js b/Tone/source/OmniOscillator.js
index c118a2782..7fede3320 100644
--- a/Tone/source/OmniOscillator.js
+++ b/Tone/source/OmniOscillator.js
@@ -1,22 +1,23 @@
-define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator",
- "Tone/source/PulseOscillator", "Tone/source/PWMOscillator"],
+define(["Tone/core/Tone", "Tone/source/Source", "Tone/source/Oscillator", "Tone/source/PulseOscillator", "Tone/source/PWMOscillator",
+ "Tone/source/FMOscillator", "Tone/source/AMOscillator", "Tone/source/FatOscillator"],
function(Tone){
"use strict";
/**
* @class Tone.OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator,
- * and Tone.PWMOscillator into one class, allowing it to have the
- * types: sine, square, triangle, sawtooth, pulse or pwm. Additionally,
- * OmniOscillator is capable of setting the first x number of partials
- * of the oscillator. For example: "sine4" would set be the first 4
- * partials of the sine wave and "triangle8" would set the first
- * 8 partials of the triangle wave.
+ * Tone.PWMOscillator, Tone.FMOscillator, Tone.AMOscillator, and Tone.FatOscillator
+ * into one class. The oscillator class can be changed by setting the `type`.
+ * `omniOsc.type = "pwm"` will set it to the Tone.PWMOscillator. Prefixing
+ * any of the basic types ("sine", "square4", etc.) with "fm", "am", or "fat"
+ * will use the FMOscillator, AMOscillator or FatOscillator respectively.
+ * For example: `omniOsc.type = "fatsawtooth"` will create set the oscillator
+ * to a FatOscillator of type "sawtooth".
*
* @extends {Tone.Oscillator}
* @constructor
* @param {Frequency} frequency The initial frequency of the oscillator.
- * @param {string} type The type of the oscillator.
+ * @param {String} type The type of the oscillator.
* @example
* var omniOsc = new Tone.OmniOscillator("C#4", "pwm");
*/
@@ -40,25 +41,23 @@ function(Tone){
/**
* the type of the oscillator source
- * @type {string}
+ * @type {String}
* @private
*/
this._sourceType = undefined;
/**
* the oscillator
- * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator}
+ * @type {Tone.Oscillator}
* @private
*/
this._oscillator = null;
//set the oscillator
this.type = options.type;
- this.phase = options.phase;
this._readOnly(["frequency", "detune"]);
- if (this.isArray(options.partials)){
- this.partials = options.partials;
- }
+ //set the options
+ this.set(options);
};
Tone.extend(Tone.OmniOscillator, Tone.Oscillator);
@@ -74,18 +73,19 @@ function(Tone){
"detune" : 0,
"type" : "sine",
"phase" : 0,
- "width" : 0.4, //only applies if the oscillator is set to "pulse",
- "modulationFrequency" : 0.4, //only applies if the oscillator is set to "pwm",
};
/**
- * @enum {string}
+ * @enum {String}
* @private
*/
var OmniOscType = {
- PulseOscillator : "PulseOscillator",
- PWMOscillator : "PWMOscillator",
- Oscillator : "Oscillator"
+ Pulse : "PulseOscillator",
+ PWM : "PWMOscillator",
+ Osc : "Oscillator",
+ FM : "FMOscillator",
+ AM : "AMOscillator",
+ Fat : "FatOscillator"
};
/**
@@ -107,35 +107,54 @@ function(Tone){
};
/**
- * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse.
+ * The type of the oscillator. Can be any of the basic types: sine, square, triangle, sawtooth. Or
+ * prefix the basic types with "fm", "am", or "fat" to use the FMOscillator, AMOscillator or FatOscillator
+ * types. The oscillator could also be set to "pwm" or "pulse". All of the parameters of the
+ * oscillator's class are accessible when the oscillator is set to that type, but throws an error
+ * when it's not.
+ *
* @memberOf Tone.OmniOscillator#
- * @type {string}
+ * @type {String}
* @name type
+ * @example
+ * omniOsc.type = "pwm";
+ * //modulationFrequency is parameter which is available
+ * //only when the type is "pwm".
+ * omniOsc.modulationFrequency.value = 0.5;
+ * @example
+ * //an square wave frequency modulated by a sawtooth
+ * omniOsc.type = "fmsquare";
+ * omniOsc.modulationType = "sawtooth";
*/
Object.defineProperty(Tone.OmniOscillator.prototype, "type", {
get : function(){
- return this._oscillator.type;
+ var prefix = "";
+ if (this._sourceType === OmniOscType.FM){
+ prefix = "fm";
+ } else if (this._sourceType === OmniOscType.AM){
+ prefix = "am";
+ } else if (this._sourceType === OmniOscType.Fat){
+ prefix = "fat";
+ }
+ return prefix + this._oscillator.type;
},
set : function(type){
- if (type.indexOf("sine") === 0 || type.indexOf("square") === 0 ||
- type.indexOf("triangle") === 0 || type.indexOf("sawtooth") === 0 || type === Tone.Oscillator.Type.Custom){
- if (this._sourceType !== OmniOscType.Oscillator){
- this._sourceType = OmniOscType.Oscillator;
- this._createNewOscillator(Tone.Oscillator);
- }
- this._oscillator.type = type;
+ if (type.substr(0, 2) === "fm"){
+ this._createNewOscillator(OmniOscType.FM);
+ this._oscillator.type = type.substr(2);
+ } else if (type.substr(0, 2) === "am"){
+ this._createNewOscillator(OmniOscType.AM);
+ this._oscillator.type = type.substr(2);
+ } else if (type.substr(0, 3) === "fat"){
+ this._createNewOscillator(OmniOscType.Fat);
+ this._oscillator.type = type.substr(3);
} else if (type === "pwm"){
- if (this._sourceType !== OmniOscType.PWMOscillator){
- this._sourceType = OmniOscType.PWMOscillator;
- this._createNewOscillator(Tone.PWMOscillator);
- }
+ this._createNewOscillator(OmniOscType.PWM);
} else if (type === "pulse"){
- if (this._sourceType !== OmniOscType.PulseOscillator){
- this._sourceType = OmniOscType.PulseOscillator;
- this._createNewOscillator(Tone.PulseOscillator);
- }
+ this._createNewOscillator(OmniOscType.Pulse);
} else {
- throw new Error("Tone.OmniOscillator does not support type "+type);
+ this._createNewOscillator(OmniOscType.Osc);
+ this._oscillator.type = type;
}
}
});
@@ -147,6 +166,7 @@ function(Tone){
* following the harmonic series.
* Setting this value will automatically set the type to "custom".
* The value is an empty array when the type is not "custom".
+ * This is not available on "pwm" and "pulse" oscillator types.
* @memberOf Tone.OmniOscillator#
* @type {Array}
* @name partials
@@ -158,35 +178,55 @@ function(Tone){
return this._oscillator.partials;
},
set : function(partials){
- if (this._sourceType !== OmniOscType.Oscillator){
- this.type = Tone.Oscillator.Type.Custom;
- }
this._oscillator.partials = partials;
}
});
+ /**
+ * Set a member/attribute of the oscillator.
+ * @param {Object|String} params
+ * @param {number=} value
+ * @param {Time=} rampTime
+ * @returns {Tone.OmniOscillator} this
+ */
+ Tone.OmniOscillator.prototype.set = function(params, value){
+ //make sure the type is set first
+ if (params === "type"){
+ this.type = value;
+ } else if (this.isObject(params) && params.hasOwnProperty("type")){
+ this.type = params.type;
+ }
+ //then set the rest
+ Tone.prototype.set.apply(this, arguments);
+ return this;
+ };
+
/**
* connect the oscillator to the frequency and detune signals
* @private
*/
- Tone.OmniOscillator.prototype._createNewOscillator = function(OscillatorConstructor){
- //short delay to avoid clicks on the change
- var now = this.now() + this.blockTime;
- if (this._oscillator !== null){
- var oldOsc = this._oscillator;
- oldOsc.stop(now);
- //dispose the old one
- setTimeout(function(){
- oldOsc.dispose();
- oldOsc = null;
- }, this.blockTime * 1000);
- }
- this._oscillator = new OscillatorConstructor();
- this.frequency.connect(this._oscillator.frequency);
- this.detune.connect(this._oscillator.detune);
- this._oscillator.connect(this.output);
- if (this.state === Tone.State.Started){
- this._oscillator.start(now);
+ Tone.OmniOscillator.prototype._createNewOscillator = function(oscType){
+ if (oscType !== this._sourceType){
+ this._sourceType = oscType;
+ var OscillatorConstructor = Tone[oscType];
+ //short delay to avoid clicks on the change
+ var now = this.now() + this.blockTime;
+ if (this._oscillator !== null){
+ var oldOsc = this._oscillator;
+ oldOsc.stop(now);
+ //dispose the old one
+ setTimeout(function(){
+ oldOsc.dispose();
+ oldOsc = null;
+ }, this.blockTime * 1000);
+ }
+ this._oscillator = new OscillatorConstructor();
+ this.frequency.connect(this._oscillator.frequency);
+ this.detune.connect(this._oscillator.detune);
+ this._oscillator.connect(this.output);
+ if (this.state === Tone.State.Started){
+ this._oscillator.start(now);
+ }
}
};
@@ -206,7 +246,7 @@ function(Tone){
});
/**
- * The width of the oscillator (only if the oscillator is set to pulse)
+ * The width of the oscillator (only if the oscillator is set to "pulse")
* @memberOf Tone.OmniOscillator#
* @type {NormalRange}
* @signal
@@ -218,15 +258,114 @@ function(Tone){
*/
Object.defineProperty(Tone.OmniOscillator.prototype, "width", {
get : function(){
- if (this._sourceType === OmniOscType.PulseOscillator){
+ if (this._sourceType === OmniOscType.Pulse){
return this._oscillator.width;
}
}
});
+ /**
+ * The number of detuned oscillators
+ * @memberOf Tone.OmniOscillator#
+ * @type {Number}
+ * @name count
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, "count", {
+ get : function(){
+ if (this._sourceType === OmniOscType.Fat){
+ return this._oscillator.count;
+ }
+ },
+ set : function(count){
+ if (this._sourceType === OmniOscType.Fat){
+ this._oscillator.count = count;
+ }
+ }
+ });
+
+ /**
+ * The detune spread between the oscillators. If "count" is
+ * set to 3 oscillators and the "spread" is set to 40,
+ * the three oscillators would be detuned like this: [-20, 0, 20]
+ * for a total detune spread of 40 cents. See Tone.FatOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @type {Cents}
+ * @name spread
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, "spread", {
+ get : function(){
+ if (this._sourceType === OmniOscType.Fat){
+ return this._oscillator.spread;
+ }
+ },
+ set : function(spread){
+ if (this._sourceType === OmniOscType.Fat){
+ this._oscillator.spread = spread;
+ }
+ }
+ });
+
+ /**
+ * The type of the modulator oscillator. Only if the oscillator
+ * is set to "am" or "fm" types. see. Tone.AMOscillator or Tone.FMOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @type {String}
+ * @name modulationType
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, "modulationType", {
+ get : function(){
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){
+ return this._oscillator.modulationType;
+ }
+ },
+ set : function(mType){
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){
+ this._oscillator.modulationType = mType;
+ }
+ }
+ });
+
+ /**
+ * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
+ * ratio of the frequency of the modulating signal (mf) to the amplitude of the
+ * modulating signal (ma) -- as in ma/mf.
+ * See Tone.FMOscillator for more info.
+ * @type {Positive}
+ * @signal
+ * @name modulationIndex
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, "modulationIndex", {
+ get : function(){
+ if (this._sourceType === OmniOscType.FM){
+ return this._oscillator.modulationIndex;
+ }
+ }
+ });
+
+ /**
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave. See Tone.AMOscillator or Tone.FMOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @signal
+ * @type {Positive}
+ * @name harmonicity
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, "harmonicity", {
+ get : function(){
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM){
+ return this._oscillator.harmonicity;
+ }
+ }
+ });
+
/**
* The modulationFrequency Signal of the oscillator
- * (only if the oscillator type is set to pwm).
+ * (only if the oscillator type is set to pwm). See
+ * Tone.PWMOscillator for more info.
* @memberOf Tone.OmniOscillator#
* @type {Frequency}
* @signal
@@ -238,7 +377,7 @@ function(Tone){
*/
Object.defineProperty(Tone.OmniOscillator.prototype, "modulationFrequency", {
get : function(){
- if (this._sourceType === OmniOscType.PWMOscillator){
+ if (this._sourceType === OmniOscType.PWM){
return this._oscillator.modulationFrequency;
}
}
diff --git a/Tone/source/Oscillator.js b/Tone/source/Oscillator.js
index 2f8203704..141f404cd 100644
--- a/Tone/source/Oscillator.js
+++ b/Tone/source/Oscillator.js
@@ -247,7 +247,7 @@ function(Tone){
b = this._partials[n - 1];
break;
default:
- throw new Error("invalid oscillator type: "+type);
+ throw new TypeError("Tone.Oscillator: invalid type: "+type);
}
if (b !== 0){
real[n] = -b * Math.sin(phase * n);
diff --git a/Tone/source/PWMOscillator.js b/Tone/source/PWMOscillator.js
index 641a5d67b..f2eb65094 100644
--- a/Tone/source/PWMOscillator.js
+++ b/Tone/source/PWMOscillator.js
@@ -46,7 +46,7 @@ function(Tone){
* @type {Tone.Multiply}
* @private
*/
- this._scale = new Tone.Multiply(1.01);
+ this._scale = new Tone.Multiply(2);
/**
* The frequency control.
diff --git a/Tone/source/Player.js b/Tone/source/Player.js
index 82ca2d8b9..d17a1ab3d 100644
--- a/Tone/source/Player.js
+++ b/Tone/source/Player.js
@@ -13,9 +13,8 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To
* Recommended to use Tone.Buffer.onload instead.
* @example
* var player = new Tone.Player("./path/to/sample.mp3").toMaster();
- * Tone.Buffer.onload = function(){
- * player.start();
- * }
+ * //play as soon as the buffer is loaded
+ * player.autostart = true;
*/
Tone.Player = function(url){
@@ -149,16 +148,25 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To
};
/**
- * play the buffer between the desired positions
+ * Play the buffer at the given startTime. Optionally add an offset
+ * and/or duration which will play the buffer from a position
+ * within the buffer for the given duration.
*
- * @private
- * @param {Time} [startTime=now] when the player should start.
- * @param {Time} [offset=0] the offset from the beginning of the sample
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
* to start at.
- * @param {Time=} duration how long the sample should play. If no duration
+ * @param {Time=} duration How long the sample should play. If no duration
* is given, it will default to the full length
* of the sample (minus any offset)
* @returns {Tone.Player} this
+ * @memberOf Tone.Player#
+ * @method start
+ * @name start
+ */
+
+ /**
+ * Internal start method
+ * @private
*/
Tone.Player.prototype._start = function(startTime, offset, duration){
if (this._buffer.loaded){
@@ -196,7 +204,7 @@ define(["Tone/core/Tone", "Tone/core/Buffer", "Tone/source/Source"], function(To
this._source.start(startTime, offset, duration);
}
} else {
- throw Error("tried to start Player before the buffer was loaded");
+ throw Error("Tone.Player: tried to start Player before the buffer was loaded");
}
return this;
};
diff --git a/Tone/source/Source.js b/Tone/source/Source.js
index e5312d5f1..7bbddb553 100644
--- a/Tone/source/Source.js
+++ b/Tone/source/Source.js
@@ -1,5 +1,5 @@
-define(["Tone/core/Tone", "Tone/core/Transport", "Tone/component/Volume",
- "Tone/core/Type", "Tone/core/TimelineState", "Tone/signal/Signal"],
+define(["Tone/core/Tone", "Tone/core/Transport", "Tone/component/Volume", "Tone/core/Master",
+ "Tone/type/Type", "Tone/core/TimelineState", "Tone/signal/Signal"],
function(Tone){
"use strict";
@@ -55,6 +55,7 @@ function(Tone){
* @private
*/
this._state = new Tone.TimelineState(Tone.State.Stopped);
+ this._state.memory = 10;
/**
* The synced `start` callback function from the transport
@@ -84,6 +85,8 @@ function(Tone){
//make the output explicitly stereo
this._volume.output.output.channelCount = 2;
this._volume.output.output.channelCountMode = "explicit";
+ //mute initially
+ this.mute = options.mute;
};
Tone.extend(Tone.Source);
@@ -96,6 +99,7 @@ function(Tone){
*/
Tone.Source.defaults = {
"volume" : 0,
+ "mute" : false
};
/**
@@ -111,6 +115,24 @@ function(Tone){
}
});
+ /**
+ * Mute the output.
+ * @memberOf Tone.Source#
+ * @type {boolean}
+ * @name mute
+ * @example
+ * //mute the output
+ * source.mute = true;
+ */
+ Object.defineProperty(Tone.Source.prototype, "mute", {
+ get : function(){
+ return this._volume.mute;
+ },
+ set : function(mute){
+ this._volume.mute = mute;
+ }
+ });
+
/**
* Start the source at the specified time. If no time is given,
* start the source now.
@@ -140,11 +162,10 @@ function(Tone){
*/
Tone.Source.prototype.stop = function(time){
time = this.toSeconds(time);
- if (this._state.getStateAtTime(time) === Tone.State.Started){
- this._state.setStateAtTime(Tone.State.Stopped, time);
- if (this._stop){
- this._stop.apply(this, arguments);
- }
+ this._state.cancel(time);
+ this._state.setStateAtTime(Tone.State.Stopped, time);
+ if (this._stop){
+ this._stop.apply(this, arguments);
}
return this;
};
diff --git a/Tone/type/Frequency.js b/Tone/type/Frequency.js
new file mode 100644
index 000000000..bec97987e
--- /dev/null
+++ b/Tone/type/Frequency.js
@@ -0,0 +1,278 @@
+define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) {
+
+ /**
+ * @class Tone.Frequency is a primitive type for encoding Frequency values.
+ * Eventually all time values are evaluated to hertz
+ * using the `eval` method.
+ * @constructor
+ * @extends {Tone.TimeBase}
+ * @param {String|Number} val The time value.
+ * @param {String=} units The units of the value.
+ * @example
+ * Tone.Frequency("C3").eval() // 261
+ * Tone.Frequency(38, "midi").eval() //
+ * Tone.Frequency("C3").transpose(4).eval();
+ */
+ Tone.Frequency = function(val, units){
+ if (this instanceof Tone.Frequency){
+
+ Tone.TimeBase.call(this, val, units);
+
+ } else {
+ return new Tone.Frequency(val, units);
+ }
+ };
+
+ Tone.extend(Tone.Frequency, Tone.TimeBase);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // AUGMENT BASE EXPRESSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ //clone the expressions so that
+ //we can add more without modifying the original
+ Tone.Frequency.prototype._primaryExpressions = Object.create(Tone.TimeBase.prototype._primaryExpressions);
+
+ /*
+ * midi type primary expression
+ * @type {Object}
+ * @private
+ */
+ Tone.Frequency.prototype._primaryExpressions.midi = {
+ regexp : /^(\d+(?:\.\d+)?midi)/,
+ method : function(value){
+ return this.midiToFrequency(value);
+ }
+ };
+
+ /*
+ * note type primary expression
+ * @type {Object}
+ * @private
+ */
+ Tone.Frequency.prototype._primaryExpressions.note = {
+ regexp : /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i,
+ method : function(pitch, octave){
+ var index = noteToScaleIndex[pitch.toLowerCase()];
+ var noteNumber = index + (parseInt(octave) + 1) * 12;
+ return this.midiToFrequency(noteNumber);
+ }
+ };
+
+ /*
+ * BeatsBarsSixteenths type primary expression
+ * @type {Object}
+ * @private
+ */
+ Tone.Frequency.prototype._primaryExpressions.tr = {
+ regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/,
+ method : function(m, q, s){
+ var total = 1;
+ if (m && m !== "0"){
+ total *= this._beatsToUnits(this._timeSignature() * parseFloat(m));
+ }
+ if (q && q !== "0"){
+ total *= this._beatsToUnits(parseFloat(q));
+ }
+ if (s && s !== "0"){
+ total *= this._beatsToUnits(parseFloat(s) / 4);
+ }
+ return total;
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // EXPRESSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Transposes the frequency by the given number of semitones.
+ * @param {Interval} interval
+ * @return {Tone.Frequency} this
+ * @example
+ * Tone.Frequency("A4").transpose(3); //"C5"
+ */
+ Tone.Frequency.prototype.transpose = function(interval){
+ this._expr = function(expr, interval){
+ var val = expr();
+ return val * this.intervalToFrequencyRatio(interval);
+ }.bind(this, this._expr, interval);
+ return this;
+ };
+
+ /**
+ * Takes an array of semitone intervals and returns
+ * an array of frequencies transposed by those intervals.
+ * @param {Array} intervals
+ * @return {Tone.Frequency} this
+ * @example
+ * Tone.Frequency("A4").harmonize([0, 3, 7]); //["A4", "C5", "E5"]
+ */
+ Tone.Frequency.prototype.harmonize = function(intervals){
+ this._expr = function(expr, intervals){
+ var val = expr();
+ var ret = [];
+ for (var i = 0; i < intervals.length; i++){
+ ret[i] = val * this.intervalToFrequencyRatio(intervals[i]);
+ }
+ return ret;
+ }.bind(this, this._expr, intervals);
+ return this;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // UNIT CONVERSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Return the value of the frequency as a MIDI note
+ * @return {MIDI}
+ * @example
+ * Tone.Frequency("C4").toMidi(); //60
+ */
+ Tone.Frequency.prototype.toMidi = function(){
+ return this.frequencyToMidi(this.eval());
+ };
+
+ /**
+ * Return the value of the frequency in Scientific Pitch Notation
+ * @return {Note}
+ * @example
+ * Tone.Frequency(69, "midi").toNote(); //"A4"
+ */
+ Tone.Frequency.prototype.toNote = function(){
+ var freq = this.eval();
+ var log = Math.log(freq / Tone.Frequency.A4) / Math.LN2;
+ var noteNumber = Math.round(12 * log) + 57;
+ var octave = Math.floor(noteNumber/12);
+ if(octave < 0){
+ noteNumber += -12 * octave;
+ }
+ var noteName = scaleIndexToNote[noteNumber % 12];
+ return noteName + octave.toString();
+ };
+
+ /**
+ * Return the duration of one cycle in seconds.
+ * @return {Seconds}
+ */
+ Tone.Frequency.prototype.toSeconds = function(){
+ return 1 / this.eval();
+ };
+
+ /**
+ * Return the duration of one cycle in ticks
+ * @return {Ticks}
+ */
+ Tone.Frequency.prototype.toTicks = function(){
+ var quarterTime = this._beatsToUnits(1);
+ var quarters = this.eval() / quarterTime;
+ return Math.floor(quarters * Tone.Transport.PPQ);
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // UNIT CONVERSIONS HELPERS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns the value of a frequency in the current units
+ * @param {Frequency} freq
+ * @return {Number}
+ * @private
+ */
+ Tone.Frequency.prototype._frequencyToUnits = function(freq){
+ return freq;
+ };
+
+ /**
+ * Returns the value of a tick in the current time units
+ * @param {Ticks} ticks
+ * @return {Number}
+ * @private
+ */
+ Tone.Frequency.prototype._ticksToUnits = function(ticks){
+ return 1 / ((ticks * 60) / (Tone.Transport.bpm.value * Tone.Transport.PPQ));
+ };
+
+ /**
+ * Return the value of the beats in the current units
+ * @param {Number} beats
+ * @return {Number}
+ * @private
+ */
+ Tone.Frequency.prototype._beatsToUnits = function(beats){
+ return 1 / Tone.TimeBase.prototype._beatsToUnits.call(this, beats);
+ };
+
+ /**
+ * Returns the value of a second in the current units
+ * @param {Seconds} seconds
+ * @return {Number}
+ * @private
+ */
+ Tone.Frequency.prototype._secondsToUnits = function(seconds){
+ return 1 / seconds;
+ };
+
+ /**
+ * The default units if none are given.
+ * @private
+ */
+ Tone.Frequency.prototype._defaultUnits = "hz";
+
+ ///////////////////////////////////////////////////////////////////////////
+ // FREQUENCY CONVERSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Note to scale index
+ * @type {Object}
+ */
+ var noteToScaleIndex = {
+ "cbb" : -2, "cb" : -1, "c" : 0, "c#" : 1, "cx" : 2,
+ "dbb" : 0, "db" : 1, "d" : 2, "d#" : 3, "dx" : 4,
+ "ebb" : 2, "eb" : 3, "e" : 4, "e#" : 5, "ex" : 6,
+ "fbb" : 3, "fb" : 4, "f" : 5, "f#" : 6, "fx" : 7,
+ "gbb" : 5, "gb" : 6, "g" : 7, "g#" : 8, "gx" : 9,
+ "abb" : 7, "ab" : 8, "a" : 9, "a#" : 10, "ax" : 11,
+ "bbb" : 9, "bb" : 10, "b" : 11, "b#" : 12, "bx" : 13,
+ };
+
+ /**
+ * scale index to note (sharps)
+ * @type {Array}
+ */
+ var scaleIndexToNote = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
+
+ /**
+ * The [concert pitch](https://en.wikipedia.org/wiki/Concert_pitch)
+ * A4's values in Hertz.
+ * @type {Frequency}
+ * @static
+ */
+ Tone.Frequency.A4 = 440;
+
+ /**
+ * Convert a MIDI note to frequency value.
+ * @param {MIDI} midi The midi number to convert.
+ * @return {Frequency} the corresponding frequency value
+ * @example
+ * tone.midiToFrequency(69); // returns 440
+ */
+ Tone.Frequency.prototype.midiToFrequency = function(midi){
+ return Tone.Frequency.A4 * Math.pow(2, (midi - 69) / 12);
+ };
+
+ /**
+ * Convert a frequency value to a MIDI note.
+ * @param {Frequency} frequency The value to frequency value to convert.
+ * @returns {MIDI}
+ * @example
+ * tone.midiToFrequency(440); // returns 69
+ */
+ Tone.Frequency.prototype.frequencyToMidi = function(frequency){
+ return 69 + 12 * Math.log(frequency / Tone.Frequency.A4) / Math.LN2;
+ };
+
+ return Tone.Frequency;
+});
\ No newline at end of file
diff --git a/Tone/type/Time.js b/Tone/type/Time.js
new file mode 100644
index 000000000..41c694975
--- /dev/null
+++ b/Tone/type/Time.js
@@ -0,0 +1,263 @@
+define(["Tone/core/Tone", "Tone/type/TimeBase"], function (Tone) {
+
+ /**
+ * @class Tone.Time is a primitive type for encoding Time values.
+ * Eventually all time values are evaluated to seconds
+ * using the `eval` method. Tone.Time can be constructed
+ * with or without the `new` keyword. Tone.Time can be passed
+ * into the parameter of any method which takes time as an argument.
+ * @constructor
+ * @extends {Tone.TimeBase}
+ * @param {String|Number} val The time value.
+ * @param {String=} units The units of the value.
+ * @example
+ * var t = Tone.Time("4n");//encodes a quarter note
+ * t.mult(4); // multiply that value by 4
+ * t.toNotation(); //returns "1m"
+ */
+ Tone.Time = function(val, units){
+ if (this instanceof Tone.Time){
+
+ /**
+ * If the current clock time should
+ * be added to the output
+ * @type {Boolean}
+ * @private
+ */
+ this._plusNow = false;
+
+ Tone.TimeBase.call(this, val, units);
+
+ } else {
+ return new Tone.Time(val, units);
+ }
+ };
+
+ Tone.extend(Tone.Time, Tone.TimeBase);
+
+ //clone the expressions so that
+ //we can add more without modifying the original
+ Tone.Time.prototype._unaryExpressions = Object.create(Tone.TimeBase.prototype._unaryExpressions);
+
+ /*
+ * Adds an additional unary expression
+ * which quantizes values to the next subdivision
+ * @type {Object}
+ * @private
+ */
+ Tone.Time.prototype._unaryExpressions.quantize = {
+ regexp : /^@/,
+ method : function(rh){
+ return Tone.Transport.nextSubdivision(rh());
+ }
+ };
+
+ /*
+ * Adds an additional unary expression
+ * which adds the current clock time.
+ * @type {Object}
+ * @private
+ */
+ Tone.Time.prototype._unaryExpressions.now = {
+ regexp : /^\+/,
+ method : function(lh){
+ this._plusNow = true;
+ return lh();
+ }
+ };
+
+ /**
+ * Quantize the time by the given subdivision. Optionally add a
+ * percentage which will move the time value towards the ideal
+ * quantized value by that percentage.
+ * @param {Number|Time} val The subdivision to quantize to
+ * @param {NormalRange} [percent=1] Move the time value
+ * towards the quantized value by
+ * a percentage.
+ * @return {Tone.Time} this
+ * @example
+ * Tone.Time(21).quantize(2).eval() //returns 22
+ * Tone.Time(0.6).quantize("4n", 0.5).eval() //returns 0.55
+ */
+ Tone.Time.prototype.quantize = function(subdiv, percent){
+ percent = this.defaultArg(percent, 1);
+ this._expr = function(expr, subdivision, percent){
+ expr = expr();
+ subdivision = subdivision.toSeconds();
+ var multiple = Math.round(expr / subdivision);
+ var ideal = multiple * subdivision;
+ var diff = ideal - expr;
+ return expr + diff * percent;
+ }.bind(this, this._expr, new this.constructor(subdiv), percent);
+ return this;
+ };
+
+ /**
+ * Adds the clock time to the time expression at the
+ * moment of evaluation.
+ * @return {Tone.Time} this
+ */
+ Tone.Time.prototype.addNow = function(){
+ this._plusNow = true;
+ return this;
+ };
+
+ /**
+ * @override
+ * Override the default value return when no arguments are passed in.
+ * The default value is 'now'
+ * @private
+ */
+ Tone.Time.prototype._defaultExpr = function(){
+ this._plusNow = true;
+ return this._noOp;
+ };
+
+
+ //CONVERSIONS//////////////////////////////////////////////////////////////
+
+ /**
+ * Convert a Time to Notation. Values will be thresholded to the nearest 128th note.
+ * @return {Notation}
+ * @example
+ * //if the Transport is at 120bpm:
+ * Tone.Time(2).toNotation();//returns "1m"
+ */
+ Tone.Time.prototype.toNotation = function(){
+ var time = this.toSeconds();
+ var testNotations = ["1m", "2n", "4n", "8n", "16n", "32n", "64n", "128n"];
+ var retNotation = this._toNotationHelper(time, testNotations);
+ //try the same thing but with tripelets
+ var testTripletNotations = ["1m", "2n", "2t", "4n", "4t", "8n", "8t", "16n", "16t", "32n", "32t", "64n", "64t", "128n"];
+ var retTripletNotation = this._toNotationHelper(time, testTripletNotations);
+ //choose the simpler expression of the two
+ if (retTripletNotation.split("+").length < retNotation.split("+").length){
+ return retTripletNotation;
+ } else {
+ return retNotation;
+ }
+ };
+
+ /**
+ * Helper method for Tone.toNotation
+ * @param {Number} units
+ * @param {Array} testNotations
+ * @return {String}
+ * @private
+ */
+ Tone.Time.prototype._toNotationHelper = function(units, testNotations){
+ //the threshold is the last value in the array
+ var threshold = this._notationToUnits(testNotations[testNotations.length - 1]);
+ var retNotation = "";
+ for (var i = 0; i < testNotations.length; i++){
+ var notationTime = this._notationToUnits(testNotations[i]);
+ //account for floating point errors (i.e. round up if the value is 0.999999)
+ var multiple = units / notationTime;
+ var floatingPointError = 0.000001;
+ if (1 - multiple % 1 < floatingPointError){
+ multiple += floatingPointError;
+ }
+ multiple = Math.floor(multiple);
+ if (multiple > 0){
+ if (multiple === 1){
+ retNotation += testNotations[i];
+ } else {
+ retNotation += multiple.toString() + "*" + testNotations[i];
+ }
+ units -= multiple * notationTime;
+ if (units < threshold){
+ break;
+ } else {
+ retNotation += " + ";
+ }
+ }
+ }
+ if (retNotation === ""){
+ retNotation = "0";
+ }
+ return retNotation;
+ };
+
+ /**
+ * Convert a notation value to the current units
+ * @param {Notation} notation
+ * @return {Number}
+ * @private
+ */
+ Tone.Time.prototype._notationToUnits = function(notation){
+ var primaryExprs = this._primaryExpressions;
+ var notationExprs = [primaryExprs.n, primaryExprs.t, primaryExprs.m];
+ for (var i = 0; i < notationExprs.length; i++){
+ var expr = notationExprs[i];
+ var match = notation.match(expr.regexp);
+ if (match){
+ return expr.method.call(this, match[1]);
+ }
+ }
+ };
+
+ /**
+ * Return the time encoded as Bars:Beats:Sixteenths.
+ * @return {BarsBeatsSixteenths}
+ */
+ Tone.Time.prototype.toBarsBeatsSixteenths = function(){
+ var quarterTime = this._beatsToUnits(1);
+ var quarters = this.toSeconds() / quarterTime;
+ var measures = Math.floor(quarters / this._timeSignature());
+ var sixteenths = (quarters % 1) * 4;
+ quarters = Math.floor(quarters) % this._timeSignature();
+ sixteenths = sixteenths.toString();
+ if (sixteenths.length > 3){
+ sixteenths = parseFloat(sixteenths).toFixed(3);
+ }
+ var progress = [measures, quarters, sixteenths];
+ return progress.join(":");
+ };
+
+ /**
+ * Return the time in ticks.
+ * @return {Ticks}
+ */
+ Tone.Time.prototype.toTicks = function(){
+ var quarterTime = this._beatsToUnits(1);
+ var quarters = this.eval() / quarterTime;
+ return Math.floor(quarters * Tone.Transport.PPQ);
+ };
+
+ /**
+ * Return the time in samples
+ * @return {Samples}
+ */
+ Tone.Time.prototype.toSamples = function(){
+ return this.toSeconds() * this.context.sampleRate;
+ };
+
+ /**
+ * Return the time as a frequency value
+ * @return {Frequency}
+ * @example
+ * Tone.Time(2).toFrequency(); //0.5
+ */
+ Tone.Time.prototype.toFrequency = function(){
+ return 1/this.eval();
+ };
+
+ /**
+ * Return the time in seconds.
+ * @return {Seconds}
+ */
+ Tone.Time.prototype.toSeconds = function(){
+ return this._expr();
+ };
+
+ /**
+ * Return the time in seconds.
+ * @return {Seconds}
+ */
+ Tone.Time.prototype.eval = function(){
+ var val = this._expr();
+ return val + (this._plusNow?this.now():0);
+ };
+
+ return Tone.Time;
+});
\ No newline at end of file
diff --git a/Tone/type/TimeBase.js b/Tone/type/TimeBase.js
new file mode 100644
index 000000000..cd1601fb7
--- /dev/null
+++ b/Tone/type/TimeBase.js
@@ -0,0 +1,532 @@
+define(["Tone/core/Tone"], function (Tone) {
+
+ /**
+ * @class Tone.TimeBase is a flexible encoding of time
+ * which can be evaluated to and from a string.
+ * Parsing code modified from https://code.google.com/p/tapdigit/
+ * Copyright 2011 2012 Ariya Hidayat, New BSD License
+ * @extends {Tone}
+ * @param {Time} val The time value as a number or string
+ * @param {String=} units Unit values
+ * @example
+ * Tone.TimeBase(4, "n")
+ * Tone.TimeBase(2, "t")
+ * Tone.TimeBase("2t").add("1m")
+ * Tone.TimeBase("2t + 1m");
+ */
+ Tone.TimeBase = function(val, units){
+
+ //allows it to be constructed with or without 'new'
+ if (this instanceof Tone.TimeBase) {
+
+ /**
+ * Any expressions parsed from the Time
+ * @type {Array}
+ * @private
+ */
+ this._expr = this._noOp;
+
+ //default units
+ units = this.defaultArg(units, this._defaultUnits);
+
+ //get the value from the given time
+ if (this.isString(val)){
+ this._expr = this._parseExprString(val);
+ } else if (this.isNumber(val)){
+ var method = this._primaryExpressions[units].method;
+ this._expr = method.bind(this, val);
+ } else if (this.isUndef(val)){
+ //default expression
+ this._expr = this._defaultExpr();
+ } else if (val instanceof Tone.TimeBase){
+ this._expr = val._expr;
+ }
+ } else {
+
+ return new Tone.TimeBase(val, units);
+ }
+ };
+
+ Tone.extend(Tone.TimeBase);
+
+ /**
+ * Repalce the current time value with the value
+ * given by the expression string.
+ * @param {String} exprString
+ * @return {Tone.TimeBase} this
+ */
+ Tone.TimeBase.prototype.set = function(exprString){
+ this._expr = this._parseExprString(exprString);
+ return this;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // ABSTRACT SYNTAX TREE PARSER
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * All the primary expressions.
+ * @private
+ * @type {Object}
+ */
+ Tone.TimeBase.prototype._primaryExpressions = {
+ "n" : {
+ regexp : /^(\d+)n/i,
+ method : function(value){
+ value = parseInt(value);
+ if (value === 1){
+ return this._beatsToUnits(this._timeSignature());
+ } else {
+ return this._beatsToUnits(4 / value);
+ }
+ }
+ },
+ "t" : {
+ regexp : /^(\d+)t/i,
+ method : function(value){
+ value = parseInt(value);
+ return this._beatsToUnits(8 / (parseInt(value) * 3));
+ }
+ },
+ "m" : {
+ regexp : /^(\d+)m/i,
+ method : function(value){
+ return this._beatsToUnits(parseInt(value) * this._timeSignature());
+ }
+ },
+ "i" : {
+ regexp : /^(\d+)i/i,
+ method : function(value){
+ return this._ticksToUnits(parseInt(value));
+ }
+ },
+ "hz" : {
+ regexp : /^(\d+(?:\.\d+)?)hz/i,
+ method : function(value){
+ return this._frequencyToUnits(parseFloat(value));
+ }
+ },
+ "tr" : {
+ regexp : /^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):?(\d+(?:\.\d+)?)?/,
+ method : function(m, q, s){
+ var total = 0;
+ if (m && m !== "0"){
+ total += this._beatsToUnits(this._timeSignature() * parseFloat(m));
+ }
+ if (q && q !== "0"){
+ total += this._beatsToUnits(parseFloat(q));
+ }
+ if (s && s !== "0"){
+ total += this._beatsToUnits(parseFloat(s) / 4);
+ }
+ return total;
+ }
+ },
+ "s" : {
+ regexp : /^(\d+(?:\.\d+)?s)/,
+ method : function(value){
+ return this._secondsToUnits(parseFloat(value));
+ }
+ },
+ "samples" : {
+ regexp : /^(\d+)samples/,
+ method : function(value){
+ return parseInt(value) / this.context.sampleRate;
+ }
+ },
+ "default" : {
+ regexp : /^(\d+(?:\.\d+)?)/,
+ method : function(value){
+ return this._primaryExpressions[this._defaultUnits].method.call(this, value);
+ }
+ }
+ };
+
+ /**
+ * All the binary expressions that TimeBase can accept.
+ * @private
+ * @type {Object}
+ */
+ Tone.TimeBase.prototype._binaryExpressions = {
+ "+" : {
+ regexp : /^\+/,
+ precedence : 2,
+ method : function(lh, rh){
+ return lh() + rh();
+ }
+ },
+ "-" : {
+ regexp : /^\-/,
+ precedence : 2,
+ method : function(lh, rh){
+ return lh() - rh();
+ }
+ },
+ "*" : {
+ regexp : /^\*/,
+ precedence : 1,
+ method : function(lh, rh){
+ return lh() * rh();
+ }
+ },
+ "/" : {
+ regexp : /^\//,
+ precedence : 1,
+ method : function(lh, rh){
+ return lh() / rh();
+ }
+ }
+ };
+
+ /**
+ * All the unary expressions.
+ * @private
+ * @type {Object}
+ */
+ Tone.TimeBase.prototype._unaryExpressions = {
+ "neg" : {
+ regexp : /^\-/,
+ method : function(lh){
+ return -lh();
+ }
+ }
+ };
+
+ /**
+ * Syntactic glue which holds expressions together
+ * @private
+ * @type {Object}
+ */
+ Tone.TimeBase.prototype._syntaxGlue = {
+ "(" : {
+ regexp : /^\(/
+ },
+ ")" : {
+ regexp : /^\)/
+ }
+ };
+
+ /**
+ * tokenize the expression based on the Expressions object
+ * @param {string} expr
+ * @return {Object} returns two methods on the tokenized list, next and peek
+ * @private
+ */
+ Tone.TimeBase.prototype._tokenize = function(expr){
+ var position = -1;
+ var tokens = [];
+
+ while(expr.length > 0){
+ expr = expr.trim();
+ var token = getNextToken(expr, this);
+ tokens.push(token);
+ expr = expr.substr(token.value.length);
+ }
+
+ function getNextToken(expr, context){
+ var expressions = ["_binaryExpressions", "_unaryExpressions", "_primaryExpressions", "_syntaxGlue"];
+ for (var i = 0; i < expressions.length; i++){
+ var group = context[expressions[i]];
+ for (var opName in group){
+ var op = group[opName];
+ var reg = op.regexp;
+ var match = expr.match(reg);
+ if (match !== null){
+ return {
+ method : op.method,
+ precedence : op.precedence,
+ regexp : op.regexp,
+ value : match[0],
+ };
+ }
+ }
+ }
+ throw new SyntaxError("Tone.TimeBase: Unexpected token "+expr);
+ }
+
+ return {
+ next : function(){
+ return tokens[++position];
+ },
+ peek : function(){
+ return tokens[position + 1];
+ }
+ };
+ };
+
+ /**
+ * Given a token, find the value within the groupName
+ * @param {Object} token
+ * @param {String} groupName
+ * @param {Number} precedence
+ * @private
+ */
+ Tone.TimeBase.prototype._matchGroup = function(token, group, prec) {
+ var ret = false;
+ if (!this.isUndef(token)){
+ for (var opName in group){
+ var op = group[opName];
+ if (op.regexp.test(token.value)){
+ if (!this.isUndef(prec)){
+ if(op.precedence === prec){
+ return op;
+ }
+ } else {
+ return op;
+ }
+ }
+ }
+ }
+ return ret;
+ };
+
+ /**
+ * Match a binary expression given the token and the precedence
+ * @param {Lexer} lexer
+ * @param {Number} precedence
+ * @private
+ */
+ Tone.TimeBase.prototype._parseBinary = function(lexer, precedence){
+ if (this.isUndef(precedence)){
+ precedence = 2;
+ }
+ var expr;
+ if (precedence < 0){
+ expr = this._parseUnary(lexer);
+ } else {
+ expr = this._parseBinary(lexer, precedence - 1);
+ }
+ var token = lexer.peek();
+ while (token && this._matchGroup(token, this._binaryExpressions, precedence)){
+ token = lexer.next();
+ expr = token.method.bind(this, expr, this._parseBinary(lexer, precedence - 1));
+ token = lexer.peek();
+ }
+ return expr;
+ };
+
+ /**
+ * Match a unary expression.
+ * @param {Lexer} lexer
+ * @private
+ */
+ Tone.TimeBase.prototype._parseUnary = function(lexer){
+ var token, expr;
+ token = lexer.peek();
+ var op = this._matchGroup(token, this._unaryExpressions);
+ if (op) {
+ token = lexer.next();
+ expr = this._parseUnary(lexer);
+ return op.method.bind(this, expr);
+ }
+ return this._parsePrimary(lexer);
+ };
+
+ /**
+ * Match a primary expression (a value).
+ * @param {Lexer} lexer
+ * @private
+ */
+ Tone.TimeBase.prototype._parsePrimary = function(lexer){
+ var token, expr;
+ token = lexer.peek();
+ if (this.isUndef(token)) {
+ throw new SyntaxError("Tone.TimeBase: Unexpected end of expression");
+ }
+ if (this._matchGroup(token, this._primaryExpressions)) {
+ token = lexer.next();
+ var matching = token.value.match(token.regexp);
+ return token.method.bind(this, matching[1], matching[2], matching[3]);
+ }
+ if (token && token.value === "("){
+ lexer.next();
+ expr = this._parseBinary(lexer);
+ token = lexer.next();
+ if (!(token && token.value === ")")) {
+ throw new SyntaxError("Expected )");
+ }
+ return expr;
+ }
+ throw new SyntaxError("Tone.TimeBase: Cannot process token " + token.value);
+ };
+
+ /**
+ * Recursively parse the string expression into a syntax tree.
+ * @param {string} expr
+ * @return {Function} the bound method to be evaluated later
+ * @private
+ */
+ Tone.TimeBase.prototype._parseExprString = function(exprString){
+ var lexer = this._tokenize(exprString);
+ var tree = this._parseBinary(lexer);
+ return tree;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // DEFAULTS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * The initial expression value
+ * @return {Number} The initial value 0
+ * @private
+ */
+ Tone.TimeBase.prototype._noOp = function(){
+ return 0;
+ };
+
+ /**
+ * The default expression value if no arguments are given
+ * @private
+ */
+ Tone.TimeBase.prototype._defaultExpr = function(){
+ return this._noOp;
+ };
+
+ /**
+ * The default units if none are given.
+ * @private
+ */
+ Tone.TimeBase.prototype._defaultUnits = "s";
+
+ ///////////////////////////////////////////////////////////////////////////
+ // UNIT CONVERSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Returns the value of a frequency in the current units
+ * @param {Frequency} freq
+ * @return {Number}
+ * @private
+ */
+ Tone.TimeBase.prototype._frequencyToUnits = function(freq){
+ return 1/freq;
+ };
+
+ /**
+ * Return the value of the beats in the current units
+ * @param {Number} beats
+ * @return {Number}
+ * @private
+ */
+ Tone.TimeBase.prototype._beatsToUnits = function(beats){
+ return (60 / Tone.Transport.bpm.value) * beats;
+ };
+
+ /**
+ * Returns the value of a second in the current units
+ * @param {Seconds} seconds
+ * @return {Number}
+ * @private
+ */
+ Tone.TimeBase.prototype._secondsToUnits = function(seconds){
+ return seconds;
+ };
+
+ /**
+ * Returns the value of a tick in the current time units
+ * @param {Ticks} ticks
+ * @return {Number}
+ * @private
+ */
+ Tone.TimeBase.prototype._ticksToUnits = function(ticks){
+ return ticks * (this._beatsToUnits(1) / Tone.Transport.PPQ);
+ };
+
+ /**
+ * Return the time signature.
+ * @return {Number}
+ * @private
+ */
+ Tone.TimeBase.prototype._timeSignature = function(){
+ return Tone.Transport.timeSignature;
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // EXPRESSIONS
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Push an expression onto the expression list
+ * @param {Time} val
+ * @param {String} type
+ * @param {String} units
+ * @return {Tone.TimeBase}
+ * @private
+ */
+ Tone.TimeBase.prototype._pushExpr = function(val, name, units){
+ //create the expression
+ if (!(val instanceof Tone.TimeBase)){
+ val = new Tone.TimeBase(val, units);
+ }
+ this._expr = this._binaryExpressions[name].method.bind(this, this._expr, val._expr);
+ return this;
+ };
+
+ /**
+ * Add to the current value.
+ * @param {Time} val The value to add
+ * @param {String=} units Optional units to use with the value.
+ * @return {Tone.TimeBase} this
+ * @example
+ * Tone.TimeBase("2m").add("1m"); //"3m"
+ */
+ Tone.TimeBase.prototype.add = function(val, units){
+ return this._pushExpr(val, "+", units);
+ };
+
+ /**
+ * Subtract the value from the current time.
+ * @param {Time} val The value to subtract
+ * @param {String=} units Optional units to use with the value.
+ * @return {Tone.TimeBase} this
+ * @example
+ * Tone.TimeBase("2m").sub("1m"); //"1m"
+ */
+ Tone.TimeBase.prototype.sub = function(val, units){
+ return this._pushExpr(val, "-", units);
+ };
+
+ /**
+ * Multiply the current value by the given time.
+ * @param {Time} val The value to multiply
+ * @param {String=} units Optional units to use with the value.
+ * @return {Tone.TimeBase} this
+ * @example
+ * Tone.TimeBase("2m").mult("2"); //"4m"
+ */
+ Tone.TimeBase.prototype.mult = function(val, units){
+ return this._pushExpr(val, "*", units);
+ };
+
+ /**
+ * Divide the current value by the given time.
+ * @param {Time} val The value to divide by
+ * @param {String=} units Optional units to use with the value.
+ * @return {Tone.TimeBase} this
+ * @example
+ * Tone.TimeBase("2m").div(2); //"1m"
+ */
+ Tone.TimeBase.prototype.div = function(val, units){
+ return this._pushExpr(val, "/", units);
+ };
+
+ /**
+ * Evaluate the time value. Returns the time
+ * in seconds.
+ * @return {Seconds}
+ */
+ Tone.TimeBase.prototype.eval = function(){
+ return this._expr();
+ };
+
+ /**
+ * Clean up
+ * @return {Tone.TimeBase} this
+ */
+ Tone.TimeBase.prototype.dispose = function(){
+ this._expr = null;
+ };
+
+ return Tone.TimeBase;
+});
\ No newline at end of file
diff --git a/Tone/type/TransportTime.js b/Tone/type/TransportTime.js
new file mode 100644
index 000000000..ef5908d82
--- /dev/null
+++ b/Tone/type/TransportTime.js
@@ -0,0 +1,82 @@
+define(["Tone/core/Tone", "Tone/type/Time"], function (Tone) {
+
+ /**
+ * @class Tone.TransportTime is a the time along the Transport's
+ * timeline. It is similar to Tone.Time, but instead of evaluating
+ * against the AudioContext's clock, it is evaluated against
+ * the Transport's position. See [TransportTime wiki](https://github.com/Tonejs/Tone.js/wiki/TransportTime).
+ * @constructor
+ * @param {Time} val The time value as a number or string
+ * @param {String=} units Unit values
+ * @extends {Tone.Time}
+ */
+ Tone.TransportTime = function(val, units){
+ if (this instanceof Tone.TransportTime){
+
+ Tone.Time.call(this, val, units);
+
+ } else {
+ return new Tone.TransportTime(val, units);
+ }
+ };
+
+ Tone.extend(Tone.TransportTime, Tone.Time);
+
+ //clone the expressions so that
+ //we can add more without modifying the original
+ Tone.TransportTime.prototype._unaryExpressions = Object.create(Tone.Time.prototype._unaryExpressions);
+
+ /**
+ * Adds an additional unary expression
+ * which quantizes values to the next subdivision
+ * @type {Object}
+ * @private
+ */
+ Tone.TransportTime.prototype._unaryExpressions.quantize = {
+ regexp : /^@/,
+ method : function(rh){
+ var subdivision = this._secondsToTicks(rh());
+ var multiple = Math.ceil(Tone.Transport.ticks / subdivision);
+ return this._ticksToUnits(multiple * subdivision);
+ }
+ };
+
+ /**
+ * Convert seconds into ticks
+ * @param {Seconds} seconds
+ * @return {Ticks}
+ * @private
+ */
+ Tone.TransportTime.prototype._secondsToTicks = function(seconds){
+ var quarterTime = this._beatsToUnits(1);
+ var quarters = seconds / quarterTime;
+ return Math.round(quarters * Tone.Transport.PPQ);
+ };
+
+ /**
+ * Evaluate the time expression. Returns values in ticks
+ * @return {Ticks}
+ */
+ Tone.TransportTime.prototype.eval = function(){
+ var val = this._secondsToTicks(this._expr());
+ return val + (this._plusNow ? Tone.Transport.ticks : 0);
+ };
+
+ /**
+ * Return the time in ticks.
+ * @return {Ticks}
+ */
+ Tone.TransportTime.prototype.toTicks = function(){
+ return this.eval();
+ };
+
+ /**
+ * Return the time as a frequency value
+ * @return {Frequency}
+ */
+ Tone.TransportTime.prototype.toFrequency = function(){
+ return 1/this.toSeconds();
+ };
+
+ return Tone.TransportTime;
+});
\ No newline at end of file
diff --git a/Tone/type/Type.js b/Tone/type/Type.js
new file mode 100644
index 000000000..76e8f6fb3
--- /dev/null
+++ b/Tone/type/Type.js
@@ -0,0 +1,224 @@
+define(["Tone/core/Tone", "Tone/type/Time", "Tone/type/Frequency", "Tone/type/TransportTime"],
+function (Tone) {
+
+ ///////////////////////////////////////////////////////////////////////////
+ // TYPES
+ ///////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Units which a value can take on.
+ * @enum {String}
+ */
+ Tone.Type = {
+ /**
+ * Default units
+ * @typedef {Default}
+ */
+ Default : "number",
+ /**
+ * Time can be described in a number of ways. Read more [Time](https://github.com/Tonejs/Tone.js/wiki/Time).
+ *
+ * input[0]
and input[1]
- * @example
- * var lt = new Tone.LessThan(2);
- * var sig = new Tone.Signal(-1).connect(lt);
- * //if (sig < 2) lt outputs 1
+ * dispose all the nodes
+ * @private
*/
- Tone.LessThan = function (value) {
- Tone.call(this, 2, 0);
- /**
- * negate the incoming signal
- * @type {Tone.Negate}
- * @private
- */
- this._neg = this.input[0] = new Tone.Negate();
- /**
- * input < value === -input > -value
- * @type {Tone.GreaterThan}
- * @private
- */
- this._gt = this.output = new Tone.GreaterThan();
- /**
- * negate the signal coming from the second input
- * @private
- * @type {Tone.Negate}
- */
- this._rhNeg = new Tone.Negate();
- /**
- * the node where the value is set
- * @private
- * @type {Tone.Signal}
- */
- this._param = this.input[1] = new Tone.Signal(value);
- //connect
- this._neg.connect(this._gt);
- this._param.connect(this._rhNeg);
- this._rhNeg.connect(this._gt, 0, 1);
+ Tone.Expr.prototype._disposeNodes = function () {
+ for (var i = 0; i < this._nodes.length; i++) {
+ var node = this._nodes[i];
+ if (this.isFunction(node.dispose)) {
+ node.dispose();
+ } else if (this.isFunction(node.disconnect)) {
+ node.disconnect();
+ }
+ node = null;
+ this._nodes[i] = null;
+ }
+ this._nodes = null;
};
- Tone.extend(Tone.LessThan, Tone.Signal);
/**
- * Clean up.
- * @returns {Tone.LessThan} this
+ * clean up
*/
- Tone.LessThan.prototype.dispose = function () {
+ Tone.Expr.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
- this._neg.dispose();
- this._neg = null;
- this._gt.dispose();
- this._gt = null;
- this._rhNeg.dispose();
- this._rhNeg = null;
- this._param.dispose();
- this._param = null;
- return this;
+ this._disposeNodes();
};
- return Tone.LessThan;
+ return Tone.Expr;
});
Module(function (Tone) {
/**
- * @class Return the absolute value of an incoming signal.
- *
- * @constructor
+ * @class Convert an incoming signal between 0, 1 to an equal power gain scale.
+ *
* @extends {Tone.SignalBase}
+ * @constructor
* @example
- * var signal = new Tone.Signal(-1);
- * var abs = new Tone.Abs();
- * signal.connect(abs);
- * //the output of abs is 1.
+ * var eqPowGain = new Tone.EqualPowerGain();
*/
- Tone.Abs = function () {
- Tone.call(this, 1, 0);
- /**
- * @type {Tone.LessThan}
- * @private
- */
- this._ltz = new Tone.LessThan(0);
- /**
- * @type {Tone.Select}
- * @private
- */
- this._switch = this.output = new Tone.Select(2);
+ Tone.EqualPowerGain = function () {
/**
- * @type {Tone.Negate}
+ * @type {Tone.WaveShaper}
* @private
*/
- this._negate = new Tone.Negate();
- //two signal paths, positive and negative
- this.input.connect(this._switch, 0, 0);
- this.input.connect(this._negate);
- this._negate.connect(this._switch, 0, 1);
- //the control signal
- this.input.chain(this._ltz, this._switch.gate);
+ this._eqPower = this.input = this.output = new Tone.WaveShaper(function (val) {
+ if (Math.abs(val) < 0.001) {
+ //should output 0 when input is 0
+ return 0;
+ } else {
+ return this.equalPowerScale(val);
+ }
+ }.bind(this), 4096);
};
- Tone.extend(Tone.Abs, Tone.SignalBase);
+ Tone.extend(Tone.EqualPowerGain, Tone.SignalBase);
/**
- * dispose method
- * @returns {Tone.Abs} this
+ * clean up
+ * @returns {Tone.EqualPowerGain} this
*/
- Tone.Abs.prototype.dispose = function () {
+ Tone.EqualPowerGain.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
- this._switch.dispose();
- this._switch = null;
- this._ltz.dispose();
- this._ltz = null;
- this._negate.dispose();
- this._negate = null;
+ this._eqPower.dispose();
+ this._eqPower = null;
return this;
};
- return Tone.Abs;
+ return Tone.EqualPowerGain;
});
Module(function (Tone) {
/**
- * @class Outputs the greater of two signals. If a number is provided in the constructor
- * it will use that instead of the signal.
- *
- * @constructor
- * @extends {Tone.Signal}
- * @param {number=} max Max value if provided. if not provided, it will use the
- * signal value from input 1.
- * @example
- * var max = new Tone.Max(2);
- * var sig = new Tone.Signal(3).connect(max);
- * //max outputs 3
- * sig.value = 1;
- * //max outputs 2
- * @example
- * var max = new Tone.Max();
- * var sigA = new Tone.Signal(3);
- * var sigB = new Tone.Signal(4);
- * sigA.connect(max, 0, 0);
- * sigB.connect(max, 0, 1);
- * //output of max is 4.
+ * @class Tone.Crossfade provides equal power fading between two inputs.
+ * More on crossfading technique [here](https://en.wikipedia.org/wiki/Fade_(audio_engineering)#Crossfading).
+ *
+ * @constructor
+ * @extends {Tone}
+ * @param {NormalRange} [initialFade=0.5]
+ * @example
+ * var crossFade = new Tone.CrossFade(0.5);
+ * //connect effect A to crossfade from
+ * //effect output 0 to crossfade input 0
+ * effectA.connect(crossFade, 0, 0);
+ * //connect effect B to crossfade from
+ * //effect output 0 to crossfade input 1
+ * effectB.connect(crossFade, 0, 1);
+ * crossFade.fade.value = 0;
+ * // ^ only effectA is output
+ * crossFade.fade.value = 1;
+ * // ^ only effectB is output
+ * crossFade.fade.value = 0.5;
+ * // ^ the two signals are mixed equally.
*/
- Tone.Max = function (max) {
- Tone.call(this, 2, 0);
- this.input[0] = this.context.createGain();
+ Tone.CrossFade = function (initialFade) {
+ Tone.call(this, 2, 1);
/**
- * the max signal
- * @type {Tone.Signal}
- * @private
+ * Alias for input[0]
.
+ * @type {GainNode}
*/
- this._param = this.input[1] = new Tone.Signal(max);
+ this.a = this.input[0] = this.context.createGain();
/**
- * @type {Tone.Select}
- * @private
+ * Alias for input[1]
.
+ * @type {GainNode}
*/
- this._ifThenElse = this.output = new Tone.IfThenElse();
+ this.b = this.input[1] = this.context.createGain();
/**
- * @type {Tone.Select}
- * @private
+ * The mix between the two inputs. A fade value of 0
+ * will output 100% input[0]
and
+ * a value of 1 will output 100% input[1]
.
+ * @type {NormalRange}
+ * @signal
*/
- this._gt = new Tone.GreaterThan();
- //connections
- this.input[0].chain(this._gt, this._ifThenElse.if);
- this.input[0].connect(this._ifThenElse.then);
- this._param.connect(this._ifThenElse.else);
- this._param.connect(this._gt, 0, 1);
- };
- Tone.extend(Tone.Max, Tone.Signal);
- /**
- * Clean up.
- * @returns {Tone.Max} this
- */
- Tone.Max.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._param.dispose();
- this._ifThenElse.dispose();
- this._gt.dispose();
- this._param = null;
- this._ifThenElse = null;
- this._gt = null;
- return this;
- };
- return Tone.Max;
- });
- Module(function (Tone) {
-
- /**
- * @class Outputs the lesser of two signals. If a number is given
- * in the constructor, it will use a signal and a number.
- *
- * @constructor
- * @extends {Tone.Signal}
- * @param {number} min The minimum to compare to the incoming signal
- * @example
- * var min = new Tone.Min(2);
- * var sig = new Tone.Signal(3).connect(min);
- * //min outputs 2
- * sig.value = 1;
- * //min outputs 1
- * @example
- * var min = new Tone.Min();
- * var sigA = new Tone.Signal(3);
- * var sigB = new Tone.Signal(4);
- * sigA.connect(min, 0, 0);
- * sigB.connect(min, 0, 1);
- * //output of min is 3.
- */
- Tone.Min = function (min) {
- Tone.call(this, 2, 0);
- this.input[0] = this.context.createGain();
+ this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange);
/**
- * @type {Tone.Select}
+ * equal power gain cross fade
* @private
+ * @type {Tone.EqualPowerGain}
*/
- this._ifThenElse = this.output = new Tone.IfThenElse();
+ this._equalPowerA = new Tone.EqualPowerGain();
/**
- * @type {Tone.Select}
+ * equal power gain cross fade
* @private
+ * @type {Tone.EqualPowerGain}
*/
- this._lt = new Tone.LessThan();
+ this._equalPowerB = new Tone.EqualPowerGain();
/**
- * the min signal
- * @type {Tone.Signal}
+ * invert the incoming signal
* @private
+ * @type {Tone}
*/
- this._param = this.input[1] = new Tone.Signal(min);
+ this._invert = new Tone.Expr('1 - $0');
//connections
- this.input[0].chain(this._lt, this._ifThenElse.if);
- this.input[0].connect(this._ifThenElse.then);
- this._param.connect(this._ifThenElse.else);
- this._param.connect(this._lt, 0, 1);
+ this.a.connect(this.output);
+ this.b.connect(this.output);
+ this.fade.chain(this._equalPowerB, this.b.gain);
+ this.fade.chain(this._invert, this._equalPowerA, this.a.gain);
+ this._readOnly('fade');
};
- Tone.extend(Tone.Min, Tone.Signal);
+ Tone.extend(Tone.CrossFade);
/**
* clean up
- * @returns {Tone.Min} this
+ * @returns {Tone.CrossFade} this
*/
- Tone.Min.prototype.dispose = function () {
+ Tone.CrossFade.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
- this._param.dispose();
- this._ifThenElse.dispose();
- this._lt.dispose();
- this._param = null;
- this._ifThenElse = null;
- this._lt = null;
+ this._writable('fade');
+ this._equalPowerA.dispose();
+ this._equalPowerA = null;
+ this._equalPowerB.dispose();
+ this._equalPowerB = null;
+ this.fade.dispose();
+ this.fade = null;
+ this._invert.dispose();
+ this._invert = null;
+ this.a.disconnect();
+ this.a = null;
+ this.b.disconnect();
+ this.b = null;
return this;
};
- return Tone.Min;
+ return Tone.CrossFade;
});
Module(function (Tone) {
/**
- * @class Signal-rate modulo operator. Only works in AudioRange [-1, 1] and for modulus
- * values in the NormalRange.
+ * @class Tone.Filter is a filter which allows for all of the same native methods
+ * as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface).
+ * Tone.Filter has the added ability to set the filter rolloff at -12
+ * (default), -24 and -48.
*
* @constructor
- * @extends {Tone.SignalBase}
- * @param {NormalRange} modulus The modulus to apply.
+ * @extends {Tone}
+ * @param {Frequency|Object} [frequency] The cutoff frequency of the filter.
+ * @param {string=} type The type of filter.
+ * @param {number=} rolloff The drop in decibels per octave after the cutoff frequency.
+ * 3 choices: -12, -24, and -48
* @example
- * var mod = new Tone.Modulo(0.2)
- * var sig = new Tone.Signal(0.5).connect(mod);
- * //mod outputs 0.1
+ * var filter = new Tone.Filter(200, "highpass");
*/
- Tone.Modulo = function (modulus) {
- Tone.call(this, 1, 1);
+ Tone.Filter = function () {
+ Tone.call(this);
+ var options = this.optionsObject(arguments, [
+ 'frequency',
+ 'type',
+ 'rolloff'
+ ], Tone.Filter.defaults);
/**
- * A waveshaper gets the integer multiple of
- * the input signal and the modulus.
+ * the filter(s)
+ * @type {Array}
* @private
- * @type {Tone.WaveShaper}
*/
- this._shaper = new Tone.WaveShaper(Math.pow(2, 16));
+ this._filters = [];
/**
- * the integer multiple is multiplied by the modulus
- * @type {Tone.Multiply}
- * @private
+ * The cutoff frequency of the filter.
+ * @type {Frequency}
+ * @signal
*/
- this._multiply = new Tone.Multiply();
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
/**
- * and subtracted from the input signal
- * @type {Tone.Subtract}
+ * The detune parameter
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = new Tone.Signal(0, Tone.Type.Cents);
+ /**
+ * The gain of the filter, only used in certain filter types
+ * @type {Number}
+ * @signal
+ */
+ this.gain = new Tone.Signal({
+ 'value': options.gain,
+ 'convert': false
+ });
+ /**
+ * The Q or Quality of the filter
+ * @type {Positive}
+ * @signal
+ */
+ this.Q = new Tone.Signal(options.Q);
+ /**
+ * the type of the filter
+ * @type {string}
* @private
*/
- this._subtract = this.output = new Tone.Subtract();
+ this._type = options.type;
/**
- * the modulus signal
- * @type {Tone.Signal}
+ * the rolloff value of the filter
+ * @type {number}
* @private
*/
- this._modSignal = new Tone.Signal(modulus);
- //connections
- this.input.fan(this._shaper, this._subtract);
- this._modSignal.connect(this._multiply, 0, 0);
- this._shaper.connect(this._multiply, 0, 1);
- this._multiply.connect(this._subtract, 0, 1);
- this._setWaveShaper(modulus);
+ this._rolloff = options.rolloff;
+ //set the rolloff;
+ this.rolloff = options.rolloff;
+ this._readOnly([
+ 'detune',
+ 'frequency',
+ 'gain',
+ 'Q'
+ ]);
};
- Tone.extend(Tone.Modulo, Tone.SignalBase);
+ Tone.extend(Tone.Filter);
/**
- * @param {number} mod the modulus to apply
- * @private
+ * the default parameters
+ *
+ * @static
+ * @type {Object}
*/
- Tone.Modulo.prototype._setWaveShaper = function (mod) {
- this._shaper.setMap(function (val) {
- var multiple = Math.floor((val + 0.0001) / mod);
- return multiple;
- });
+ Tone.Filter.defaults = {
+ 'type': 'lowpass',
+ 'frequency': 350,
+ 'rolloff': -12,
+ 'Q': 1,
+ 'gain': 0
};
/**
- * The modulus value.
- * @memberOf Tone.Modulo#
- * @type {NormalRange}
- * @name value
+ * The type of the filter. Types: "lowpass", "highpass",
+ * "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking".
+ * @memberOf Tone.Filter#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.Modulo.prototype, 'value', {
+ Object.defineProperty(Tone.Filter.prototype, 'type', {
get: function () {
- return this._modSignal.value;
+ return this._type;
},
- set: function (mod) {
- this._modSignal.value = mod;
- this._setWaveShaper(mod);
+ set: function (type) {
+ var types = [
+ 'lowpass',
+ 'highpass',
+ 'bandpass',
+ 'lowshelf',
+ 'highshelf',
+ 'notch',
+ 'allpass',
+ 'peaking'
+ ];
+ if (types.indexOf(type) === -1) {
+ throw new TypeError('Tone.Filter: invalid type ' + type);
+ }
+ this._type = type;
+ for (var i = 0; i < this._filters.length; i++) {
+ this._filters[i].type = type;
+ }
}
});
/**
- * clean up
- * @returns {Tone.Modulo} this
+ * The rolloff of the filter which is the drop in db
+ * per octave. Implemented internally by cascading filters.
+ * Only accepts the values -12, -24, -48 and -96.
+ * @memberOf Tone.Filter#
+ * @type {number}
+ * @name rolloff
*/
- Tone.Modulo.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._shaper.dispose();
- this._shaper = null;
- this._multiply.dispose();
- this._multiply = null;
- this._subtract.dispose();
- this._subtract = null;
- this._modSignal.dispose();
- this._modSignal = null;
+ Object.defineProperty(Tone.Filter.prototype, 'rolloff', {
+ get: function () {
+ return this._rolloff;
+ },
+ set: function (rolloff) {
+ rolloff = parseInt(rolloff, 10);
+ var possibilities = [
+ -12,
+ -24,
+ -48,
+ -96
+ ];
+ var cascadingCount = possibilities.indexOf(rolloff);
+ //check the rolloff is valid
+ if (cascadingCount === -1) {
+ throw new RangeError('Tone.Filter: rolloff can only be -12, -24, -48 or -96');
+ }
+ cascadingCount += 1;
+ this._rolloff = rolloff;
+ //first disconnect the filters and throw them away
+ this.input.disconnect();
+ for (var i = 0; i < this._filters.length; i++) {
+ this._filters[i].disconnect();
+ this._filters[i] = null;
+ }
+ this._filters = new Array(cascadingCount);
+ for (var count = 0; count < cascadingCount; count++) {
+ var filter = this.context.createBiquadFilter();
+ filter.type = this._type;
+ this.frequency.connect(filter.frequency);
+ this.detune.connect(filter.detune);
+ this.Q.connect(filter.Q);
+ this.gain.connect(filter.gain);
+ this._filters[count] = filter;
+ }
+ //connect them up
+ var connectionChain = [this.input].concat(this._filters).concat([this.output]);
+ this.connectSeries.apply(this, connectionChain);
+ }
+ });
+ /**
+ * Clean up.
+ * @return {Tone.Filter} this
+ */
+ Tone.Filter.prototype.dispose = function () {
+ Tone.prototype.dispose.call(this);
+ for (var i = 0; i < this._filters.length; i++) {
+ this._filters[i].disconnect();
+ this._filters[i] = null;
+ }
+ this._filters = null;
+ this._writable([
+ 'detune',
+ 'frequency',
+ 'gain',
+ 'Q'
+ ]);
+ this.frequency.dispose();
+ this.Q.dispose();
+ this.frequency = null;
+ this.Q = null;
+ this.detune.dispose();
+ this.detune = null;
+ this.gain.dispose();
+ this.gain = null;
return this;
};
- return Tone.Modulo;
+ return Tone.Filter;
});
Module(function (Tone) {
/**
- * @class AudioToGain converts an input in AudioRange [-1,1] to NormalRange [0,1].
- * See Tone.GainToAudio.
+ * @class Split the incoming signal into three bands (low, mid, high)
+ * with two crossover frequency controls.
*
- * @extends {Tone.SignalBase}
+ * @extends {Tone}
* @constructor
- * @example
- * var a2g = new Tone.AudioToGain();
+ * @param {Frequency|Object} [lowFrequency] the low/mid crossover frequency
+ * @param {Frequency} [highFrequency] the mid/high crossover frequency
*/
- Tone.AudioToGain = function () {
+ Tone.MultibandSplit = function () {
+ var options = this.optionsObject(arguments, [
+ 'lowFrequency',
+ 'highFrequency'
+ ], Tone.MultibandSplit.defaults);
/**
- * @type {WaveShaperNode}
+ * the input
+ * @type {GainNode}
* @private
*/
- this._norm = this.input = this.output = new Tone.WaveShaper(function (x) {
- return (x + 1) / 2;
- });
+ this.input = this.context.createGain();
+ /**
+ * the outputs
+ * @type {Array}
+ * @private
+ */
+ this.output = new Array(3);
+ /**
+ * The low band. Alias for output[0]
+ * @type {Tone.Filter}
+ */
+ this.low = this.output[0] = new Tone.Filter(0, 'lowpass');
+ /**
+ * the lower filter of the mid band
+ * @type {Tone.Filter}
+ * @private
+ */
+ this._lowMidFilter = new Tone.Filter(0, 'highpass');
+ /**
+ * The mid band output. Alias for output[1]
+ * @type {Tone.Filter}
+ */
+ this.mid = this.output[1] = new Tone.Filter(0, 'lowpass');
+ /**
+ * The high band output. Alias for output[2]
+ * @type {Tone.Filter}
+ */
+ this.high = this.output[2] = new Tone.Filter(0, 'highpass');
+ /**
+ * The low/mid crossover frequency.
+ * @type {Frequency}
+ * @signal
+ */
+ this.lowFrequency = new Tone.Signal(options.lowFrequency, Tone.Type.Frequency);
+ /**
+ * The mid/high crossover frequency.
+ * @type {Frequency}
+ * @signal
+ */
+ this.highFrequency = new Tone.Signal(options.highFrequency, Tone.Type.Frequency);
+ /**
+ * The quality of all the filters
+ * @type {Number}
+ * @signal
+ */
+ this.Q = new Tone.Signal(options.Q);
+ this.input.fan(this.low, this.high);
+ this.input.chain(this._lowMidFilter, this.mid);
+ //the frequency control signal
+ this.lowFrequency.connect(this.low.frequency);
+ this.lowFrequency.connect(this._lowMidFilter.frequency);
+ this.highFrequency.connect(this.mid.frequency);
+ this.highFrequency.connect(this.high.frequency);
+ //the Q value
+ this.Q.connect(this.low.Q);
+ this.Q.connect(this._lowMidFilter.Q);
+ this.Q.connect(this.mid.Q);
+ this.Q.connect(this.high.Q);
+ this._readOnly([
+ 'high',
+ 'mid',
+ 'low',
+ 'highFrequency',
+ 'lowFrequency'
+ ]);
};
- Tone.extend(Tone.AudioToGain, Tone.SignalBase);
+ Tone.extend(Tone.MultibandSplit);
/**
- * clean up
- * @returns {Tone.AudioToGain} this
+ * @private
+ * @static
+ * @type {Object}
*/
- Tone.AudioToGain.prototype.dispose = function () {
+ Tone.MultibandSplit.defaults = {
+ 'lowFrequency': 400,
+ 'highFrequency': 2500,
+ 'Q': 1
+ };
+ /**
+ * Clean up.
+ * @returns {Tone.MultibandSplit} this
+ */
+ Tone.MultibandSplit.prototype.dispose = function () {
Tone.prototype.dispose.call(this);
- this._norm.dispose();
- this._norm = null;
+ this._writable([
+ 'high',
+ 'mid',
+ 'low',
+ 'highFrequency',
+ 'lowFrequency'
+ ]);
+ this.low.dispose();
+ this.low = null;
+ this._lowMidFilter.dispose();
+ this._lowMidFilter = null;
+ this.mid.dispose();
+ this.mid = null;
+ this.high.dispose();
+ this.high = null;
+ this.lowFrequency.dispose();
+ this.lowFrequency = null;
+ this.highFrequency.dispose();
+ this.highFrequency = null;
+ this.Q.dispose();
+ this.Q = null;
return this;
};
- return Tone.AudioToGain;
+ return Tone.MultibandSplit;
});
Module(function (Tone) {
/**
- * @class Evaluate an expression at audio rate. input[0]
.
- * @type {GainNode}
- */
- this.a = this.input[0] = this.context.createGain();
- /**
- * Alias for input[1]
.
- * @type {GainNode}
- */
- this.b = this.input[1] = this.context.createGain();
- /**
- * The mix between the two inputs. A fade value of 0
- * will output 100% input[0]
and
- * a value of 1 will output 100% input[1]
.
- * @type {NormalRange}
- * @signal
- */
- this.fade = new Tone.Signal(this.defaultArg(initialFade, 0.5), Tone.Type.NormalRange);
- /**
- * equal power gain cross fade
- * @private
- * @type {Tone.EqualPowerGain}
- */
- this._equalPowerA = new Tone.EqualPowerGain();
- /**
- * equal power gain cross fade
- * @private
- * @type {Tone.EqualPowerGain}
- */
- this._equalPowerB = new Tone.EqualPowerGain();
- /**
- * invert the incoming signal
- * @private
- * @type {Tone}
- */
- this._invert = new Tone.Expr('1 - $0');
- //connections
- this.a.connect(this.output);
- this.b.connect(this.output);
- this.fade.chain(this._equalPowerB, this.b.gain);
- this.fade.chain(this._invert, this._equalPowerA, this.a.gain);
- this._readOnly('fade');
- };
- Tone.extend(Tone.CrossFade);
- /**
- * clean up
- * @returns {Tone.CrossFade} this
- */
- Tone.CrossFade.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._writable('fade');
- this._equalPowerA.dispose();
- this._equalPowerA = null;
- this._equalPowerB.dispose();
- this._equalPowerB = null;
- this.fade.dispose();
- this.fade = null;
- this._invert.dispose();
- this._invert = null;
- this.a.disconnect();
- this.a = null;
- this.b.disconnect();
- this.b = null;
- return this;
- };
- return Tone.CrossFade;
- });
- Module(function (Tone) {
-
- /**
- * @class Tone.Filter is a filter which allows for all of the same native methods
- * as the [BiquadFilterNode](http://webaudio.github.io/web-audio-api/#the-biquadfilternode-interface).
- * Tone.Filter has the added ability to set the filter rolloff at -12
- * (default), -24 and -48.
- *
- * @constructor
- * @extends {Tone}
- * @param {Frequency|Object} [frequency] The cutoff frequency of the filter.
- * @param {string=} type The type of filter.
- * @param {number=} rolloff The drop in decibels per octave after the cutoff frequency.
- * 3 choices: -12, -24, and -48
- * @example
- * var filter = new Tone.Filter(200, "highpass");
- */
- Tone.Filter = function () {
- Tone.call(this);
- var options = this.optionsObject(arguments, [
- 'frequency',
- 'type',
- 'rolloff'
- ], Tone.Filter.defaults);
- /**
- * the filter(s)
- * @type {Array}
- * @private
- */
- this._filters = [];
- /**
- * The cutoff frequency of the filter.
- * @type {Frequency}
- * @signal
- */
- this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
- /**
- * The detune parameter
- * @type {Cents}
- * @signal
- */
- this.detune = new Tone.Signal(0, Tone.Type.Cents);
- /**
- * The gain of the filter, only used in certain filter types
- * @type {Number}
- * @signal
- */
- this.gain = new Tone.Signal({
- 'value': options.gain,
- 'convert': false
- });
- /**
- * The Q or Quality of the filter
- * @type {Positive}
- * @signal
- */
- this.Q = new Tone.Signal(options.Q);
- /**
- * the type of the filter
- * @type {string}
- * @private
- */
- this._type = options.type;
- /**
- * the rolloff value of the filter
- * @type {number}
- * @private
- */
- this._rolloff = options.rolloff;
- //set the rolloff;
- this.rolloff = options.rolloff;
- this._readOnly([
- 'detune',
- 'frequency',
- 'gain',
- 'Q'
- ]);
- };
- Tone.extend(Tone.Filter);
- /**
- * the default parameters
- *
- * @static
- * @type {Object}
- */
- Tone.Filter.defaults = {
- 'type': 'lowpass',
- 'frequency': 350,
- 'rolloff': -12,
- 'Q': 1,
- 'gain': 0
- };
- /**
- * The type of the filter. Types: "lowpass", "highpass",
- * "bandpass", "lowshelf", "highshelf", "notch", "allpass", or "peaking".
- * @memberOf Tone.Filter#
- * @type {string}
- * @name type
- */
- Object.defineProperty(Tone.Filter.prototype, 'type', {
- get: function () {
- return this._type;
- },
- set: function (type) {
- var types = [
- 'lowpass',
- 'highpass',
- 'bandpass',
- 'lowshelf',
- 'highshelf',
- 'notch',
- 'allpass',
- 'peaking'
- ];
- if (types.indexOf(type) === -1) {
- throw new Error('Tone.Filter does not have filter type ' + type);
- }
- this._type = type;
- for (var i = 0; i < this._filters.length; i++) {
- this._filters[i].type = type;
- }
- }
- });
- /**
- * The rolloff of the filter which is the drop in db
- * per octave. Implemented internally by cascading filters.
- * Only accepts the values -12, -24, -48 and -96.
- * @memberOf Tone.Filter#
- * @type {number}
- * @name rolloff
- */
- Object.defineProperty(Tone.Filter.prototype, 'rolloff', {
- get: function () {
- return this._rolloff;
- },
- set: function (rolloff) {
- rolloff = parseInt(rolloff, 10);
- var possibilities = [
- -12,
- -24,
- -48,
- -96
- ];
- var cascadingCount = possibilities.indexOf(rolloff);
- //check the rolloff is valid
- if (cascadingCount === -1) {
- throw new Error('Filter rolloff can only be -12, -24, -48 or -96');
- }
- cascadingCount += 1;
- this._rolloff = rolloff;
- //first disconnect the filters and throw them away
- this.input.disconnect();
- for (var i = 0; i < this._filters.length; i++) {
- this._filters[i].disconnect();
- this._filters[i] = null;
- }
- this._filters = new Array(cascadingCount);
- for (var count = 0; count < cascadingCount; count++) {
- var filter = this.context.createBiquadFilter();
- filter.type = this._type;
- this.frequency.connect(filter.frequency);
- this.detune.connect(filter.detune);
- this.Q.connect(filter.Q);
- this.gain.connect(filter.gain);
- this._filters[count] = filter;
- }
- //connect them up
- var connectionChain = [this.input].concat(this._filters).concat([this.output]);
- this.connectSeries.apply(this, connectionChain);
- }
- });
- /**
- * Clean up.
- * @return {Tone.Filter} this
- */
- Tone.Filter.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- for (var i = 0; i < this._filters.length; i++) {
- this._filters[i].disconnect();
- this._filters[i] = null;
- }
- this._filters = null;
- this._writable([
- 'detune',
- 'frequency',
- 'gain',
- 'Q'
- ]);
- this.frequency.dispose();
- this.Q.dispose();
- this.frequency = null;
- this.Q = null;
- this.detune.dispose();
- this.detune = null;
- this.gain.dispose();
- this.gain = null;
- return this;
- };
- return Tone.Filter;
- });
- Module(function (Tone) {
-
- /**
- * @class Split the incoming signal into three bands (low, mid, high)
- * with two crossover frequency controls.
- *
- * @extends {Tone}
- * @constructor
- * @param {Frequency|Object} [lowFrequency] the low/mid crossover frequency
- * @param {Frequency} [highFrequency] the mid/high crossover frequency
- */
- Tone.MultibandSplit = function () {
- var options = this.optionsObject(arguments, [
- 'lowFrequency',
- 'highFrequency'
- ], Tone.MultibandSplit.defaults);
- /**
- * the input
- * @type {GainNode}
- * @private
- */
- this.input = this.context.createGain();
- /**
- * the outputs
- * @type {Array}
- * @private
- */
- this.output = new Array(3);
- /**
- * The low band. Alias for output[0]
- * @type {Tone.Filter}
- */
- this.low = this.output[0] = new Tone.Filter(0, 'lowpass');
- /**
- * the lower filter of the mid band
- * @type {Tone.Filter}
- * @private
- */
- this._lowMidFilter = new Tone.Filter(0, 'highpass');
- /**
- * The mid band output. Alias for output[1]
- * @type {Tone.Filter}
- */
- this.mid = this.output[1] = new Tone.Filter(0, 'lowpass');
- /**
- * The high band output. Alias for output[2]
- * @type {Tone.Filter}
- */
- this.high = this.output[2] = new Tone.Filter(0, 'highpass');
- /**
- * The low/mid crossover frequency.
- * @type {Frequency}
- * @signal
- */
- this.lowFrequency = new Tone.Signal(options.lowFrequency, Tone.Type.Frequency);
- /**
- * The mid/high crossover frequency.
- * @type {Frequency}
- * @signal
- */
- this.highFrequency = new Tone.Signal(options.highFrequency, Tone.Type.Frequency);
- /**
- * The quality of all the filters
- * @type {Number}
- * @signal
- */
- this.Q = new Tone.Signal(options.Q);
- this.input.fan(this.low, this.high);
- this.input.chain(this._lowMidFilter, this.mid);
- //the frequency control signal
- this.lowFrequency.connect(this.low.frequency);
- this.lowFrequency.connect(this._lowMidFilter.frequency);
- this.highFrequency.connect(this.mid.frequency);
- this.highFrequency.connect(this.high.frequency);
- //the Q value
- this.Q.connect(this.low.Q);
- this.Q.connect(this._lowMidFilter.Q);
- this.Q.connect(this.mid.Q);
- this.Q.connect(this.high.Q);
- this._readOnly([
- 'high',
- 'mid',
- 'low',
- 'highFrequency',
- 'lowFrequency'
- ]);
- };
- Tone.extend(Tone.MultibandSplit);
- /**
- * @private
- * @static
- * @type {Object}
- */
- Tone.MultibandSplit.defaults = {
- 'lowFrequency': 400,
- 'highFrequency': 2500,
- 'Q': 1
- };
- /**
- * Clean up.
- * @returns {Tone.MultibandSplit} this
- */
- Tone.MultibandSplit.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._writable([
- 'high',
- 'mid',
- 'low',
- 'highFrequency',
- 'lowFrequency'
- ]);
- this.low.dispose();
- this.low = null;
- this._lowMidFilter.dispose();
- this._lowMidFilter = null;
- this.mid.dispose();
- this.mid = null;
- this.high.dispose();
- this.high = null;
- this.lowFrequency.dispose();
- this.lowFrequency = null;
- this.highFrequency.dispose();
- this.highFrequency = null;
- this.Q.dispose();
- this.Q = null;
- return this;
- };
- return Tone.MultibandSplit;
- });
- Module(function (Tone) {
-
- /**
- * @class Tone.EQ3 is a three band EQ with control over low, mid, and high gain as
- * well as the low and high crossover frequencies.
- *
- * @constructor
- * @extends {Tone}
- *
- * @param {Decibels|Object} [lowLevel] The gain applied to the lows.
- * @param {Decibels} [midLevel] The gain applied to the mid.
- * @param {Decibels} [highLevel] The gain applied to the high.
- * @example
- * var eq = new Tone.EQ3(-10, 3, -20);
- */
- Tone.EQ3 = function () {
- var options = this.optionsObject(arguments, [
- 'low',
- 'mid',
- 'high'
- ], Tone.EQ3.defaults);
- /**
- * the output node
- * @type {GainNode}
- * @private
- */
- this.output = this.context.createGain();
- /**
- * the multiband split
- * @type {Tone.MultibandSplit}
- * @private
- */
- this._multibandSplit = this.input = new Tone.MultibandSplit({
- 'lowFrequency': options.lowFrequency,
- 'highFrequency': options.highFrequency
- });
+ this._multibandSplit = this.input = new Tone.MultibandSplit({
+ 'lowFrequency': options.lowFrequency,
+ 'highFrequency': options.highFrequency
+ });
/**
* The gain for the lower signals
* @type {Tone.Gain}
@@ -6053,8 +6223,8 @@
*/
Tone.Follower.prototype._setAttackRelease = function (attack, release) {
var minTime = this.blockTime;
- attack = this.secondsToFrequency(this.toSeconds(attack));
- release = this.secondsToFrequency(this.toSeconds(release));
+ attack = Tone.Time(attack).toFrequency();
+ release = Tone.Time(release).toFrequency();
attack = Math.max(attack, minTime);
release = Math.max(release, minTime);
this._frequencyValues.setMap(function (val) {
@@ -6546,13 +6716,7 @@
* @type {Number}
* @private
*/
- this._computedLookAhead = 1 / 60;
- /**
- * The value afterwhich events are thrown out
- * @type {Number}
- * @private
- */
- this._threshold = 0.5;
+ this._computedLookAhead = UPDATE_RATE / 1000;
/**
* The next time the callback is scheduled.
* @type {Number}
@@ -6564,7 +6728,7 @@
* @type {Number}
* @private
*/
- this._lastUpdate = 0;
+ this._lastUpdate = -1;
/**
* The id of the requestAnimationFrame
* @type {Number}
@@ -6591,15 +6755,15 @@
*/
this._state = new Tone.TimelineState(Tone.State.Stopped);
/**
- * A pre-binded loop function to save a tiny bit of overhead
- * of rebinding the function on every frame.
- * @type {Function}
+ * The loop function bound to its context.
+ * This is necessary to remove the event in the end.
+ * @type {Function}
* @private
*/
this._boundLoop = this._loop.bind(this);
+ //bind a callback to the worker thread
+ Tone.Clock._worker.addEventListener('message', this._boundLoop);
this._readOnly('frequency');
- //start the loop
- this._loop();
};
Tone.extend(Tone.Clock);
/**
@@ -6676,9 +6840,8 @@
*/
Tone.Clock.prototype.stop = function (time) {
time = this.toSeconds(time);
- if (this._state.getStateAtTime(time) !== Tone.State.Stopped) {
- this._state.setStateAtTime(Tone.State.Stopped, time);
- }
+ this._state.cancel(time);
+ this._state.setStateAtTime(Tone.State.Stopped, time);
return this;
};
/**
@@ -6699,19 +6862,18 @@
* when the page was loaded.
* @private
*/
- Tone.Clock.prototype._loop = function (time) {
- this._loopID = requestAnimationFrame(this._boundLoop);
+ Tone.Clock.prototype._loop = function () {
//compute the look ahead
if (this._lookAhead === 'auto') {
- if (!this.isUndef(time)) {
- var diff = (time - this._lastUpdate) / 1000;
- this._lastUpdate = time;
- //throw away large differences
- if (diff < this._threshold) {
- //averaging
- this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10;
- }
+ var time = this.now();
+ if (this._lastUpdate !== -1) {
+ var diff = time - this._lastUpdate;
+ //max size on the diff
+ diff = Math.min(10 * UPDATE_RATE / 1000, diff);
+ //averaging
+ this._computedLookAhead = (9 * this._computedLookAhead + diff) / 10;
}
+ this._lastUpdate = time;
} else {
this._computedLookAhead = this._lookAhead;
}
@@ -6733,10 +6895,6 @@
}
if (state === Tone.State.Started) {
while (now + lookAhead > this._nextTick) {
- //catch up
- if (now > this._nextTick + this._threshold) {
- this._nextTick = now;
- }
var tickTime = this._nextTick;
this._nextTick += 1 / this.frequency.getValueAtTime(this._nextTick);
this.callback(tickTime);
@@ -6765,15 +6923,44 @@
Tone.Clock.prototype.dispose = function () {
cancelAnimationFrame(this._loopID);
Tone.TimelineState.prototype.dispose.call(this);
+ Tone.Clock._worker.removeEventListener('message', this._boundLoop);
this._writable('frequency');
this.frequency.dispose();
this.frequency = null;
- this._boundLoop = Tone.noOp;
+ this._boundLoop = null;
this._nextTick = Infinity;
this.callback = null;
this._state.dispose();
this._state = null;
};
+ //URL Shim
+ window.URL = window.URL || window.webkitURL;
+ /**
+ * The update rate in Milliseconds
+ * @const
+ * @type {Number}
+ * @private
+ */
+ var UPDATE_RATE = 20;
+ /**
+ * The script which runs in a web worker
+ * @type {Blob}
+ * @private
+ */
+ var blob = new Blob(['setInterval(function(){self.postMessage(\'tick\')}, ' + UPDATE_RATE + ')']);
+ /**
+ * Create a blob url from the Blob
+ * @type {URL}
+ * @private
+ */
+ var blobUrl = URL.createObjectURL(blob);
+ /**
+ * The Worker which generates a regular callback
+ * @type {Worker}
+ * @private
+ * @static
+ */
+ Tone.Clock._worker = new Worker(blobUrl);
return Tone.Clock;
});
Module(function (Tone) {
@@ -6923,7 +7110,7 @@
*/
Tone.IntervalTimeline.prototype.addEvent = function (event) {
if (this.isUndef(event.time) || this.isUndef(event.duration)) {
- throw new Error('events must have time and duration parameters');
+ throw new Error('Tone.IntervalTimeline: events must have time and duration parameters');
}
var node = new IntervalNode(event.time, event.time + event.duration, event);
if (this._root === null) {
@@ -7193,7 +7380,7 @@
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.IntervalTimeline} this
*/
- Tone.IntervalTimeline.prototype.forEachOverlap = function (time, callback) {
+ Tone.IntervalTimeline.prototype.forEachAtTime = function (time, callback) {
time = this.toSeconds(time);
if (this._root !== null) {
var results = [];
@@ -7314,7 +7501,7 @@
this.left.search(point, results);
}
// Check this node
- if (this.low <= point && this.high >= point) {
+ if (this.low <= point && this.high > point) {
results.push(this);
}
// If p is to the left of the time of this interval,
@@ -7472,17 +7659,12 @@
* @singleton
* @example
* //repeated event every 8th note
- * Tone.Transport.setInterval(function(time){
+ * Tone.Transport.scheduleRepeat(function(time){
* //do something with the time
* }, "8n");
* @example
- * //one time event 1 second in the future
- * Tone.Transport.setTimeout(function(time){
- * //do something with the time
- * }, 1);
- * @example
- * //event fixed to the Transports timeline.
- * Tone.Transport.setTimeline(function(time){
+ * //schedule an event on the 16th measure
+ * Tone.Transport.schedule(function(time){
* //do something with the time
* }, "16:0:0");
*/
@@ -7591,13 +7773,13 @@
///////////////////////////////////////////////////////////////////////
// SWING
//////////////////////////////////////////////////////////////////////
- var swingSeconds = this.notationToSeconds(TransportConstructor.defaults.swingSubdivision, TransportConstructor.defaults.bpm, TransportConstructor.defaults.timeSignature);
/**
* The subdivision of the swing
* @type {Ticks}
* @private
*/
- this._swingTicks = swingSeconds / (60 / TransportConstructor.defaults.bpm) * this._ppq;
+ this._swingTicks = TransportConstructor.defaults.PPQ / 2;
+ //8n
/**
* The swing amount
* @type {NormalRange}
@@ -7615,11 +7797,11 @@
Tone.Transport.defaults = {
'bpm': 120,
'swing': 0,
- 'swingSubdivision': '16n',
+ 'swingSubdivision': '8n',
'timeSignature': 4,
'loopStart': 0,
'loopEnd': '4m',
- 'PPQ': 48
+ 'PPQ': 192
};
///////////////////////////////////////////////////////////////////////////////
// TICKS
@@ -7630,36 +7812,39 @@
* @private
*/
Tone.Transport.prototype._processTick = function (tickTime) {
+ var ticks = this._clock.ticks;
//handle swing
- if (this._swingAmount > 0 && this._clock.ticks % this._ppq !== 0 && //not on a downbeat
- this._clock.ticks % this._swingTicks === 0) {
+ if (this._swingAmount > 0 && ticks % this._ppq !== 0 && //not on a downbeat
+ ticks % (this._swingTicks * 2) !== 0) {
//add some swing
- tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount;
+ var progress = ticks % (this._swingTicks * 2) / (this._swingTicks * 2);
+ var amount = Math.sin(progress * Math.PI) * this._swingAmount;
+ tickTime += Tone.Time(this._swingTicks * 2 / 3, 'i').eval() * amount;
}
//do the loop test
if (this.loop) {
- if (this._clock.ticks === this._loopEnd) {
+ if (ticks === this._loopEnd) {
this.ticks = this._loopStart;
+ ticks = this._loopStart;
this.trigger('loop', tickTime);
}
}
- var ticks = this._clock.ticks;
+ //process the single occurrence events
+ this._onceEvents.forEachBefore(ticks, function (event) {
+ event.callback(tickTime);
+ });
+ //and clear the single occurrence timeline
+ this._onceEvents.cancelBefore(ticks);
//fire the next tick events if their time has come
this._timeline.forEachAtTime(ticks, function (event) {
event.callback(tickTime);
});
//process the repeated events
- this._repeatedEvents.forEachOverlap(ticks, function (event) {
+ this._repeatedEvents.forEachAtTime(ticks, function (event) {
if ((ticks - event.time) % event.interval === 0) {
event.callback(tickTime);
}
});
- //process the single occurrence events
- this._onceEvents.forEachBefore(ticks, function (event) {
- event.callback(tickTime);
- });
- //and clear the single occurrence timeline
- this._onceEvents.cancelBefore(ticks);
};
///////////////////////////////////////////////////////////////////////////////
// SCHEDULABLE EVENTS
@@ -7667,7 +7852,7 @@
/**
* Schedule an event along the timeline.
* @param {Function} callback The callback to be invoked at the time.
- * @param {Time} time The time to invoke the callback at.
+ * @param {TransportTime} time The time to invoke the callback at.
* @return {Number} The id of the event which can be used for canceling the event.
* @example
* //trigger the callback when the Transport reaches the desired time
@@ -7695,7 +7880,7 @@
* @param {Function} callback The callback to invoke.
* @param {Time} interval The duration between successive
* callbacks.
- * @param {Time=} startTime When along the timeline the events should
+ * @param {TimelinePosition=} startTime When along the timeline the events should
* start being invoked.
* @param {Time} [duration=Infinity] How long the event should repeat.
* @return {Number} The ID of the scheduled event. Use this to cancel
@@ -7706,7 +7891,7 @@
*/
Tone.Transport.prototype.scheduleRepeat = function (callback, interval, startTime, duration) {
if (interval <= 0) {
- throw new Error('repeat events must have an interval larger than 0');
+ throw new Error('Tone.Transport: repeat events must have an interval larger than 0');
}
var event = {
'time': this.toTicks(startTime),
@@ -7727,7 +7912,7 @@
* Note that if the given time is less than the current transport time,
* the event will be invoked immediately.
* @param {Function} callback The callback to invoke once.
- * @param {Time} time The time the callback should be invoked.
+ * @param {TransportTime} time The time the callback should be invoked.
* @returns {Number} The ID of the scheduled event.
*/
Tone.Transport.prototype.scheduleOnce = function (callback, time) {
@@ -7760,7 +7945,7 @@
* Remove scheduled events from the timeline after
* the given time. Repeated events will be removed
* if their startTime is after the given time
- * @param {Time} [after=0] Clear all events after
+ * @param {TransportTime} [after=0] Clear all events after
* this time.
* @returns {Tone.Transport} this
*/
@@ -7773,34 +7958,6 @@
return this;
};
///////////////////////////////////////////////////////////////////////////////
- // QUANTIZATION
- ///////////////////////////////////////////////////////////////////////////////
- /**
- * Returns the time closest time (equal to or after the given time) that aligns
- * to the subidivision.
- * @param {Time} time The time value to quantize to the given subdivision
- * @param {String} [subdivision="4n"] The subdivision to quantize to.
- * @return {Number} the time in seconds until the next subdivision.
- * @example
- * Tone.Transport.bpm.value = 120;
- * Tone.Transport.quantize("3 * 4n", "1m"); //return 0.5
- * //if the clock is started, it will return a value less than 0.5
- */
- Tone.Transport.prototype.quantize = function (time, subdivision) {
- subdivision = this.defaultArg(subdivision, '4n');
- var tickTime = this.toTicks(time);
- subdivision = this.toTicks(subdivision);
- var remainingTicks = subdivision - tickTime % subdivision;
- if (remainingTicks === subdivision) {
- remainingTicks = 0;
- }
- var now = this.now();
- if (this.state === Tone.State.Started) {
- now = this._clock._nextTick;
- }
- return this.toSeconds(time, now) + this.ticksToSeconds(remainingTicks);
- };
- ///////////////////////////////////////////////////////////////////////////////
// START/STOP/PAUSE
///////////////////////////////////////////////////////////////////////////////
/**
@@ -7818,7 +7975,7 @@
/**
* Start the transport and all sources synced to the transport.
* @param {Time} [time=now] The time when the transport should start.
- * @param {Time=} offset The timeline offset to start the transport.
+ * @param {TransportTime=} offset The timeline offset to start the transport.
* @returns {Tone.Transport} this
* @example
* //start the transport in one second starting at beginning of the 5th measure.
@@ -7827,13 +7984,13 @@
Tone.Transport.prototype.start = function (time, offset) {
time = this.toSeconds(time);
if (!this.isUndef(offset)) {
- offset = this.toTicks(offset);
+ offset = new Tone.Time(offset);
} else {
- offset = this.defaultArg(offset, this._clock.ticks);
+ offset = new Tone.Time(this._clock.ticks, 'i');
}
//start the clock
- this._clock.start(time, offset);
- this.trigger('start', time, this.ticksToSeconds(offset));
+ this._clock.start(time, offset.toTicks());
+ this.trigger('start', time, offset.toSeconds());
return this;
};
/**
@@ -7891,12 +8048,12 @@
/**
* When the Tone.Transport.loop = true, this is the starting position of the loop.
* @memberOf Tone.Transport#
- * @type {Time}
+ * @type {TransportTime}
* @name loopStart
*/
Object.defineProperty(Tone.Transport.prototype, 'loopStart', {
get: function () {
- return this.ticksToSeconds(this._loopStart);
+ return Tone.TransportTime(this._loopStart, 'i').toSeconds();
},
set: function (startPosition) {
this._loopStart = this.toTicks(startPosition);
@@ -7905,12 +8062,12 @@
/**
* When the Tone.Transport.loop = true, this is the ending position of the loop.
* @memberOf Tone.Transport#
- * @type {Time}
+ * @type {TransportTime}
* @name loopEnd
*/
Object.defineProperty(Tone.Transport.prototype, 'loopEnd', {
get: function () {
- return this.ticksToSeconds(this._loopEnd);
+ return Tone.TransportTime(this._loopEnd, 'i').toSeconds();
},
set: function (endPosition) {
this._loopEnd = this.toTicks(endPosition);
@@ -7918,8 +8075,8 @@
});
/**
* Set the loop start and stop at the same time.
- * @param {Time} startPosition
- * @param {Time} endPosition
+ * @param {TransportTime} startPosition
+ * @param {TransportTime} endPosition
* @returns {Tone.Transport} this
* @example
* //loop over the first measure
@@ -7940,11 +8097,11 @@
*/
Object.defineProperty(Tone.Transport.prototype, 'swing', {
get: function () {
- return this._swingAmount * 2;
+ return this._swingAmount;
},
set: function (amount) {
//scale the values to a normal range
- this._swingAmount = amount * 0.5;
+ this._swingAmount = amount;
}
});
/**
@@ -7958,36 +8115,22 @@
*/
Object.defineProperty(Tone.Transport.prototype, 'swingSubdivision', {
get: function () {
- return this.toNotation(this._swingTicks + 'i');
+ return Tone.Time(this._swingTicks, 'i').toNotation();
},
set: function (subdivision) {
this._swingTicks = this.toTicks(subdivision);
}
});
/**
- * The Transport's position in MEASURES:BEATS:SIXTEENTHS.
+ * The Transport's position in Bars:Beats:Sixteenths.
* Setting the value will jump to that position right away.
- *
* @memberOf Tone.Transport#
- * @type {TransportTime}
+ * @type {BarsBeatsSixteenths}
* @name position
*/
Object.defineProperty(Tone.Transport.prototype, 'position', {
get: function () {
- var quarters = this.ticks / this._ppq;
- var measures = Math.floor(quarters / this._timeSignature);
- var sixteenths = quarters % 1 * 4;
- //if the sixteenths aren't a whole number, fix their length
- if (sixteenths % 1 > 0) {
- sixteenths = sixteenths.toFixed(3);
- }
- quarters = Math.floor(quarters) % this._timeSignature;
- var progress = [
- measures,
- quarters,
- sixteenths
- ];
- return progress.join(':');
+ return Tone.TransportTime(this.ticks, 'i').toBarsBeatsSixteenths();
},
set: function (progress) {
var ticks = this.toTicks(progress);
@@ -8040,8 +8183,9 @@
return this._ppq;
},
set: function (ppq) {
+ var bpm = this.bpm.value;
this._ppq = ppq;
- this.bpm.value = this.bpm.value;
+ this.bpm.value = bpm;
}
});
/**
@@ -8065,6 +8209,33 @@
///////////////////////////////////////////////////////////////////////////////
// SYNCING
///////////////////////////////////////////////////////////////////////////////
+ /**
+ * Returns the time aligned to the next subdivision
+ * of the Transport. If the Transport is not started,
+ * it will return 0.
+ * Note: this will not work precisely during tempo ramps.
+ * @param {Time} subdivision The subdivision to quantize to
+ * @return {Number} The context time of the next subdivision.
+ * @example
+ * Tone.Transport.start(); //the transport must be started
+ * Tone.Transport.nextSubdivision("4n");
+ */
+ Tone.Transport.prototype.nextSubdivision = function (subdivision) {
+ subdivision = this.toSeconds(subdivision);
+ //if the transport's not started, return 0
+ var now;
+ if (this.state === Tone.State.Started) {
+ now = this._clock._nextTick;
+ } else {
+ return 0;
+ }
+ var transportPos = Tone.Time(this.ticks, 'i').eval();
+ var remainingTime = subdivision - transportPos % subdivision;
+ if (remainingTime === 0) {
+ remainingTime = subdivision;
+ }
+ return now + remainingTime;
+ };
/**
* Attaches the signal to the tempo control signal so that
* any changes in the tempo will change the signal in the same
@@ -8129,103 +8300,7 @@
this._onceEvents = null;
this._repeatedEvents.dispose();
this._repeatedEvents = null;
- return this;
- };
- ///////////////////////////////////////////////////////////////////////////////
- // DEPRECATED FUNCTIONS
- // (will be removed in r7)
- ///////////////////////////////////////////////////////////////////////////////
- /**
- * @deprecated Use Tone.scheduleRepeat instead.
- * Set a callback for a recurring event.
- * @param {function} callback
- * @param {Time} interval
- * @return {number} the id of the interval
- * @example
- * //triggers a callback every 8th note with the exact time of the event
- * Tone.Transport.setInterval(function(time){
- * envelope.triggerAttack(time);
- * }, "8n");
- * @private
- */
- Tone.Transport.prototype.setInterval = function (callback, interval) {
- console.warn('This method is deprecated. Use Tone.Transport.scheduleRepeat instead.');
- return Tone.Transport.scheduleRepeat(callback, interval);
- };
- /**
- * @deprecated Use Tone.cancel instead.
- * Stop and ongoing interval.
- * @param {number} intervalID The ID of interval to remove. The interval
- * ID is given as the return value in Tone.Transport.setInterval.
- * @return {boolean} true if the event was removed
- * @private
- */
- Tone.Transport.prototype.clearInterval = function (id) {
- console.warn('This method is deprecated. Use Tone.Transport.clear instead.');
- return Tone.Transport.clear(id);
- };
- /**
- * @deprecated Use Tone.Note instead.
- * Set a timeout to occur after time from now. NB: the transport must be
- * running for this to be triggered. All timeout events are cleared when the
- * transport is stopped.
- *
- * @param {function} callback
- * @param {Time} time The time (from now) that the callback will be invoked.
- * @return {number} The id of the timeout.
- * @example
- * //trigger an event to happen 1 second from now
- * Tone.Transport.setTimeout(function(time){
- * player.start(time);
- * }, 1)
- * @private
- */
- Tone.Transport.prototype.setTimeout = function (callback, timeout) {
- console.warn('This method is deprecated. Use Tone.Transport.scheduleOnce instead.');
- return Tone.Transport.scheduleOnce(callback, timeout);
- };
- /**
- * @deprecated Use Tone.Note instead.
- * Clear a timeout using it's ID.
- * @param {number} intervalID The ID of timeout to remove. The timeout
- * ID is given as the return value in Tone.Transport.setTimeout.
- * @return {boolean} true if the timeout was removed
- * @private
- */
- Tone.Transport.prototype.clearTimeout = function (id) {
- console.warn('This method is deprecated. Use Tone.Transport.clear instead.');
- return Tone.Transport.clear(id);
- };
- /**
- * @deprecated Use Tone.Note instead.
- * Timeline events are synced to the timeline of the Tone.Transport.
- * Unlike Timeout, Timeline events will restart after the
- * Tone.Transport has been stopped and restarted.
- *
- * @param {function} callback
- * @param {Time} time
- * @return {number} the id for clearing the transportTimeline event
- * @example
- * //trigger the start of a part on the 16th measure
- * Tone.Transport.setTimeline(function(time){
- * part.start(time);
- * }, "16m");
- * @private
- */
- Tone.Transport.prototype.setTimeline = function (callback, time) {
- console.warn('This method is deprecated. Use Tone.Transport.schedule instead.');
- return Tone.Transport.schedule(callback, time);
- };
- /**
- * @deprecated Use Tone.Note instead.
- * Clear the timeline event.
- * @param {number} id
- * @return {boolean} true if it was removed
- * @private
- */
- Tone.Transport.prototype.clearTimeline = function (id) {
- console.warn('This method is deprecated. Use Tone.Transport.clear instead.');
- return Tone.Transport.clear(id);
+ return this;
};
///////////////////////////////////////////////////////////////////////////////
// INITIALIZATION
@@ -8270,6 +8345,18 @@
* @private
*/
this.output = this.input = new Tone.Gain(options.volume, Tone.Type.Decibels);
+ /**
+ * The unmuted volume
+ * @type {Decibels}
+ * @private
+ */
+ this._unmutedVolume = 0;
+ /**
+ * if the volume is muted
+ * @type {Boolean}
+ * @private
+ */
+ this._muted = false;
/**
* The volume control in decibels.
* @type {Decibels}
@@ -8277,6 +8364,8 @@
*/
this.volume = this.output.gain;
this._readOnly('volume');
+ //set the mute initially
+ this.mute = options.mute;
};
Tone.extend(Tone.Volume);
/**
@@ -8285,7 +8374,34 @@
* @const
* @static
*/
- Tone.Volume.defaults = { 'volume': 0 };
+ Tone.Volume.defaults = {
+ 'volume': 0,
+ 'mute': false
+ };
+ /**
+ * Mute the output.
+ * @memberOf Tone.Volume#
+ * @type {boolean}
+ * @name mute
+ * @example
+ * //mute the output
+ * volume.mute = true;
+ */
+ Object.defineProperty(Tone.Volume.prototype, 'mute', {
+ get: function () {
+ return this._muted;
+ },
+ set: function (mute) {
+ if (!this._muted && mute) {
+ this._unmutedVolume = this.volume.value;
+ //maybe it should ramp here?
+ this.volume.value = -Infinity;
+ } else if (this._muted && !mute) {
+ this.volume.value = this._unmutedVolume;
+ }
+ this._muted = mute;
+ }
+ });
/**
* clean up
* @returns {Tone.Volume} this
@@ -8300,6 +8416,146 @@
};
return Tone.Volume;
});
+ Module(function (Tone) {
+
+ /**
+ * @class A single master output which is connected to the
+ * AudioDestinationNode (aka your speakers).
+ * It provides useful conveniences such as the ability
+ * to set the volume and mute the entire application.
+ * It also gives you the ability to apply master effects to your application.
+ *
+ * Mid *= 2*(1-width)
+ * Side *= 2*width
+ *
*
- * @extends {Tone.Effect}
+ * @extends {Tone.MidSideEffect}
* @constructor
+ * @param {NormalRange|Object} [width] The stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change.
*/
- Tone.MidSideEffect = function () {
- Tone.Effect.apply(this, arguments);
- /**
- * The mid/side split
- * @type {Tone.MidSideSplit}
- * @private
- */
- this._midSideSplit = new Tone.MidSideSplit();
- /**
- * The mid/side merge
- * @type {Tone.MidSideMerge}
- * @private
- */
- this._midSideMerge = new Tone.MidSideMerge();
+ Tone.StereoWidener = function () {
+ var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults);
+ Tone.MidSideEffect.call(this, options);
/**
- * The mid send. Connect to mid processing
- * @type {Tone.Expr}
- * @private
+ * The width control. 0 = 100% mid. 1 = 100% side. 0.5 = no change.
+ * @type {NormalRange}
+ * @signal
*/
- this.midSend = this._midSideSplit.mid;
+ this.width = new Tone.Signal(options.width, Tone.Type.NormalRange);
/**
- * The side send. Connect to side processing
+ * Mid multiplier
* @type {Tone.Expr}
* @private
*/
- this.sideSend = this._midSideSplit.side;
+ this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))');
/**
- * The mid return connection
- * @type {GainNode}
+ * Side multiplier
+ * @type {Tone.Expr}
* @private
*/
- this.midReturn = this._midSideMerge.mid;
+ this._sideMult = new Tone.Expr('$0 * ($1 * $2)');
/**
- * The side return connection
- * @type {GainNode}
+ * constant output of 2
+ * @type {Tone}
* @private
*/
- this.sideReturn = this._midSideMerge.side;
- //the connections
- this.effectSend.connect(this._midSideSplit);
- this._midSideMerge.connect(this.effectReturn);
+ this._two = new Tone.Signal(2);
+ //the mid chain
+ this._two.connect(this._midMult, 0, 1);
+ this.width.connect(this._midMult, 0, 2);
+ //the side chain
+ this._two.connect(this._sideMult, 0, 1);
+ this.width.connect(this._sideMult, 0, 2);
+ //connect it to the effect send/return
+ this.midSend.chain(this._midMult, this.midReturn);
+ this.sideSend.chain(this._sideMult, this.sideReturn);
+ this._readOnly(['width']);
};
- Tone.extend(Tone.MidSideEffect, Tone.Effect);
+ Tone.extend(Tone.StereoWidener, Tone.MidSideEffect);
+ /**
+ * the default values
+ * @static
+ * @type {Object}
+ */
+ Tone.StereoWidener.defaults = { 'width': 0.5 };
/**
* Clean up.
- * @returns {Tone.MidSideEffect} this
+ * @returns {Tone.StereoWidener} this
*/
- Tone.MidSideEffect.prototype.dispose = function () {
- Tone.Effect.prototype.dispose.call(this);
- this._midSideSplit.dispose();
- this._midSideSplit = null;
- this._midSideMerge.dispose();
- this._midSideMerge = null;
- this.midSend = null;
- this.sideSend = null;
- this.midReturn = null;
- this.sideReturn = null;
+ Tone.StereoWidener.prototype.dispose = function () {
+ Tone.MidSideEffect.prototype.dispose.call(this);
+ this._writable(['width']);
+ this.width.dispose();
+ this.width = null;
+ this._midMult.dispose();
+ this._midMult = null;
+ this._sideMult.dispose();
+ this._sideMult = null;
+ this._two.dispose();
+ this._two = null;
return this;
};
- return Tone.MidSideEffect;
+ return Tone.StereoWidener;
});
Module(function (Tone) {
/**
- * @class Tone.Phaser is a phaser effect. Phasers work by changing the phase
- * of different frequency components of an incoming signal. Read more on
- * [Wikipedia](https://en.wikipedia.org/wiki/Phaser_(effect)).
- * Inspiration for this phaser comes from [Tuna.js](https://github.com/Dinahmoe/tuna/).
+ * @class Tone.Tremolo modulates the amplitude of an incoming signal using a Tone.LFO.
+ * The type, frequency, and depth of the LFO is controllable.
*
- * @extends {Tone.StereoEffect}
- * @constructor
- * @param {Frequency|Object} [frequency] The speed of the phasing.
- * @param {number} [octaves] The octaves of the effect.
- * @param {Frequency} [baseFrequency] The base frequency of the filters.
- * @example
- * var phaser = new Tone.Phaser({
- * "frequency" : 15,
- * "octaves" : 5,
- * "baseFrequency" : 1000
- * }).toMaster();
- * var synth = new Tone.FMSynth().connect(phaser);
- * synth.triggerAttackRelease("E3", "2n");
+ * @extends {Tone.StereoEffect}
+ * @constructor
+ * @param {Frequency} [frequency] The rate of the effect.
+ * @param {NormalRange} [depth] The depth of the effect.
+ * @example
+ * //create a tremolo and start it's LFO
+ * var tremolo = new Tone.Tremolo(9, 0.75).toMaster().start();
+ * //route an oscillator through the tremolo and start it
+ * var oscillator = new Tone.Oscillator().connect(tremolo).start();
*/
- Tone.Phaser = function () {
- //set the defaults
+ Tone.Tremolo = function () {
var options = this.optionsObject(arguments, [
'frequency',
- 'octaves',
- 'baseFrequency'
- ], Tone.Phaser.defaults);
+ 'depth'
+ ], Tone.Tremolo.defaults);
Tone.StereoEffect.call(this, options);
/**
- * the lfo which controls the frequency on the left side
- * @type {Tone.LFO}
+ * The tremelo LFO in the left channel
+ * @type {Tone.LFO}
* @private
*/
- this._lfoL = new Tone.LFO(options.frequency, 0, 1);
+ this._lfoL = new Tone.LFO({
+ 'phase': options.spread,
+ 'min': 1,
+ 'max': 0
+ });
/**
- * the lfo which controls the frequency on the right side
- * @type {Tone.LFO}
+ * The tremelo LFO in the left channel
+ * @type {Tone.LFO}
* @private
*/
- this._lfoR = new Tone.LFO(options.frequency, 0, 1);
- this._lfoR.phase = 180;
+ this._lfoR = new Tone.LFO({
+ 'phase': options.spread,
+ 'min': 1,
+ 'max': 0
+ });
/**
- * the base modulation frequency
- * @type {number}
+ * Where the gain is multiplied
+ * @type {Tone.Gain}
* @private
*/
- this._baseFrequency = options.baseFrequency;
+ this._amplitudeL = new Tone.Gain();
/**
- * the octaves of the phasing
- * @type {number}
+ * Where the gain is multiplied
+ * @type {Tone.Gain}
* @private
*/
- this._octaves = options.octaves;
+ this._amplitudeR = new Tone.Gain();
/**
- * The quality factor of the filters
- * @type {Positive}
+ * The frequency of the tremolo.
+ * @type {Frequency}
* @signal
*/
- this.Q = new Tone.Signal(options.Q, Tone.Type.Positive);
- /**
- * the array of filters for the left side
- * @type {Array}
- * @private
- */
- this._filtersL = this._makeFilters(options.stages, this._lfoL, this.Q);
- /**
- * the array of filters for the left side
- * @type {Array}
- * @private
- */
- this._filtersR = this._makeFilters(options.stages, this._lfoR, this.Q);
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
/**
- * the frequency of the effect
- * @type {Tone.Signal}
+ * The depth of the effect. A depth of 0, has no effect
+ * on the amplitude, and a depth of 1 makes the amplitude
+ * modulate fully between 0 and 1.
+ * @type {NormalRange}
+ * @signal
*/
- this.frequency = this._lfoL.frequency;
- this.frequency.value = options.frequency;
- //connect them up
- this.effectSendL.connect(this._filtersL[0]);
- this.effectSendR.connect(this._filtersR[0]);
- this._filtersL[options.stages - 1].connect(this.effectReturnL);
- this._filtersR[options.stages - 1].connect(this.effectReturnR);
- //control the frequency with one LFO
- this._lfoL.frequency.connect(this._lfoR.frequency);
- //set the options
- this.baseFrequency = options.baseFrequency;
- this.octaves = options.octaves;
- //start the lfo
- this._lfoL.start();
- this._lfoR.start();
+ this.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange);
this._readOnly([
'frequency',
- 'Q'
+ 'depth'
]);
+ this.effectSendL.chain(this._amplitudeL, this.effectReturnL);
+ this.effectSendR.chain(this._amplitudeR, this.effectReturnR);
+ this._lfoL.connect(this._amplitudeL.gain);
+ this._lfoR.connect(this._amplitudeR.gain);
+ this.frequency.fan(this._lfoL.frequency, this._lfoR.frequency);
+ this.depth.fan(this._lfoR.amplitude, this._lfoL.amplitude);
+ this.type = options.type;
+ this.spread = options.spread;
};
- Tone.extend(Tone.Phaser, Tone.StereoEffect);
+ Tone.extend(Tone.Tremolo, Tone.StereoEffect);
/**
- * defaults
* @static
- * @type {object}
+ * @const
+ * @type {Object}
*/
- Tone.Phaser.defaults = {
- 'frequency': 0.5,
- 'octaves': 3,
- 'stages': 10,
- 'Q': 10,
- 'baseFrequency': 350
+ Tone.Tremolo.defaults = {
+ 'frequency': 10,
+ 'type': 'sine',
+ 'depth': 0.5,
+ 'spread': 180
};
/**
- * @param {number} stages
- * @returns {Array} the number of filters all connected together
- * @private
+ * Start the tremolo.
+ * @param {Time} [time=now] When the tremolo begins.
+ * @returns {Tone.Tremolo} this
*/
- Tone.Phaser.prototype._makeFilters = function (stages, connectToFreq, Q) {
- var filters = new Array(stages);
- //make all the filters
- for (var i = 0; i < stages; i++) {
- var filter = this.context.createBiquadFilter();
- filter.type = 'allpass';
- Q.connect(filter.Q);
- connectToFreq.connect(filter.frequency);
- filters[i] = filter;
- }
- this.connectSeries.apply(this, filters);
- return filters;
+ Tone.Tremolo.prototype.start = function (time) {
+ this._lfoL.start(time);
+ this._lfoR.start(time);
+ return this;
};
/**
- * The number of octaves the phase goes above
- * the baseFrequency
- * @memberOf Tone.Phaser#
- * @type {Positive}
- * @name octaves
+ * Stop the tremolo.
+ * @param {Time} [time=now] When the tremolo stops.
+ * @returns {Tone.Tremolo} this
+ */
+ Tone.Tremolo.prototype.stop = function (time) {
+ this._lfoL.stop(time);
+ this._lfoR.stop(time);
+ return this;
+ };
+ /**
+ * Sync the effect to the transport.
+ * @param {Time} [delay=0] Delay time before starting the effect after the
+ * Transport has started.
+ * @returns {Tone.AutoFilter} this
+ */
+ Tone.Tremolo.prototype.sync = function (delay) {
+ this._lfoL.sync(delay);
+ this._lfoR.sync(delay);
+ return this;
+ };
+ /**
+ * Unsync the filter from the transport
+ * @returns {Tone.Tremolo} this
+ */
+ Tone.Tremolo.prototype.unsync = function () {
+ this._lfoL.unsync();
+ this._lfoR.unsync();
+ return this;
+ };
+ /**
+ * The Tremolo's oscillator type.
+ * @memberOf Tone.Tremolo#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.Phaser.prototype, 'octaves', {
+ Object.defineProperty(Tone.Tremolo.prototype, 'type', {
get: function () {
- return this._octaves;
+ return this._lfoL.type;
},
- set: function (octaves) {
- this._octaves = octaves;
- var max = this._baseFrequency * Math.pow(2, octaves);
- this._lfoL.max = max;
- this._lfoR.max = max;
+ set: function (type) {
+ this._lfoL.type = type;
+ this._lfoR.type = type;
}
});
- /**
- * The the base frequency of the filters.
- * @memberOf Tone.Phaser#
- * @type {number}
- * @name baseFrequency
+ /**
+ * Amount of stereo spread. When set to 0, both LFO's will be panned centrally.
+ * When set to 180, LFO's will be panned hard left and right respectively.
+ * @memberOf Tone.Tremolo#
+ * @type {Degrees}
+ * @name spread
*/
- Object.defineProperty(Tone.Phaser.prototype, 'baseFrequency', {
+ Object.defineProperty(Tone.Tremolo.prototype, 'spread', {
get: function () {
- return this._baseFrequency;
+ return this._lfoR.phase - this._lfoL.phase; //180
},
- set: function (freq) {
- this._baseFrequency = freq;
- this._lfoL.min = freq;
- this._lfoR.min = freq;
- this.octaves = this._octaves;
+ set: function (spread) {
+ this._lfoL.phase = 90 - spread / 2;
+ this._lfoR.phase = spread / 2 + 90;
}
});
/**
* clean up
- * @returns {Tone.Phaser} this
+ * @returns {Tone.Tremolo} this
*/
- Tone.Phaser.prototype.dispose = function () {
+ Tone.Tremolo.prototype.dispose = function () {
Tone.StereoEffect.prototype.dispose.call(this);
this._writable([
'frequency',
- 'Q'
+ 'depth'
]);
- this.Q.dispose();
- this.Q = null;
this._lfoL.dispose();
this._lfoL = null;
this._lfoR.dispose();
this._lfoR = null;
- for (var i = 0; i < this._filtersL.length; i++) {
- this._filtersL[i].disconnect();
- this._filtersL[i] = null;
- }
- this._filtersL = null;
- for (var j = 0; j < this._filtersR.length; j++) {
- this._filtersR[j].disconnect();
- this._filtersR[j] = null;
- }
- this._filtersR = null;
+ this._amplitudeL.dispose();
+ this._amplitudeL = null;
+ this._amplitudeR.dispose();
+ this._amplitudeR = null;
this.frequency = null;
+ this.depth = null;
return this;
};
- return Tone.Phaser;
+ return Tone.Tremolo;
});
Module(function (Tone) {
/**
- * @class Tone.PingPongDelay is a feedback delay effect where the echo is heard
- * first in one channel and next in the opposite channel. In a stereo
- * system these are the right and left channels.
- * PingPongDelay in more simplified terms is two Tone.FeedbackDelays
- * with independent delay values. Each delay is routed to one channel
- * (left or right), and the channel triggered second will always
- * trigger at the same interval after the first.
- *
- * @constructor
- * @extends {Tone.StereoXFeedbackEffect}
- * @param {Time|Object} [delayTime] The delayTime between consecutive echos.
- * @param {NormalRange=} feedback The amount of the effected signal which
- * is fed back through the delay.
- * @example
- * var pingPong = new Tone.PingPongDelay("4n", 0.2).toMaster();
- * var drum = new Tone.DrumSynth().connect(pingPong);
- * drum.triggerAttackRelease("C4", "32n");
+ * @class A Vibrato effect composed of a Tone.Delay and a Tone.LFO. The LFO
+ * modulates the delayTime of the delay, causing the pitch to rise
+ * and fall.
+ * @extends {Tone.Effect}
+ * @param {Frequency} frequency The frequency of the vibrato.
+ * @param {NormalRange} depth The amount the pitch is modulated.
*/
- Tone.PingPongDelay = function () {
+ Tone.Vibrato = function () {
var options = this.optionsObject(arguments, [
- 'delayTime',
- 'feedback'
- ], Tone.PingPongDelay.defaults);
- Tone.StereoXFeedbackEffect.call(this, options);
+ 'frequency',
+ 'depth'
+ ], Tone.Vibrato.defaults);
+ Tone.Effect.call(this, options);
/**
- * the delay node on the left side
- * @type {DelayNode}
+ * The delay node used for the vibrato effect
+ * @type {Tone.Delay}
* @private
*/
- this._leftDelay = this.context.createDelay(options.maxDelayTime);
+ this._delayNode = new Tone.Delay(0, options.maxDelay);
/**
- * the delay node on the right side
- * @type {DelayNode}
+ * The LFO used to control the vibrato
+ * @type {Tone.LFO}
* @private
*/
- this._rightDelay = this.context.createDelay(options.maxDelayTime);
+ this._lfo = new Tone.LFO({
+ 'type': options.type,
+ 'min': 0,
+ 'max': options.maxDelay,
+ 'frequency': options.frequency,
+ 'phase': -90 //offse the phase so the resting position is in the center
+ }).start().connect(this._delayNode.delayTime);
/**
- * the predelay on the right side
- * @type {DelayNode}
- * @private
+ * The frequency of the vibrato
+ * @type {Frequency}
+ * @signal
*/
- this._rightPreDelay = this.context.createDelay(options.maxDelayTime);
+ this.frequency = this._lfo.frequency;
/**
- * the delay time signal
- * @type {Time}
+ * The depth of the vibrato.
+ * @type {NormalRange}
* @signal
*/
- this.delayTime = new Tone.Signal(options.delayTime, Tone.Type.Time);
- //connect it up
- this.effectSendL.chain(this._leftDelay, this.effectReturnL);
- this.effectSendR.chain(this._rightPreDelay, this._rightDelay, this.effectReturnR);
- this.delayTime.fan(this._leftDelay.delayTime, this._rightDelay.delayTime, this._rightPreDelay.delayTime);
- //rearranged the feedback to be after the rightPreDelay
- this._feedbackLR.disconnect();
- this._feedbackLR.connect(this._rightDelay);
- this._readOnly(['delayTime']);
+ this.depth = this._lfo.amplitude;
+ this.depth.value = options.depth;
+ this._readOnly([
+ 'frequency',
+ 'depth'
+ ]);
+ this.effectSend.chain(this._delayNode, this.effectReturn);
};
- Tone.extend(Tone.PingPongDelay, Tone.StereoXFeedbackEffect);
+ Tone.extend(Tone.Vibrato, Tone.Effect);
/**
- * @static
- * @type {Object}
+ * The defaults
+ * @type {Object}
+ * @const
*/
- Tone.PingPongDelay.defaults = {
- 'delayTime': 0.25,
- 'maxDelayTime': 1
+ Tone.Vibrato.defaults = {
+ 'maxDelay': 0.005,
+ 'frequency': 5,
+ 'depth': 0.1,
+ 'type': 'sine'
};
/**
- * Clean up.
- * @returns {Tone.PingPongDelay} this
+ * Type of oscillator attached to the Vibrato.
+ * @memberOf Tone.Vibrato#
+ * @type {string}
+ * @name type
*/
- Tone.PingPongDelay.prototype.dispose = function () {
- Tone.StereoXFeedbackEffect.prototype.dispose.call(this);
- this._leftDelay.disconnect();
- this._leftDelay = null;
- this._rightDelay.disconnect();
- this._rightDelay = null;
- this._rightPreDelay.disconnect();
- this._rightPreDelay = null;
- this._writable(['delayTime']);
- this.delayTime.dispose();
- this.delayTime = null;
- return this;
+ Object.defineProperty(Tone.Vibrato.prototype, 'type', {
+ get: function () {
+ return this._lfo.type;
+ },
+ set: function (type) {
+ this._lfo.type = type;
+ }
+ });
+ /**
+ * Clean up.
+ * @returns {Tone.Vibrato} this
+ */
+ Tone.Vibrato.prototype.dispose = function () {
+ Tone.Effect.prototype.dispose.call(this);
+ this._delayNode.dispose();
+ this._delayNode = null;
+ this._lfo.dispose();
+ this._lfo = null;
+ this._writable([
+ 'frequency',
+ 'depth'
+ ]);
+ this.frequency = null;
+ this.depth = null;
};
- return Tone.PingPongDelay;
+ return Tone.Vibrato;
});
Module(function (Tone) {
/**
- * @class Tone.PitchShift does near-realtime pitch shifting to the incoming signal.
- * The effect is achieved by speeding up or slowing down the delayTime
- * of a DelayNode using a sawtooth wave.
- * Algorithm found in [this pdf](http://dsp-book.narod.ru/soundproc.pdf).
- * Additional reference by [Miller Pucket](http://msp.ucsd.edu/techniques/v0.11/book-html/node115.html).
- *
- * @extends {Tone.FeedbackEffect}
- * @param {Interval=} pitch The interval to transpose the incoming signal by.
+ * @class Tone.Event abstracts away Tone.Transport.schedule and provides a schedulable
+ * callback for a single or repeatable events along the timeline.
+ *
+ * @extends {Tone}
+ * @param {function} callback The callback to invoke at the time.
+ * @param {*} value The value or values which should be passed to
+ * the callback function on invocation.
+ * @example
+ * var chord = new Tone.Event(function(time, chord){
+ * //the chord as well as the exact time of the event
+ * //are passed in as arguments to the callback function
+ * }, ["D4", "E4", "F4"]);
+ * //start the chord at the beginning of the transport timeline
+ * chord.start();
+ * //loop it every measure for 8 measures
+ * chord.loop = 8;
+ * chord.loopEnd = "1m";
*/
- Tone.PitchShift = function () {
- var options = this.optionsObject(arguments, ['pitch'], Tone.PitchShift.defaults);
- Tone.FeedbackEffect.call(this, options);
+ Tone.Event = function () {
+ var options = this.optionsObject(arguments, [
+ 'callback',
+ 'value'
+ ], Tone.Event.defaults);
/**
- * The pitch signal
- * @type {Tone.Signal}
+ * Loop value
+ * @type {Boolean|Positive}
* @private
*/
- this._frequency = new Tone.Signal(0);
+ this._loop = options.loop;
/**
- * Uses two DelayNodes to cover up the jump in
- * the sawtooth wave.
- * @type {DelayNode}
- * @private
+ * The callback to invoke.
+ * @type {Function}
*/
- this._delayA = new Tone.Delay(0, 1);
+ this.callback = options.callback;
/**
- * The first LFO.
- * @type {Tone.LFO}
- * @private
- */
- this._lfoA = new Tone.LFO({
- 'min': 0,
- 'max': 0.1,
- 'type': 'sawtooth'
- }).connect(this._delayA.delayTime);
+ * The value which is passed to the
+ * callback function.
+ * @type {*}
+ * @private
+ */
+ this.value = options.value;
/**
- * The second DelayNode
- * @type {DelayNode}
+ * When the note is scheduled to start.
+ * @type {Number}
* @private
*/
- this._delayB = new Tone.Delay(0, 1);
+ this._loopStart = this.toTicks(options.loopStart);
/**
- * The first LFO.
- * @type {Tone.LFO}
+ * When the note is scheduled to start.
+ * @type {Number}
* @private
*/
- this._lfoB = new Tone.LFO({
- 'min': 0,
- 'max': 0.1,
- 'type': 'sawtooth',
- 'phase': 180
- }).connect(this._delayB.delayTime);
+ this._loopEnd = this.toTicks(options.loopEnd);
/**
- * Crossfade quickly between the two delay lines
- * to cover up the jump in the sawtooth wave
- * @type {Tone.CrossFade}
+ * Tracks the scheduled events
+ * @type {Tone.TimelineState}
* @private
*/
- this._crossFade = new Tone.CrossFade();
+ this._state = new Tone.TimelineState(Tone.State.Stopped);
/**
- * LFO which alternates between the two
- * delay lines to cover up the disparity in the
- * sawtooth wave.
- * @type {Tone.LFO}
+ * The playback speed of the note. A speed of 1
+ * is no change.
* @private
+ * @type {Positive}
*/
- this._crossFadeLFO = new Tone.LFO({
- 'min': 0,
- 'max': 1,
- 'type': 'triangle',
- 'phase': 90
- }).connect(this._crossFade.fade);
+ this._playbackRate = 1;
/**
- * The delay node
- * @type {Tone.Delay}
+ * A delay time from when the event is scheduled to start
+ * @type {Ticks}
* @private
*/
- this._feedbackDelay = new Tone.Delay(options.delayTime);
+ this._startOffset = 0;
/**
- * The amount of delay on the input signal
- * @type {Time}
- * @signal
+ * The probability that the callback will be invoked
+ * at the scheduled time.
+ * @type {NormalRange}
+ * @example
+ * //the callback will be invoked 50% of the time
+ * event.probability = 0.5;
*/
- this.delayTime = this._feedbackDelay.delayTime;
- this._readOnly('delayTime');
+ this.probability = options.probability;
/**
- * Hold the current pitch
- * @type {Number}
- * @private
+ * If set to true, will apply small (+/-0.02 seconds) random variation
+ * to the callback time. If the value is given as a time, it will randomize
+ * by that amount.
+ * @example
+ * event.humanize = true;
+ * @type {Boolean|Time}
*/
- this._pitch = options.pitch;
+ this.humanize = options.humanize;
/**
- * Hold the current windowSize
- * @type {Number}
- * @private
+ * If mute is true, the callback won't be
+ * invoked.
+ * @type {Boolean}
*/
- this._windowSize = options.windowSize;
- //connect the two delay lines up
- this._delayA.connect(this._crossFade.a);
- this._delayB.connect(this._crossFade.b);
- //connect the frequency
- this._frequency.fan(this._lfoA.frequency, this._lfoB.frequency, this._crossFadeLFO.frequency);
- //route the input
- this.effectSend.fan(this._delayA, this._delayB);
- this._crossFade.chain(this._feedbackDelay, this.effectReturn);
- //start the LFOs at the same time
- var now = this.now();
- this._lfoA.start(now);
- this._lfoB.start(now);
- this._crossFadeLFO.start(now);
- //set the initial value
- this.windowSize = this._windowSize;
+ this.mute = options.mute;
+ //set the initial values
+ this.playbackRate = options.playbackRate;
};
- Tone.extend(Tone.PitchShift, Tone.FeedbackEffect);
+ Tone.extend(Tone.Event);
/**
- * default values
- * @static
- * @type {Object}
+ * The default values
+ * @type {Object}
* @const
*/
- Tone.PitchShift.defaults = {
- 'pitch': 0,
- 'windowSize': 0.1,
- 'delayTime': 0,
- 'feedback': 0
+ Tone.Event.defaults = {
+ 'callback': Tone.noOp,
+ 'loop': false,
+ 'loopEnd': '1m',
+ 'loopStart': 0,
+ 'playbackRate': 1,
+ 'value': null,
+ 'probability': 1,
+ 'mute': false,
+ 'humanize': false
+ };
+ /**
+ * Reschedule all of the events along the timeline
+ * with the updated values.
+ * @param {Time} after Only reschedules events after the given time.
+ * @return {Tone.Event} this
+ * @private
+ */
+ Tone.Event.prototype._rescheduleEvents = function (after) {
+ //if no argument is given, schedules all of the events
+ after = this.defaultArg(after, -1);
+ this._state.forEachFrom(after, function (event) {
+ var duration;
+ if (event.state === Tone.State.Started) {
+ if (!this.isUndef(event.id)) {
+ Tone.Transport.clear(event.id);
+ }
+ var startTick = event.time + Math.round(this.startOffset / this._playbackRate);
+ if (this._loop) {
+ duration = Infinity;
+ if (this.isNumber(this._loop)) {
+ duration = this._loop * this._getLoopDuration();
+ }
+ var nextEvent = this._state.getEventAfter(startTick);
+ if (nextEvent !== null) {
+ duration = Math.min(duration, nextEvent.time - startTick);
+ }
+ if (duration !== Infinity) {
+ //schedule a stop since it's finite duration
+ this._state.setStateAtTime(Tone.State.Stopped, startTick + duration + 1);
+ duration = Tone.Time(duration, 'i');
+ }
+ var interval = Tone.Time(this._getLoopDuration(), 'i');
+ event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), interval, Tone.TransportTime(startTick, 'i'), duration);
+ } else {
+ event.id = Tone.Transport.schedule(this._tick.bind(this), startTick + 'i');
+ }
+ }
+ }.bind(this));
+ return this;
+ };
+ /**
+ * Returns the playback state of the note, either "started" or "stopped".
+ * @type {String}
+ * @readOnly
+ * @memberOf Tone.Event#
+ * @name state
+ */
+ Object.defineProperty(Tone.Event.prototype, 'state', {
+ get: function () {
+ return this._state.getStateAtTime(Tone.Transport.ticks);
+ }
+ });
+ /**
+ * The start from the scheduled start time
+ * @type {Ticks}
+ * @memberOf Tone.Event#
+ * @name startOffset
+ * @private
+ */
+ Object.defineProperty(Tone.Event.prototype, 'startOffset', {
+ get: function () {
+ return this._startOffset;
+ },
+ set: function (offset) {
+ this._startOffset = offset;
+ }
+ });
+ /**
+ * Start the note at the given time.
+ * @param {TimelinePosition} time When the note should start.
+ * @return {Tone.Event} this
+ */
+ Tone.Event.prototype.start = function (time) {
+ time = this.toTicks(time);
+ if (this._state.getStateAtTime(time) === Tone.State.Stopped) {
+ this._state.addEvent({
+ 'state': Tone.State.Started,
+ 'time': time,
+ 'id': undefined
+ });
+ this._rescheduleEvents(time);
+ }
+ return this;
+ };
+ /**
+ * Stop the Event at the given time.
+ * @param {TimelinePosition} time When the note should stop.
+ * @return {Tone.Event} this
+ */
+ Tone.Event.prototype.stop = function (time) {
+ this.cancel(time);
+ time = this.toTicks(time);
+ if (this._state.getStateAtTime(time) === Tone.State.Started) {
+ this._state.setStateAtTime(Tone.State.Stopped, time);
+ var previousEvent = this._state.getEventBefore(time);
+ var reschedulTime = time;
+ if (previousEvent !== null) {
+ reschedulTime = previousEvent.time;
+ }
+ this._rescheduleEvents(reschedulTime);
+ }
+ return this;
+ };
+ /**
+ * Cancel all scheduled events greater than or equal to the given time
+ * @param {TimelinePosition} [time=0] The time after which events will be cancel.
+ * @return {Tone.Event} this
+ */
+ Tone.Event.prototype.cancel = function (time) {
+ time = this.defaultArg(time, -Infinity);
+ time = this.toTicks(time);
+ this._state.forEachFrom(time, function (event) {
+ Tone.Transport.clear(event.id);
+ });
+ this._state.cancel(time);
+ return this;
+ };
+ /**
+ * The callback function invoker. Also
+ * checks if the Event is done playing
+ * @param {Number} time The time of the event in seconds
+ * @private
+ */
+ Tone.Event.prototype._tick = function (time) {
+ if (!this.mute && this._state.getStateAtTime(Tone.Transport.ticks) === Tone.State.Started) {
+ if (this.probability < 1 && Math.random() > this.probability) {
+ return;
+ }
+ if (this.humanize) {
+ var variation = 0.02;
+ if (!this.isBoolean(this.humanize)) {
+ variation = this.toSeconds(this.humanize);
+ }
+ time += (Math.random() * 2 - 1) * variation;
+ }
+ this.callback(time, this.value);
+ }
+ };
+ /**
+ * Get the duration of the loop.
+ * @return {Ticks}
+ * @private
+ */
+ Tone.Event.prototype._getLoopDuration = function () {
+ return Math.round((this._loopEnd - this._loopStart) / this._playbackRate);
};
/**
- * Repitch the incoming signal by some interval (measured
- * in semi-tones).
- * @memberOf Tone.PitchShift#
- * @type {Interval}
- * @name pitch
- * @example
- * pitchShift.pitch = -12; //down one octave
- * pitchShift.pitch = 7; //up a fifth
+ * If the note should loop or not
+ * between Tone.Event.loopStart and
+ * Tone.Event.loopEnd. An integer
+ * value corresponds to the number of
+ * loops the Event does after it starts.
+ * @memberOf Tone.Event#
+ * @type {Boolean|Positive}
+ * @name loop
*/
- Object.defineProperty(Tone.PitchShift.prototype, 'pitch', {
+ Object.defineProperty(Tone.Event.prototype, 'loop', {
get: function () {
- return this._pitch;
+ return this._loop;
},
- set: function (interval) {
- this._pitch = interval;
- var factor = 0;
- if (interval < 0) {
- this._lfoA.min = 0;
- this._lfoA.max = this._windowSize;
- this._lfoB.min = 0;
- this._lfoB.max = this._windowSize;
- factor = this.intervalToFrequencyRatio(interval - 1) + 1;
- } else {
- this._lfoA.min = this._windowSize;
- this._lfoA.max = 0;
- this._lfoB.min = this._windowSize;
- this._lfoB.max = 0;
- factor = this.intervalToFrequencyRatio(interval) - 1;
- }
- this._frequency.value = factor * (1.2 / this._windowSize);
+ set: function (loop) {
+ this._loop = loop;
+ this._rescheduleEvents();
}
});
/**
- * The window size corresponds roughly to the sample length in a looping sampler.
- * Smaller values are desirable for a less noticeable delay time of the pitch shifted
- * signal, but larger values will result in smoother pitch shifting for larger intervals.
- * A nominal range of 0.03 to 0.1 is recommended.
- * @memberOf Tone.PitchShift#
- * @type {Time}
- * @name windowSize
- * @example
- * pitchShift.windowSize = 0.1;
+ * The playback rate of the note. Defaults to 1.
+ * @memberOf Tone.Event#
+ * @type {Positive}
+ * @name playbackRate
+ * @example
+ * note.loop = true;
+ * //repeat the note twice as fast
+ * note.playbackRate = 2;
*/
- Object.defineProperty(Tone.PitchShift.prototype, 'windowSize', {
+ Object.defineProperty(Tone.Event.prototype, 'playbackRate', {
get: function () {
- return this._windowSize;
+ return this._playbackRate;
},
- set: function (size) {
- this._windowSize = this.toSeconds(size);
- this.pitch = this._pitch;
+ set: function (rate) {
+ this._playbackRate = rate;
+ this._rescheduleEvents();
}
});
/**
- * Clean up.
- * @return {Tone.PitchShift} this
- */
- Tone.PitchShift.prototype.dispose = function () {
- Tone.FeedbackEffect.prototype.dispose.call(this);
- this._frequency.dispose();
- this._frequency = null;
- this._delayA.disconnect();
- this._delayA = null;
- this._delayB.disconnect();
- this._delayB = null;
- this._lfoA.dispose();
- this._lfoA = null;
- this._lfoB.dispose();
- this._lfoB = null;
- this._crossFade.dispose();
- this._crossFade = null;
- this._crossFadeLFO.dispose();
- this._crossFadeLFO = null;
- this._writable('delayTime');
- this._feedbackDelay.dispose();
- this._feedbackDelay = null;
- this.delayTime = null;
- return this;
- };
- return Tone.PitchShift;
- });
- Module(function (Tone) {
-
- /**
- * @class Base class for stereo feedback effects where the effectReturn
- * is fed back into the same channel.
- *
- * @constructor
- * @extends {Tone.FeedbackEffect}
- */
- Tone.StereoFeedbackEffect = function () {
- var options = this.optionsObject(arguments, ['feedback'], Tone.FeedbackEffect.defaults);
- Tone.StereoEffect.call(this, options);
- /**
- * controls the amount of feedback
- * @type {NormalRange}
- * @signal
- */
- this.feedback = new Tone.Signal(options.feedback, Tone.Type.NormalRange);
- /**
- * the left side feeback
- * @type {GainNode}
- * @private
- */
- this._feedbackL = this.context.createGain();
- /**
- * the right side feeback
- * @type {GainNode}
- * @private
- */
- this._feedbackR = this.context.createGain();
- //connect it up
- this.effectReturnL.chain(this._feedbackL, this.effectSendL);
- this.effectReturnR.chain(this._feedbackR, this.effectSendR);
- this.feedback.fan(this._feedbackL.gain, this._feedbackR.gain);
- this._readOnly(['feedback']);
- };
- Tone.extend(Tone.StereoFeedbackEffect, Tone.FeedbackEffect);
- /**
- * clean up
- * @returns {Tone.StereoFeedbackEffect} this
+ * The loopEnd point is the time the event will loop
+ * if Tone.Event.loop is true.
+ * @memberOf Tone.Event#
+ * @type {TransportTime}
+ * @name loopEnd
*/
- Tone.StereoFeedbackEffect.prototype.dispose = function () {
- Tone.StereoEffect.prototype.dispose.call(this);
- this._writable(['feedback']);
- this.feedback.dispose();
- this.feedback = null;
- this._feedbackL.disconnect();
- this._feedbackL = null;
- this._feedbackR.disconnect();
- this._feedbackR = null;
- return this;
- };
- return Tone.StereoFeedbackEffect;
- });
- Module(function (Tone) {
-
+ Object.defineProperty(Tone.Event.prototype, 'loopEnd', {
+ get: function () {
+ return Tone.TransportTime(this._loopEnd, 'i').toNotation();
+ },
+ set: function (loopEnd) {
+ this._loopEnd = this.toTicks(loopEnd);
+ if (this._loop) {
+ this._rescheduleEvents();
+ }
+ }
+ });
/**
- * @class Applies a width factor to the mid/side seperation.
- * 0 is all mid and 1 is all side.
- * Algorithm found in [kvraudio forums](http://www.kvraudio.com/forum/viewtopic.php?t=212587).
- *
- * Mid *= 2*(1-width)
- * Side *= 2*width
- *
- *
- * @extends {Tone.MidSideEffect}
- * @constructor
- * @param {NormalRange|Object} [width] The stereo width. A width of 0 is mono and 1 is stereo. 0.5 is no change.
+ * The time when the loop should start.
+ * @memberOf Tone.Event#
+ * @type {TransportTime}
+ * @name loopStart
*/
- Tone.StereoWidener = function () {
- var options = this.optionsObject(arguments, ['width'], Tone.StereoWidener.defaults);
- Tone.MidSideEffect.call(this, options);
- /**
- * The width control. 0 = 100% mid. 1 = 100% side. 0.5 = no change.
- * @type {NormalRange}
- * @signal
- */
- this.width = new Tone.Signal(options.width, Tone.Type.NormalRange);
- /**
- * Mid multiplier
- * @type {Tone.Expr}
- * @private
- */
- this._midMult = new Tone.Expr('$0 * ($1 * (1 - $2))');
- /**
- * Side multiplier
- * @type {Tone.Expr}
- * @private
- */
- this._sideMult = new Tone.Expr('$0 * ($1 * $2)');
- /**
- * constant output of 2
- * @type {Tone}
- * @private
- */
- this._two = new Tone.Signal(2);
- //the mid chain
- this._two.connect(this._midMult, 0, 1);
- this.width.connect(this._midMult, 0, 2);
- //the side chain
- this._two.connect(this._sideMult, 0, 1);
- this.width.connect(this._sideMult, 0, 2);
- //connect it to the effect send/return
- this.midSend.chain(this._midMult, this.midReturn);
- this.sideSend.chain(this._sideMult, this.sideReturn);
- this._readOnly(['width']);
- };
- Tone.extend(Tone.StereoWidener, Tone.MidSideEffect);
+ Object.defineProperty(Tone.Event.prototype, 'loopStart', {
+ get: function () {
+ return Tone.TransportTime(this._loopStart, 'i').toNotation();
+ },
+ set: function (loopStart) {
+ this._loopStart = this.toTicks(loopStart);
+ if (this._loop) {
+ this._rescheduleEvents();
+ }
+ }
+ });
/**
- * the default values
- * @static
- * @type {Object}
+ * The current progress of the loop interval.
+ * Returns 0 if the event is not started yet or
+ * it is not set to loop.
+ * @memberOf Tone.Event#
+ * @type {NormalRange}
+ * @name progress
+ * @readOnly
*/
- Tone.StereoWidener.defaults = { 'width': 0.5 };
+ Object.defineProperty(Tone.Event.prototype, 'progress', {
+ get: function () {
+ if (this._loop) {
+ var ticks = Tone.Transport.ticks;
+ var lastEvent = this._state.getEvent(ticks);
+ if (lastEvent !== null && lastEvent.state === Tone.State.Started) {
+ var loopDuration = this._getLoopDuration();
+ var progress = (ticks - lastEvent.time) % loopDuration;
+ return progress / loopDuration;
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ });
/**
- * Clean up.
- * @returns {Tone.StereoWidener} this
+ * Clean up
+ * @return {Tone.Event} this
*/
- Tone.StereoWidener.prototype.dispose = function () {
- Tone.MidSideEffect.prototype.dispose.call(this);
- this._writable(['width']);
- this.width.dispose();
- this.width = null;
- this._midMult.dispose();
- this._midMult = null;
- this._sideMult.dispose();
- this._sideMult = null;
- this._two.dispose();
- this._two = null;
- return this;
+ Tone.Event.prototype.dispose = function () {
+ this.cancel();
+ this._state.dispose();
+ this._state = null;
+ this.callback = null;
+ this.value = null;
};
- return Tone.StereoWidener;
+ return Tone.Event;
});
Module(function (Tone) {
-
/**
- * @class Tone.Tremolo modulates the amplitude of an incoming signal using a Tone.LFO.
- * The type, frequency, and depth of the LFO is controllable.
- *
- * @extends {Tone.StereoEffect}
- * @constructor
- * @param {Frequency} [frequency] The rate of the effect.
- * @param {NormalRange} [depth] The depth of the effect.
+ * @class Tone.Loop creates a looped callback at the
+ * specified interval. The callback can be
+ * started, stopped and scheduled along
+ * the Transport's timeline.
* @example
- * //create a tremolo and start it's LFO
- * var tremolo = new Tone.Tremolo(9, 0.75).toMaster().start();
- * //route an oscillator through the tremolo and start it
- * var oscillator = new Tone.Oscillator().connect(tremolo).start();
+ * var loop = new Tone.Loop(function(time){
+ * //triggered every eighth note.
+ * console.log(time);
+ * }, "8n").start(0);
+ * Tone.Transport.start();
+ * @extends {Tone}
+ * @param {Function} callback The callback to invoke with the event.
+ * @param {Time} interval The time between successive callback calls.
*/
- Tone.Tremolo = function () {
+ Tone.Loop = function () {
var options = this.optionsObject(arguments, [
- 'frequency',
- 'depth'
- ], Tone.Tremolo.defaults);
- Tone.StereoEffect.call(this, options);
- /**
- * The tremelo LFO in the left channel
- * @type {Tone.LFO}
- * @private
- */
- this._lfoL = new Tone.LFO({
- 'phase': options.spread,
- 'min': 1,
- 'max': 0
- });
+ 'callback',
+ 'interval'
+ ], Tone.Loop.defaults);
/**
- * The tremelo LFO in the left channel
- * @type {Tone.LFO}
- * @private
+ * The event which produces the callbacks
*/
- this._lfoR = new Tone.LFO({
- 'phase': options.spread,
- 'min': 1,
- 'max': 0
+ this._event = new Tone.Event({
+ 'callback': this._tick.bind(this),
+ 'loop': true,
+ 'loopEnd': options.interval,
+ 'playbackRate': options.playbackRate,
+ 'probability': options.probability
});
/**
- * Where the gain is multiplied
- * @type {Tone.Gain}
- * @private
- */
- this._amplitudeL = new Tone.Gain();
- /**
- * Where the gain is multiplied
- * @type {Tone.Gain}
- * @private
- */
- this._amplitudeR = new Tone.Gain();
- /**
- * The frequency of the tremolo.
- * @type {Frequency}
- * @signal
- */
- this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
- /**
- * The depth of the effect. A depth of 0, has no effect
- * on the amplitude, and a depth of 1 makes the amplitude
- * modulate fully between 0 and 1.
- * @type {NormalRange}
- * @signal
+ * The callback to invoke with the next event in the pattern
+ * @type {Function}
*/
- this.depth = new Tone.Signal(options.depth, Tone.Type.NormalRange);
- this._readOnly([
- 'frequency',
- 'depth'
- ]);
- this.effectSendL.chain(this._amplitudeL, this.effectReturnL);
- this.effectSendR.chain(this._amplitudeR, this.effectReturnR);
- this._lfoL.connect(this._amplitudeL.gain);
- this._lfoR.connect(this._amplitudeR.gain);
- this.frequency.fan(this._lfoL.frequency, this._lfoR.frequency);
- this.depth.fan(this._lfoR.amplitude, this._lfoL.amplitude);
- this.type = options.type;
- this.spread = options.spread;
+ this.callback = options.callback;
+ //set the iterations
+ this.iterations = options.iterations;
};
- Tone.extend(Tone.Tremolo, Tone.StereoEffect);
+ Tone.extend(Tone.Loop);
/**
- * @static
+ * The defaults
* @const
- * @type {Object}
+ * @type {Object}
*/
- Tone.Tremolo.defaults = {
- 'frequency': 10,
- 'type': 'sine',
- 'depth': 0.5,
- 'spread': 180
+ Tone.Loop.defaults = {
+ 'interval': '4n',
+ 'callback': Tone.noOp,
+ 'playbackRate': 1,
+ 'iterations': Infinity,
+ 'probability': true,
+ 'mute': false
};
/**
- * Start the tremolo.
- * @param {Time} [time=now] When the tremolo begins.
- * @returns {Tone.Tremolo} this
+ * Start the loop at the specified time along the Transport's
+ * timeline.
+ * @param {TimelinePosition=} time When to start the Loop.
+ * @return {Tone.Loop} this
*/
- Tone.Tremolo.prototype.start = function (time) {
- this._lfoL.start(time);
- this._lfoR.start(time);
+ Tone.Loop.prototype.start = function (time) {
+ this._event.start(time);
return this;
};
/**
- * Stop the tremolo.
- * @param {Time} [time=now] When the tremolo stops.
- * @returns {Tone.Tremolo} this
+ * Stop the loop at the given time.
+ * @param {TimelinePosition=} time When to stop the Arpeggio
+ * @return {Tone.Loop} this
*/
- Tone.Tremolo.prototype.stop = function (time) {
- this._lfoL.stop(time);
- this._lfoR.stop(time);
+ Tone.Loop.prototype.stop = function (time) {
+ this._event.stop(time);
return this;
};
/**
- * Sync the effect to the transport.
- * @param {Time} [delay=0] Delay time before starting the effect after the
- * Transport has started.
- * @returns {Tone.AutoFilter} this
+ * Cancel all scheduled events greater than or equal to the given time
+ * @param {TimelinePosition} [time=0] The time after which events will be cancel.
+ * @return {Tone.Loop} this
*/
- Tone.Tremolo.prototype.sync = function (delay) {
- this._lfoL.sync(delay);
- this._lfoR.sync(delay);
+ Tone.Loop.prototype.cancel = function (time) {
+ this._event.cancel(time);
return this;
};
/**
- * Unsync the filter from the transport
- * @returns {Tone.Tremolo} this
+ * Internal function called when the notes should be called
+ * @param {Number} time The time the event occurs
+ * @private
*/
- Tone.Tremolo.prototype.unsync = function () {
- this._lfoL.unsync();
- this._lfoR.unsync();
- return this;
+ Tone.Loop.prototype._tick = function (time) {
+ this.callback(time);
};
/**
- * The Tremolo's oscillator type.
- * @memberOf Tone.Tremolo#
- * @type {string}
- * @name type
+ * The state of the Loop, either started or stopped.
+ * @memberOf Tone.Loop#
+ * @type {String}
+ * @name state
+ * @readOnly
+ */
+ Object.defineProperty(Tone.Loop.prototype, 'state', {
+ get: function () {
+ return this._event.state;
+ }
+ });
+ /**
+ * The progress of the loop as a value between 0-1. 0, when
+ * the loop is stopped or done iterating.
+ * @memberOf Tone.Loop#
+ * @type {NormalRange}
+ * @name progress
+ * @readOnly
+ */
+ Object.defineProperty(Tone.Loop.prototype, 'progress', {
+ get: function () {
+ return this._event.progress;
+ }
+ });
+ /**
+ * The time between successive callbacks.
+ * @example
+ * loop.interval = "8n"; //loop every 8n
+ * @memberOf Tone.Loop#
+ * @type {Time}
+ * @name interval
+ */
+ Object.defineProperty(Tone.Loop.prototype, 'interval', {
+ get: function () {
+ return this._event.loopEnd;
+ },
+ set: function (interval) {
+ this._event.loopEnd = interval;
+ }
+ });
+ /**
+ * The playback rate of the loop. The normal playback rate is 1 (no change).
+ * A `playbackRate` of 2 would be twice as fast.
+ * @memberOf Tone.Loop#
+ * @type {Time}
+ * @name playbackRate
+ */
+ Object.defineProperty(Tone.Loop.prototype, 'playbackRate', {
+ get: function () {
+ return this._event.playbackRate;
+ },
+ set: function (rate) {
+ this._event.playbackRate = rate;
+ }
+ });
+ /**
+ * Random variation +/-0.01s to the scheduled time.
+ * Or give it a time value which it will randomize by.
+ * @type {Boolean|Time}
+ * @memberOf Tone.Loop#
+ * @name humanize
+ */
+ Object.defineProperty(Tone.Loop.prototype, 'humanize', {
+ get: function () {
+ return this._event.humanize;
+ },
+ set: function (variation) {
+ this._event.humanize = variation;
+ }
+ });
+ /**
+ * The probably of the callback being invoked.
+ * @memberOf Tone.Loop#
+ * @type {NormalRange}
+ * @name probability
*/
- Object.defineProperty(Tone.Tremolo.prototype, 'type', {
+ Object.defineProperty(Tone.Loop.prototype, 'probability', {
get: function () {
- return this._lfoL.type;
+ return this._event.probability;
},
- set: function (type) {
- this._lfoL.type = type;
- this._lfoR.type = type;
+ set: function (prob) {
+ this._event.probability = prob;
}
});
- /**
- * Amount of stereo spread. When set to 0, both LFO's will be panned centrally.
- * When set to 180, LFO's will be panned hard left and right respectively.
- * @memberOf Tone.Tremolo#
- * @type {Degrees}
- * @name spread
+ /**
+ * Muting the Loop means that no callbacks are invoked.
+ * @memberOf Tone.Loop#
+ * @type {Boolean}
+ * @name mute
*/
- Object.defineProperty(Tone.Tremolo.prototype, 'spread', {
+ Object.defineProperty(Tone.Loop.prototype, 'mute', {
get: function () {
- return this._lfoR.phase - this._lfoL.phase; //180
+ return this._event.mute;
},
- set: function (spread) {
- this._lfoL.phase = 90 - spread / 2;
- this._lfoR.phase = spread / 2 + 90;
+ set: function (mute) {
+ this._event.mute = mute;
}
});
/**
- * clean up
- * @returns {Tone.Tremolo} this
- */
- Tone.Tremolo.prototype.dispose = function () {
- Tone.StereoEffect.prototype.dispose.call(this);
- this._writable([
- 'frequency',
- 'depth'
- ]);
- this._lfoL.dispose();
- this._lfoL = null;
- this._lfoR.dispose();
- this._lfoR = null;
- this._amplitudeL.dispose();
- this._amplitudeL = null;
- this._amplitudeR.dispose();
- this._amplitudeR = null;
- this.frequency = null;
- this.depth = null;
- return this;
- };
- return Tone.Tremolo;
- });
- Module(function (Tone) {
-
- /**
- * @class A Vibrato effect composed of a Tone.Delay and a Tone.LFO. The LFO
- * modulates the delayTime of the delay, causing the pitch to rise
- * and fall.
- * @extends {Tone.Effect}
- * @param {Frequency} frequency The frequency of the vibrato.
- * @param {NormalRange} depth The amount the pitch is modulated.
- */
- Tone.Vibrato = function () {
- var options = this.optionsObject(arguments, [
- 'frequency',
- 'depth'
- ], Tone.Vibrato.defaults);
- Tone.Effect.call(this, options);
- /**
- * The delay node used for the vibrato effect
- * @type {Tone.Delay}
- * @private
- */
- this._delayNode = new Tone.Delay(0, options.maxDelay);
- /**
- * The LFO used to control the vibrato
- * @type {Tone.LFO}
- * @private
- */
- this._lfo = new Tone.LFO({
- 'type': options.type,
- 'min': 0,
- 'max': options.maxDelay,
- 'frequency': options.frequency,
- 'phase': -90 //offse the phase so the resting position is in the center
- }).start().connect(this._delayNode.delayTime);
- /**
- * The frequency of the vibrato
- * @type {Frequency}
- * @signal
- */
- this.frequency = this._lfo.frequency;
- /**
- * The depth of the vibrato.
- * @type {NormalRange}
- * @signal
- */
- this.depth = this._lfo.amplitude;
- this.depth.value = options.depth;
- this._readOnly([
- 'frequency',
- 'depth'
- ]);
- this.effectSend.chain(this._delayNode, this.effectReturn);
- };
- Tone.extend(Tone.Vibrato, Tone.Effect);
- /**
- * The defaults
- * @type {Object}
- * @const
- */
- Tone.Vibrato.defaults = {
- 'maxDelay': 0.005,
- 'frequency': 5,
- 'depth': 0.1,
- 'type': 'sine'
- };
- /**
- * Type of oscillator attached to the Vibrato.
- * @memberOf Tone.Vibrato#
- * @type {string}
- * @name type
+ * The number of iterations of the loop. The default
+ * value is Infinity (loop forever).
+ * @memberOf Tone.Loop#
+ * @type {Positive}
+ * @name iterations
*/
- Object.defineProperty(Tone.Vibrato.prototype, 'type', {
+ Object.defineProperty(Tone.Loop.prototype, 'iterations', {
get: function () {
- return this._lfo.type;
+ if (this._event.loop === true) {
+ return Infinity;
+ } else {
+ return this._event.loop;
+ }
+ return this._pattern.index;
},
- set: function (type) {
- this._lfo.type = type;
+ set: function (iters) {
+ if (iters === Infinity) {
+ this._event.loop = true;
+ } else {
+ this._event.loop = iters;
+ }
}
});
/**
- * Clean up.
- * @returns {Tone.Vibrato} this
+ * Clean up
+ * @return {Tone.Loop} this
*/
- Tone.Vibrato.prototype.dispose = function () {
- Tone.Effect.prototype.dispose.call(this);
- this._delayNode.dispose();
- this._delayNode = null;
- this._lfo.dispose();
- this._lfo = null;
- this._writable([
- 'frequency',
- 'depth'
- ]);
- this.frequency = null;
- this.depth = null;
+ Tone.Loop.prototype.dispose = function () {
+ this._event.dispose();
+ this._event = null;
+ this.callback = null;
};
- return Tone.Vibrato;
+ return Tone.Loop;
});
Module(function (Tone) {
/**
- * @class Tone.Event abstracts away Tone.Transport.schedule and provides a schedulable
- * callback for a single or repeatable events along the timeline.
+ * @class Tone.Part is a collection Tone.Events which can be
+ * started/stoped and looped as a single unit.
*
- * @extends {Tone}
- * @param {function} callback The callback to invoke at the time.
- * @param {*} value The value or values which should be passed to
- * the callback function on invocation.
+ * @extends {Tone.Event}
+ * @param {Function} callback The callback to invoke on each event
+ * @param {Array} events the array of events
* @example
- * var chord = new Tone.Event(function(time, chord){
- * //the chord as well as the exact time of the event
- * //are passed in as arguments to the callback function
- * }, ["D4", "E4", "F4"]);
- * //start the chord at the beginning of the transport timeline
- * chord.start();
- * //loop it every measure for 8 measures
- * chord.loop = 8;
- * chord.loopEnd = "1m";
+ * var part = new Tone.Part(function(time, note){
+ * //the notes given as the second element in the array
+ * //will be passed in as the second argument
+ * synth.triggerAttackRelease(note, "8n", time);
+ * }, [[0, "C2"], ["0:2", "C3"], ["0:3:2", "G2"]]);
+ * @example
+ * //use an array of objects as long as the object has a "time" attribute
+ * var part = new Tone.Part(function(time, value){
+ * //the value is an object which contains both the note and the velocity
+ * synth.triggerAttackRelease(value.note, "8n", time, value.velocity);
+ * }, [{"time" : 0, "note" : "C3", "velocity": 0.9},
+ * {"time" : "0:2", "note" : "C4", "velocity": 0.5}
+ * ]).start(0);
*/
- Tone.Event = function () {
+ Tone.Part = function () {
var options = this.optionsObject(arguments, [
'callback',
- 'value'
- ], Tone.Event.defaults);
+ 'events'
+ ], Tone.Part.defaults);
/**
- * Loop value
+ * If the part is looping or not
* @type {Boolean|Positive}
* @private
*/
this._loop = options.loop;
- /**
- * The callback to invoke.
- * @type {Function}
- */
- this.callback = options.callback;
- /**
- * The value which is passed to the
- * callback function.
- * @type {*}
- * @private
- */
- this.value = options.value;
/**
* When the note is scheduled to start.
- * @type {Number}
+ * @type {Ticks}
* @private
*/
this._loopStart = this.toTicks(options.loopStart);
/**
* When the note is scheduled to start.
- * @type {Number}
+ * @type {Ticks}
* @private
*/
this._loopEnd = this.toTicks(options.loopEnd);
/**
- * Tracks the scheduled events
- * @type {Tone.TimelineState}
+ * The playback rate of the part
+ * @type {Positive}
* @private
*/
- this._state = new Tone.TimelineState(Tone.State.Stopped);
+ this._playbackRate = options.playbackRate;
/**
- * The playback speed of the note. A speed of 1
- * is no change.
+ * private holder of probability value
+ * @type {NormalRange}
* @private
- * @type {Positive}
*/
- this._playbackRate = 1;
+ this._probability = options.probability;
/**
- * A delay time from when the event is scheduled to start
+ * the amount of variation from the
+ * given time.
+ * @type {Boolean|Time}
+ * @private
+ */
+ this._humanize = options.humanize;
+ /**
+ * The start offset
* @type {Ticks}
* @private
*/
this._startOffset = 0;
/**
- * The probability that the callback will be invoked
- * at the scheduled time.
- * @type {NormalRange}
- * @example
- * //the callback will be invoked 50% of the time
- * event.probability = 0.5;
+ * Keeps track of the current state
+ * @type {Tone.TimelineState}
+ * @private
*/
- this.probability = options.probability;
+ this._state = new Tone.TimelineState(Tone.State.Stopped);
/**
- * If set to true, will apply small (+/-0.02 seconds) random variation
- * to the callback time. If the value is given as a time, it will randomize
- * by that amount.
- * @example
- * event.humanize = true;
- * @type {Boolean|Time}
+ * An array of Objects.
+ * @type {Array}
+ * @private
*/
- this.humanize = options.humanize;
+ this._events = [];
+ /**
+ * The callback to invoke at all the scheduled events.
+ * @type {Function}
+ */
+ this.callback = options.callback;
/**
* If mute is true, the callback won't be
* invoked.
* @type {Boolean}
*/
this.mute = options.mute;
- //set the initial values
- this.playbackRate = options.playbackRate;
+ //add the events
+ var events = this.defaultArg(options.events, []);
+ if (!this.isUndef(options.events)) {
+ for (var i = 0; i < events.length; i++) {
+ if (Array.isArray(events[i])) {
+ this.add(events[i][0], events[i][1]);
+ } else {
+ this.add(events[i]);
+ }
+ }
+ }
};
- Tone.extend(Tone.Event);
+ Tone.extend(Tone.Part, Tone.Event);
/**
* The default values
* @type {Object}
* @const
*/
- Tone.Event.defaults = {
+ Tone.Part.defaults = {
'callback': Tone.noOp,
'loop': false,
'loopEnd': '1m',
'loopStart': 0,
'playbackRate': 1,
- 'value': null,
'probability': 1,
- 'mute': false,
- 'humanize': false
+ 'humanize': false,
+ 'mute': false
};
/**
- * Reschedule all of the events along the timeline
- * with the updated values.
- * @param {Time} after Only reschedules events after the given time.
- * @return {Tone.Event} this
- * @private
+ * Start the part at the given time.
+ * @param {TransportTime} time When to start the part.
+ * @param {Time=} offset The offset from the start of the part
+ * to begin playing at.
+ * @return {Tone.Part} this
*/
- Tone.Event.prototype._rescheduleEvents = function (after) {
- //if no argument is given, schedules all of the events
- after = this.defaultArg(after, -1);
- this._state.forEachFrom(after, function (event) {
- var duration;
- if (event.state === Tone.State.Started) {
- if (!this.isUndef(event.id)) {
- Tone.Transport.clear(event.id);
- }
- var startTick = event.time + Math.round(this.startOffset / this._playbackRate);
- if (this._loop) {
- duration = Infinity;
- if (this.isNumber(this._loop)) {
- duration = (this._loop - 1) * this._getLoopDuration();
- }
- var nextEvent = this._state.getEventAfter(startTick);
- if (nextEvent !== null) {
- duration = Math.min(duration, nextEvent.time - startTick);
- }
- if (duration !== Infinity) {
- //schedule a stop since it's finite duration
- this._state.setStateAtTime(Tone.State.Stopped, startTick + duration + 1);
- duration += 'i';
- }
- event.id = Tone.Transport.scheduleRepeat(this._tick.bind(this), this._getLoopDuration().toString() + 'i', startTick + 'i', duration);
- } else {
- event.id = Tone.Transport.schedule(this._tick.bind(this), startTick + 'i');
- }
+ Tone.Part.prototype.start = function (time, offset) {
+ var ticks = this.toTicks(time);
+ if (this._state.getStateAtTime(ticks) !== Tone.State.Started) {
+ if (this._loop) {
+ offset = this.defaultArg(offset, this._loopStart);
+ } else {
+ offset = this.defaultArg(offset, 0);
}
- }.bind(this));
+ offset = this.toTicks(offset);
+ this._state.addEvent({
+ 'state': Tone.State.Started,
+ 'time': ticks,
+ 'offset': offset
+ });
+ this._forEach(function (event) {
+ this._startNote(event, ticks, offset);
+ });
+ }
return this;
};
/**
- * Returns the playback state of the note, either "started" or "stopped".
- * @type {String}
- * @readOnly
- * @memberOf Tone.Event#
- * @name state
+ * Start the event in the given event at the correct time given
+ * the ticks and offset and looping.
+ * @param {Tone.Event} event
+ * @param {Ticks} ticks
+ * @param {Ticks} offset
+ * @private
*/
- Object.defineProperty(Tone.Event.prototype, 'state', {
- get: function () {
- return this._state.getStateAtTime(Tone.Transport.ticks);
+ Tone.Part.prototype._startNote = function (event, ticks, offset) {
+ ticks -= offset;
+ if (this._loop) {
+ if (event.startOffset >= this._loopStart && event.startOffset < this._loopEnd) {
+ if (event.startOffset < offset) {
+ //start it on the next loop
+ ticks += this._getLoopDuration();
+ }
+ event.start(Tone.TransportTime(ticks, 'i'));
+ } else if (event.startOffset < this._loopStart && event.startOffset >= offset) {
+ event.loop = false;
+ event.start(Tone.TransportTime(ticks, 'i'));
+ }
+ } else {
+ if (event.startOffset >= offset) {
+ event.start(Tone.TransportTime(ticks, 'i'));
+ }
}
- });
+ };
/**
* The start from the scheduled start time
* @type {Ticks}
- * @memberOf Tone.Event#
+ * @memberOf Tone.Part#
* @name startOffset
* @private
*/
- Object.defineProperty(Tone.Event.prototype, 'startOffset', {
+ Object.defineProperty(Tone.Part.prototype, 'startOffset', {
get: function () {
return this._startOffset;
},
set: function (offset) {
this._startOffset = offset;
- }
- });
- /**
- * Start the note at the given time.
- * @param {Time} time When the note should start.
- * @return {Tone.Event} this
- */
- Tone.Event.prototype.start = function (time) {
- time = this.toTicks(time);
- if (this._state.getStateAtTime(time) === Tone.State.Stopped) {
- this._state.addEvent({
- 'state': Tone.State.Started,
- 'time': time,
- 'id': undefined
+ this._forEach(function (event) {
+ event.startOffset += this._startOffset;
});
- this._rescheduleEvents(time);
- }
- return this;
- };
- /**
- * Stop the Event at the given time.
- * @param {Time} time When the note should stop.
- * @return {Tone.Event} this
- */
- Tone.Event.prototype.stop = function (time) {
- this.cancel(time);
- time = this.toTicks(time);
- if (this._state.getStateAtTime(time) === Tone.State.Started) {
- this._state.setStateAtTime(Tone.State.Stopped, time);
- var previousEvent = this._state.getEventBefore(time);
- var reschedulTime = time;
- if (previousEvent !== null) {
- reschedulTime = previousEvent.time;
- }
- this._rescheduleEvents(reschedulTime);
}
- return this;
- };
+ });
/**
- * Cancel all scheduled events greater than or equal to the given time
- * @param {Time} [time=0] The time after which events will be cancel.
- * @return {Tone.Event} this
+ * Stop the part at the given time.
+ * @param {TimelinePosition} time When to stop the part.
+ * @return {Tone.Part} this
*/
- Tone.Event.prototype.cancel = function (time) {
- time = this.defaultArg(time, -Infinity);
- time = this.toTicks(time);
- this._state.forEachFrom(time, function (event) {
- Tone.Transport.clear(event.id);
+ Tone.Part.prototype.stop = function (time) {
+ var ticks = this.toTicks(time);
+ this._state.cancel(ticks);
+ this._state.setStateAtTime(Tone.State.Stopped, ticks);
+ this._forEach(function (event) {
+ event.stop(time);
});
- this._state.cancel(time);
return this;
};
/**
- * The callback function invoker. Also
- * checks if the Event is done playing
- * @param {Number} time The time of the event in seconds
- * @private
+ * Get/Set an Event's value at the given time.
+ * If a value is passed in and no event exists at
+ * the given time, one will be created with that value.
+ * If two events are at the same time, the first one will
+ * be returned.
+ * @example
+ * part.at("1m"); //returns the part at the first measure
+ *
+ * part.at("2m", "C2"); //set the value at "2m" to C2.
+ * //if an event didn't exist at that time, it will be created.
+ * @param {TransportTime} time The time of the event to get or set.
+ * @param {*=} value If a value is passed in, the value of the
+ * event at the given time will be set to it.
+ * @return {Tone.Event} the event at the time
*/
- Tone.Event.prototype._tick = function (time) {
- if (!this.mute && this._state.getStateAtTime(Tone.Transport.ticks) === Tone.State.Started) {
- if (this.probability < 1 && Math.random() > this.probability) {
- return;
- }
- if (this.humanize) {
- var variation = 0.02;
- if (!this.isBoolean(this.humanize)) {
- variation = this.toSeconds(this.humanize);
+ Tone.Part.prototype.at = function (time, value) {
+ time = Tone.TransportTime(time);
+ var tickTime = Tone.Time(1, 'i').toSeconds();
+ for (var i = 0; i < this._events.length; i++) {
+ var event = this._events[i];
+ if (Math.abs(time.toTicks() - event.startOffset) < tickTime) {
+ if (!this.isUndef(value)) {
+ event.value = value;
}
- time += (Math.random() * 2 - 1) * variation;
+ return event;
}
- this.callback(time, this.value);
- }
- };
- /**
- * Get the duration of the loop.
- * @return {Ticks}
- * @private
- */
- Tone.Event.prototype._getLoopDuration = function () {
- return Math.round((this._loopEnd - this._loopStart) / this._playbackRate);
- };
- /**
- * If the note should loop or not
- * between Tone.Event.loopStart and
- * Tone.Event.loopEnd. An integer
- * value corresponds to the number of
- * loops the Event does after it starts.
- * @memberOf Tone.Event#
- * @type {Boolean|Positive}
- * @name loop
- */
- Object.defineProperty(Tone.Event.prototype, 'loop', {
- get: function () {
- return this._loop;
- },
- set: function (loop) {
- this._loop = loop;
- this._rescheduleEvents();
- }
- });
- /**
- * The playback rate of the note. Defaults to 1.
- * @memberOf Tone.Event#
- * @type {Positive}
- * @name playbackRate
- * @example
- * note.loop = true;
- * //repeat the note twice as fast
- * note.playbackRate = 2;
- */
- Object.defineProperty(Tone.Event.prototype, 'playbackRate', {
- get: function () {
- return this._playbackRate;
- },
- set: function (rate) {
- this._playbackRate = rate;
- this._rescheduleEvents();
}
- });
+ //if there was no event at that time, create one
+ if (!this.isUndef(value)) {
+ this.add(time, value);
+ //return the new event
+ return this._events[this._events.length - 1];
+ } else {
+ return null;
+ }
+ };
/**
- * The loopEnd point is the time the event will loop.
- * Note: only loops if Tone.Event.loop is true.
- * @memberOf Tone.Event#
- * @type {Boolean|Positive}
- * @name loopEnd
+ * Add a an event to the part.
+ * @param {Time} time The time the note should start.
+ * If an object is passed in, it should
+ * have a 'time' attribute and the rest
+ * of the object will be used as the 'value'.
+ * @param {Tone.Event|*} value
+ * @returns {Tone.Part} this
+ * @example
+ * part.add("1m", "C#+11");
*/
- Object.defineProperty(Tone.Event.prototype, 'loopEnd', {
- get: function () {
- return this.toNotation(this._loopEnd + 'i');
- },
- set: function (loopEnd) {
- this._loopEnd = this.toTicks(loopEnd);
- if (this._loop) {
- this._rescheduleEvents();
- }
+ Tone.Part.prototype.add = function (time, value) {
+ //extract the parameters
+ if (this.isObject(time) && time.hasOwnProperty('time')) {
+ value = time;
+ time = value.time;
+ delete value.time;
}
- });
+ time = this.toTicks(time);
+ var event;
+ if (value instanceof Tone.Event) {
+ event = value;
+ event.callback = this._tick.bind(this);
+ } else {
+ event = new Tone.Event({
+ 'callback': this._tick.bind(this),
+ 'value': value
+ });
+ }
+ //the start offset
+ event.startOffset = time;
+ //initialize the values
+ event.set({
+ 'loopEnd': this.loopEnd,
+ 'loopStart': this.loopStart,
+ 'loop': this.loop,
+ 'humanize': this.humanize,
+ 'playbackRate': this.playbackRate,
+ 'probability': this.probability
+ });
+ this._events.push(event);
+ //start the note if it should be played right now
+ this._restartEvent(event);
+ return this;
+ };
/**
- * The time when the loop should start.
- * @memberOf Tone.Event#
- * @type {Boolean|Positive}
- * @name loopStart
+ * Restart the given event
+ * @param {Tone.Event} event
+ * @private
*/
- Object.defineProperty(Tone.Event.prototype, 'loopStart', {
- get: function () {
- return this.toNotation(this._loopStart + 'i');
- },
- set: function (loopStart) {
- this._loopStart = this.toTicks(loopStart);
- if (this._loop) {
- this._rescheduleEvents();
- }
+ Tone.Part.prototype._restartEvent = function (event) {
+ var stateEvent = this._state.getEvent(this.now());
+ if (stateEvent && stateEvent.state === Tone.State.Started) {
+ this._startNote(event, stateEvent.time, stateEvent.offset);
}
- });
+ };
/**
- * The current progress of the loop interval.
- * Returns 0 if the event is not started yet or
- * it is not set to loop.
- * @memberOf Tone.Event#
- * @type {NormalRange}
- * @name progress
- * @readOnly
+ * Remove an event from the part. Will recursively iterate
+ * into nested parts to find the event.
+ * @param {Time} time The time of the event
+ * @param {*} value Optionally select only a specific event value
*/
- Object.defineProperty(Tone.Event.prototype, 'progress', {
- get: function () {
- if (this._loop) {
- var ticks = Tone.Transport.ticks;
- var lastEvent = this._state.getEvent(ticks);
- if (lastEvent !== null && lastEvent.state === Tone.State.Started) {
- var loopDuration = this._getLoopDuration();
- var progress = (ticks - lastEvent.time) % loopDuration;
- return progress / loopDuration;
- } else {
- return 0;
- }
+ Tone.Part.prototype.remove = function (time, value) {
+ //extract the parameters
+ if (this.isObject(time) && time.hasOwnProperty('time')) {
+ value = time;
+ time = value.time;
+ }
+ time = this.toTicks(time);
+ for (var i = this._events.length - 1; i >= 0; i--) {
+ var event = this._events[i];
+ if (event instanceof Tone.Part) {
+ event.remove(time, value);
} else {
- return 0;
+ if (event.startOffset === time) {
+ if (this.isUndef(value) || !this.isUndef(value) && event.value === value) {
+ this._events.splice(i, 1);
+ event.dispose();
+ }
+ }
}
}
- });
- /**
- * Clean up
- * @return {Tone.Event} this
- */
- Tone.Event.prototype.dispose = function () {
- this.cancel();
- this._state.dispose();
- this._state = null;
- this.callback = null;
- this.value = null;
+ return this;
};
- return Tone.Event;
- });
- Module(function (Tone) {
/**
- * @class Tone.Loop creates a looped callback at the
- * specified interval. The callback can be
- * started, stopped and scheduled along
- * the Transport's timeline.
- * @example
- * var loop = new Tone.Loop(function(time){
- * //triggered every eighth note.
- * console.log(time);
- * }, "8n").start(0);
- * Tone.Transport.start();
- * @extends {Tone}
- * @param {Function} callback The callback to invoke with the
- * event.
- * @param {Array} events The events to arpeggiate over.
+ * Remove all of the notes from the group.
+ * @return {Tone.Part} this
*/
- Tone.Loop = function () {
- var options = this.optionsObject(arguments, [
- 'callback',
- 'interval'
- ], Tone.Loop.defaults);
- /**
- * The event which produces the callbacks
- */
- this._event = new Tone.Event({
- 'callback': this._tick.bind(this),
- 'loop': true,
- 'loopEnd': options.interval,
- 'playbackRate': options.playbackRate,
- 'probability': options.probability
+ Tone.Part.prototype.removeAll = function () {
+ this._forEach(function (event) {
+ event.dispose();
});
- /**
- * The callback to invoke with the next event in the pattern
- * @type {Function}
- */
- this.callback = options.callback;
- //set the iterations
- this.iterations = options.iterations;
- };
- Tone.extend(Tone.Loop);
- /**
- * The defaults
- * @const
- * @type {Object}
- */
- Tone.Loop.defaults = {
- 'interval': '4n',
- 'callback': Tone.noOp,
- 'playbackRate': 1,
- 'iterations': Infinity,
- 'probability': true,
- 'mute': false
+ this._events = [];
+ return this;
};
/**
- * Start the loop at the specified time along the Transport's
- * timeline.
- * @param {Time=} time When to start the Loop.
- * @return {Tone.Loop} this
+ * Cancel scheduled state change events: i.e. "start" and "stop".
+ * @param {TimelinePosition} after The time after which to cancel the scheduled events.
+ * @return {Tone.Part} this
*/
- Tone.Loop.prototype.start = function (time) {
- this._event.start(time);
+ Tone.Part.prototype.cancel = function (after) {
+ this._forEach(function (event) {
+ event.cancel(after);
+ });
+ this._state.cancel(after);
return this;
};
/**
- * Stop the loop at the given time.
- * @param {Time=} time When to stop the Arpeggio
- * @return {Tone.Loop} this
+ * Iterate over all of the events
+ * @param {Function} callback
+ * @param {Object} ctx The context
+ * @private
*/
- Tone.Loop.prototype.stop = function (time) {
- this._event.stop(time);
+ Tone.Part.prototype._forEach = function (callback, ctx) {
+ ctx = this.defaultArg(ctx, this);
+ for (var i = this._events.length - 1; i >= 0; i--) {
+ var e = this._events[i];
+ if (e instanceof Tone.Part) {
+ e._forEach(callback, ctx);
+ } else {
+ callback.call(ctx, e);
+ }
+ }
return this;
};
/**
- * Cancel all scheduled events greater than or equal to the given time
- * @param {Time} [time=0] The time after which events will be cancel.
- * @return {Tone.Loop} this
+ * Set the attribute of all of the events
+ * @param {String} attr the attribute to set
+ * @param {*} value The value to set it to
+ * @private
*/
- Tone.Loop.prototype.cancel = function (time) {
- this._event.cancel(time);
- return this;
+ Tone.Part.prototype._setAll = function (attr, value) {
+ this._forEach(function (event) {
+ event[attr] = value;
+ });
};
/**
- * Internal function called when the notes should be called
- * @param {Number} time The time the event occurs
+ * Internal tick method
+ * @param {Number} time The time of the event in seconds
* @private
*/
- Tone.Loop.prototype._tick = function (time) {
- this.callback(time);
+ Tone.Part.prototype._tick = function (time, value) {
+ if (!this.mute) {
+ this.callback(time, value);
+ }
};
/**
- * The state of the Loop, either started or stopped.
- * @memberOf Tone.Loop#
- * @type {String}
- * @name state
- * @readOnly
+ * Determine if the event should be currently looping
+ * given the loop boundries of this Part.
+ * @param {Tone.Event} event The event to test
+ * @private
*/
- Object.defineProperty(Tone.Loop.prototype, 'state', {
- get: function () {
- return this._event.state;
+ Tone.Part.prototype._testLoopBoundries = function (event) {
+ if (event.startOffset < this._loopStart || event.startOffset >= this._loopEnd) {
+ event.cancel();
+ } else {
+ //reschedule it if it's stopped
+ if (event.state === Tone.State.Stopped) {
+ this._restartEvent(event);
+ }
}
- });
+ };
/**
- * The progress of the loop as a value between 0-1. 0, when
- * the loop is stopped or done iterating.
- * @memberOf Tone.Loop#
+ * The probability of the notes being triggered.
+ * @memberOf Tone.Part#
* @type {NormalRange}
- * @name progress
- * @readOnly
+ * @name probability
*/
- Object.defineProperty(Tone.Loop.prototype, 'progress', {
+ Object.defineProperty(Tone.Part.prototype, 'probability', {
get: function () {
- return this._event.progress;
+ return this._probability;
+ },
+ set: function (prob) {
+ this._probability = prob;
+ this._setAll('probability', prob);
}
});
/**
- * The time between successive callbacks.
+ * If set to true, will apply small random variation
+ * to the callback time. If the value is given as a time, it will randomize
+ * by that amount.
* @example
- * loop.interval = "8n"; //loop every 8n
- * @memberOf Tone.Loop#
- * @type {Time}
- * @name interval
+ * event.humanize = true;
+ * @type {Boolean|Time}
+ * @name humanize
*/
- Object.defineProperty(Tone.Loop.prototype, 'interval', {
+ Object.defineProperty(Tone.Part.prototype, 'humanize', {
get: function () {
- return this._event.loopEnd;
+ return this._humanize;
},
- set: function (interval) {
- this._event.loopEnd = interval;
+ set: function (variation) {
+ this._humanize = variation;
+ this._setAll('humanize', variation);
}
});
/**
- * The playback rate of the loop. The normal playback rate is 1 (no change).
- * A `playbackRate` of 2 would be twice as fast.
- * @memberOf Tone.Loop#
- * @type {Time}
- * @name playbackRate
+ * If the part should loop or not
+ * between Tone.Part.loopStart and
+ * Tone.Part.loopEnd. An integer
+ * value corresponds to the number of
+ * loops the Part does after it starts.
+ * @memberOf Tone.Part#
+ * @type {Boolean|Positive}
+ * @name loop
+ * @example
+ * //loop the part 8 times
+ * part.loop = 8;
*/
- Object.defineProperty(Tone.Loop.prototype, 'playbackRate', {
+ Object.defineProperty(Tone.Part.prototype, 'loop', {
get: function () {
- return this._event.playbackRate;
+ return this._loop;
},
- set: function (rate) {
- this._event.playbackRate = rate;
+ set: function (loop) {
+ this._loop = loop;
+ this._forEach(function (event) {
+ event._loopStart = this._loopStart;
+ event._loopEnd = this._loopEnd;
+ event.loop = loop;
+ this._testLoopBoundries(event);
+ });
}
});
/**
- * Random variation +/-0.01s to the scheduled time.
- * Or give it a time value which it will randomize by.
- * @type {Boolean|Time}
- * @memberOf Tone.Loop#
- * @name humanize
+ * The loopEnd point determines when it will
+ * loop if Tone.Part.loop is true.
+ * @memberOf Tone.Part#
+ * @type {TransportTime}
+ * @name loopEnd
*/
- Object.defineProperty(Tone.Loop.prototype, 'humanize', {
+ Object.defineProperty(Tone.Part.prototype, 'loopEnd', {
get: function () {
- return this._event.humanize;
+ return Tone.TransportTime(this._loopEnd, 'i').toNotation();
},
- set: function (variation) {
- this._event.humanize = variation;
+ set: function (loopEnd) {
+ this._loopEnd = this.toTicks(loopEnd);
+ if (this._loop) {
+ this._forEach(function (event) {
+ event.loopEnd = this.loopEnd;
+ this._testLoopBoundries(event);
+ });
+ }
}
});
/**
- * The probably of the callback being invoked.
- * @memberOf Tone.Loop#
- * @type {NormalRange}
- * @name probability
+ * The loopStart point determines when it will
+ * loop if Tone.Part.loop is true.
+ * @memberOf Tone.Part#
+ * @type {TransportTime}
+ * @name loopStart
*/
- Object.defineProperty(Tone.Loop.prototype, 'probability', {
+ Object.defineProperty(Tone.Part.prototype, 'loopStart', {
get: function () {
- return this._event.probability;
+ return Tone.TransportTime(this._loopStart, 'i').toNotation();
},
- set: function (prob) {
- this._event.probability = prob;
+ set: function (loopStart) {
+ this._loopStart = this.toTicks(loopStart);
+ if (this._loop) {
+ this._forEach(function (event) {
+ event.loopStart = this.loopStart;
+ this._testLoopBoundries(event);
+ });
+ }
}
});
/**
- * Muting the Loop means that no callbacks are invoked.
- * @memberOf Tone.Loop#
- * @type {Boolean}
- * @name mute
+ * The playback rate of the part
+ * @memberOf Tone.Part#
+ * @type {Positive}
+ * @name playbackRate
*/
- Object.defineProperty(Tone.Loop.prototype, 'mute', {
+ Object.defineProperty(Tone.Part.prototype, 'playbackRate', {
get: function () {
- return this._event.mute;
+ return this._playbackRate;
},
- set: function (mute) {
- this._event.mute = mute;
+ set: function (rate) {
+ this._playbackRate = rate;
+ this._setAll('playbackRate', rate);
}
});
/**
- * The number of iterations of the loop. The default
- * value is Infinity (loop forever).
- * @memberOf Tone.Loop#
+ * The number of scheduled notes in the part.
+ * @memberOf Tone.Part#
* @type {Positive}
- * @name iterations
+ * @name length
+ * @readOnly
*/
- Object.defineProperty(Tone.Loop.prototype, 'iterations', {
+ Object.defineProperty(Tone.Part.prototype, 'length', {
get: function () {
- if (this._event.loop === true) {
- return Infinity;
- } else {
- return this._event.loop;
- }
- return this._pattern.index;
- },
- set: function (iters) {
- if (iters === Infinity) {
- this._event.loop = true;
- } else {
- this._event.loop = iters;
- }
+ return this._events.length;
}
});
/**
* Clean up
- * @return {Tone.Loop} this
+ * @return {Tone.Part} this
*/
- Tone.Loop.prototype.dispose = function () {
- this._event.dispose();
- this._event = null;
+ Tone.Part.prototype.dispose = function () {
+ this.removeAll();
+ this._state.dispose();
+ this._state = null;
this.callback = null;
- };
- return Tone.Loop;
- });
- Module(function (Tone) {
-
- /**
- * @class Tone.Part is a collection Tone.Events which can be
- * started/stoped and looped as a single unit.
- *
- * @extends {Tone.Event}
- * @param {Function} callback The callback to invoke on each event
- * @param {Array} events the array of events
- * @example
- * var part = new Tone.Part(function(time, note){
- * //the notes given as the second element in the array
- * //will be passed in as the second argument
- * synth.triggerAttackRelease(note, "8n", time);
- * }, [[0, "C2"], ["0:2", "C3"], ["0:3:2", "G2"]]);
- * @example
- * //use an array of objects as long as the object has a "time" attribute
- * var part = new Tone.Part(function(time, value){
- * //the value is an object which contains both the note and the velocity
- * synth.triggerAttackRelease(value.note, "8n", time, value.velocity);
- * }, [{"time" : 0, "note" : "C3", "velocity": 0.9},
- * {"time" : "0:2", "note" : "C4", "velocity": 0.5}
- * ]).start(0);
- */
- Tone.Part = function () {
- var options = this.optionsObject(arguments, [
- 'callback',
- 'events'
- ], Tone.Part.defaults);
- /**
- * If the part is looping or not
- * @type {Boolean|Positive}
- * @private
- */
- this._loop = options.loop;
- /**
- * When the note is scheduled to start.
- * @type {Ticks}
- * @private
- */
- this._loopStart = this.toTicks(options.loopStart);
- /**
- * When the note is scheduled to start.
- * @type {Ticks}
- * @private
- */
- this._loopEnd = this.toTicks(options.loopEnd);
- /**
- * The playback rate of the part
- * @type {Positive}
- * @private
- */
- this._playbackRate = options.playbackRate;
- /**
- * private holder of probability value
- * @type {NormalRange}
- * @private
- */
- this._probability = options.probability;
- /**
- * the amount of variation from the
- * given time.
- * @type {Boolean|Time}
- * @private
- */
- this._humanize = options.humanize;
- /**
- * The start offset
- * @type {Ticks}
- * @private
- */
- this._startOffset = 0;
- /**
- * Keeps track of the current state
- * @type {Tone.TimelineState}
- * @private
- */
- this._state = new Tone.TimelineState(Tone.State.Stopped);
+ this._events = null;
+ return this;
+ };
+ return Tone.Part;
+ });
+ Module(function (Tone) {
+ /**
+ * @class Tone.Pattern arpeggiates between the given notes
+ * in a number of patterns. See Tone.CtrlPattern for
+ * a full list of patterns.
+ * @example
+ * var pattern = new Tone.Pattern(function(time, note){
+ * //the order of the notes passed in depends on the pattern
+ * }, ["C2", "D4", "E5", "A6"], "upDown");
+ * @extends {Tone.Loop}
+ * @param {Function} callback The callback to invoke with the
+ * event.
+ * @param {Array} values The values to arpeggiate over.
+ */
+ Tone.Pattern = function () {
+ var options = this.optionsObject(arguments, [
+ 'callback',
+ 'values',
+ 'pattern'
+ ], Tone.Pattern.defaults);
+ Tone.Loop.call(this, options);
/**
- * An array of Objects.
- * @type {Array}
+ * The pattern manager
+ * @type {Tone.CtrlPattern}
* @private
*/
- this._events = [];
- /**
- * The callback to invoke at all the scheduled events.
- * @type {Function}
- */
- this.callback = options.callback;
- /**
- * If mute is true, the callback won't be
- * invoked.
- * @type {Boolean}
- */
- this.mute = options.mute;
- //add the events
- var events = this.defaultArg(options.events, []);
- if (!this.isUndef(options.events)) {
- for (var i = 0; i < events.length; i++) {
- if (Array.isArray(events[i])) {
- this.add(events[i][0], events[i][1]);
- } else {
- this.add(events[i]);
- }
- }
- }
+ this._pattern = new Tone.CtrlPattern({
+ 'values': options.values,
+ 'type': options.pattern,
+ 'index': options.index
+ });
};
- Tone.extend(Tone.Part, Tone.Event);
+ Tone.extend(Tone.Pattern, Tone.Loop);
/**
- * The default values
- * @type {Object}
+ * The defaults
* @const
+ * @type {Object}
*/
- Tone.Part.defaults = {
- 'callback': Tone.noOp,
- 'loop': false,
- 'loopEnd': '1m',
- 'loopStart': 0,
- 'playbackRate': 1,
- 'probability': 1,
- 'humanize': false,
- 'mute': false
+ Tone.Pattern.defaults = {
+ 'pattern': Tone.CtrlPattern.Type.Up,
+ 'values': []
};
/**
- * Start the part at the given time.
- * @param {Time} time When to start the part.
- * @param {Time=} offset The offset from the start of the part
- * to begin playing at.
- * @return {Tone.Part} this
+ * Internal function called when the notes should be called
+ * @param {Number} time The time the event occurs
+ * @private
*/
- Tone.Part.prototype.start = function (time, offset) {
- var ticks = this.toTicks(time);
- if (this._state.getStateAtTime(ticks) !== Tone.State.Started) {
- offset = this.defaultArg(offset, 0);
- offset = this.toTicks(offset);
- this._state.addEvent({
- 'state': Tone.State.Started,
- 'time': ticks,
- 'offset': offset
- });
- this._forEach(function (event) {
- this._startNote(event, ticks, offset);
- });
- }
- return this;
+ Tone.Pattern.prototype._tick = function (time) {
+ this.callback(time, this._pattern.value);
+ this._pattern.next();
};
/**
- * Start the event in the given event at the correct time given
- * the ticks and offset and looping.
- * @param {Tone.Event} event
- * @param {Ticks} ticks
- * @param {Ticks} offset
- * @private
+ * The current index in the values array.
+ * @memberOf Tone.Pattern#
+ * @type {Positive}
+ * @name index
*/
- Tone.Part.prototype._startNote = function (event, ticks, offset) {
- ticks -= offset;
- if (this._loop) {
- if (event.startOffset >= this._loopStart && event.startOffset < this._loopEnd) {
- if (event.startOffset < offset) {
- //start it on the next loop
- ticks += this._getLoopDuration();
- }
- event.start(ticks + 'i');
- }
- } else {
- if (event.startOffset >= offset) {
- event.start(ticks + 'i');
- }
+ Object.defineProperty(Tone.Pattern.prototype, 'index', {
+ get: function () {
+ return this._pattern.index;
+ },
+ set: function (i) {
+ this._pattern.index = i;
}
- };
+ });
/**
- * The start from the scheduled start time
- * @type {Ticks}
- * @memberOf Tone.Part#
- * @name startOffset
- * @private
+ * The array of events.
+ * @memberOf Tone.Pattern#
+ * @type {Array}
+ * @name values
*/
- Object.defineProperty(Tone.Part.prototype, 'startOffset', {
+ Object.defineProperty(Tone.Pattern.prototype, 'values', {
get: function () {
- return this._startOffset;
+ return this._pattern.values;
},
- set: function (offset) {
- this._startOffset = offset;
- this._forEach(function (event) {
- event.startOffset += this._startOffset;
- });
+ set: function (vals) {
+ this._pattern.values = vals;
}
});
/**
- * Stop the part at the given time.
- * @param {Time} time When to stop the part.
- * @return {Tone.Part} this
+ * The current value of the pattern.
+ * @memberOf Tone.Pattern#
+ * @type {*}
+ * @name value
+ * @readOnly
*/
- Tone.Part.prototype.stop = function (time) {
- var ticks = this.toTicks(time);
- if (this._state.getStateAtTime(ticks) === Tone.State.Started) {
- this._state.setStateAtTime(Tone.State.Stopped, ticks);
- this._forEach(function (event) {
- event.stop(time);
- });
+ Object.defineProperty(Tone.Pattern.prototype, 'value', {
+ get: function () {
+ return this._pattern.value;
}
- return this;
+ });
+ /**
+ * The pattern type. See Tone.CtrlPattern for the full list of patterns.
+ * @memberOf Tone.Pattern#
+ * @type {String}
+ * @name pattern
+ */
+ Object.defineProperty(Tone.Pattern.prototype, 'pattern', {
+ get: function () {
+ return this._pattern.type;
+ },
+ set: function (pattern) {
+ this._pattern.type = pattern;
+ }
+ });
+ /**
+ * Clean up
+ * @return {Tone.Pattern} this
+ */
+ Tone.Pattern.prototype.dispose = function () {
+ Tone.Loop.prototype.dispose.call(this);
+ this._pattern.dispose();
+ this._pattern = null;
};
+ return Tone.Pattern;
+ });
+ Module(function (Tone) {
+
/**
- * Get/Set an Event's value at the given time.
- * If a value is passed in and no event exists at
- * the given time, one will be created with that value.
- * If two events are at the same time, the first one will
- * be returned.
+ * @class A sequence is an alternate notation of a part. Instead
+ * of passing in an array of [time, event] pairs, pass
+ * in an array of events which will be spaced at the
+ * given subdivision. Sub-arrays will subdivide that beat
+ * by the number of items are in the array.
+ * Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/)
+ * @param {Function} callback The callback to invoke with every note
+ * @param {Array} events The sequence
+ * @param {Time} subdivision The subdivision between which events are placed.
+ * @extends {Tone.Part}
* @example
- * part.at("1m"); //returns the part at the first measure
- *
- * part.at("2m", "C2"); //set the value at "2m" to C2.
- * //if an event didn't exist at that time, it will be created.
- * @param {Time} time the time of the event to get or set
- * @param {*=} value If a value is passed in, the value of the
- * event at the given time will be set to it.
- * @return {Tone.Event} the event at the time
+ * var seq = new Tone.Sequence(function(time, note){
+ * console.log(note);
+ * //straight quater notes
+ * }, ["C4", "E4", "G4", "A4"], "4n");
+ * @example
+ * var seq = new Tone.Sequence(function(time, note){
+ * console.log(note);
+ * //subdivisions are given as subarrays
+ * }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]);
*/
- Tone.Part.prototype.at = function (time, value) {
- time = this.toTicks(time);
- var tickTime = this.ticksToSeconds(1);
- for (var i = 0; i < this._events.length; i++) {
- var event = this._events[i];
- if (Math.abs(time - event.startOffset) < tickTime) {
- if (!this.isUndef(value)) {
- event.value = value;
- }
- return event;
- }
+ Tone.Sequence = function () {
+ var options = this.optionsObject(arguments, [
+ 'callback',
+ 'events',
+ 'subdivision'
+ ], Tone.Sequence.defaults);
+ //remove the events
+ var events = options.events;
+ delete options.events;
+ Tone.Part.call(this, options);
+ /**
+ * The subdivison of each note
+ * @type {Ticks}
+ * @private
+ */
+ this._subdivision = this.toTicks(options.subdivision);
+ //if no time was passed in, the loop end is the end of the cycle
+ if (this.isUndef(options.loopEnd) && !this.isUndef(events)) {
+ this._loopEnd = events.length * this._subdivision;
}
- //if there was no event at that time, create one
- if (!this.isUndef(value)) {
- this.add(time + 'i', value);
- //return the new event
- return this._events[this._events.length - 1];
- } else {
- return null;
+ //defaults to looping
+ this._loop = true;
+ //add all of the events
+ if (!this.isUndef(events)) {
+ for (var i = 0; i < events.length; i++) {
+ this.add(i, events[i]);
+ }
}
};
+ Tone.extend(Tone.Sequence, Tone.Part);
/**
- * Add a an event to the part.
- * @param {Time} time The time the note should start.
- * If an object is passed in, it should
- * have a 'time' attribute and the rest
- * of the object will be used as the 'value'.
- * @param {Tone.Event|*} value
- * @returns {Tone.Part} this
- * @example
- * part.add("1m", "C#+11");
+ * The default values.
+ * @type {Object}
*/
- Tone.Part.prototype.add = function (time, value) {
- //extract the parameters
- if (this.isObject(time) && time.hasOwnProperty('time')) {
- value = time;
- time = value.time;
- delete value.time;
- }
- time = this.toTicks(time);
- var event;
- if (value instanceof Tone.Event) {
- event = value;
- event.callback = this._tick.bind(this);
- } else {
- event = new Tone.Event({
- 'callback': this._tick.bind(this),
- 'value': value
- });
+ Tone.Sequence.defaults = { 'subdivision': '4n' };
+ /**
+ * The subdivision of the sequence. This can only be
+ * set in the constructor. The subdivision is the
+ * interval between successive steps.
+ * @type {Time}
+ * @memberOf Tone.Sequence#
+ * @name subdivision
+ * @readOnly
+ */
+ Object.defineProperty(Tone.Sequence.prototype, 'subdivision', {
+ get: function () {
+ return Tone.Time(this._subdivision, 'i').toNotation();
}
- //the start offset
- event.startOffset = time;
- //initialize the values
- event.set({
- 'loopEnd': this.loopEnd,
- 'loopStart': this.loopStart,
- 'loop': this.loop,
- 'humanize': this.humanize,
- 'playbackRate': this.playbackRate,
- 'probability': this.probability
- });
- this._events.push(event);
- //start the note if it should be played right now
- this._restartEvent(event);
- return this;
- };
+ });
/**
- * Restart the given event
- * @param {Tone.Event} event
- * @private
+ * Get/Set an index of the sequence. If the index contains a subarray,
+ * a Tone.Sequence representing that sub-array will be returned.
+ * @example
+ * var sequence = new Tone.Sequence(playNote, ["E4", "C4", "F#4", ["A4", "Bb3"]])
+ * sequence.at(0)// => returns "E4"
+ * //set a value
+ * sequence.at(0, "G3");
+ * //get a nested sequence
+ * sequence.at(3).at(1)// => returns "Bb3"
+ * @param {Positive} index The index to get or set
+ * @param {*} value Optionally pass in the value to set at the given index.
*/
- Tone.Part.prototype._restartEvent = function (event) {
- var stateEvent = this._state.getEvent(this.now());
- if (stateEvent && stateEvent.state === Tone.State.Started) {
- this._startNote(event, stateEvent.time, stateEvent.offset);
+ Tone.Sequence.prototype.at = function (index, value) {
+ //if the value is an array,
+ if (this.isArray(value)) {
+ //remove the current event at that index
+ this.remove(index);
}
+ //call the parent's method
+ return Tone.Part.prototype.at.call(this, this._indexTime(index), value);
};
/**
- * Remove an event from the part. Will recursively iterate
- * into nested parts to find the event.
- * @param {Time} time The time of the event
- * @param {*} value Optionally select only a specific event value
+ * Add an event at an index, if there's already something
+ * at that index, overwrite it. If `value` is an array,
+ * it will be parsed as a subsequence.
+ * @param {Number} index The index to add the event to
+ * @param {*} value The value to add at that index
+ * @returns {Tone.Sequence} this
*/
- Tone.Part.prototype.remove = function (time, value) {
- //extract the parameters
- if (this.isObject(time) && time.hasOwnProperty('time')) {
- value = time;
- time = value.time;
+ Tone.Sequence.prototype.add = function (index, value) {
+ if (value === null) {
+ return this;
}
- time = this.toTicks(time);
- for (var i = this._events.length - 1; i >= 0; i--) {
- var event = this._events[i];
- if (event instanceof Tone.Part) {
- event.remove(time, value);
- } else {
- if (event.startOffset === time) {
- if (this.isUndef(value) || !this.isUndef(value) && event.value === value) {
- this._events.splice(i, 1);
- event.dispose();
- }
- }
- }
+ if (this.isArray(value)) {
+ //make a subsequence and add that to the sequence
+ var subSubdivision = Math.round(this._subdivision / value.length);
+ value = new Tone.Sequence(this._tick.bind(this), value, Tone.Time(subSubdivision, 'i'));
}
+ Tone.Part.prototype.add.call(this, this._indexTime(index), value);
return this;
};
/**
- * Remove all of the notes from the group.
- * @return {Tone.Part} this
+ * Remove a value from the sequence by index
+ * @param {Number} index The index of the event to remove
+ * @returns {Tone.Sequence} this
*/
- Tone.Part.prototype.removeAll = function () {
- this._forEach(function (event) {
- event.dispose();
- });
- this._events = [];
+ Tone.Sequence.prototype.remove = function (index, value) {
+ Tone.Part.prototype.remove.call(this, this._indexTime(index), value);
return this;
};
/**
- * Cancel scheduled state change events: i.e. "start" and "stop".
- * @param {Time} after The time after which to cancel the scheduled events.
- * @return {Tone.Part} this
+ * Get the time of the index given the Sequence's subdivision
+ * @param {Number} index
+ * @return {Time} The time of that index
+ * @private
*/
- Tone.Part.prototype.cancel = function (after) {
- this._forEach(function (event) {
- event.cancel(after);
- });
- this._state.cancel(after);
- return this;
+ Tone.Sequence.prototype._indexTime = function (index) {
+ if (index instanceof Tone.TransportTime) {
+ return index;
+ } else {
+ return Tone.TransportTime(index * this._subdivision + this.startOffset, 'i');
+ }
};
/**
- * Iterate over all of the events
- * @param {Function} callback
- * @param {Object} ctx The context
- * @private
+ * Clean up.
+ * @return {Tone.Sequence} this
*/
- Tone.Part.prototype._forEach = function (callback, ctx) {
- ctx = this.defaultArg(ctx, this);
- for (var i = this._events.length - 1; i >= 0; i--) {
- var e = this._events[i];
- if (e instanceof Tone.Part) {
- e._forEach(callback, ctx);
- } else {
- callback.call(ctx, e);
- }
- }
+ Tone.Sequence.prototype.dispose = function () {
+ Tone.Part.prototype.dispose.call(this);
return this;
};
+ return Tone.Sequence;
+ });
+ Module(function (Tone) {
+
/**
- * Set the attribute of all of the events
- * @param {String} attr the attribute to set
- * @param {*} value The value to set it to
- * @private
+ * @class Tone.PulseOscillator is a pulse oscillator with control over pulse width,
+ * also known as the duty cycle. At 50% duty cycle (width = 0.5) the wave is
+ * a square and only odd-numbered harmonics are present. At all other widths
+ * even-numbered harmonics are present. Read more
+ * [here](https://wigglewave.wordpress.com/2014/08/16/pulse-waveforms-and-harmonics/).
+ *
+ * @constructor
+ * @extends {Tone.Oscillator}
+ * @param {Frequency} [frequency] The frequency of the oscillator
+ * @param {NormalRange} [width] The width of the pulse
+ * @example
+ * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start();
*/
- Tone.Part.prototype._setAll = function (attr, value) {
- this._forEach(function (event) {
- event[attr] = value;
+ Tone.PulseOscillator = function () {
+ var options = this.optionsObject(arguments, [
+ 'frequency',
+ 'width'
+ ], Tone.Oscillator.defaults);
+ Tone.Source.call(this, options);
+ /**
+ * The width of the pulse.
+ * @type {NormalRange}
+ * @signal
+ */
+ this.width = new Tone.Signal(options.width, Tone.Type.NormalRange);
+ /**
+ * gate the width amount
+ * @type {GainNode}
+ * @private
+ */
+ this._widthGate = this.context.createGain();
+ /**
+ * the sawtooth oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._sawtooth = new Tone.Oscillator({
+ frequency: options.frequency,
+ detune: options.detune,
+ type: 'sawtooth',
+ phase: options.phase
+ });
+ /**
+ * The frequency control.
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = this._sawtooth.frequency;
+ /**
+ * The detune in cents.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this._sawtooth.detune;
+ /**
+ * Threshold the signal to turn it into a square
+ * @type {Tone.WaveShaper}
+ * @private
+ */
+ this._thresh = new Tone.WaveShaper(function (val) {
+ if (val < 0) {
+ return -1;
+ } else {
+ return 1;
+ }
});
+ //connections
+ this._sawtooth.chain(this._thresh, this.output);
+ this.width.chain(this._widthGate, this._thresh);
+ this._readOnly([
+ 'width',
+ 'frequency',
+ 'detune'
+ ]);
+ };
+ Tone.extend(Tone.PulseOscillator, Tone.Oscillator);
+ /**
+ * The default parameters.
+ * @static
+ * @const
+ * @type {Object}
+ */
+ Tone.PulseOscillator.defaults = {
+ 'frequency': 440,
+ 'detune': 0,
+ 'phase': 0,
+ 'width': 0.2
};
/**
- * Internal tick method
- * @param {Number} time The time of the event in seconds
+ * start the oscillator
+ * @param {Time} time
* @private
*/
- Tone.Part.prototype._tick = function (time, value) {
- if (!this.mute) {
- this.callback(time, value);
- }
+ Tone.PulseOscillator.prototype._start = function (time) {
+ time = this.toSeconds(time);
+ this._sawtooth.start(time);
+ this._widthGate.gain.setValueAtTime(1, time);
};
/**
- * Determine if the event should be currently looping
- * given the loop boundries of this Part.
- * @param {Tone.Event} event The event to test
+ * stop the oscillator
+ * @param {Time} time
* @private
*/
- Tone.Part.prototype._testLoopBoundries = function (event) {
- if (event.startOffset < this._loopStart || event.startOffset >= this._loopEnd) {
- event.cancel();
- } else {
- //reschedule it if it's stopped
- if (event.state === Tone.State.Stopped) {
- this._restartEvent(event);
- }
- }
+ Tone.PulseOscillator.prototype._stop = function (time) {
+ time = this.toSeconds(time);
+ this._sawtooth.stop(time);
+ //the width is still connected to the output.
+ //that needs to be stopped also
+ this._widthGate.gain.setValueAtTime(0, time);
};
/**
- * The probability of the notes being triggered.
- * @memberOf Tone.Part#
- * @type {NormalRange}
- * @name probability
- */
- Object.defineProperty(Tone.Part.prototype, 'probability', {
- get: function () {
- return this._probability;
- },
- set: function (prob) {
- this._probability = prob;
- this._setAll('probability', prob);
- }
- });
- /**
- * If set to true, will apply small random variation
- * to the callback time. If the value is given as a time, it will randomize
- * by that amount.
- * @example
- * event.humanize = true;
- * @type {Boolean|Time}
- * @name humanize
- */
- Object.defineProperty(Tone.Part.prototype, 'humanize', {
- get: function () {
- return this._humanize;
- },
- set: function (variation) {
- this._humanize = variation;
- this._setAll('humanize', variation);
- }
- });
- /**
- * If the part should loop or not
- * between Tone.Part.loopStart and
- * Tone.Part.loopEnd. An integer
- * value corresponds to the number of
- * loops the Part does after it starts.
- * @memberOf Tone.Part#
- * @type {Boolean|Positive}
- * @name loop
- * @example
- * //loop the part 8 times
- * part.loop = 8;
- */
- Object.defineProperty(Tone.Part.prototype, 'loop', {
- get: function () {
- return this._loop;
- },
- set: function (loop) {
- this._loop = loop;
- this._forEach(function (event) {
- event._loopStart = this._loopStart;
- event._loopEnd = this._loopEnd;
- event.loop = loop;
- this._testLoopBoundries(event);
- });
- }
- });
- /**
- * The loopEnd point determines when it will
- * loop if Tone.Part.loop is true.
- * @memberOf Tone.Part#
- * @type {Boolean|Positive}
- * @name loopEnd
- */
- Object.defineProperty(Tone.Part.prototype, 'loopEnd', {
- get: function () {
- return this.toNotation(this._loopEnd + 'i');
- },
- set: function (loopEnd) {
- this._loopEnd = this.toTicks(loopEnd);
- if (this._loop) {
- this._forEach(function (event) {
- event.loopEnd = this.loopEnd;
- this._testLoopBoundries(event);
- });
- }
- }
- });
- /**
- * The loopStart point determines when it will
- * loop if Tone.Part.loop is true.
- * @memberOf Tone.Part#
- * @type {Boolean|Positive}
- * @name loopStart
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.PulseOscillator#
+ * @type {Degrees}
+ * @name phase
*/
- Object.defineProperty(Tone.Part.prototype, 'loopStart', {
+ Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', {
get: function () {
- return this.toNotation(this._loopStart + 'i');
+ return this._sawtooth.phase;
},
- set: function (loopStart) {
- this._loopStart = this.toTicks(loopStart);
- if (this._loop) {
- this._forEach(function (event) {
- event.loopStart = this.loopStart;
- this._testLoopBoundries(event);
- });
- }
+ set: function (phase) {
+ this._sawtooth.phase = phase;
}
});
/**
- * The playback rate of the part
- * @memberOf Tone.Part#
- * @type {Positive}
- * @name playbackRate
+ * The type of the oscillator. Always returns "pulse".
+ * @readOnly
+ * @memberOf Tone.PulseOscillator#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.Part.prototype, 'playbackRate', {
+ Object.defineProperty(Tone.PulseOscillator.prototype, 'type', {
get: function () {
- return this._playbackRate;
- },
- set: function (rate) {
- this._playbackRate = rate;
- this._setAll('playbackRate', rate);
+ return 'pulse';
}
});
/**
- * The number of scheduled notes in the part.
- * @memberOf Tone.Part#
- * @type {Positive}
- * @name length
- * @readOnly
+ * The partials of the waveform. Cannot set partials for this waveform type
+ * @memberOf Tone.PulseOscillator#
+ * @type {Array}
+ * @name partials
+ * @private
*/
- Object.defineProperty(Tone.Part.prototype, 'length', {
+ Object.defineProperty(Tone.PulseOscillator.prototype, 'partials', {
get: function () {
- return this._events.length;
+ return [];
}
});
/**
- * Clean up
- * @return {Tone.Part} this
+ * Clean up method.
+ * @return {Tone.PulseOscillator} this
*/
- Tone.Part.prototype.dispose = function () {
- this.removeAll();
- this._state.dispose();
- this._state = null;
- this.callback = null;
- this._events = null;
+ Tone.PulseOscillator.prototype.dispose = function () {
+ Tone.Source.prototype.dispose.call(this);
+ this._sawtooth.dispose();
+ this._sawtooth = null;
+ this._writable([
+ 'width',
+ 'frequency',
+ 'detune'
+ ]);
+ this.width.dispose();
+ this.width = null;
+ this._widthGate.disconnect();
+ this._widthGate = null;
+ this._widthGate = null;
+ this._thresh.disconnect();
+ this._thresh = null;
+ this.frequency = null;
+ this.detune = null;
return this;
};
- return Tone.Part;
+ return Tone.PulseOscillator;
});
Module(function (Tone) {
+
/**
- * @class Tone.Pattern arpeggiates between the given notes
- * in a number of patterns. See Tone.CtrlPattern for
- * a full list of patterns.
+ * @class Tone.PWMOscillator modulates the width of a Tone.PulseOscillator
+ * at the modulationFrequency. This has the effect of continuously
+ * changing the timbre of the oscillator by altering the harmonics
+ * generated.
+ *
+ * @extends {Tone.Oscillator}
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse.
* @example
- * var pattern = new Tone.Pattern(function(time, note){
- * //the order of the notes passed in depends on the pattern
- * }, ["C2", "D4", "E5", "A6"], "upDown");
- * @extends {Tone.Loop}
- * @param {Function} callback The callback to invoke with the
- * event.
- * @param {Array} events The events to arpeggiate over.
+ * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start();
*/
- Tone.Pattern = function () {
+ Tone.PWMOscillator = function () {
var options = this.optionsObject(arguments, [
- 'callback',
- 'events',
- 'pattern'
- ], Tone.Pattern.defaults);
- Tone.Loop.call(this, options);
+ 'frequency',
+ 'modulationFrequency'
+ ], Tone.PWMOscillator.defaults);
+ Tone.Source.call(this, options);
/**
- * The pattern manager
- * @type {Tone.CtrlPattern}
+ * the pulse oscillator
+ * @type {Tone.PulseOscillator}
+ * @private
+ */
+ this._pulse = new Tone.PulseOscillator(options.modulationFrequency);
+ //change the pulse oscillator type
+ this._pulse._sawtooth.type = 'sine';
+ /**
+ * the modulator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._modulator = new Tone.Oscillator({
+ 'frequency': options.frequency,
+ 'detune': options.detune,
+ 'phase': options.phase
+ });
+ /**
+ * Scale the oscillator so it doesn't go silent
+ * at the extreme values.
+ * @type {Tone.Multiply}
* @private
*/
- this._pattern = new Tone.CtrlPattern({
- 'values': options.events,
- 'type': options.pattern,
- 'index': options.index
- });
+ this._scale = new Tone.Multiply(2);
+ /**
+ * The frequency control.
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = this._modulator.frequency;
+ /**
+ * The detune of the oscillator.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this._modulator.detune;
+ /**
+ * The modulation rate of the oscillator.
+ * @type {Frequency}
+ * @signal
+ */
+ this.modulationFrequency = this._pulse.frequency;
+ //connections
+ this._modulator.chain(this._scale, this._pulse.width);
+ this._pulse.connect(this.output);
+ this._readOnly([
+ 'modulationFrequency',
+ 'frequency',
+ 'detune'
+ ]);
};
- Tone.extend(Tone.Pattern, Tone.Loop);
+ Tone.extend(Tone.PWMOscillator, Tone.Oscillator);
/**
- * The defaults
+ * default values
+ * @static
+ * @type {Object}
* @const
- * @type {Object}
*/
- Tone.Pattern.defaults = {
- 'pattern': Tone.CtrlPattern.Type.Up,
- 'events': []
+ Tone.PWMOscillator.defaults = {
+ 'frequency': 440,
+ 'detune': 0,
+ 'phase': 0,
+ 'modulationFrequency': 0.4
};
/**
- * Internal function called when the notes should be called
- * @param {Number} time The time the event occurs
+ * start the oscillator
+ * @param {Time} [time=now]
* @private
*/
- Tone.Pattern.prototype._tick = function (time) {
- this.callback(time, this._pattern.value);
- this._pattern.next();
+ Tone.PWMOscillator.prototype._start = function (time) {
+ time = this.toSeconds(time);
+ this._modulator.start(time);
+ this._pulse.start(time);
};
/**
- * The current index in the events array.
- * @memberOf Tone.Pattern#
- * @type {Positive}
- * @name index
+ * stop the oscillator
+ * @param {Time} time (optional) timing parameter
+ * @private
*/
- Object.defineProperty(Tone.Pattern.prototype, 'index', {
- get: function () {
- return this._pattern.index;
- },
- set: function (i) {
- this._pattern.index = i;
- }
- });
+ Tone.PWMOscillator.prototype._stop = function (time) {
+ time = this.toSeconds(time);
+ this._modulator.stop(time);
+ this._pulse.stop(time);
+ };
/**
- * The array of events.
- * @memberOf Tone.Pattern#
- * @type {Array}
- * @name events
+ * The type of the oscillator. Always returns "pwm".
+ * @readOnly
+ * @memberOf Tone.PWMOscillator#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.Pattern.prototype, 'events', {
+ Object.defineProperty(Tone.PWMOscillator.prototype, 'type', {
get: function () {
- return this._pattern.values;
- },
- set: function (vals) {
- this._pattern.values = vals;
+ return 'pwm';
}
});
/**
- * The current value of the pattern.
- * @memberOf Tone.Pattern#
- * @type {*}
- * @name value
- * @readOnly
+ * The partials of the waveform. Cannot set partials for this waveform type
+ * @memberOf Tone.PWMOscillator#
+ * @type {Array}
+ * @name partials
+ * @private
*/
- Object.defineProperty(Tone.Pattern.prototype, 'value', {
+ Object.defineProperty(Tone.PWMOscillator.prototype, 'partials', {
get: function () {
- return this._pattern.value;
+ return [];
}
});
/**
- * The pattern type. See Tone.CtrlPattern for the full list of patterns.
- * @memberOf Tone.Pattern#
- * @type {String}
- * @name pattern
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.PWMOscillator#
+ * @type {number}
+ * @name phase
*/
- Object.defineProperty(Tone.Pattern.prototype, 'pattern', {
+ Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', {
get: function () {
- return this._pattern.type;
+ return this._modulator.phase;
},
- set: function (pattern) {
- this._pattern.type = pattern;
+ set: function (phase) {
+ this._modulator.phase = phase;
}
});
/**
- * Clean up
- * @return {Tone.Pattern} this
+ * Clean up.
+ * @return {Tone.PWMOscillator} this
*/
- Tone.Pattern.prototype.dispose = function () {
- Tone.Loop.prototype.dispose.call(this);
- this._pattern.dispose();
- this._pattern = null;
+ Tone.PWMOscillator.prototype.dispose = function () {
+ Tone.Source.prototype.dispose.call(this);
+ this._pulse.dispose();
+ this._pulse = null;
+ this._scale.dispose();
+ this._scale = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this._writable([
+ 'modulationFrequency',
+ 'frequency',
+ 'detune'
+ ]);
+ this.frequency = null;
+ this.detune = null;
+ this.modulationFrequency = null;
+ return this;
};
- return Tone.Pattern;
+ return Tone.PWMOscillator;
});
Module(function (Tone) {
/**
- * @class A sequence is an alternate notation of a part. Instead
- * of passing in an array of [time, event] pairs, pass
- * in an array of events which will be spaced at the
- * given subdivision. Sub-arrays will subdivide that beat
- * by the number of items are in the array.
- * Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/)
- * @param {Function} callback The callback to invoke with every note
- * @param {Array} events The sequence
- * @param {Time} subdivision The subdivision between which events are placed.
- * @extends {Tone.Part}
- * @example
- * var seq = new Tone.Sequence(function(time, note){
- * console.log(note);
- * //straight quater notes
- * }, ["C4", "E4", "G4", "A4"], "4n");
+ * @class Tone.FMOscillator
+ *
+ * @extends {Tone.Oscillator}
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
* @example
- * var seq = new Tone.Sequence(function(time, note){
- * console.log(note);
- * //subdivisions are given as subarrays
- * }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]);
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.FMOscillator("Ab3", "sine", "square").toMaster().start();
*/
- Tone.Sequence = function () {
+ Tone.FMOscillator = function () {
var options = this.optionsObject(arguments, [
- 'callback',
- 'events',
- 'subdivision'
- ], Tone.Sequence.defaults);
- //remove the events
- var events = options.events;
- delete options.events;
- Tone.Part.call(this, options);
+ 'frequency',
+ 'type',
+ 'modulationType'
+ ], Tone.FMOscillator.defaults);
+ Tone.Source.call(this, options);
/**
- * The subdivison of each note
- * @type {Ticks}
+ * The carrier oscillator
+ * @type {Tone.Oscillator}
* @private
*/
- this._subdivision = this.toTicks(options.subdivision);
- //if no time was passed in, the loop end is the end of the cycle
- if (this.isUndef(options.loopEnd) && !this.isUndef(events)) {
- this._loopEnd = events.length * this._subdivision;
- }
- //defaults to looping
- this._loop = true;
- //add all of the events
- if (!this.isUndef(events)) {
- for (var i = 0; i < events.length; i++) {
- this.add(i, events[i]);
- }
- }
+ this._carrier = new Tone.Oscillator(options.frequency, options.type);
+ /**
+ * The oscillator's frequency
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
+ /**
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this._carrier.detune;
+ this.detune.value = options.detune;
+ /**
+ * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
+ * ratio of the frequency of the modulating signal (mf) to the amplitude of the
+ * modulating signal (ma) -- as in ma/mf.
+ * @type {Positive}
+ * @signal
+ */
+ this.modulationIndex = new Tone.Multiply(options.modulationIndex);
+ this.modulationIndex.units = Tone.Type.Positive;
+ /**
+ * The modulating oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._modulator = new Tone.Oscillator(options.frequency, options.modulationType);
+ /**
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
+ * @signal
+ * @example
+ * //pitch the modulator an octave below carrier
+ * synth.harmonicity.value = 0.5;
+ */
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
+ /**
+ * the node where the modulation happens
+ * @type {Tone.Gain}
+ * @private
+ */
+ this._modulationNode = new Tone.Gain(0);
+ //connections
+ this.frequency.connect(this._carrier.frequency);
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.frequency.chain(this.modulationIndex, this._modulationNode);
+ this._modulator.connect(this._modulationNode.gain);
+ this._modulationNode.connect(this._carrier.frequency);
+ this._carrier.connect(this.output);
+ this.detune.connect(this._modulator.detune);
+ this.phase = options.phase;
+ this._readOnly([
+ 'modulationIndex',
+ 'frequency',
+ 'detune',
+ 'harmonicity'
+ ]);
+ };
+ Tone.extend(Tone.FMOscillator, Tone.Oscillator);
+ /**
+ * default values
+ * @static
+ * @type {Object}
+ * @const
+ */
+ Tone.FMOscillator.defaults = {
+ 'frequency': 440,
+ 'detune': 0,
+ 'phase': 0,
+ 'modulationIndex': 2,
+ 'modulationType': 'square',
+ 'harmonicity': 1
+ };
+ /**
+ * start the oscillator
+ * @param {Time} [time=now]
+ * @private
+ */
+ Tone.FMOscillator.prototype._start = function (time) {
+ time = this.toSeconds(time);
+ this._modulator.start(time);
+ this._carrier.start(time);
};
- Tone.extend(Tone.Sequence, Tone.Part);
/**
- * The default values.
- * @type {Object}
+ * stop the oscillator
+ * @param {Time} time (optional) timing parameter
+ * @private
*/
- Tone.Sequence.defaults = { 'subdivision': '4n' };
+ Tone.FMOscillator.prototype._stop = function (time) {
+ time = this.toSeconds(time);
+ this._modulator.stop(time);
+ this._carrier.stop(time);
+ };
/**
- * The subdivision of the sequence. This can only be
- * set in the constructor. The subdivision is the
- * interval between successive steps.
- * @type {Time}
- * @memberOf Tone.Sequence#
- * @name subdivision
- * @readOnly
+ * The type of the carrier oscillator
+ * @memberOf Tone.FMOscillator#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.Sequence.prototype, 'subdivision', {
+ Object.defineProperty(Tone.FMOscillator.prototype, 'type', {
get: function () {
- return this.toNotation(this._subdivision + 'i');
+ return this._carrier.type;
+ },
+ set: function (type) {
+ this._carrier.type = type;
}
});
/**
- * Get/Set an index of the sequence. If the index contains a subarray,
- * a Tone.Sequence representing that sub-array will be returned.
- * @example
- * var sequence = new Tone.Sequence(playNote, ["E4", "C4", "F#4", ["A4", "Bb3"]])
- * sequence.at(0)// => returns "E4"
- * //set a value
- * sequence.at(0, "G3");
- * //get a nested sequence
- * sequence.at(3).at(1)// => returns "Bb3"
- * @param {Positive} index The index to get or set
- * @param {*} value Optionally pass in the value to set at the given index.
+ * The type of the modulator oscillator
+ * @memberOf Tone.FMOscillator#
+ * @type {String}
+ * @name modulationType
*/
- Tone.Sequence.prototype.at = function (index, value) {
- //if the value is an array,
- if (this.isArray(value)) {
- //remove the current event at that index
- this.remove(index);
+ Object.defineProperty(Tone.FMOscillator.prototype, 'modulationType', {
+ get: function () {
+ return this._modulator.type;
+ },
+ set: function (type) {
+ this._modulator.type = type;
}
- //call the parent's method
- return Tone.Part.prototype.at.call(this, this._indexTime(index), value);
- };
+ });
/**
- * Add an event at an index, if there's already something
- * at that index, overwrite it. If `value` is an array,
- * it will be parsed as a subsequence.
- * @param {Number} index The index to add the event to
- * @param {*} value The value to add at that index
- * @returns {Tone.Sequence} this
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.FMOscillator#
+ * @type {number}
+ * @name phase
*/
- Tone.Sequence.prototype.add = function (index, value) {
- if (value === null) {
- return this;
- }
- if (this.isArray(value)) {
- //make a subsequence and add that to the sequence
- var subSubdivision = Math.round(this._subdivision / value.length) + 'i';
- value = new Tone.Sequence(this._tick.bind(this), value, subSubdivision);
+ Object.defineProperty(Tone.FMOscillator.prototype, 'phase', {
+ get: function () {
+ return this._carrier.phase;
+ },
+ set: function (phase) {
+ this._carrier.phase = phase;
+ this._modulator.phase = phase;
}
- Tone.Part.prototype.add.call(this, this._indexTime(index), value);
- return this;
- };
- /**
- * Remove a value from the sequence by index
- * @param {Number} index The index of the event to remove
- * @returns {Tone.Sequence} this
- */
- Tone.Sequence.prototype.remove = function (index, value) {
- Tone.Part.prototype.remove.call(this, this._indexTime(index), value);
- return this;
- };
+ });
/**
- * Get the time of the index given the Sequence's subdivision
- * @param {Number} index
- * @return {Time} The time of that index
- * @private
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.FMOscillator#
+ * @type {Array}
+ * @name partials
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
*/
- Tone.Sequence.prototype._indexTime = function (index) {
- if (this.isTicks(index)) {
- return index;
- } else {
- return index * this._subdivision + this.startOffset + 'i';
+ Object.defineProperty(Tone.FMOscillator.prototype, 'partials', {
+ get: function () {
+ return this._carrier.partials;
+ },
+ set: function (partials) {
+ this._carrier.partials = partials;
}
- };
+ });
/**
* Clean up.
- * @return {Tone.Sequence} this
+ * @return {Tone.FMOscillator} this
*/
- Tone.Sequence.prototype.dispose = function () {
- Tone.Part.prototype.dispose.call(this);
+ Tone.FMOscillator.prototype.dispose = function () {
+ Tone.Source.prototype.dispose.call(this);
+ this._writable([
+ 'modulationIndex',
+ 'frequency',
+ 'detune',
+ 'harmonicity'
+ ]);
+ this.frequency.dispose();
+ this.frequency = null;
+ this.detune = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this._modulationNode.dispose();
+ this._modulationNode = null;
+ this.modulationIndex.dispose();
+ this.modulationIndex = null;
return this;
};
- return Tone.Sequence;
+ return Tone.FMOscillator;
});
Module(function (Tone) {
/**
- * @class Tone.PulseOscillator is a pulse oscillator with control over pulse width,
- * also known as the duty cycle. At 50% duty cycle (width = 0.5) the wave is
- * a square and only odd-numbered harmonics are present. At all other widths
- * even-numbered harmonics are present. Read more
- * [here](https://wigglewave.wordpress.com/2014/08/16/pulse-waveforms-and-harmonics/).
+ * @class Tone.AMOscillator
*
- * @constructor
* @extends {Tone.Oscillator}
- * @param {Frequency} [frequency] The frequency of the oscillator
- * @param {NormalRange} [width] The width of the pulse
+ * @constructor
+ * @param {Frequency} frequency The starting frequency of the oscillator.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
* @example
- * var pulse = new Tone.PulseOscillator("E5", 0.4).toMaster().start();
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.AMOscillator("Ab3", "sine", "square").toMaster().start();
*/
- Tone.PulseOscillator = function () {
+ Tone.AMOscillator = function () {
var options = this.optionsObject(arguments, [
'frequency',
- 'width'
- ], Tone.Oscillator.defaults);
+ 'type',
+ 'modulationType'
+ ], Tone.AMOscillator.defaults);
Tone.Source.call(this, options);
/**
- * The width of the pulse.
- * @type {NormalRange}
+ * The carrier oscillator
+ * @type {Tone.Oscillator}
+ * @private
+ */
+ this._carrier = new Tone.Oscillator(options.frequency, options.type);
+ /**
+ * The oscillator's frequency
+ * @type {Frequency}
* @signal
*/
- this.width = new Tone.Signal(options.width, Tone.Type.NormalRange);
+ this.frequency = this._carrier.frequency;
/**
- * gate the width amount
- * @type {GainNode}
- * @private
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
*/
- this._widthGate = this.context.createGain();
+ this.detune = this._carrier.detune;
+ this.detune.value = options.detune;
/**
- * the sawtooth oscillator
- * @type {Tone.Oscillator}
+ * The modulating oscillator
+ * @type {Tone.Oscillator}
* @private
*/
- this._sawtooth = new Tone.Oscillator({
- frequency: options.frequency,
- detune: options.detune,
- type: 'sawtooth',
- phase: options.phase
- });
+ this._modulator = new Tone.Oscillator(options.frequency, options.modulationType);
/**
- * The frequency control.
- * @type {Frequency}
- * @signal
+ * convert the -1,1 output to 0,1
+ * @type {Tone.AudioToGain}
+ * @private
*/
- this.frequency = this._sawtooth.frequency;
+ this._modulationScale = new Tone.AudioToGain();
/**
- * The detune in cents.
- * @type {Cents}
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
* @signal
+ * @example
+ * //pitch the modulator an octave below carrier
+ * synth.harmonicity.value = 0.5;
*/
- this.detune = this._sawtooth.detune;
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
/**
- * Threshold the signal to turn it into a square
- * @type {Tone.WaveShaper}
+ * the node where the modulation happens
+ * @type {Tone.Gain}
* @private
*/
- this._thresh = new Tone.WaveShaper(function (val) {
- if (val < 0) {
- return -1;
- } else {
- return 1;
- }
- });
+ this._modulationNode = new Tone.Gain(0);
//connections
- this._sawtooth.chain(this._thresh, this.output);
- this.width.chain(this._widthGate, this._thresh);
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.detune.connect(this._modulator.detune);
+ this._modulator.chain(this._modulationScale, this._modulationNode.gain);
+ this._carrier.chain(this._modulationNode, this.output);
+ this.phase = options.phase;
this._readOnly([
- 'width',
'frequency',
- 'detune'
+ 'detune',
+ 'harmonicity'
]);
};
- Tone.extend(Tone.PulseOscillator, Tone.Oscillator);
+ Tone.extend(Tone.AMOscillator, Tone.Oscillator);
/**
- * The default parameters.
+ * default values
* @static
- * @const
* @type {Object}
+ * @const
*/
- Tone.PulseOscillator.defaults = {
+ Tone.AMOscillator.defaults = {
'frequency': 440,
'detune': 0,
'phase': 0,
- 'width': 0.2
+ 'modulationType': 'square',
+ 'harmonicity': 1
};
/**
* start the oscillator
- * @param {Time} time
+ * @param {Time} [time=now]
* @private
*/
- Tone.PulseOscillator.prototype._start = function (time) {
+ Tone.AMOscillator.prototype._start = function (time) {
time = this.toSeconds(time);
- this._sawtooth.start(time);
- this._widthGate.gain.setValueAtTime(1, time);
+ this._modulator.start(time);
+ this._carrier.start(time);
};
/**
* stop the oscillator
- * @param {Time} time
+ * @param {Time} time (optional) timing parameter
* @private
*/
- Tone.PulseOscillator.prototype._stop = function (time) {
+ Tone.AMOscillator.prototype._stop = function (time) {
time = this.toSeconds(time);
- this._sawtooth.stop(time);
- //the width is still connected to the output.
- //that needs to be stopped also
- this._widthGate.gain.setValueAtTime(0, time);
+ this._modulator.stop(time);
+ this._carrier.stop(time);
};
/**
- * The phase of the oscillator in degrees.
- * @memberOf Tone.PulseOscillator#
- * @type {Degrees}
- * @name phase
+ * The type of the carrier oscillator
+ * @memberOf Tone.AMOscillator#
+ * @type {string}
+ * @name type
*/
- Object.defineProperty(Tone.PulseOscillator.prototype, 'phase', {
+ Object.defineProperty(Tone.AMOscillator.prototype, 'type', {
get: function () {
- return this._sawtooth.phase;
+ return this._carrier.type;
},
- set: function (phase) {
- this._sawtooth.phase = phase;
+ set: function (type) {
+ this._carrier.type = type;
}
});
/**
- * The type of the oscillator. Always returns "pulse".
- * @readOnly
- * @memberOf Tone.PulseOscillator#
+ * The type of the modulator oscillator
+ * @memberOf Tone.AMOscillator#
* @type {string}
- * @name type
+ * @name modulationType
*/
- Object.defineProperty(Tone.PulseOscillator.prototype, 'type', {
+ Object.defineProperty(Tone.AMOscillator.prototype, 'modulationType', {
get: function () {
- return 'pulse';
+ return this._modulator.type;
+ },
+ set: function (type) {
+ this._modulator.type = type;
}
});
/**
- * The partials of the waveform. Cannot set partials for this waveform type
- * @memberOf Tone.PulseOscillator#
+ * The phase of the oscillator in degrees.
+ * @memberOf Tone.AMOscillator#
+ * @type {number}
+ * @name phase
+ */
+ Object.defineProperty(Tone.AMOscillator.prototype, 'phase', {
+ get: function () {
+ return this._carrier.phase;
+ },
+ set: function (phase) {
+ this._carrier.phase = phase;
+ this._modulator.phase = phase;
+ }
+ });
+ /**
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.AMOscillator#
* @type {Array}
* @name partials
- * @private
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
*/
- Object.defineProperty(Tone.PulseOscillator.prototype, 'partials', {
+ Object.defineProperty(Tone.AMOscillator.prototype, 'partials', {
get: function () {
- return [];
+ return this._carrier.partials;
+ },
+ set: function (partials) {
+ this._carrier.partials = partials;
}
});
/**
- * Clean up method.
- * @return {Tone.PulseOscillator} this
+ * Clean up.
+ * @return {Tone.AMOscillator} this
*/
- Tone.PulseOscillator.prototype.dispose = function () {
+ Tone.AMOscillator.prototype.dispose = function () {
Tone.Source.prototype.dispose.call(this);
- this._sawtooth.dispose();
- this._sawtooth = null;
this._writable([
- 'width',
'frequency',
- 'detune'
+ 'detune',
+ 'harmonicity'
]);
- this.width.dispose();
- this.width = null;
- this._widthGate.disconnect();
- this._widthGate = null;
- this._widthGate = null;
- this._thresh.disconnect();
- this._thresh = null;
this.frequency = null;
this.detune = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this._modulationNode.dispose();
+ this._modulationNode = null;
+ this._modulationScale.dispose();
+ this._modulationScale = null;
return this;
};
- return Tone.PulseOscillator;
+ return Tone.AMOscillator;
});
Module(function (Tone) {
/**
- * @class Tone.PWMOscillator modulates the width of a Tone.PulseOscillator
- * at the modulationFrequency. This has the effect of continuously
- * changing the timbre of the oscillator by altering the harmonics
- * generated.
+ * @class Tone.FatOscillator
*
* @extends {Tone.Oscillator}
* @constructor
* @param {Frequency} frequency The starting frequency of the oscillator.
- * @param {Frequency} modulationFrequency The modulation frequency of the width of the pulse.
+ * @param {String} type The type of the carrier oscillator.
+ * @param {String} modulationType The type of the modulator oscillator.
* @example
- * var pwm = new Tone.PWMOscillator("Ab3", 0.3).toMaster().start();
+ * //a sine oscillator frequency-modulated by a square wave
+ * var fmOsc = new Tone.FatOscillator("Ab3", "sine", "square").toMaster().start();
*/
- Tone.PWMOscillator = function () {
+ Tone.FatOscillator = function () {
var options = this.optionsObject(arguments, [
'frequency',
- 'modulationFrequency'
- ], Tone.PWMOscillator.defaults);
+ 'type',
+ 'spread'
+ ], Tone.FatOscillator.defaults);
Tone.Source.call(this, options);
/**
- * the pulse oscillator
- * @type {Tone.PulseOscillator}
- * @private
+ * The oscillator's frequency
+ * @type {Frequency}
+ * @signal
*/
- this._pulse = new Tone.PulseOscillator(options.modulationFrequency);
- //change the pulse oscillator type
- this._pulse._sawtooth.type = 'sine';
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
/**
- * the modulator
- * @type {Tone.Oscillator}
+ * The detune control signal.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
+ /**
+ * The array of oscillators
+ * @type {Array}
* @private
*/
- this._modulator = new Tone.Oscillator({
- 'frequency': options.frequency,
- 'detune': options.detune,
- 'phase': options.phase
- });
+ this._oscillators = [];
/**
- * Scale the oscillator so it doesn't go silent
- * at the extreme values.
- * @type {Tone.Multiply}
+ * The total spread of the oscillators
+ * @type {Cents}
* @private
*/
- this._scale = new Tone.Multiply(1.01);
+ this._spread = options.spread;
/**
- * The frequency control.
- * @type {Frequency}
- * @signal
+ * The type of the oscillator
+ * @type {String}
+ * @private
*/
- this.frequency = this._modulator.frequency;
+ this._type = options.type;
/**
- * The detune of the oscillator.
- * @type {Cents}
- * @signal
+ * The phase of the oscillators
+ * @type {Degrees}
+ * @private
*/
- this.detune = this._modulator.detune;
+ this._phase = options.phase;
/**
- * The modulation rate of the oscillator.
- * @type {Frequency}
- * @signal
+ * The partials array
+ * @type {Array}
+ * @private
*/
- this.modulationFrequency = this._pulse.frequency;
- //connections
- this._modulator.chain(this._scale, this._pulse.width);
- this._pulse.connect(this.output);
+ this._partials = this.defaultArg(options.partials, []);
+ //set the count initially
+ this.count = options.count;
this._readOnly([
- 'modulationFrequency',
'frequency',
'detune'
]);
};
- Tone.extend(Tone.PWMOscillator, Tone.Oscillator);
+ Tone.extend(Tone.FatOscillator, Tone.Oscillator);
/**
* default values
* @static
* @type {Object}
* @const
*/
- Tone.PWMOscillator.defaults = {
+ Tone.FatOscillator.defaults = {
'frequency': 440,
'detune': 0,
'phase': 0,
- 'modulationFrequency': 0.4
+ 'spread': 20,
+ 'count': 3,
+ 'type': 'sawtooth'
};
/**
* start the oscillator
* @param {Time} [time=now]
* @private
*/
- Tone.PWMOscillator.prototype._start = function (time) {
+ Tone.FatOscillator.prototype._start = function (time) {
time = this.toSeconds(time);
- this._modulator.start(time);
- this._pulse.start(time);
+ this._forEach(function (osc) {
+ osc.start(time);
+ });
};
/**
* stop the oscillator
* @param {Time} time (optional) timing parameter
* @private
*/
- Tone.PWMOscillator.prototype._stop = function (time) {
+ Tone.FatOscillator.prototype._stop = function (time) {
time = this.toSeconds(time);
- this._modulator.stop(time);
- this._pulse.stop(time);
+ this._forEach(function (osc) {
+ osc.stop(time);
+ });
};
/**
- * The type of the oscillator. Always returns "pwm".
- * @readOnly
- * @memberOf Tone.PWMOscillator#
+ * Iterate over all of the oscillators
+ * @param {Function} iterator The iterator function
+ * @private
+ */
+ Tone.FatOscillator.prototype._forEach = function (iterator) {
+ for (var i = 0; i < this._oscillators.length; i++) {
+ iterator.call(this, this._oscillators[i], i);
+ }
+ };
+ /**
+ * The type of the carrier oscillator
+ * @memberOf Tone.FatOscillator#
* @type {string}
* @name type
*/
- Object.defineProperty(Tone.PWMOscillator.prototype, 'type', {
+ Object.defineProperty(Tone.FatOscillator.prototype, 'type', {
get: function () {
- return 'pwm';
+ return this._type;
+ },
+ set: function (type) {
+ this._type = type;
+ this._forEach(function (osc) {
+ osc.type = type;
+ });
}
});
/**
- * The partials of the waveform. Cannot set partials for this waveform type
- * @memberOf Tone.PWMOscillator#
- * @type {Array}
- * @name partials
- * @private
+ * The detune spread between the oscillators. If "count" is
+ * set to 3 oscillators and the "spread" is set to 40,
+ * the three oscillators would be detuned like this: [-20, 0, 20]
+ * for a total detune spread of 40 cents.
+ * @memberOf Tone.FatOscillator#
+ * @type {Cents}
+ * @name spread
*/
- Object.defineProperty(Tone.PWMOscillator.prototype, 'partials', {
+ Object.defineProperty(Tone.FatOscillator.prototype, 'spread', {
get: function () {
- return [];
+ return this._spread;
+ },
+ set: function (spread) {
+ this._spread = spread;
+ if (this._oscillators.length > 1) {
+ var start = -spread / 2;
+ var step = spread / (this._oscillators.length - 1);
+ this._forEach(function (osc, i) {
+ osc.detune.value = start + step * i;
+ });
+ }
+ }
+ });
+ /**
+ * The number of detuned oscillators
+ * @memberOf Tone.FatOscillator#
+ * @type {Number}
+ * @name count
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, 'count', {
+ get: function () {
+ return this._oscillators.length;
+ },
+ set: function (count) {
+ count = Math.max(count, 1);
+ if (this._oscillators.length !== count) {
+ // var partials = this.partials;
+ // var type = this.type;
+ //dispose the previous oscillators
+ this._forEach(function (osc) {
+ osc.dispose();
+ });
+ this._oscillators = [];
+ for (var i = 0; i < count; i++) {
+ var osc = new Tone.Oscillator();
+ if (this.type === Tone.Oscillator.Type.Custom) {
+ osc.partials = this._partials;
+ } else {
+ osc.type = this._type;
+ }
+ osc.phase = this._phase;
+ osc.volume.value = -6 - count;
+ this.frequency.connect(osc.frequency);
+ this.detune.connect(osc.detune);
+ osc.connect(this.output);
+ this._oscillators[i] = osc;
+ }
+ //set the spread
+ this.spread = this._spread;
+ if (this.state === Tone.State.Started) {
+ this._forEach(function (osc) {
+ osc.start();
+ });
+ }
+ }
}
});
/**
* The phase of the oscillator in degrees.
- * @memberOf Tone.PWMOscillator#
- * @type {number}
+ * @memberOf Tone.FatOscillator#
+ * @type {Number}
* @name phase
*/
- Object.defineProperty(Tone.PWMOscillator.prototype, 'phase', {
+ Object.defineProperty(Tone.FatOscillator.prototype, 'phase', {
get: function () {
- return this._modulator.phase;
+ return this._phase;
},
set: function (phase) {
- this._modulator.phase = phase;
+ this._phase = phase;
+ this._forEach(function (osc) {
+ osc.phase = phase;
+ });
+ }
+ });
+ /**
+ * The partials of the carrier waveform. A partial represents
+ * the amplitude at a harmonic. The first harmonic is the
+ * fundamental frequency, the second is the octave and so on
+ * following the harmonic series.
+ * Setting this value will automatically set the type to "custom".
+ * The value is an empty array when the type is not "custom".
+ * @memberOf Tone.FatOscillator#
+ * @type {Array}
+ * @name partials
+ * @example
+ * osc.partials = [1, 0.2, 0.01];
+ */
+ Object.defineProperty(Tone.FatOscillator.prototype, 'partials', {
+ get: function () {
+ return this._partials;
+ },
+ set: function (partials) {
+ this._partials = partials;
+ this._type = Tone.Oscillator.Type.Custom;
+ this._forEach(function (osc) {
+ osc.partials = partials;
+ });
}
});
/**
* Clean up.
- * @return {Tone.PWMOscillator} this
+ * @return {Tone.FatOscillator} this
*/
- Tone.PWMOscillator.prototype.dispose = function () {
+ Tone.FatOscillator.prototype.dispose = function () {
Tone.Source.prototype.dispose.call(this);
- this._pulse.dispose();
- this._pulse = null;
- this._scale.dispose();
- this._scale = null;
- this._modulator.dispose();
- this._modulator = null;
this._writable([
- 'modulationFrequency',
'frequency',
'detune'
]);
+ this.frequency.dispose();
this.frequency = null;
+ this.detune.dispose();
this.detune = null;
- this.modulationFrequency = null;
+ this._forEach(function (osc) {
+ osc.dispose();
+ });
+ this._oscillators = null;
+ this._partials = null;
return this;
};
- return Tone.PWMOscillator;
+ return Tone.FatOscillator;
});
Module(function (Tone) {
/**
* @class Tone.OmniOscillator aggregates Tone.Oscillator, Tone.PulseOscillator,
- * and Tone.PWMOscillator into one class, allowing it to have the
- * types: sine, square, triangle, sawtooth, pulse or pwm. Additionally,
- * OmniOscillator is capable of setting the first x number of partials
- * of the oscillator. For example: "sine4" would set be the first 4
- * partials of the sine wave and "triangle8" would set the first
- * 8 partials of the triangle wave.
+ * Tone.PWMOscillator, Tone.FMOscillator, Tone.AMOscillator, and Tone.FatOscillator
+ * into one class. The oscillator class can be changed by setting the `type`.
+ * `omniOsc.type = "pwm"` will set it to the Tone.PWMOscillator. Prefixing
+ * any of the basic types ("sine", "square4", etc.) with "fm", "am", or "fat"
+ * will use the FMOscillator, AMOscillator or FatOscillator respectively.
+ * For example: `omniOsc.type = "fatsawtooth"` will create set the oscillator
+ * to a FatOscillator of type "sawtooth".
*
* @extends {Tone.Oscillator}
* @constructor
* @param {Frequency} frequency The initial frequency of the oscillator.
- * @param {string} type The type of the oscillator.
+ * @param {String} type The type of the oscillator.
* @example
* var omniOsc = new Tone.OmniOscillator("C#4", "pwm");
*/
@@ -16090,26 +16846,24 @@
this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
/**
* the type of the oscillator source
- * @type {string}
+ * @type {String}
* @private
*/
this._sourceType = undefined;
/**
* the oscillator
- * @type {Tone.Oscillator|Tone.PWMOscillator|Tone.PulseOscillator}
+ * @type {Tone.Oscillator}
* @private
*/
this._oscillator = null;
//set the oscillator
this.type = options.type;
- this.phase = options.phase;
this._readOnly([
'frequency',
'detune'
]);
- if (this.isArray(options.partials)) {
- this.partials = options.partials;
- }
+ //set the options
+ this.set(options);
};
Tone.extend(Tone.OmniOscillator, Tone.Oscillator);
/**
@@ -16122,19 +16876,19 @@
'frequency': 440,
'detune': 0,
'type': 'sine',
- 'phase': 0,
- 'width': 0.4,
- //only applies if the oscillator is set to "pulse",
- 'modulationFrequency': 0.4
+ 'phase': 0
};
/**
- * @enum {string}
+ * @enum {String}
* @private
*/
var OmniOscType = {
- PulseOscillator: 'PulseOscillator',
- PWMOscillator: 'PWMOscillator',
- Oscillator: 'Oscillator'
+ Pulse: 'PulseOscillator',
+ PWM: 'PWMOscillator',
+ Osc: 'Oscillator',
+ FM: 'FMOscillator',
+ AM: 'AMOscillator',
+ Fat: 'FatOscillator'
};
/**
* start the oscillator
@@ -16153,34 +16907,54 @@
this._oscillator.stop(time);
};
/**
- * The type of the oscillator. sine, square, triangle, sawtooth, pwm, or pulse.
+ * The type of the oscillator. Can be any of the basic types: sine, square, triangle, sawtooth. Or
+ * prefix the basic types with "fm", "am", or "fat" to use the FMOscillator, AMOscillator or FatOscillator
+ * types. The oscillator could also be set to "pwm" or "pulse". All of the parameters of the
+ * oscillator's class are accessible when the oscillator is set to that type, but throws an error
+ * when it's not.
+ *
* @memberOf Tone.OmniOscillator#
- * @type {string}
+ * @type {String}
* @name type
+ * @example
+ * omniOsc.type = "pwm";
+ * //modulationFrequency is parameter which is available
+ * //only when the type is "pwm".
+ * omniOsc.modulationFrequency.value = 0.5;
+ * @example
+ * //an square wave frequency modulated by a sawtooth
+ * omniOsc.type = "fmsquare";
+ * omniOsc.modulationType = "sawtooth";
*/
Object.defineProperty(Tone.OmniOscillator.prototype, 'type', {
get: function () {
- return this._oscillator.type;
+ var prefix = '';
+ if (this._sourceType === OmniOscType.FM) {
+ prefix = 'fm';
+ } else if (this._sourceType === OmniOscType.AM) {
+ prefix = 'am';
+ } else if (this._sourceType === OmniOscType.Fat) {
+ prefix = 'fat';
+ }
+ return prefix + this._oscillator.type;
},
set: function (type) {
- if (type.indexOf('sine') === 0 || type.indexOf('square') === 0 || type.indexOf('triangle') === 0 || type.indexOf('sawtooth') === 0 || type === Tone.Oscillator.Type.Custom) {
- if (this._sourceType !== OmniOscType.Oscillator) {
- this._sourceType = OmniOscType.Oscillator;
- this._createNewOscillator(Tone.Oscillator);
- }
- this._oscillator.type = type;
+ if (type.substr(0, 2) === 'fm') {
+ this._createNewOscillator(OmniOscType.FM);
+ this._oscillator.type = type.substr(2);
+ } else if (type.substr(0, 2) === 'am') {
+ this._createNewOscillator(OmniOscType.AM);
+ this._oscillator.type = type.substr(2);
+ } else if (type.substr(0, 3) === 'fat') {
+ this._createNewOscillator(OmniOscType.Fat);
+ this._oscillator.type = type.substr(3);
} else if (type === 'pwm') {
- if (this._sourceType !== OmniOscType.PWMOscillator) {
- this._sourceType = OmniOscType.PWMOscillator;
- this._createNewOscillator(Tone.PWMOscillator);
- }
+ this._createNewOscillator(OmniOscType.PWM);
} else if (type === 'pulse') {
- if (this._sourceType !== OmniOscType.PulseOscillator) {
- this._sourceType = OmniOscType.PulseOscillator;
- this._createNewOscillator(Tone.PulseOscillator);
- }
+ this._createNewOscillator(OmniOscType.Pulse);
} else {
- throw new Error('Tone.OmniOscillator does not support type ' + type);
+ this._createNewOscillator(OmniOscType.Osc);
+ this._oscillator.type = type;
}
}
});
@@ -16191,6 +16965,7 @@
* following the harmonic series.
* Setting this value will automatically set the type to "custom".
* The value is an empty array when the type is not "custom".
+ * This is not available on "pwm" and "pulse" oscillator types.
* @memberOf Tone.OmniOscillator#
* @type {Array}
* @name partials
@@ -16202,34 +16977,53 @@
return this._oscillator.partials;
},
set: function (partials) {
- if (this._sourceType !== OmniOscType.Oscillator) {
- this.type = Tone.Oscillator.Type.Custom;
- }
this._oscillator.partials = partials;
}
});
+ /**
+ * Set a member/attribute of the oscillator.
+ * @param {Object|String} params
+ * @param {number=} value
+ * @param {Time=} rampTime
+ * @returns {Tone.OmniOscillator} this
+ */
+ Tone.OmniOscillator.prototype.set = function (params, value) {
+ //make sure the type is set first
+ if (params === 'type') {
+ this.type = value;
+ } else if (this.isObject(params) && params.hasOwnProperty('type')) {
+ this.type = params.type;
+ }
+ //then set the rest
+ Tone.prototype.set.apply(this, arguments);
+ return this;
+ };
/**
* connect the oscillator to the frequency and detune signals
* @private
*/
- Tone.OmniOscillator.prototype._createNewOscillator = function (OscillatorConstructor) {
- //short delay to avoid clicks on the change
- var now = this.now() + this.blockTime;
- if (this._oscillator !== null) {
- var oldOsc = this._oscillator;
- oldOsc.stop(now);
- //dispose the old one
- setTimeout(function () {
- oldOsc.dispose();
- oldOsc = null;
- }, this.blockTime * 1000);
- }
- this._oscillator = new OscillatorConstructor();
- this.frequency.connect(this._oscillator.frequency);
- this.detune.connect(this._oscillator.detune);
- this._oscillator.connect(this.output);
- if (this.state === Tone.State.Started) {
- this._oscillator.start(now);
+ Tone.OmniOscillator.prototype._createNewOscillator = function (oscType) {
+ if (oscType !== this._sourceType) {
+ this._sourceType = oscType;
+ var OscillatorConstructor = Tone[oscType];
+ //short delay to avoid clicks on the change
+ var now = this.now() + this.blockTime;
+ if (this._oscillator !== null) {
+ var oldOsc = this._oscillator;
+ oldOsc.stop(now);
+ //dispose the old one
+ setTimeout(function () {
+ oldOsc.dispose();
+ oldOsc = null;
+ }, this.blockTime * 1000);
+ }
+ this._oscillator = new OscillatorConstructor();
+ this.frequency.connect(this._oscillator.frequency);
+ this.detune.connect(this._oscillator.detune);
+ this._oscillator.connect(this.output);
+ if (this.state === Tone.State.Started) {
+ this._oscillator.start(now);
+ }
}
};
/**
@@ -16247,7 +17041,7 @@
}
});
/**
- * The width of the oscillator (only if the oscillator is set to pulse)
+ * The width of the oscillator (only if the oscillator is set to "pulse")
* @memberOf Tone.OmniOscillator#
* @type {NormalRange}
* @signal
@@ -16257,16 +17051,110 @@
* //can access the width attribute only if type === "pulse"
* omniOsc.width.value = 0.2;
*/
- Object.defineProperty(Tone.OmniOscillator.prototype, 'width', {
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'width', {
+ get: function () {
+ if (this._sourceType === OmniOscType.Pulse) {
+ return this._oscillator.width;
+ }
+ }
+ });
+ /**
+ * The number of detuned oscillators
+ * @memberOf Tone.OmniOscillator#
+ * @type {Number}
+ * @name count
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'count', {
+ get: function () {
+ if (this._sourceType === OmniOscType.Fat) {
+ return this._oscillator.count;
+ }
+ },
+ set: function (count) {
+ if (this._sourceType === OmniOscType.Fat) {
+ this._oscillator.count = count;
+ }
+ }
+ });
+ /**
+ * The detune spread between the oscillators. If "count" is
+ * set to 3 oscillators and the "spread" is set to 40,
+ * the three oscillators would be detuned like this: [-20, 0, 20]
+ * for a total detune spread of 40 cents. See Tone.FatOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @type {Cents}
+ * @name spread
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'spread', {
+ get: function () {
+ if (this._sourceType === OmniOscType.Fat) {
+ return this._oscillator.spread;
+ }
+ },
+ set: function (spread) {
+ if (this._sourceType === OmniOscType.Fat) {
+ this._oscillator.spread = spread;
+ }
+ }
+ });
+ /**
+ * The type of the modulator oscillator. Only if the oscillator
+ * is set to "am" or "fm" types. see. Tone.AMOscillator or Tone.FMOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @type {String}
+ * @name modulationType
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationType', {
+ get: function () {
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) {
+ return this._oscillator.modulationType;
+ }
+ },
+ set: function (mType) {
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) {
+ this._oscillator.modulationType = mType;
+ }
+ }
+ });
+ /**
+ * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
+ * ratio of the frequency of the modulating signal (mf) to the amplitude of the
+ * modulating signal (ma) -- as in ma/mf.
+ * See Tone.FMOscillator for more info.
+ * @type {Positive}
+ * @signal
+ * @name modulationIndex
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationIndex', {
+ get: function () {
+ if (this._sourceType === OmniOscType.FM) {
+ return this._oscillator.modulationIndex;
+ }
+ }
+ });
+ /**
+ * Harmonicity is the frequency ratio between the carrier and the modulator oscillators.
+ * A harmonicity of 1 gives both oscillators the same frequency.
+ * Harmonicity = 2 means a change of an octave. See Tone.AMOscillator or Tone.FMOscillator
+ * for more info.
+ * @memberOf Tone.OmniOscillator#
+ * @signal
+ * @type {Positive}
+ * @name harmonicity
+ */
+ Object.defineProperty(Tone.OmniOscillator.prototype, 'harmonicity', {
get: function () {
- if (this._sourceType === OmniOscType.PulseOscillator) {
- return this._oscillator.width;
+ if (this._sourceType === OmniOscType.FM || this._sourceType === OmniOscType.AM) {
+ return this._oscillator.harmonicity;
}
}
});
/**
* The modulationFrequency Signal of the oscillator
- * (only if the oscillator type is set to pwm).
+ * (only if the oscillator type is set to pwm). See
+ * Tone.PWMOscillator for more info.
* @memberOf Tone.OmniOscillator#
* @type {Frequency}
* @signal
@@ -16278,7 +17166,7 @@
*/
Object.defineProperty(Tone.OmniOscillator.prototype, 'modulationFrequency', {
get: function () {
- if (this._sourceType === OmniOscType.PWMOscillator) {
+ if (this._sourceType === OmniOscType.PWM) {
return this._oscillator.modulationFrequency;
}
}
@@ -16485,7 +17373,311 @@
}
return this;
};
- return Tone.Monophonic;
+ return Tone.Monophonic;
+ });
+ Module(function (Tone) {
+
+ /**
+ * @class Tone.Synth is composed simply of a Tone.OmniOscillator
+ * routed through a Tone.AmplitudeEnvelope.
+ *
+ *
+ * @constructor
+ * @extends {Tone.Monophonic}
+ * @param {Object} [options] the options available for the synth
+ * see defaults below
+ * @example
+ * var synth = new Tone.Synth().toMaster();
+ * synth.triggerAttackRelease("C4", "8n");
+ */
+ Tone.Synth = function (options) {
+ //get the defaults
+ options = this.defaultArg(options, Tone.Synth.defaults);
+ Tone.Monophonic.call(this, options);
+ /**
+ * The oscillator.
+ * @type {Tone.OmniOscillator}
+ */
+ this.oscillator = new Tone.OmniOscillator(options.oscillator);
+ /**
+ * The frequency control.
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = this.oscillator.frequency;
+ /**
+ * The detune control.
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = this.oscillator.detune;
+ /**
+ * The amplitude envelope.
+ * @type {Tone.AmplitudeEnvelope}
+ */
+ this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
+ //connect the oscillators to the output
+ this.oscillator.chain(this.envelope, this.output);
+ //start the oscillators
+ this.oscillator.start();
+ this._readOnly([
+ 'oscillator',
+ 'frequency',
+ 'detune',
+ 'envelope'
+ ]);
+ };
+ Tone.extend(Tone.Synth, Tone.Monophonic);
+ /**
+ * @const
+ * @static
+ * @type {Object}
+ */
+ Tone.Synth.defaults = {
+ 'oscillator': { 'type': 'triangle' },
+ 'envelope': {
+ 'attack': 0.005,
+ 'decay': 0.1,
+ 'sustain': 0.3,
+ 'release': 1
+ }
+ };
+ /**
+ * start the attack portion of the envelope
+ * @param {Time} [time=now] the time the attack should start
+ * @param {number} [velocity=1] the velocity of the note (0-1)
+ * @returns {Tone.Synth} this
+ * @private
+ */
+ Tone.Synth.prototype._triggerEnvelopeAttack = function (time, velocity) {
+ //the envelopes
+ this.envelope.triggerAttack(time, velocity);
+ return this;
+ };
+ /**
+ * start the release portion of the envelope
+ * @param {Time} [time=now] the time the release should start
+ * @returns {Tone.Synth} this
+ * @private
+ */
+ Tone.Synth.prototype._triggerEnvelopeRelease = function (time) {
+ this.envelope.triggerRelease(time);
+ return this;
+ };
+ /**
+ * clean up
+ * @returns {Tone.Synth} this
+ */
+ Tone.Synth.prototype.dispose = function () {
+ Tone.Monophonic.prototype.dispose.call(this);
+ this._writable([
+ 'oscillator',
+ 'frequency',
+ 'detune',
+ 'envelope'
+ ]);
+ this.oscillator.dispose();
+ this.oscillator = null;
+ this.envelope.dispose();
+ this.envelope = null;
+ this.frequency = null;
+ this.detune = null;
+ return this;
+ };
+ return Tone.Synth;
+ });
+ Module(function (Tone) {
+
+ /**
+ * @class AMSynth uses the output of one Tone.Synth to modulate the
+ * amplitude of another Tone.Synth. The harmonicity (the ratio between
+ * the two signals) affects the timbre of the output signal greatly.
+ * Read more about Amplitude Modulation Synthesis on
+ * [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm).
+ *
+ *
+ * @constructor
+ * @extends {Tone.Monophonic}
+ * @param {Object} [options] the options available for the synth
+ * see defaults below
+ * @example
+ * var synth = new Tone.AMSynth().toMaster();
+ * synth.triggerAttackRelease("C4", "4n");
+ */
+ Tone.AMSynth = function (options) {
+ options = this.defaultArg(options, Tone.AMSynth.defaults);
+ Tone.Monophonic.call(this, options);
+ /**
+ * The carrier voice.
+ * @type {Tone.Synth}
+ */
+ this._carrier = new Tone.Synth();
+ this._carrier.volume.value = -10;
+ /**
+ * The carrier's oscillator
+ * @type {Tone.Oscillator}
+ */
+ this.oscillator = this._carrier.oscillator;
+ /**
+ * The carrier's envelope
+ * @type {Tone.Oscillator}
+ */
+ this.envelope = this._carrier.envelope.set(options.envelope);
+ /**
+ * The modulator voice.
+ * @type {Tone.Synth}
+ */
+ this._modulator = new Tone.Synth();
+ this._modulator.volume.value = -10;
+ /**
+ * The modulator's oscillator which is applied
+ * to the amplitude of the oscillator
+ * @type {Tone.Oscillator}
+ */
+ this.modulation = this._modulator.oscillator.set(options.modulation);
+ /**
+ * The modulator's envelope
+ * @type {Tone.Oscillator}
+ */
+ this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope);
+ /**
+ * The frequency.
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ /**
+ * The detune in cents
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
+ /**
+ * Harmonicity is the ratio between the two voices. A harmonicity of
+ * 1 is no change. Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
+ * @signal
+ * @example
+ * //pitch voice1 an octave below voice0
+ * synth.harmonicity.value = 0.5;
+ */
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
+ /**
+ * convert the -1,1 output to 0,1
+ * @type {Tone.AudioToGain}
+ * @private
+ */
+ this._modulationScale = new Tone.AudioToGain();
+ /**
+ * the node where the modulation happens
+ * @type {GainNode}
+ * @private
+ */
+ this._modulationNode = this.context.createGain();
+ //control the two voices frequency
+ this.frequency.connect(this._carrier.frequency);
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.detune.fan(this._carrier.detune, this._modulator.detune);
+ this._modulator.chain(this._modulationScale, this._modulationNode.gain);
+ this._carrier.chain(this._modulationNode, this.output);
+ this._readOnly([
+ 'frequency',
+ 'harmonicity',
+ 'oscillator',
+ 'envelope',
+ 'modulation',
+ 'modulationEnvelope',
+ 'detune'
+ ]);
+ };
+ Tone.extend(Tone.AMSynth, Tone.Monophonic);
+ /**
+ * @static
+ * @type {Object}
+ */
+ Tone.AMSynth.defaults = {
+ 'harmonicity': 3,
+ 'detune': 0,
+ 'oscillator': { 'type': 'sine' },
+ 'envelope': {
+ 'attack': 0.01,
+ 'decay': 0.01,
+ 'sustain': 1,
+ 'release': 0.5
+ },
+ 'moduation': { 'type': 'square' },
+ 'modulationEnvelope': {
+ 'attack': 0.5,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
+ }
+ };
+ /**
+ * trigger the attack portion of the note
+ *
+ * @param {Time} [time=now] the time the note will occur
+ * @param {NormalRange} [velocity=1] the velocity of the note
+ * @private
+ * @returns {Tone.AMSynth} this
+ */
+ Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
+ //the port glide
+ time = this.toSeconds(time);
+ //the envelopes
+ this.envelope.triggerAttack(time, velocity);
+ this.modulationEnvelope.triggerAttack(time, velocity);
+ return this;
+ };
+ /**
+ * trigger the release portion of the note
+ *
+ * @param {Time} [time=now] the time the note will release
+ * @private
+ * @returns {Tone.AMSynth} this
+ */
+ Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) {
+ this.envelope.triggerRelease(time);
+ this.modulationEnvelope.triggerRelease(time);
+ return this;
+ };
+ /**
+ * clean up
+ * @returns {Tone.AMSynth} this
+ */
+ Tone.AMSynth.prototype.dispose = function () {
+ Tone.Monophonic.prototype.dispose.call(this);
+ this._writable([
+ 'frequency',
+ 'harmonicity',
+ 'oscillator',
+ 'envelope',
+ 'modulation',
+ 'modulationEnvelope',
+ 'detune'
+ ]);
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
+ this.frequency.dispose();
+ this.frequency = null;
+ this.detune.dispose();
+ this.detune = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this._modulationScale.dispose();
+ this._modulationScale = null;
+ this._modulationNode.disconnect();
+ this._modulationNode = null;
+ this.oscillator = null;
+ this.envelope = null;
+ this.modulationEnvelope = null;
+ this.modulation = null;
+ return this;
+ };
+ return Tone.AMSynth;
});
Module(function (Tone) {
@@ -16647,42 +17839,264 @@
Module(function (Tone) {
/**
- * @class AMSynth uses the output of one Tone.MonoSynth to modulate the
- * amplitude of another Tone.MonoSynth. The harmonicity (the ratio between
- * the two signals) affects the timbre of the output signal the most.
- * Read more about Amplitude Modulation Synthesis on
- * [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm).
- *
+ * @class Tone.DuoSynth is a monophonic synth composed of two
+ * MonoSynths run in parallel with control over the
+ * frequency ratio between the two voices and vibrato effect.
+ *
*
* @constructor
* @extends {Tone.Monophonic}
* @param {Object} [options] the options available for the synth
- * see defaults below
+ * see defaults below
* @example
- * var synth = new Tone.AMSynth().toMaster();
- * synth.triggerAttackRelease("C4", "4n");
+ * var duoSynth = new Tone.DuoSynth().toMaster();
+ * duoSynth.triggerAttackRelease("C4", "2n");
*/
- Tone.AMSynth = function (options) {
- options = this.defaultArg(options, Tone.AMSynth.defaults);
+ Tone.DuoSynth = function (options) {
+ options = this.defaultArg(options, Tone.DuoSynth.defaults);
Tone.Monophonic.call(this, options);
/**
- * The carrier voice.
+ * the first voice
* @type {Tone.MonoSynth}
*/
- this.carrier = new Tone.MonoSynth(options.carrier);
- this.carrier.volume.value = -10;
+ this.voice0 = new Tone.MonoSynth(options.voice0);
+ this.voice0.volume.value = -10;
/**
- * The modulator voice.
+ * the second voice
* @type {Tone.MonoSynth}
*/
- this.modulator = new Tone.MonoSynth(options.modulator);
- this.modulator.volume.value = -10;
+ this.voice1 = new Tone.MonoSynth(options.voice1);
+ this.voice1.volume.value = -10;
/**
- * The frequency.
+ * The vibrato LFO.
+ * @type {Tone.LFO}
+ * @private
+ */
+ this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50);
+ this._vibrato.start();
+ /**
+ * the vibrato frequency
+ * @type {Frequency}
+ * @signal
+ */
+ this.vibratoRate = this._vibrato.frequency;
+ /**
+ * the vibrato gain
+ * @type {GainNode}
+ * @private
+ */
+ this._vibratoGain = this.context.createGain();
+ /**
+ * The amount of vibrato
+ * @type {Positive}
+ * @signal
+ */
+ this.vibratoAmount = new Tone.Param({
+ 'param': this._vibratoGain.gain,
+ 'units': Tone.Type.Positive,
+ 'value': options.vibratoAmount
+ });
+ /**
+ * the frequency control
+ * @type {Frequency}
+ * @signal
+ */
+ this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ /**
+ * Harmonicity is the ratio between the two voices. A harmonicity of
+ * 1 is no change. Harmonicity = 2 means a change of an octave.
+ * @type {Positive}
+ * @signal
+ * @example
+ * //pitch voice1 an octave below voice0
+ * duoSynth.harmonicity.value = 0.5;
+ */
+ this.harmonicity = new Tone.Multiply(options.harmonicity);
+ this.harmonicity.units = Tone.Type.Positive;
+ //control the two voices frequency
+ this.frequency.connect(this.voice0.frequency);
+ this.frequency.chain(this.harmonicity, this.voice1.frequency);
+ this._vibrato.connect(this._vibratoGain);
+ this._vibratoGain.fan(this.voice0.detune, this.voice1.detune);
+ this.voice0.connect(this.output);
+ this.voice1.connect(this.output);
+ this._readOnly([
+ 'voice0',
+ 'voice1',
+ 'frequency',
+ 'vibratoAmount',
+ 'vibratoRate'
+ ]);
+ };
+ Tone.extend(Tone.DuoSynth, Tone.Monophonic);
+ /**
+ * @static
+ * @type {Object}
+ */
+ Tone.DuoSynth.defaults = {
+ 'vibratoAmount': 0.5,
+ 'vibratoRate': 5,
+ 'harmonicity': 1.5,
+ 'voice0': {
+ 'volume': -10,
+ 'portamento': 0,
+ 'oscillator': { 'type': 'sine' },
+ 'filterEnvelope': {
+ 'attack': 0.01,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
+ },
+ 'envelope': {
+ 'attack': 0.01,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
+ }
+ },
+ 'voice1': {
+ 'volume': -10,
+ 'portamento': 0,
+ 'oscillator': { 'type': 'sine' },
+ 'filterEnvelope': {
+ 'attack': 0.01,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
+ },
+ 'envelope': {
+ 'attack': 0.01,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
+ }
+ }
+ };
+ /**
+ * start the attack portion of the envelopes
+ *
+ * @param {Time} [time=now] the time the attack should start
+ * @param {NormalRange} [velocity=1] the velocity of the note (0-1)
+ * @returns {Tone.DuoSynth} this
+ * @private
+ */
+ Tone.DuoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
+ time = this.toSeconds(time);
+ this.voice0.envelope.triggerAttack(time, velocity);
+ this.voice1.envelope.triggerAttack(time, velocity);
+ this.voice0.filterEnvelope.triggerAttack(time);
+ this.voice1.filterEnvelope.triggerAttack(time);
+ return this;
+ };
+ /**
+ * start the release portion of the envelopes
+ *
+ * @param {Time} [time=now] the time the release should start
+ * @returns {Tone.DuoSynth} this
+ * @private
+ */
+ Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) {
+ this.voice0.triggerRelease(time);
+ this.voice1.triggerRelease(time);
+ return this;
+ };
+ /**
+ * clean up
+ * @returns {Tone.DuoSynth} this
+ */
+ Tone.DuoSynth.prototype.dispose = function () {
+ Tone.Monophonic.prototype.dispose.call(this);
+ this._writable([
+ 'voice0',
+ 'voice1',
+ 'frequency',
+ 'vibratoAmount',
+ 'vibratoRate'
+ ]);
+ this.voice0.dispose();
+ this.voice0 = null;
+ this.voice1.dispose();
+ this.voice1 = null;
+ this.frequency.dispose();
+ this.frequency = null;
+ this._vibrato.dispose();
+ this._vibrato = null;
+ this._vibratoGain.disconnect();
+ this._vibratoGain = null;
+ this.harmonicity.dispose();
+ this.harmonicity = null;
+ this.vibratoAmount.dispose();
+ this.vibratoAmount = null;
+ this.vibratoRate = null;
+ return this;
+ };
+ return Tone.DuoSynth;
+ });
+ Module(function (Tone) {
+
+ /**
+ * @class FMSynth is composed of two Tone.Synths where one Tone.Synth modulates
+ * the frequency of a second Tone.Synth. A lot of spectral content
+ * can be explored using the modulationIndex parameter. Read more about
+ * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm).
+ *
+ *
+ * @constructor
+ * @extends {Tone.Monophonic}
+ * @param {Object} [options] the options available for the synth
+ * see defaults below
+ * @example
+ * var fmSynth = new Tone.FMSynth().toMaster();
+ * fmSynth.triggerAttackRelease("C5", "4n");
+ */
+ Tone.FMSynth = function (options) {
+ options = this.defaultArg(options, Tone.FMSynth.defaults);
+ Tone.Monophonic.call(this, options);
+ /**
+ * The carrier voice.
+ * @type {Tone.Synth}
+ */
+ this._carrier = new Tone.Synth(options.carrier);
+ this._carrier.volume.value = -10;
+ /**
+ * The carrier's oscillator
+ * @type {Tone.Oscillator}
+ */
+ this.oscillator = this._carrier.oscillator;
+ /**
+ * The carrier's envelope
+ * @type {Tone.Oscillator}
+ */
+ this.envelope = this._carrier.envelope.set(options.envelope);
+ /**
+ * The modulator voice.
+ * @type {Tone.Synth}
+ */
+ this._modulator = new Tone.Synth(options.modulator);
+ this._modulator.volume.value = -10;
+ /**
+ * The modulator's oscillator which is applied
+ * to the amplitude of the oscillator
+ * @type {Tone.Oscillator}
+ */
+ this.modulation = this._modulator.oscillator.set(options.modulation);
+ /**
+ * The modulator's envelope
+ * @type {Tone.Oscillator}
+ */
+ this.modulationEnvelope = this._modulator.envelope.set(options.modulationEnvelope);
+ /**
+ * The frequency control.
* @type {Frequency}
* @signal
*/
this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ /**
+ * The detune in cents
+ * @type {Cents}
+ * @signal
+ */
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
/**
* Harmonicity is the ratio between the two voices. A harmonicity of
* 1 is no change. Harmonicity = 2 means a change of an octave.
@@ -16695,11 +18109,14 @@
this.harmonicity = new Tone.Multiply(options.harmonicity);
this.harmonicity.units = Tone.Type.Positive;
/**
- * convert the -1,1 output to 0,1
- * @type {Tone.AudioToGain}
- * @private
+ * The modulation index which essentially the depth or amount of the modulation. It is the
+ * ratio of the frequency of the modulating signal (mf) to the amplitude of the
+ * modulating signal (ma) -- as in ma/mf.
+ * @type {Positive}
+ * @signal
*/
- this._modulationScale = new Tone.AudioToGain();
+ this.modulationIndex = new Tone.Multiply(options.modulationIndex);
+ this.modulationIndex.units = Tone.Type.Positive;
/**
* the node where the modulation happens
* @type {GainNode}
@@ -16707,137 +18124,123 @@
*/
this._modulationNode = this.context.createGain();
//control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.modulator.chain(this._modulationScale, this._modulationNode.gain);
- this.carrier.chain(this._modulationNode, this.output);
+ this.frequency.connect(this._carrier.frequency);
+ this.frequency.chain(this.harmonicity, this._modulator.frequency);
+ this.frequency.chain(this.modulationIndex, this._modulationNode);
+ this.detune.fan(this._carrier.detune, this._modulator.detune);
+ this._modulator.connect(this._modulationNode.gain);
+ this._modulationNode.gain.value = 0;
+ this._modulationNode.connect(this._carrier.frequency);
+ this._carrier.connect(this.output);
this._readOnly([
- 'carrier',
- 'modulator',
'frequency',
- 'harmonicity'
+ 'harmonicity',
+ 'modulationIndex',
+ 'oscillator',
+ 'envelope',
+ 'modulation',
+ 'modulationEnvelope',
+ 'detune'
]);
};
- Tone.extend(Tone.AMSynth, Tone.Monophonic);
+ Tone.extend(Tone.FMSynth, Tone.Monophonic);
/**
* @static
* @type {Object}
*/
- Tone.AMSynth.defaults = {
+ Tone.FMSynth.defaults = {
'harmonicity': 3,
- 'carrier': {
- 'volume': -10,
- 'oscillator': { 'type': 'sine' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0.01,
- 'sustain': 1,
- 'release': 0.5
- },
- 'filterEnvelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5,
- 'baseFrequency': 20000,
- 'octaves': 0
- },
- 'filter': {
- 'Q': 6,
- 'type': 'lowpass',
- 'rolloff': -24
- }
+ 'modulationIndex': 10,
+ 'detune': 0,
+ 'oscillator': { 'type': 'sine' },
+ 'envelope': {
+ 'attack': 0.01,
+ 'decay': 0.01,
+ 'sustain': 1,
+ 'release': 0.5
},
- 'modulator': {
- 'volume': -10,
- 'oscillator': { 'type': 'square' },
- 'envelope': {
- 'attack': 2,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- },
- 'filterEnvelope': {
- 'attack': 4,
- 'decay': 0.2,
- 'sustain': 0.5,
- 'release': 0.5,
- 'baseFrequency': 20,
- 'octaves': 6
- },
- 'filter': {
- 'Q': 6,
- 'type': 'lowpass',
- 'rolloff': -24
- }
+ 'moduation': { 'type': 'square' },
+ 'modulationEnvelope': {
+ 'attack': 0.5,
+ 'decay': 0,
+ 'sustain': 1,
+ 'release': 0.5
}
};
/**
- * trigger the attack portion of the note
+ * trigger the attack portion of the note
*
* @param {Time} [time=now] the time the note will occur
- * @param {NormalRange} [velocity=1] the velocity of the note
+ * @param {number} [velocity=1] the velocity of the note
+ * @returns {Tone.FMSynth} this
* @private
- * @returns {Tone.AMSynth} this
*/
- Tone.AMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
- //the port glide
+ Tone.FMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
time = this.toSeconds(time);
//the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
- this.carrier.filterEnvelope.triggerAttack(time);
- this.modulator.filterEnvelope.triggerAttack(time);
+ this.envelope.triggerAttack(time, velocity);
+ this.modulationEnvelope.triggerAttack(time);
return this;
};
/**
* trigger the release portion of the note
*
* @param {Time} [time=now] the time the note will release
+ * @returns {Tone.FMSynth} this
* @private
- * @returns {Tone.AMSynth} this
*/
- Tone.AMSynth.prototype._triggerEnvelopeRelease = function (time) {
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
+ Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) {
+ time = this.toSeconds(time);
+ this.envelope.triggerRelease(time);
+ this.modulationEnvelope.triggerRelease(time);
return this;
};
/**
* clean up
- * @returns {Tone.AMSynth} this
+ * @returns {Tone.FMSynth} this
*/
- Tone.AMSynth.prototype.dispose = function () {
+ Tone.FMSynth.prototype.dispose = function () {
Tone.Monophonic.prototype.dispose.call(this);
this._writable([
- 'carrier',
- 'modulator',
'frequency',
- 'harmonicity'
+ 'harmonicity',
+ 'modulationIndex',
+ 'oscillator',
+ 'envelope',
+ 'modulation',
+ 'modulationEnvelope',
+ 'detune'
]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
+ this._carrier.dispose();
+ this._carrier = null;
+ this._modulator.dispose();
+ this._modulator = null;
this.frequency.dispose();
this.frequency = null;
+ this.detune.dispose();
+ this.detune = null;
+ this.modulationIndex.dispose();
+ this.modulationIndex = null;
this.harmonicity.dispose();
this.harmonicity = null;
- this._modulationScale.dispose();
- this._modulationScale = null;
this._modulationNode.disconnect();
this._modulationNode = null;
+ this.oscillator = null;
+ this.envelope = null;
+ this.modulationEnvelope = null;
+ this.modulation = null;
return this;
};
- return Tone.AMSynth;
+ return Tone.FMSynth;
});
Module(function (Tone) {
/**
- * @class Tone.DrumSynth makes kick and tom sounds using a single oscillator
+ * @class Tone.MembraneSynth makes kick and tom sounds using a single oscillator
* with an amplitude envelope and frequency ramp. A Tone.Oscillator
* is routed through a Tone.AmplitudeEnvelope to the output. The drum
* quality of the sound comes from the frequency envelope applied
- * during during Tone.DrumSynth.triggerAttack(note). The frequency
+ * during during Tone.MembraneSynth.triggerAttack(note). The frequency
* envelope starts at note * .octaves
and ramps to
* note
over the duration of .pitchDecay
.
*
@@ -16846,11 +18249,11 @@
* @param {Object} [options] the options available for the synth
* see defaults below
* @example
- * var synth = new Tone.DrumSynth().toMaster();
+ * var synth = new Tone.MembraneSynth().toMaster();
* synth.triggerAttackRelease("C2", "8n");
*/
- Tone.DrumSynth = function (options) {
- options = this.defaultArg(options, Tone.DrumSynth.defaults);
+ Tone.MembraneSynth = function (options) {
+ options = this.defaultArg(options, Tone.MembraneSynth.defaults);
Tone.Instrument.call(this, options);
/**
* The oscillator.
@@ -16878,458 +18281,322 @@
'envelope'
]);
};
- Tone.extend(Tone.DrumSynth, Tone.Instrument);
+ Tone.extend(Tone.MembraneSynth, Tone.Instrument);
/**
* @static
* @type {Object}
*/
- Tone.DrumSynth.defaults = {
+ Tone.MembraneSynth.defaults = {
'pitchDecay': 0.05,
'octaves': 10,
'oscillator': { 'type': 'sine' },
'envelope': {
- 'attack': 0.001,
- 'decay': 0.4,
- 'sustain': 0.01,
- 'release': 1.4,
- 'attackCurve': 'exponential'
- }
- };
- /**
- * Trigger the note at the given time with the given velocity.
- *
- * @param {Frequency} note the note
- * @param {Time} [time=now] the time, if not given is now
- * @param {number} [velocity=1] velocity defaults to 1
- * @returns {Tone.DrumSynth} this
- * @example
- * kick.triggerAttack(60);
- */
- Tone.DrumSynth.prototype.triggerAttack = function (note, time, velocity) {
- time = this.toSeconds(time);
- note = this.toFrequency(note);
- var maxNote = note * this.octaves;
- this.oscillator.frequency.setValueAtTime(maxNote, time);
- this.oscillator.frequency.exponentialRampToValueAtTime(note, time + this.toSeconds(this.pitchDecay));
- this.envelope.triggerAttack(time, velocity);
- return this;
- };
- /**
- * Trigger the release portion of the note.
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.DrumSynth} this
- */
- Tone.DrumSynth.prototype.triggerRelease = function (time) {
- this.envelope.triggerRelease(time);
- return this;
- };
- /**
- * Clean up.
- * @returns {Tone.DrumSynth} this
- */
- Tone.DrumSynth.prototype.dispose = function () {
- Tone.Instrument.prototype.dispose.call(this);
- this._writable([
- 'oscillator',
- 'envelope'
- ]);
- this.oscillator.dispose();
- this.oscillator = null;
- this.envelope.dispose();
- this.envelope = null;
- return this;
- };
- return Tone.DrumSynth;
- });
- Module(function (Tone) {
-
- /**
- * @class Tone.DuoSynth is a monophonic synth composed of two
- * MonoSynths run in parallel with control over the
- * frequency ratio between the two voices and vibrato effect.
- *
- *
- * @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var duoSynth = new Tone.DuoSynth().toMaster();
- * duoSynth.triggerAttackRelease("C4", "2n");
- */
- Tone.DuoSynth = function (options) {
- options = this.defaultArg(options, Tone.DuoSynth.defaults);
- Tone.Monophonic.call(this, options);
- /**
- * the first voice
- * @type {Tone.MonoSynth}
- */
- this.voice0 = new Tone.MonoSynth(options.voice0);
- this.voice0.volume.value = -10;
- /**
- * the second voice
- * @type {Tone.MonoSynth}
- */
- this.voice1 = new Tone.MonoSynth(options.voice1);
- this.voice1.volume.value = -10;
- /**
- * The vibrato LFO.
- * @type {Tone.LFO}
- * @private
- */
- this._vibrato = new Tone.LFO(options.vibratoRate, -50, 50);
- this._vibrato.start();
- /**
- * the vibrato frequency
- * @type {Frequency}
- * @signal
- */
- this.vibratoRate = this._vibrato.frequency;
- /**
- * the vibrato gain
- * @type {GainNode}
- * @private
- */
- this._vibratoGain = this.context.createGain();
- /**
- * The amount of vibrato
- * @type {Positive}
- * @signal
- */
- this.vibratoAmount = new Tone.Param({
- 'param': this._vibratoGain.gain,
- 'units': Tone.Type.Positive,
- 'value': options.vibratoAmount
- });
- /**
- * the delay before the vibrato starts
- * @type {number}
- * @private
- */
- this._vibratoDelay = this.toSeconds(options.vibratoDelay);
- /**
- * the frequency control
- * @type {Frequency}
- * @signal
- */
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
- /**
- * Harmonicity is the ratio between the two voices. A harmonicity of
- * 1 is no change. Harmonicity = 2 means a change of an octave.
- * @type {Positive}
- * @signal
- * @example
- * //pitch voice1 an octave below voice0
- * duoSynth.harmonicity.value = 0.5;
- */
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
- //control the two voices frequency
- this.frequency.connect(this.voice0.frequency);
- this.frequency.chain(this.harmonicity, this.voice1.frequency);
- this._vibrato.connect(this._vibratoGain);
- this._vibratoGain.fan(this.voice0.detune, this.voice1.detune);
- this.voice0.connect(this.output);
- this.voice1.connect(this.output);
- this._readOnly([
- 'voice0',
- 'voice1',
- 'frequency',
- 'vibratoAmount',
- 'vibratoRate'
- ]);
- };
- Tone.extend(Tone.DuoSynth, Tone.Monophonic);
- /**
- * @static
- * @type {Object}
- */
- Tone.DuoSynth.defaults = {
- 'vibratoAmount': 0.5,
- 'vibratoRate': 5,
- 'vibratoDelay': 1,
- 'harmonicity': 1.5,
- 'voice0': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'filterEnvelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- }
- },
- 'voice1': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'filterEnvelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- }
+ 'attack': 0.001,
+ 'decay': 0.4,
+ 'sustain': 0.01,
+ 'release': 1.4,
+ 'attackCurve': 'exponential'
}
};
/**
- * start the attack portion of the envelopes
+ * Trigger the note at the given time with the given velocity.
*
- * @param {Time} [time=now] the time the attack should start
- * @param {NormalRange} [velocity=1] the velocity of the note (0-1)
- * @returns {Tone.DuoSynth} this
- * @private
+ * @param {Frequency} note the note
+ * @param {Time} [time=now] the time, if not given is now
+ * @param {number} [velocity=1] velocity defaults to 1
+ * @returns {Tone.MembraneSynth} this
+ * @example
+ * kick.triggerAttack(60);
*/
- Tone.DuoSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
+ Tone.MembraneSynth.prototype.triggerAttack = function (note, time, velocity) {
time = this.toSeconds(time);
- this.voice0.envelope.triggerAttack(time, velocity);
- this.voice1.envelope.triggerAttack(time, velocity);
- this.voice0.filterEnvelope.triggerAttack(time);
- this.voice1.filterEnvelope.triggerAttack(time);
+ note = this.toFrequency(note);
+ var maxNote = note * this.octaves;
+ this.oscillator.frequency.setValueAtTime(maxNote, time);
+ this.oscillator.frequency.exponentialRampToValueAtTime(note, time + this.toSeconds(this.pitchDecay));
+ this.envelope.triggerAttack(time, velocity);
return this;
};
/**
- * start the release portion of the envelopes
+ * Trigger the release portion of the note.
*
- * @param {Time} [time=now] the time the release should start
- * @returns {Tone.DuoSynth} this
- * @private
+ * @param {Time} [time=now] the time the note will release
+ * @returns {Tone.MembraneSynth} this
*/
- Tone.DuoSynth.prototype._triggerEnvelopeRelease = function (time) {
- this.voice0.triggerRelease(time);
- this.voice1.triggerRelease(time);
+ Tone.MembraneSynth.prototype.triggerRelease = function (time) {
+ this.envelope.triggerRelease(time);
return this;
};
/**
- * clean up
- * @returns {Tone.DuoSynth} this
+ * Clean up.
+ * @returns {Tone.MembraneSynth} this
*/
- Tone.DuoSynth.prototype.dispose = function () {
- Tone.Monophonic.prototype.dispose.call(this);
+ Tone.MembraneSynth.prototype.dispose = function () {
+ Tone.Instrument.prototype.dispose.call(this);
this._writable([
- 'voice0',
- 'voice1',
- 'frequency',
- 'vibratoAmount',
- 'vibratoRate'
+ 'oscillator',
+ 'envelope'
]);
- this.voice0.dispose();
- this.voice0 = null;
- this.voice1.dispose();
- this.voice1 = null;
- this.frequency.dispose();
- this.frequency = null;
- this._vibrato.dispose();
- this._vibrato = null;
- this._vibratoGain.disconnect();
- this._vibratoGain = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this.vibratoAmount.dispose();
- this.vibratoAmount = null;
- this.vibratoRate = null;
+ this.oscillator.dispose();
+ this.oscillator = null;
+ this.envelope.dispose();
+ this.envelope = null;
return this;
};
- return Tone.DuoSynth;
+ return Tone.MembraneSynth;
});
Module(function (Tone) {
-
/**
- * @class FMSynth is composed of two Tone.MonoSynths where one Tone.MonoSynth modulates
- * the frequency of a second Tone.MonoSynth. A lot of spectral content
- * can be explored using the modulationIndex parameter. Read more about
- * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm).
- *
+ * Inharmonic ratio of frequencies based on the Roland TR-808
+ * Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model
+ * @private
+ * @static
+ * @type {Array}
+ */
+ var inharmRatios = [
+ 1,
+ 1.483,
+ 1.932,
+ 2.546,
+ 2.63,
+ 3.897
+ ];
+ /**
+ * @class A highly inharmonic and spectrally complex source with a highpass filter
+ * and amplitude envelope which is good for making metalophone sounds. Based
+ * on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic).
+ * Inspiration from [Sound on Sound](http://www.soundonsound.com/sos/jul02/articles/synthsecrets0702.asp).
*
* @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var fmSynth = new Tone.FMSynth().toMaster();
- * fmSynth.triggerAttackRelease("C5", "4n");
+ * @extends {Tone.Instrument}
+ * @param {Object} [options] The options availble for the synth
+ * see defaults below
*/
- Tone.FMSynth = function (options) {
- options = this.defaultArg(options, Tone.FMSynth.defaults);
- Tone.Monophonic.call(this, options);
+ Tone.MetalSynth = function (options) {
+ options = this.defaultArg(options, Tone.MetalSynth.defaults);
+ Tone.Instrument.call(this, options);
/**
- * The carrier voice.
- * @type {Tone.MonoSynth}
+ * The frequency of the cymbal
+ * @type {Frequency}
+ * @signal
*/
- this.carrier = new Tone.MonoSynth(options.carrier);
- this.carrier.volume.value = -10;
+ this.frequency = new Tone.Signal(options.frequency, Tone.Type.Frequency);
/**
- * The modulator voice.
- * @type {Tone.MonoSynth}
+ * The array of FMOscillators
+ * @type {Array}
+ * @private
*/
- this.modulator = new Tone.MonoSynth(options.modulator);
- this.modulator.volume.value = -10;
+ this._oscillators = [];
/**
- * The frequency control.
- * @type {Frequency}
- * @signal
+ * The frequency multipliers
+ * @type {Array}
+ * @private
*/
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ this._freqMultipliers = [];
/**
- * Harmonicity is the ratio between the two voices. A harmonicity of
- * 1 is no change. Harmonicity = 2 means a change of an octave.
- * @type {Positive}
- * @signal
- * @example
- * //pitch voice1 an octave below voice0
- * synth.harmonicity.value = 0.5;
+ * The amplitude for the body
+ * @type {Tone.Gain}
+ * @private
*/
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
+ this._amplitue = new Tone.Gain(0).connect(this.output);
/**
- * The modulation index which essentially the depth or amount of the modulation. It is the
- * ratio of the frequency of the modulating signal (mf) to the amplitude of the
- * modulating signal (ma) -- as in ma/mf.
- * @type {Positive}
- * @signal
+ * highpass the output
+ * @type {Tone.Filter}
+ * @private
*/
- this.modulationIndex = new Tone.Multiply(options.modulationIndex);
- this.modulationIndex.units = Tone.Type.Positive;
+ this._highpass = new Tone.Filter({
+ 'type': 'highpass',
+ 'Q': 0
+ }).connect(this._amplitue);
/**
- * the node where the modulation happens
- * @type {GainNode}
+ * The number of octaves the highpass
+ * filter frequency ramps
+ * @type {Number}
* @private
*/
- this._modulationNode = this.context.createGain();
- //control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.frequency.chain(this.modulationIndex, this._modulationNode);
- this.modulator.connect(this._modulationNode.gain);
- this._modulationNode.gain.value = 0;
- this._modulationNode.connect(this.carrier.frequency);
- this.carrier.connect(this.output);
- this._readOnly([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity',
- 'modulationIndex'
- ]);
+ this._octaves = options.octaves;
+ /**
+ * Scale the body envelope
+ * for the bandpass
+ * @type {Tone.Scale}
+ * @private
+ */
+ this._filterFreqScaler = new Tone.Scale(options.resonance, 7000);
+ /**
+ * The envelope which is connected both to the
+ * amplitude and highpass filter's cutoff frequency
+ * @type {Tone.Envelope}
+ */
+ this.envelope = new Tone.Envelope({
+ 'attack': options.envelope.attack,
+ 'attackCurve': 'exponential',
+ 'decay': options.envelope.decay,
+ 'sustain': 0,
+ 'release': options.envelope.release
+ }).chain(this._filterFreqScaler, this._highpass.frequency);
+ this.envelope.connect(this._amplitue.gain);
+ for (var i = 0; i < inharmRatios.length; i++) {
+ var osc = new Tone.FMOscillator({
+ 'type': 'square',
+ 'modulationType': 'square',
+ 'harmonicity': options.harmonicity,
+ 'modulationIndex': options.modulationIndex
+ });
+ osc.connect(this._highpass).start(0);
+ this._oscillators[i] = osc;
+ var mult = new Tone.Multiply(inharmRatios[i]);
+ this._freqMultipliers[i] = mult;
+ this.frequency.chain(mult, osc.frequency);
+ }
+ //set the octaves
+ this.octaves = options.octaves;
};
- Tone.extend(Tone.FMSynth, Tone.Monophonic);
+ Tone.extend(Tone.MetalSynth, Tone.Instrument);
/**
+ * default values
* @static
+ * @const
* @type {Object}
*/
- Tone.FMSynth.defaults = {
- 'harmonicity': 3,
- 'modulationIndex': 10,
- 'carrier': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- },
- 'filterEnvelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5,
- 'baseFrequency': 200,
- 'octaves': 8
- }
+ Tone.MetalSynth.defaults = {
+ 'frequency': 200,
+ 'envelope': {
+ 'attack': 0.0015,
+ 'decay': 1.4,
+ 'release': 0.2
},
- 'modulator': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'triangle' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- },
- 'filterEnvelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5,
- 'baseFrequency': 600,
- 'octaves': 5
- }
- }
+ 'harmonicity': 5.1,
+ 'modulationIndex': 32,
+ 'resonance': 4000,
+ 'octaves': 1.5
};
/**
- * trigger the attack portion of the note
- *
- * @param {Time} [time=now] the time the note will occur
- * @param {number} [velocity=1] the velocity of the note
- * @returns {Tone.FMSynth} this
- * @private
+ * Trigger the attack.
+ * @param {Time} time When the attack should be triggered.
+ * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at.
+ * @return {Tone.MetalSynth} this
*/
- Tone.FMSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
- //the port glide
+ Tone.MetalSynth.prototype.triggerAttack = function (time, vel) {
time = this.toSeconds(time);
- //the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
- this.carrier.filterEnvelope.triggerAttack(time);
- this.modulator.filterEnvelope.triggerAttack(time);
+ vel = this.defaultArg(vel, 1);
+ this.envelope.triggerAttack(time, vel);
return this;
};
/**
- * trigger the release portion of the note
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.FMSynth} this
- * @private
+ * Trigger the release of the envelope.
+ * @param {Time} time When the release should be triggered.
+ * @return {Tone.MetalSynth} this
*/
- Tone.FMSynth.prototype._triggerEnvelopeRelease = function (time) {
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
+ Tone.MetalSynth.prototype.triggerRelease = function (time) {
+ time = this.toSeconds(time);
+ this.envelope.triggerRelease(time);
return this;
};
/**
- * clean up
- * @returns {Tone.FMSynth} this
+ * Trigger the attack and release of the envelope after the given
+ * duration.
+ * @param {Time} duration The duration before triggering the release
+ * @param {Time} time When the attack should be triggered.
+ * @param {NormalRange=1} velocity The velocity that the envelope should be triggered at.
+ * @return {Tone.MetalSynth} this
*/
- Tone.FMSynth.prototype.dispose = function () {
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity',
- 'modulationIndex'
- ]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
- this.frequency.dispose();
- this.frequency = null;
- this.modulationIndex.dispose();
- this.modulationIndex = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this._modulationNode.disconnect();
- this._modulationNode = null;
+ Tone.MetalSynth.prototype.triggerAttackRelease = function (duration, time, velocity) {
+ var now = this.now();
+ time = this.toSeconds(time, now);
+ duration = this.toSeconds(duration, now);
+ this.triggerAttack(time, velocity);
+ this.triggerRelease(time + duration);
return this;
};
- return Tone.FMSynth;
+ /**
+ * The modulationIndex of the oscillators which make up the source.
+ * see Tone.FMOscillator.modulationIndex
+ * @memberOf Tone.MetalSynth#
+ * @type {Positive}
+ * @name modulationIndex
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, 'modulationIndex', {
+ get: function () {
+ return this._oscillators[0].modulationIndex.value;
+ },
+ set: function (val) {
+ for (var i = 0; i < this._oscillators.length; i++) {
+ this._oscillators[i].modulationIndex.value = val;
+ }
+ }
+ });
+ /**
+ * The harmonicity of the oscillators which make up the source.
+ * see Tone.FMOscillator.harmonicity
+ * @memberOf Tone.MetalSynth#
+ * @type {Positive}
+ * @name harmonicity
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, 'harmonicity', {
+ get: function () {
+ return this._oscillators[0].harmonicity.value;
+ },
+ set: function (val) {
+ for (var i = 0; i < this._oscillators.length; i++) {
+ this._oscillators[i].harmonicity.value = val;
+ }
+ }
+ });
+ /**
+ * The frequency of the highpass filter attached to the envelope
+ * @memberOf Tone.MetalSynth#
+ * @type {Frequency}
+ * @name resonance
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, 'resonance', {
+ get: function () {
+ return this._filterFreqScaler.min;
+ },
+ set: function (val) {
+ this._filterFreqScaler.min = val;
+ this.octaves = this._octaves;
+ }
+ });
+ /**
+ * The number of octaves above the "resonance" frequency
+ * that the filter ramps during the attack/decay envelope
+ * @memberOf Tone.MetalSynth#
+ * @type {Number}
+ * @name octaves
+ */
+ Object.defineProperty(Tone.MetalSynth.prototype, 'octaves', {
+ get: function () {
+ return this._octaves;
+ },
+ set: function (octs) {
+ this._octaves = octs;
+ this._filterFreqScaler.max = this._filterFreqScaler.min * Math.pow(2, octs);
+ }
+ });
+ /**
+ * Clean up
+ * @returns {Tone.MetalSynth} this
+ */
+ Tone.MetalSynth.prototype.dispose = function () {
+ Tone.Instrument.prototype.dispose.call(this);
+ for (var i = 0; i < this._oscillators.length; i++) {
+ this._oscillators[i].dispose();
+ this._freqMultipliers[i].dispose();
+ }
+ this._oscillators = null;
+ this._freqMultipliers = null;
+ this.frequency.dispose();
+ this.frequency = null;
+ this._filterFreqScaler.dispose();
+ this._filterFreqScaler = null;
+ this._amplitue.dispose();
+ this._amplitue = null;
+ this.envelope.dispose();
+ this.envelope = null;
+ this._highpass.dispose();
+ this._highpass = null;
+ };
+ return Tone.MetalSynth;
});
Module(function (Tone) {
@@ -17423,7 +18690,7 @@
this._buffer = _brownNoise;
break;
default:
- throw new Error('invalid noise type: ' + type);
+ throw new TypeError('Tone.Noise: invalid type: ' + type);
}
//if it's playing, stop and restart it
if (this.state === Tone.State.Started) {
@@ -17464,7 +18731,7 @@
this._source.loop = true;
this._source.playbackRate.value = this._playbackRate;
this._source.connect(this.output);
- this._source.start(this.toSeconds(time));
+ this._source.start(this.toSeconds(time), Math.random() * (this._buffer.duration - 0.001));
};
/**
* internal stop method
@@ -17583,31 +18850,17 @@
* noiseSynth.set("noise.type", "brown");
*/
this.noise = new Tone.Noise();
- /**
- * The filter.
- * @type {Tone.Filter}
- */
- this.filter = new Tone.Filter(options.filter);
- /**
- * The filter envelope.
- * @type {Tone.FrequencyEnvelope}
- */
- this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope);
/**
* The amplitude envelope.
* @type {Tone.AmplitudeEnvelope}
*/
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
//connect the noise to the output
- this.noise.chain(this.filter, this.envelope, this.output);
+ this.noise.chain(this.envelope, this.output);
//start the noise
this.noise.start();
- //connect the filter envelope
- this.filterEnvelope.connect(this.filter.frequency);
this._readOnly([
'noise',
- 'filter',
- 'filterEnvelope',
'envelope'
]);
};
@@ -17619,23 +18872,10 @@
*/
Tone.NoiseSynth.defaults = {
'noise': { 'type': 'white' },
- 'filter': {
- 'Q': 6,
- 'type': 'highpass',
- 'rolloff': -24
- },
'envelope': {
'attack': 0.005,
'decay': 0.1,
'sustain': 0
- },
- 'filterEnvelope': {
- 'attack': 0.06,
- 'decay': 0.2,
- 'sustain': 0,
- 'release': 2,
- 'baseFrequency': 20,
- 'octaves': 5
}
};
/**
@@ -17650,7 +18890,6 @@
Tone.NoiseSynth.prototype.triggerAttack = function (time, velocity) {
//the envelopes
this.envelope.triggerAttack(time, velocity);
- this.filterEnvelope.triggerAttack(time);
return this;
};
/**
@@ -17660,7 +18899,6 @@
*/
Tone.NoiseSynth.prototype.triggerRelease = function (time) {
this.envelope.triggerRelease(time);
- this.filterEnvelope.triggerRelease(time);
return this;
};
/**
@@ -17685,18 +18923,12 @@
Tone.Instrument.prototype.dispose.call(this);
this._writable([
'noise',
- 'filter',
- 'filterEnvelope',
'envelope'
]);
this.noise.dispose();
this.noise = null;
this.envelope.dispose();
this.envelope = null;
- this.filterEnvelope.dispose();
- this.filterEnvelope = null;
- this.filter.dispose();
- this.filter = null;
return this;
};
return Tone.NoiseSynth;
@@ -17816,11 +19048,11 @@
* @constructor
* @extends {Tone.Instrument}
* @param {number|Object} [polyphony=4] The number of voices to create
- * @param {function} [voice=Tone.MonoSynth] The constructor of the voices
- * uses Tone.MonoSynth by default.
+ * @param {function} [voice=Tone.Synth] The constructor of the voices
+ * uses Tone.Synth by default.
* @example
- * //a polysynth composed of 6 Voices of MonoSynth
- * var synth = new Tone.PolySynth(6, Tone.MonoSynth).toMaster();
+ * //a polysynth composed of 6 Voices of Synth
+ * var synth = new Tone.PolySynth(6, Tone.Synth).toMaster();
* //set the attributes using the set interface
* synth.set("detune", -1200);
* //play a chord
@@ -17832,37 +19064,44 @@
'polyphony',
'voice'
], Tone.PolySynth.defaults);
+ options = this.defaultArg(options, Tone.Instrument.defaults);
+ //max polyphony
+ options.polyphony = Math.min(Tone.PolySynth.MAX_POLYPHONY, options.polyphony);
/**
* the array of voices
* @type {Array}
*/
this.voices = new Array(options.polyphony);
/**
- * If there are no more voices available,
- * should an active voice be stolen to play the new note?
- * @type {Boolean}
- */
- this.stealVoices = true;
- /**
- * the queue of free voices
+ * The queue of voices with data about last trigger
+ * and the triggered note
* @private
* @type {Array}
*/
- this._freeVoices = [];
+ this._triggers = new Array(options.polyphony);
/**
- * keeps track of which notes are down
- * @private
- * @type {Object}
+ * The detune in cents
+ * @type {Cents}
+ * @signal
*/
- this._activeVoices = {};
+ this.detune = new Tone.Signal(options.detune, Tone.Type.Cents);
+ this._readOnly('detune');
//create the voices
for (var i = 0; i < options.polyphony; i++) {
var v = new options.voice(arguments[2], arguments[3]);
this.voices[i] = v;
v.connect(this.output);
+ if (v.hasOwnProperty('detune')) {
+ this.detune.connect(v.detune);
+ }
+ this._triggers[i] = {
+ release: -1,
+ note: null,
+ voice: v
+ };
}
- //make a copy of the voices
- this._freeVoices = this.voices.slice(0); //get the prototypes and properties
+ //set the volume initially
+ this.volume.value = options.volume;
};
Tone.extend(Tone.PolySynth, Tone.Instrument);
/**
@@ -17873,7 +19112,9 @@
*/
Tone.PolySynth.defaults = {
'polyphony': 4,
- 'voice': Tone.MonoSynth
+ 'volume': 0,
+ 'detune': 0,
+ 'voice': Tone.Synth
};
/**
* Trigger the attack portion of the note
@@ -17890,24 +19131,21 @@
if (!Array.isArray(notes)) {
notes = [notes];
}
+ time = this.toSeconds(time);
for (var i = 0; i < notes.length; i++) {
var val = notes[i];
- var stringified = JSON.stringify(val);
- //retrigger the same note if possible
- if (this._activeVoices.hasOwnProperty(stringified)) {
- this._activeVoices[stringified].triggerAttack(val, time, velocity);
- } else if (this._freeVoices.length > 0) {
- var voice = this._freeVoices.shift();
- voice.triggerAttack(val, time, velocity);
- this._activeVoices[stringified] = voice;
- } else if (this.stealVoices) {
- //steal a voice
- //take the first voice
- for (var voiceName in this._activeVoices) {
- this._activeVoices[voiceName].triggerAttack(val, time, velocity);
- break;
+ //trigger the oldest voice
+ var oldest = this._triggers[0];
+ var oldestIndex = 0;
+ for (var j = 1; j < this._triggers.length; j++) {
+ if (this._triggers[j].release < oldest.release) {
+ oldest = this._triggers[j];
+ oldestIndex = j;
}
}
+ oldest.release = Infinity;
+ oldest.note = JSON.stringify(val);
+ oldest.voice.triggerAttack(val, time, velocity);
}
return this;
};
@@ -17923,11 +19161,21 @@
* @example
* //trigger a chord for a duration of a half note
* poly.triggerAttackRelease(["Eb3", "G4", "C5"], "2n");
+ * @example
+ * //can pass in an array of durations as well
+ * poly.triggerAttackRelease(["Eb3", "G4", "C5"], ["2n", "4n", "4n"]);
*/
Tone.PolySynth.prototype.triggerAttackRelease = function (notes, duration, time, velocity) {
time = this.toSeconds(time);
this.triggerAttack(notes, time, velocity);
- this.triggerRelease(notes, time + this.toSeconds(duration));
+ if (this.isArray(duration) && this.isArray(notes)) {
+ for (var i = 0; i < notes.length; i++) {
+ var d = duration[Math.min(i, duration.length - 1)];
+ this.triggerRelease(notes[i], time + this.toSeconds(d));
+ }
+ } else {
+ this.triggerRelease(notes, time + this.toSeconds(duration));
+ }
return this;
};
/**
@@ -17944,15 +19192,16 @@
if (!Array.isArray(notes)) {
notes = [notes];
}
+ time = this.toSeconds(time);
for (var i = 0; i < notes.length; i++) {
//get the voice
var stringified = JSON.stringify(notes[i]);
- var voice = this._activeVoices[stringified];
- if (voice) {
- voice.triggerRelease(time);
- this._freeVoices.push(voice);
- delete this._activeVoices[stringified];
- voice = null;
+ for (var v = 0; v < this._triggers.length; v++) {
+ var desc = this._triggers[v];
+ if (desc.note === stringified && desc.release > time) {
+ desc.voice.triggerRelease(time);
+ desc.release = time;
+ }
}
}
return this;
@@ -17997,8 +19246,13 @@
* @return {Tone.PolySynth} this
*/
Tone.PolySynth.prototype.releaseAll = function (time) {
- for (var i = 0; i < this.voices.length; i++) {
- this.voices[i].triggerRelease(time);
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._triggers.length; i++) {
+ var desc = this._triggers[i];
+ if (desc.release > time) {
+ desc.release = time;
+ desc.voice.triggerRelease(time);
+ }
}
return this;
};
@@ -18012,11 +19266,20 @@
this.voices[i].dispose();
this.voices[i] = null;
}
+ this._writable('detune');
+ this.detune.dispose();
+ this.detune = null;
this.voices = null;
- this._activeVoices = null;
- this._freeVoices = null;
+ this._triggers = null;
return this;
};
+ /**
+ * The maximum number of notes that can be allocated
+ * to a polysynth.
+ * @type {Number}
+ * @static
+ */
+ Tone.PolySynth.MAX_POLYPHONY = 20;
return Tone.PolySynth;
});
Module(function (Tone) {
@@ -18032,9 +19295,8 @@
* Recommended to use Tone.Buffer.onload instead.
* @example
* var player = new Tone.Player("./path/to/sample.mp3").toMaster();
- * Tone.Buffer.onload = function(){
- * player.start();
- * }
+ * //play as soon as the buffer is loaded
+ * player.autostart = true;
*/
Tone.Player = function (url) {
var options;
@@ -18157,16 +19419,24 @@
}
};
/**
- * play the buffer between the desired positions
+ * Play the buffer at the given startTime. Optionally add an offset
+ * and/or duration which will play the buffer from a position
+ * within the buffer for the given duration.
*
- * @private
- * @param {Time} [startTime=now] when the player should start.
- * @param {Time} [offset=0] the offset from the beginning of the sample
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
* to start at.
- * @param {Time=} duration how long the sample should play. If no duration
+ * @param {Time=} duration How long the sample should play. If no duration
* is given, it will default to the full length
* of the sample (minus any offset)
* @returns {Tone.Player} this
+ * @memberOf Tone.Player#
+ * @method start
+ * @name start
+ */
+ /**
+ * Internal start method
+ * @private
*/
Tone.Player.prototype._start = function (startTime, offset, duration) {
if (this._buffer.loaded) {
@@ -18204,7 +19474,7 @@
this._source.start(startTime, offset, duration);
}
} else {
- throw Error('tried to start Player before the buffer was loaded');
+ throw Error('Tone.Player: tried to start Player before the buffer was loaded');
}
return this;
};
@@ -18353,84 +19623,42 @@
Module(function (Tone) {
/**
- * @class A sampler instrument which plays an audio buffer
- * through an amplitude envelope and a filter envelope. The sampler takes
- * an Object in the constructor which maps a sample name to the URL
- * of the sample. Nested Objects will be flattened and can be accessed using
- * a dot notation (see the example).
- *
+ * @class Sampler wraps Tone.Player in an AmplitudeEnvelope.
*
* @constructor
* @extends {Tone.Instrument}
- * @param {Object|string} urls the urls of the audio file
- * @param {Object} [options] the options object for the synth
+ * @param {String} url the url of the audio file
+ * @param {Function=} onload The callback to invoke when the sample is loaded.
* @example
- * var sampler = new Sampler({
- * A : {
- * 1 : "./audio/casio/A1.mp3",
- * 2 : "./audio/casio/A2.mp3",
- * },
- * "B.1" : "./audio/casio/B1.mp3",
+ * var sampler = new Sampler("./audio/casio/A1.mp3", function(){
+ * //repitch the sample down a half step
+ * sampler.triggerAttack(-1);
* }).toMaster();
- *
- * //listen for when all the samples have loaded
- * Tone.Buffer.onload = function(){
- * sampler.triggerAttack("A.1", time, velocity);
- * };
*/
- Tone.Sampler = function (urls, options) {
- options = this.defaultArg(options, Tone.Sampler.defaults);
+ Tone.Sampler = function () {
+ var options = this.optionsObject(arguments, [
+ 'url',
+ 'onload'
+ ], Tone.Sampler.defaults);
Tone.Instrument.call(this, options);
/**
* The sample player.
* @type {Tone.Player}
*/
- this.player = new Tone.Player(options.player);
+ this.player = new Tone.Player(options.url, options.onload);
this.player.retrigger = true;
- /**
- * the buffers
- * @type {Object}
- * @private
- */
- this._buffers = {};
/**
* The amplitude envelope.
* @type {Tone.AmplitudeEnvelope}
*/
this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
- /**
- * The filter envelope.
- * @type {Tone.FrequencyEnvelope}
- */
- this.filterEnvelope = new Tone.FrequencyEnvelope(options.filterEnvelope);
- /**
- * The name of the current sample.
- * @type {string}
- * @private
- */
- this._sample = options.sample;
- /**
- * the private reference to the pitch
- * @type {number}
- * @private
- */
- this._pitch = options.pitch;
- /**
- * The filter.
- * @type {Tone.Filter}
- */
- this.filter = new Tone.Filter(options.filter);
- //connections / setup
- this._loadBuffers(urls);
- this.pitch = options.pitch;
- this.player.chain(this.filter, this.envelope, this.output);
- this.filterEnvelope.connect(this.filter.frequency);
+ this.player.chain(this.envelope, this.output);
this._readOnly([
'player',
- 'filterEnvelope',
- 'envelope',
- 'filter'
+ 'envelope'
]);
+ this.loop = options.loop;
+ this.reverse = options.reverse;
};
Tone.extend(Tone.Sampler, Tone.Instrument);
/**
@@ -18438,87 +19666,32 @@
* @static
*/
Tone.Sampler.defaults = {
- 'sample': 0,
- 'pitch': 0,
- 'player': { 'loop': false },
+ 'onload': Tone.noOp,
+ 'loop': false,
+ 'reverse': false,
'envelope': {
'attack': 0.001,
'decay': 0,
'sustain': 1,
'release': 0.1
- },
- 'filterEnvelope': {
- 'attack': 0.001,
- 'decay': 0.001,
- 'sustain': 1,
- 'release': 0.5,
- 'baseFrequency': 20,
- 'octaves': 10
- },
- 'filter': { 'type': 'lowpass' }
- };
- /**
- * load the buffers
- * @param {Object} urls the urls
- * @private
- */
- Tone.Sampler.prototype._loadBuffers = function (urls) {
- if (this.isString(urls)) {
- this._buffers['0'] = new Tone.Buffer(urls, function () {
- this.sample = '0';
- }.bind(this));
- } else {
- urls = this._flattenUrls(urls);
- for (var buffName in urls) {
- this._sample = buffName;
- var urlString = urls[buffName];
- this._buffers[buffName] = new Tone.Buffer(urlString);
- }
- }
- };
- /**
- * Flatten an object into a single depth object.
- * thanks to https://gist.github.com/penguinboy/762197
- * @param {Object} ob
- * @return {Object}
- * @private
- */
- Tone.Sampler.prototype._flattenUrls = function (ob) {
- var toReturn = {};
- for (var i in ob) {
- if (!ob.hasOwnProperty(i))
- continue;
- if (this.isObject(ob[i])) {
- var flatObject = this._flattenUrls(ob[i]);
- for (var x in flatObject) {
- if (!flatObject.hasOwnProperty(x))
- continue;
- toReturn[i + '.' + x] = flatObject[x];
- }
- } else {
- toReturn[i] = ob[i];
- }
}
- return toReturn;
};
/**
- * Start the sample and simultaneously trigger the envelopes.
- * @param {string=} sample The name of the sample to trigger, defaults to
- * the last sample used.
+ * Trigger the start of the sample.
+ * @param {Interval} [pitch=0] The amount the sample should
+ * be repitched.
* @param {Time} [time=now] The time when the sample should start
- * @param {number} [velocity=1] The velocity of the note
+ * @param {NormalRange} [velocity=1] The velocity of the note
* @returns {Tone.Sampler} this
* @example
- * sampler.triggerAttack("B.1");
+ * sampler.triggerAttack(0, "+0.1", 0.5);
*/
- Tone.Sampler.prototype.triggerAttack = function (name, time, velocity) {
+ Tone.Sampler.prototype.triggerAttack = function (pitch, time, velocity) {
time = this.toSeconds(time);
- if (name) {
- this.sample = name;
- }
+ pitch = this.defaultArg(pitch, 0);
+ this.player.playbackRate = this.intervalToFrequencyRatio(pitch);
this.player.start(time);
this.envelope.triggerAttack(time, velocity);
- this.filterEnvelope.triggerAttack(time);
return this;
};
/**
@@ -18532,31 +19705,22 @@
*/
Tone.Sampler.prototype.triggerRelease = function (time) {
time = this.toSeconds(time);
- this.filterEnvelope.triggerRelease(time);
this.envelope.triggerRelease(time);
this.player.stop(this.toSeconds(this.envelope.release) + time);
return this;
};
/**
- * The name of the sample to trigger.
+ * If the output sample should loop or not.
* @memberOf Tone.Sampler#
* @type {number|string}
- * @name sample
- * @example
- * //set the sample to "A.2" for next time the sample is triggered
- * sampler.sample = "A.2";
+ * @name loop
*/
- Object.defineProperty(Tone.Sampler.prototype, 'sample', {
+ Object.defineProperty(Tone.Sampler.prototype, 'loop', {
get: function () {
- return this._sample;
+ return this.player.loop;
},
- set: function (name) {
- if (this._buffers.hasOwnProperty(name)) {
- this._sample = name;
- this.player.buffer = this._buffers[name];
- } else {
- throw new Error('Sampler does not have a sample named ' + name);
- }
+ set: function (loop) {
+ this.player.loop = loop;
}
});
/**
@@ -18567,33 +19731,10 @@
*/
Object.defineProperty(Tone.Sampler.prototype, 'reverse', {
get: function () {
- for (var i in this._buffers) {
- return this._buffers[i].reverse;
- }
+ return this.player.reverse;
},
set: function (rev) {
- for (var i in this._buffers) {
- this._buffers[i].reverse = rev;
- }
- }
- });
- /**
- * Repitch the sampled note by some interval (measured
- * in semi-tones).
- * @memberOf Tone.Sampler#
- * @type {Interval}
- * @name pitch
- * @example
- * sampler.pitch = -12; //down one octave
- * sampler.pitch = 7; //up a fifth
- */
- Object.defineProperty(Tone.Sampler.prototype, 'pitch', {
- get: function () {
- return this._pitch;
- },
- set: function (interval) {
- this._pitch = interval;
- this.player.playbackRate = this.intervalToFrequencyRatio(interval);
+ this.player.reverse = rev;
}
});
/**
@@ -18604,23 +19745,12 @@
Tone.Instrument.prototype.dispose.call(this);
this._writable([
'player',
- 'filterEnvelope',
- 'envelope',
- 'filter'
+ 'envelope'
]);
this.player.dispose();
- this.filterEnvelope.dispose();
- this.envelope.dispose();
- this.filter.dispose();
this.player = null;
- this.filterEnvelope = null;
+ this.envelope.dispose();
this.envelope = null;
- this.filter = null;
- for (var sample in this._buffers) {
- this._buffers[sample].dispose();
- this._buffers[sample] = null;
- }
- this._buffers = null;
return this;
};
return Tone.Sampler;
@@ -18628,440 +19758,412 @@
Module(function (Tone) {
/**
- * @class Tone.SimpleSynth is composed simply of a Tone.OmniOscillator
- * routed through a Tone.AmplitudeEnvelope.
- *
- *
+ * @class Now called Tone.Synth
* @constructor
* @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var synth = new Tone.SimpleSynth().toMaster();
- * synth.triggerAttackRelease("C4", "8n");
*/
Tone.SimpleSynth = function (options) {
- //get the defaults
- options = this.defaultArg(options, Tone.SimpleSynth.defaults);
- Tone.Monophonic.call(this, options);
- /**
- * The oscillator.
- * @type {Tone.OmniOscillator}
- */
- this.oscillator = new Tone.OmniOscillator(options.oscillator);
- /**
- * The frequency control.
- * @type {Frequency}
- * @signal
- */
- this.frequency = this.oscillator.frequency;
- /**
- * The detune control.
- * @type {Cents}
- * @signal
- */
- this.detune = this.oscillator.detune;
- /**
- * The amplitude envelope.
- * @type {Tone.AmplitudeEnvelope}
- */
- this.envelope = new Tone.AmplitudeEnvelope(options.envelope);
- //connect the oscillators to the output
- this.oscillator.chain(this.envelope, this.output);
- //start the oscillators
- this.oscillator.start();
- this._readOnly([
- 'oscillator',
- 'frequency',
- 'detune',
- 'envelope'
- ]);
- };
- Tone.extend(Tone.SimpleSynth, Tone.Monophonic);
- /**
- * @const
- * @static
- * @type {Object}
- */
- Tone.SimpleSynth.defaults = {
- 'oscillator': { 'type': 'triangle' },
- 'envelope': {
- 'attack': 0.005,
- 'decay': 0.1,
- 'sustain': 0.3,
- 'release': 1
- }
- };
- /**
- * start the attack portion of the envelope
- * @param {Time} [time=now] the time the attack should start
- * @param {number} [velocity=1] the velocity of the note (0-1)
- * @returns {Tone.SimpleSynth} this
- * @private
- */
- Tone.SimpleSynth.prototype._triggerEnvelopeAttack = function (time, velocity) {
- //the envelopes
- this.envelope.triggerAttack(time, velocity);
- return this;
+ console.warn('Tone.SimpleSynth is now called Tone.Synth');
+ Tone.Synth.call(this, options);
};
+ Tone.extend(Tone.SimpleSynth, Tone.Synth);
+ return Tone.SimpleSynth;
+ });
+ Module(function (Tone) {
+
/**
- * start the release portion of the envelope
- * @param {Time} [time=now] the time the release should start
- * @returns {Tone.SimpleSynth} this
- * @private
+ * @class Maps a NormalRange [0, 1] to an AudioRange [-1, 1].
+ * See also Tone.AudioToGain.
+ *
+ * @extends {Tone.SignalBase}
+ * @constructor
+ * @example
+ * var g2a = new Tone.GainToAudio();
*/
- Tone.SimpleSynth.prototype._triggerEnvelopeRelease = function (time) {
- this.envelope.triggerRelease(time);
- return this;
+ Tone.GainToAudio = function () {
+ /**
+ * @type {WaveShaperNode}
+ * @private
+ */
+ this._norm = this.input = this.output = new Tone.WaveShaper(function (x) {
+ return Math.abs(x) * 2 - 1;
+ });
};
+ Tone.extend(Tone.GainToAudio, Tone.SignalBase);
/**
* clean up
- * @returns {Tone.SimpleSynth} this
+ * @returns {Tone.GainToAudio} this
*/
- Tone.SimpleSynth.prototype.dispose = function () {
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable([
- 'oscillator',
- 'frequency',
- 'detune',
- 'envelope'
- ]);
- this.oscillator.dispose();
- this.oscillator = null;
- this.envelope.dispose();
- this.envelope = null;
- this.frequency = null;
- this.detune = null;
+ Tone.GainToAudio.prototype.dispose = function () {
+ Tone.prototype.dispose.call(this);
+ this._norm.dispose();
+ this._norm = null;
return this;
};
- return Tone.SimpleSynth;
+ return Tone.GainToAudio;
});
Module(function (Tone) {
/**
- * @class AMSynth uses the output of one Tone.SimpleSynth to modulate the
- * amplitude of another Tone.SimpleSynth. The harmonicity (the ratio between
- * the two signals) affects the timbre of the output signal the most.
- * Read more about Amplitude Modulation Synthesis on [SoundOnSound](http://www.soundonsound.com/sos/mar00/articles/synthsecrets.htm).
- *
+ * @class Normalize takes an input min and max and maps it linearly to NormalRange [0,1]
*
+ * @extends {Tone.SignalBase}
* @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
+ * @param {number} inputMin the min input value
+ * @param {number} inputMax the max input value
* @example
- * var synth = new Tone.SimpleAM().toMaster();
- * synth.triggerAttackRelease("C4", "8n");
+ * var norm = new Tone.Normalize(2, 4);
+ * var sig = new Tone.Signal(3).connect(norm);
+ * //output of norm is 0.5.
*/
- Tone.SimpleAM = function (options) {
- options = this.defaultArg(options, Tone.SimpleAM.defaults);
- Tone.Monophonic.call(this, options);
- /**
- * The carrier voice.
- * @type {Tone.SimpleSynth}
- */
- this.carrier = new Tone.SimpleSynth(options.carrier);
- /**
- * The modulator voice.
- * @type {Tone.SimpleSynth}
- */
- this.modulator = new Tone.SimpleSynth(options.modulator);
+ Tone.Normalize = function (inputMin, inputMax) {
/**
- * the frequency control
- * @type {Frequency}
- * @signal
+ * the min input value
+ * @type {number}
+ * @private
*/
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ this._inputMin = this.defaultArg(inputMin, 0);
/**
- * The ratio between the carrier and the modulator frequencies. A value of 1
- * makes both voices in unison, a value of 0.5 puts the modulator an octave below
- * the carrier.
- * @type {Positive}
- * @signal
- * @example
- * //set the modulator an octave above the carrier frequency
- * simpleAM.harmonicity.value = 2;
+ * the max input value
+ * @type {number}
+ * @private
*/
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
+ this._inputMax = this.defaultArg(inputMax, 1);
/**
- * convert the -1,1 output to 0,1
- * @type {Tone.AudioToGain}
+ * subtract the min from the input
+ * @type {Tone.Add}
* @private
*/
- this._modulationScale = new Tone.AudioToGain();
+ this._sub = this.input = new Tone.Add(0);
/**
- * the node where the modulation happens
- * @type {GainNode}
+ * divide by the difference between the input and output
+ * @type {Tone.Multiply}
* @private
*/
- this._modulationNode = this.context.createGain();
- //control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.modulator.chain(this._modulationScale, this._modulationNode.gain);
- this.carrier.chain(this._modulationNode, this.output);
- this._readOnly([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity'
- ]);
+ this._div = this.output = new Tone.Multiply(1);
+ this._sub.connect(this._div);
+ this._setRange();
};
- Tone.extend(Tone.SimpleAM, Tone.Monophonic);
+ Tone.extend(Tone.Normalize, Tone.SignalBase);
/**
- * @static
- * @type {Object}
+ * The minimum value the input signal will reach.
+ * @memberOf Tone.Normalize#
+ * @type {number}
+ * @name min
*/
- Tone.SimpleAM.defaults = {
- 'harmonicity': 3,
- 'carrier': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0.01,
- 'sustain': 1,
- 'release': 0.5
- }
+ Object.defineProperty(Tone.Normalize.prototype, 'min', {
+ get: function () {
+ return this._inputMin;
},
- 'modulator': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'envelope': {
- 'attack': 0.5,
- 'decay': 0.1,
- 'sustain': 1,
- 'release': 0.5
- }
+ set: function (min) {
+ this._inputMin = min;
+ this._setRange();
}
- };
+ });
/**
- * trigger the attack portion of the note
- *
- * @param {Time} [time=now] the time the note will occur
- * @param {number} [velocity=1] the velocity of the note
- * @returns {Tone.SimpleAM} this
- * @private
+ * The maximum value the input signal will reach.
+ * @memberOf Tone.Normalize#
+ * @type {number}
+ * @name max
*/
- Tone.SimpleAM.prototype._triggerEnvelopeAttack = function (time, velocity) {
- //the port glide
- time = this.toSeconds(time);
- //the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
- return this;
- };
+ Object.defineProperty(Tone.Normalize.prototype, 'max', {
+ get: function () {
+ return this._inputMax;
+ },
+ set: function (max) {
+ this._inputMax = max;
+ this._setRange();
+ }
+ });
/**
- * trigger the release portion of the note
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.SimpleAM} this
+ * set the values
* @private
*/
- Tone.SimpleAM.prototype._triggerEnvelopeRelease = function (time) {
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
- return this;
+ Tone.Normalize.prototype._setRange = function () {
+ this._sub.value = -this._inputMin;
+ this._div.value = 1 / (this._inputMax - this._inputMin);
};
/**
* clean up
- * @returns {Tone.SimpleAM} this
+ * @returns {Tone.Normalize} this
*/
- Tone.SimpleAM.prototype.dispose = function () {
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity'
- ]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
- this.frequency.dispose();
- this.frequency = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this._modulationScale.dispose();
- this._modulationScale = null;
- this._modulationNode.disconnect();
- this._modulationNode = null;
+ Tone.Normalize.prototype.dispose = function () {
+ Tone.prototype.dispose.call(this);
+ this._sub.dispose();
+ this._sub = null;
+ this._div.dispose();
+ this._div = null;
return this;
};
- return Tone.SimpleAM;
+ return Tone.Normalize;
});
Module(function (Tone) {
-
/**
- * @class SimpleFM is composed of two Tone.SimpleSynths where one Tone.SimpleSynth modulates
- * the frequency of a second Tone.SimpleSynth. A lot of spectral content
- * can be explored using the Tone.FMSynth.modulationIndex parameter. Read more about
- * frequency modulation synthesis on [SoundOnSound](http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm).
- *
- *
- * @constructor
- * @extends {Tone.Monophonic}
- * @param {Object} [options] the options available for the synth
- * see defaults below
- * @example
- * var fmSynth = new Tone.SimpleFM().toMaster();
- * fmSynth.triggerAttackRelease("C4", "8n");
+ * @class Wrapper around the native BufferSourceNode.
+ * @param {AudioBuffer|Tone.Buffer} buffer The buffer to play
+ * @param {Function} onended The callback to invoke when the
+ * buffer is done playing.
*/
- Tone.SimpleFM = function (options) {
- options = this.defaultArg(options, Tone.SimpleFM.defaults);
- Tone.Monophonic.call(this, options);
+ Tone.BufferSource = function () {
+ var options = this.optionsObject(arguments, [
+ 'buffer',
+ 'onended'
+ ], Tone.BufferSource.defaults);
/**
- * The carrier voice.
- * @type {Tone.SimpleSynth}
+ * The callback to invoke after the
+ * buffer source is done playing.
+ * @type {Function}
*/
- this.carrier = new Tone.SimpleSynth(options.carrier);
- this.carrier.volume.value = -10;
+ this.onended = options.onended;
/**
- * The modulator voice.
- * @type {Tone.SimpleSynth}
+ * The time that the buffer was started.
+ * @type {Number}
+ * @private
*/
- this.modulator = new Tone.SimpleSynth(options.modulator);
- this.modulator.volume.value = -10;
+ this._startTime = -1;
/**
- * the frequency control
- * @type {Frequency}
- * @signal
+ * The gain node which envelopes the BufferSource
+ * @type {GainNode}
+ * @private
*/
- this.frequency = new Tone.Signal(440, Tone.Type.Frequency);
+ this._gainNode = this.output = this.context.createGain();
/**
- * Harmonicity is the ratio between the two voices. A harmonicity of
- * 1 is no change. Harmonicity = 2 means a change of an octave.
- * @type {Positive}
- * @signal
- * @example
- * //pitch voice1 an octave below voice0
- * synth.harmonicity.value = 0.5;
+ * The buffer source
+ * @type {AudioBufferSourceNode}
+ * @private
*/
- this.harmonicity = new Tone.Multiply(options.harmonicity);
- this.harmonicity.units = Tone.Type.Positive;
+ this._source = this.context.createBufferSource();
+ this._source.connect(this._gainNode);
+ this._source.onended = this._onended.bind(this);
/**
- * The modulation index which is in essence the depth or amount of the modulation. In other terms it is the
- * ratio of the frequency of the modulating signal (mf) to the amplitude of the
- * modulating signal (ma) -- as in ma/mf.
- * @type {Positive}
- * @signal
+ * The playbackRate of the buffer
+ * @type {AudioParam}
*/
- this.modulationIndex = new Tone.Multiply(options.modulationIndex);
- this.modulationIndex.units = Tone.Type.Positive;
+ this.playbackRate = this._source.playbackRate;
/**
- * the node where the modulation happens
- * @type {GainNode}
+ * The fadeIn time of the amplitude envelope.
+ * @type {Time}
+ */
+ this.fadeIn = options.fadeIn;
+ /**
+ * The fadeOut time of the amplitude envelope.
+ * @type {Time}
+ */
+ this.fadeOut = options.fadeOut;
+ /**
+ * The value that the buffer ramps to
+ * @type {Gain}
* @private
*/
- this._modulationNode = this.context.createGain();
- //control the two voices frequency
- this.frequency.connect(this.carrier.frequency);
- this.frequency.chain(this.harmonicity, this.modulator.frequency);
- this.frequency.chain(this.modulationIndex, this._modulationNode);
- this.modulator.connect(this._modulationNode.gain);
- this._modulationNode.gain.value = 0;
- this._modulationNode.connect(this.carrier.frequency);
- this.carrier.connect(this.output);
- this._readOnly([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity',
- 'modulationIndex'
- ]);
- ;
+ this._gain = 1;
+ //set the buffer initially
+ if (!this.isUndef(options.buffer)) {
+ this.buffer = options.buffer;
+ }
+ this.loop = options.loop;
};
- Tone.extend(Tone.SimpleFM, Tone.Monophonic);
+ Tone.extend(Tone.BufferSource);
/**
- * @static
- * @type {Object}
+ * The defaults
+ * @const
+ * @type {Object}
*/
- Tone.SimpleFM.defaults = {
- 'harmonicity': 3,
- 'modulationIndex': 10,
- 'carrier': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'sine' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
- }
- },
- 'modulator': {
- 'volume': -10,
- 'portamento': 0,
- 'oscillator': { 'type': 'triangle' },
- 'envelope': {
- 'attack': 0.01,
- 'decay': 0,
- 'sustain': 1,
- 'release': 0.5
+ Tone.BufferSource.defaults = {
+ 'onended': Tone.noOp,
+ 'fadeIn': 0,
+ 'fadeOut': 0
+ };
+ /**
+ * Returns the playback state of the source, either "started" or "stopped".
+ * @type {Tone.State}
+ * @readOnly
+ * @memberOf Tone.BufferSource#
+ * @name state
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, 'state', {
+ get: function () {
+ var now = this.now();
+ if (this._startTime !== -1 && now > this._startTime) {
+ return Tone.State.Started;
+ } else {
+ return Tone.State.Stopped;
}
}
+ });
+ /**
+ * Start the buffer
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
+ * to start at.
+ * @param {Time=} duration How long the sample should play. If no duration
+ * is given, it will default to the full length
+ * of the sample (minus any offset)
+ * @param {Gain} [gain=1] The gain to play the buffer back at.
+ * @param {Time=} fadeInTime The optional fadeIn ramp time.
+ * @return {Tone.BufferSource} this
+ */
+ Tone.BufferSource.prototype.start = function (time, offset, duration, gain, fadeInTime) {
+ if (this._startTime !== -1) {
+ throw new Error('Tone.BufferSource: can only be started once.');
+ }
+ if (!this.buffer) {
+ throw new Error('Tone.BufferSource: no buffer set.');
+ }
+ time = this.toSeconds(time);
+ //if it's a loop the default offset is the loopstart point
+ if (this.loop) {
+ offset = this.defaultArg(offset, this.loopStart);
+ } else {
+ //otherwise the default offset is 0
+ offset = this.defaultArg(offset, 0);
+ }
+ offset = this.toSeconds(offset);
+ //the values in seconds
+ time = this.toSeconds(time);
+ this._source.start(time, offset);
+ gain = this.defaultArg(gain, 1);
+ this._gain = gain;
+ //the fadeIn time
+ if (this.isUndef(fadeInTime)) {
+ fadeInTime = this.toSeconds(this.fadeIn);
+ } else {
+ fadeInTime = this.toSeconds(fadeInTime);
+ }
+ if (fadeInTime > 0) {
+ this._gainNode.gain.setValueAtTime(0, time);
+ this._gainNode.gain.linearRampToValueAtTime(this._gain, time + fadeInTime);
+ } else {
+ this._gainNode.gain.setValueAtTime(gain, time);
+ }
+ this._startTime = time + fadeInTime;
+ if (!this.isUndef(duration)) {
+ duration = this.defaultArg(duration, this.buffer.duration - offset);
+ duration = this.toSeconds(duration);
+ this.stop(time + duration + fadeInTime, fadeInTime);
+ }
+ return this;
};
/**
- * trigger the attack portion of the note
- *
- * @param {Time} [time=now] the time the note will occur
- * @param {number} [velocity=1] the velocity of the note
- * @returns {Tone.SimpleFM} this
- * @private
+ * Stop the buffer. Optionally add a ramp time to fade the
+ * buffer out.
+ * @param {Time=} time The time the buffer should stop.
+ * @param {Time=} fadeOutTime How long the gain should fade out for
+ * @return {Tone.BufferSource} this
*/
- Tone.SimpleFM.prototype._triggerEnvelopeAttack = function (time, velocity) {
- //the port glide
+ Tone.BufferSource.prototype.stop = function (time, fadeOutTime) {
+ if (!this.buffer) {
+ throw new Error('Tone.BufferSource: no buffer set.');
+ }
time = this.toSeconds(time);
- //the envelopes
- this.carrier.envelope.triggerAttack(time, velocity);
- this.modulator.envelope.triggerAttack(time);
+ //the fadeOut time
+ if (this.isUndef(fadeOutTime)) {
+ fadeOutTime = this.toSeconds(this.fadeOut);
+ } else {
+ fadeOutTime = this.toSeconds(fadeOutTime);
+ }
+ //cancel the end curve
+ this._gainNode.gain.cancelScheduledValues(this._startTime + this.sampleTime);
+ //set a new one
+ if (fadeOutTime > 0) {
+ this._gainNode.gain.setValueAtTime(this._gain, time);
+ this._gainNode.gain.linearRampToValueAtTime(0, time + fadeOutTime);
+ time += fadeOutTime;
+ } else {
+ this._gainNode.gain.setValueAtTime(0, time);
+ }
+ this._source.stop(time);
return this;
};
/**
- * trigger the release portion of the note
- *
- * @param {Time} [time=now] the time the note will release
- * @returns {Tone.SimpleFM} this
+ * Internal callback when the buffer is ended.
+ * Invokes `onended` and disposes the node.
* @private
*/
- Tone.SimpleFM.prototype._triggerEnvelopeRelease = function (time) {
- this.carrier.triggerRelease(time);
- this.modulator.triggerRelease(time);
- return this;
+ Tone.BufferSource.prototype._onended = function () {
+ this.onended(this);
+ this.dispose();
};
/**
- * clean up
- * @returns {Tone.SimpleFM} this
+ * If loop is true, the loop will start at this position.
+ * @memberOf Tone.BufferSource#
+ * @type {Time}
+ * @name loopStart
*/
- Tone.SimpleFM.prototype.dispose = function () {
- Tone.Monophonic.prototype.dispose.call(this);
- this._writable([
- 'carrier',
- 'modulator',
- 'frequency',
- 'harmonicity',
- 'modulationIndex'
- ]);
- this.carrier.dispose();
- this.carrier = null;
- this.modulator.dispose();
- this.modulator = null;
- this.frequency.dispose();
- this.frequency = null;
- this.modulationIndex.dispose();
- this.modulationIndex = null;
- this.harmonicity.dispose();
- this.harmonicity = null;
- this._modulationNode.disconnect();
- this._modulationNode = null;
+ Object.defineProperty(Tone.BufferSource.prototype, 'loopStart', {
+ get: function () {
+ return this._source.loopStart;
+ },
+ set: function (loopStart) {
+ this._source.loopStart = this.toSeconds(loopStart);
+ }
+ });
+ /**
+ * If loop is true, the loop will end at this position.
+ * @memberOf Tone.BufferSource#
+ * @type {Time}
+ * @name loopEnd
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, 'loopEnd', {
+ get: function () {
+ return this._source.loopEnd;
+ },
+ set: function (loopEnd) {
+ this._source.loopEnd = this.toSeconds(loopEnd);
+ }
+ });
+ /**
+ * The audio buffer belonging to the player.
+ * @memberOf Tone.BufferSource#
+ * @type {AudioBuffer}
+ * @name buffer
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, 'buffer', {
+ get: function () {
+ return this._source.buffer;
+ },
+ set: function (buffer) {
+ if (buffer instanceof Tone.Buffer) {
+ this._source.buffer = buffer.get();
+ } else {
+ this._source.buffer = buffer;
+ }
+ }
+ });
+ /**
+ * If the buffer should loop once it's over.
+ * @memberOf Tone.BufferSource#
+ * @type {boolean}
+ * @name loop
+ */
+ Object.defineProperty(Tone.BufferSource.prototype, 'loop', {
+ get: function () {
+ return this._source.loop;
+ },
+ set: function (loop) {
+ this._source.loop = loop;
+ }
+ });
+ /**
+ * Clean up.
+ * @return {Tone.BufferSource} this
+ */
+ Tone.BufferSource.prototype.dispose = function () {
+ this.onended = null;
+ if (this._source) {
+ this._source.onended = null;
+ this._source.disconnect();
+ this._source = null;
+ }
+ if (this._gainNode) {
+ this._gainNode.disconnect();
+ this._gainNode = null;
+ }
+ this._startTime = -1;
+ this.playbackRate = null;
+ this.output = null;
return this;
};
- return Tone.SimpleFM;
+ return Tone.BufferSource;
});
Module(function (Tone) {
@@ -19137,11 +20239,12 @@
/**
* wrapper for getUserMedia function
* @param {function} callback
+ * @param {function} error
* @private
*/
- Tone.ExternalInput.prototype._getUserMedia = function (callback) {
+ Tone.ExternalInput.prototype._getUserMedia = function (callback, error) {
if (!Tone.ExternalInput.supported) {
- throw new Error('browser does not support \'getUserMedia\'');
+ error('browser does not support \'getUserMedia\'');
}
if (Tone.ExternalInput.sources[this._inputNum]) {
this._constraints = { audio: { optional: [{ sourceId: Tone.ExternalInput.sources[this._inputNum].id }] } };
@@ -19150,7 +20253,7 @@
this._onStream(stream);
callback();
}.bind(this), function (err) {
- callback(err);
+ error(err);
});
};
/**
@@ -19160,7 +20263,7 @@
*/
Tone.ExternalInput.prototype._onStream = function (stream) {
if (!this.isFunction(this.context.createMediaStreamSource)) {
- throw new Error('browser does not support the \'MediaStreamSourceNode\'');
+ throw new Error('Tone.ExternalInput: browser does not support the \'MediaStreamSourceNode\'');
}
//can only start a new source if the previous one is closed
if (!this._stream) {
@@ -19175,12 +20278,18 @@
* Open the media stream
* @param {function=} callback The callback function to
* execute when the stream is open
+ * @param {function=} error The callback function to execute
+ * when the media stream can't open.
+ * This is fired either because the browser
+ * doesn't support the media stream,
+ * or the user blocked opening the microphone.
* @return {Tone.ExternalInput} this
*/
- Tone.ExternalInput.prototype.open = function (callback) {
+ Tone.ExternalInput.prototype.open = function (callback, error) {
callback = this.defaultArg(callback, Tone.noOp);
+ error = this.defaultArg(error, Tone.noOp);
Tone.ExternalInput.getSources(function () {
- this._getUserMedia(callback);
+ this._getUserMedia(callback, error);
}.bind(this));
return this;
};
@@ -19300,384 +20409,560 @@
return Tone.ExternalInput;
});
Module(function (Tone) {
-
/**
- * @class Opens up the default source (typically the microphone).
- *
- * @constructor
- * @extends {Tone.ExternalInput}
+ * @class Tone.MultiPlayer is well suited for one-shots, multi-sampled istruments
+ * or any time you need to play a bunch of audio buffers.
+ * @param {Object|Array|Tone.Buffers} buffers The buffers which are available
+ * to the MultiPlayer
+ * @param {Function} onload The callback to invoke when all of the buffers are loaded.
+ * @extends {Tone}
* @example
- * //mic will feedback if played through master
- * var mic = new Tone.Microphone();
- * mic.open(function(){
- * //start the mic at ten seconds
- * mic.start(10);
- * });
- * //stop the mic
- * mic.stop(20);
+ * var multiPlayer = new MultiPlayer({
+ * "kick" : "path/to/kick.mp3",
+ * "snare" : "path/to/snare.mp3",
+ * }, function(){
+ * multiPlayer.start("kick");
+ * });
+ * @example
+ * //can also store the values in an array
+ * var multiPlayer = new MultiPlayer(["path/to/kick.mp3", "path/to/snare.mp3"],
+ * function(){
+ * //if an array is passed in, the samples are referenced to by index
+ * multiPlayer.start(1);
+ * });
*/
- Tone.Microphone = function () {
- Tone.ExternalInput.call(this, 0);
+ Tone.MultiPlayer = function () {
+ var options = this.optionsObject(arguments, [
+ 'urls',
+ 'onload'
+ ], Tone.MultiPlayer.defaults);
+ if (options.urls instanceof Tone.Buffers) {
+ /**
+ * All the buffers belonging to the player.
+ * @type {Tone.Buffers}
+ */
+ this.buffers = options.urls;
+ } else {
+ this.buffers = new Tone.Buffers(options.urls, options.onload);
+ }
+ /**
+ * Keeps track of the currently playing sources.
+ * @type {Array}
+ * @private
+ */
+ this._activeSources = [];
+ /**
+ * The fade in envelope which is applied
+ * to the beginning of the BufferSource
+ * @type {Time}
+ */
+ this.fadeIn = options.fadeIn;
+ /**
+ * The fade out envelope which is applied
+ * to the end of the BufferSource
+ * @type {Time}
+ */
+ this.fadeOut = options.fadeOut;
+ /**
+ * The output volume node
+ * @type {Tone.Volume}
+ * @private
+ */
+ this._volume = this.output = new Tone.Volume(options.volume);
+ /**
+ * The volume of the output in decibels.
+ * @type {Decibels}
+ * @signal
+ * @example
+ * source.volume.value = -6;
+ */
+ this.volume = this._volume.volume;
+ this._readOnly('volume');
+ //make the output explicitly stereo
+ this._volume.output.output.channelCount = 2;
+ this._volume.output.output.channelCountMode = 'explicit';
+ //mute initially
+ this.mute = options.mute;
};
- Tone.extend(Tone.Microphone, Tone.ExternalInput);
+ Tone.extend(Tone.MultiPlayer, Tone.Source);
/**
- * If getUserMedia is supported by the browser.
- * @type {Boolean}
- * @memberOf Tone.Microphone#
- * @name supported
- * @static
- * @readOnly
+ * The defaults
+ * @type {Object}
*/
- Object.defineProperty(Tone.Microphone, 'supported', {
- get: function () {
- return Tone.ExternalInput.supported;
+ Tone.MultiPlayer.defaults = {
+ 'onload': Tone.noOp,
+ 'fadeIn': 0,
+ 'fadeOut': 0
+ };
+ /**
+ * Get the given buffer.
+ * @param {String|Number|AudioBuffer|Tone.Buffer} buffer
+ * @return {AudioBuffer} The requested buffer.
+ * @private
+ */
+ Tone.MultiPlayer.prototype._getBuffer = function (buffer) {
+ if (this.isNumber(buffer) || this.isString(buffer)) {
+ return this.buffers.get(buffer).get();
+ } else if (buffer instanceof Tone.Buffer) {
+ return buffer.get();
+ } else {
+ return buffer;
}
- });
- return Tone.Microphone;
- });
- Module(function (Tone) {
-
+ };
/**
- * @class Clip the incoming signal so that the output is always between min and max.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number} min the minimum value of the outgoing signal
- * @param {number} max the maximum value of the outgoing signal
- * @example
- * var clip = new Tone.Clip(0.5, 1);
- * var osc = new Tone.Oscillator().connect(clip);
- * //clips the output of the oscillator to between 0.5 and 1.
+ * Start a buffer by name. The `start` method allows a number of options
+ * to be passed in such as offset, interval, and gain. This is good for multi-sampled
+ * instruments and sound sprites where samples are repitched played back at different velocities.
+ * @param {String|AudioBuffer} buffer The name of the buffer to start.
+ * Or pass in a buffer which will be started.
+ * @param {Time} time When to start the buffer.
+ * @param {Time} [offset=0] The offset into the buffer to play from.
+ * @param {Time=} duration How long to play the buffer for.
+ * @param {Interval} [interval=0] The interval to repitch the buffer.
+ * @param {Gain} [gain=1] The gain to play the sample at.
+ * @return {Tone.MultiPlayer} this
*/
- Tone.Clip = function (min, max) {
- //make sure the args are in the right order
- if (min > max) {
- var tmp = min;
- min = max;
- max = tmp;
+ Tone.MultiPlayer.prototype.start = function (buffer, time, offset, duration, interval, gain) {
+ buffer = this._getBuffer(buffer);
+ var source = new Tone.BufferSource(buffer).connect(this.output);
+ this._activeSources.push(source);
+ time = this.toSeconds(time);
+ source.start(time, offset, duration, this.defaultArg(gain, 1), this.fadeIn);
+ if (duration) {
+ source.stop(time + this.toSeconds(duration), this.fadeOut);
}
- /**
- * The min clip value
- * @type {Number}
- * @signal
- */
- this.min = this.input = new Tone.Min(max);
- this._readOnly('min');
- /**
- * The max clip value
- * @type {Number}
- * @signal
- */
- this.max = this.output = new Tone.Max(min);
- this._readOnly('max');
- this.min.connect(this.max);
+ source.onended = this._onended.bind(this);
+ interval = this.defaultArg(interval, 0);
+ source.playbackRate.value = this.intervalToFrequencyRatio(interval);
+ return this;
};
- Tone.extend(Tone.Clip, Tone.SignalBase);
/**
- * clean up
- * @returns {Tone.Clip} this
+ * Internal callback when a buffer is done playing.
+ * @param {Tone.BufferSource} source The stopped source
+ * @private
*/
- Tone.Clip.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._writable('min');
- this.min.dispose();
- this.min = null;
- this._writable('max');
- this.max.dispose();
- this.max = null;
+ Tone.MultiPlayer.prototype._onended = function (source) {
+ var index = this._activeSources.indexOf(source);
+ this._activeSources.splice(index, 1);
+ };
+ /**
+ * Stop all instances of the currently playing buffer at the given time.
+ * @param {String|AudioBuffer} buffer The buffer to stop.
+ * @param {Time=} time When to stop the buffer
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.stop = function (buffer, time) {
+ buffer = this._getBuffer(buffer);
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._activeSources.length; i++) {
+ if (this._activeSources[i].buffer === buffer) {
+ this._activeSources[i].stop(time, this.fadeOut);
+ }
+ }
return this;
};
- return Tone.Clip;
- });
- Module(function (Tone) {
-
/**
- * @class Normalize takes an input min and max and maps it linearly to NormalRange [0,1]
- *
- * @extends {Tone.SignalBase}
- * @constructor
- * @param {number} inputMin the min input value
- * @param {number} inputMax the max input value
- * @example
- * var norm = new Tone.Normalize(2, 4);
- * var sig = new Tone.Signal(3).connect(norm);
- * //output of norm is 0.5.
+ * Stop all currently playing buffers at the given time.
+ * @param {Time=} time When to stop the buffers.
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.stopAll = function (time) {
+ time = this.toSeconds(time);
+ for (var i = 0; i < this._activeSources.length; i++) {
+ this._activeSources[i].stop(time, this.fadeOut);
+ }
+ return this;
+ };
+ /**
+ * Add another buffer to the available buffers.
+ * @param {String} name The name to that the buffer is refered
+ * to in start/stop methods.
+ * @param {String|Tone.Buffer} url The url of the buffer to load
+ * or the buffer.
+ * @param {Function} callback The function to invoke after the buffer is loaded.
+ */
+ Tone.MultiPlayer.prototype.add = function (name, url, callback) {
+ this.buffers.add(name, url, callback);
+ return this;
+ };
+ /**
+ * Returns the playback state of the source. "started"
+ * if there are any buffers playing. "stopped" otherwise.
+ * @type {Tone.State}
+ * @readOnly
+ * @memberOf Tone.MultiPlayer#
+ * @name state
+ */
+ Object.defineProperty(Tone.MultiPlayer.prototype, 'state', {
+ get: function () {
+ return this._activeSources.length > 0 ? Tone.State.Started : Tone.State.Stopped;
+ }
+ });
+ /**
+ * Mute the output.
+ * @memberOf Tone.MultiPlayer#
+ * @type {boolean}
+ * @name mute
+ * @example
+ * //mute the output
+ * source.mute = true;
+ */
+ Object.defineProperty(Tone.MultiPlayer.prototype, 'mute', {
+ get: function () {
+ return this._volume.mute;
+ },
+ set: function (mute) {
+ this._volume.mute = mute;
+ }
+ });
+ /**
+ * Clean up.
+ * @return {Tone.MultiPlayer} this
+ */
+ Tone.MultiPlayer.prototype.dispose = function () {
+ Tone.prototype.dispose.call(this);
+ this._volume.dispose();
+ this._volume = null;
+ this._writable('volume');
+ this.volume = null;
+ this.buffers.dispose();
+ this.buffers = null;
+ for (var i = 0; i < this._activeSources.length; i++) {
+ this._activeSources[i].dispose();
+ }
+ this._activeSources = null;
+ return this;
+ };
+ return Tone.MultiPlayer;
+ });
+ Module(function (Tone) {
+ /**
+ * @class Tone.GrainPlayer implements [granular synthesis](https://en.wikipedia.org/wiki/Granular_synthesis).
+ * Granular Synthesis enables you to adjust pitch and playback rate independently. The grainSize is the
+ * amount of time each small chunk of audio is played for and the overlap is the
+ * amount of crossfading transition time between successive grains.
+ * @extends {Tone}
+ * @param {String|Tone.Buffer} url The url to load, or the Tone.Buffer to play.
+ * @param {Function=} callback The callback to invoke after the url is loaded.
*/
- Tone.Normalize = function (inputMin, inputMax) {
+ Tone.GrainPlayer = function () {
+ var options = this.optionsObject(arguments, [
+ 'url',
+ 'onload'
+ ], Tone.GrainPlayer.defaults);
+ Tone.Source.call(this);
/**
- * the min input value
- * @type {number}
+ * The audio buffer belonging to the player.
+ * @type {Tone.Buffer}
+ */
+ this.buffer = new Tone.Buffer(options.url, options.onload);
+ /**
+ * Plays the buffer with a small envelope
+ * @type {Tone.MultiPlayer}
* @private
*/
- this._inputMin = this.defaultArg(inputMin, 0);
+ this._player = this.output = new Tone.MultiPlayer();
/**
- * the max input value
- * @type {number}
+ * Create a repeating tick to schedule
+ * the grains.
+ * @type {Tone.Clock}
* @private
*/
- this._inputMax = this.defaultArg(inputMax, 1);
+ this._clock = new Tone.Clock(this._tick.bind(this), 1);
/**
- * subtract the min from the input
- * @type {Tone.Add}
+ * @type {Number}
* @private
*/
- this._sub = this.input = new Tone.Add(0);
+ this._loopStart = 0;
/**
- * divide by the difference between the input and output
- * @type {Tone.Multiply}
+ * @type {Number}
* @private
*/
- this._div = this.output = new Tone.Multiply(1);
- this._sub.connect(this._div);
- this._setRange();
+ this._loopEnd = 0;
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._playbackRate = options.playbackRate;
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._grainSize = options.grainSize;
+ /**
+ * @private
+ * @type {Number}
+ */
+ this._overlap = options.overlap;
+ /**
+ * Adjust the pitch independently of the playbackRate.
+ * @type {Cents}
+ */
+ this.detune = options.detune;
+ /**
+ * The amount of time randomly added
+ * or subtracted from the grain's offset
+ * @type {Time}
+ */
+ this.drift = options.drift;
+ //setup
+ this.overlap = options.overlap;
+ this.loop = options.loop;
+ this.playbackRate = options.playbackRate;
+ this.grainSize = options.grainSize;
+ this.loopStart = options.loopStart;
+ this.loopEnd = options.loopEnd;
+ this.reverse = options.reverse;
};
- Tone.extend(Tone.Normalize, Tone.SignalBase);
+ Tone.extend(Tone.GrainPlayer, Tone.Source);
/**
- * The minimum value the input signal will reach.
- * @memberOf Tone.Normalize#
- * @type {number}
- * @name min
+ * the default parameters
+ * @static
+ * @const
+ * @type {Object}
*/
- Object.defineProperty(Tone.Normalize.prototype, 'min', {
- get: function () {
- return this._inputMin;
- },
- set: function (min) {
- this._inputMin = min;
- this._setRange();
- }
- });
+ Tone.GrainPlayer.defaults = {
+ 'onload': Tone.noOp,
+ 'overlap': 0.1,
+ 'grainSize': 0.2,
+ 'drift': 0,
+ 'playbackRate': 1,
+ 'detune': 0,
+ 'loop': false,
+ 'loopStart': 0,
+ 'loopEnd': 0,
+ 'reverse': false
+ };
/**
- * The maximum value the input signal will reach.
- * @memberOf Tone.Normalize#
- * @type {number}
- * @name max
+ * Play the buffer at the given startTime. Optionally add an offset
+ * from the start of the buffer to play from.
+ *
+ * @param {Time} [startTime=now] When the player should start.
+ * @param {Time} [offset=0] The offset from the beginning of the sample
+ * to start at.
+ * @return {Tone.GrainPlayer} this
*/
- Object.defineProperty(Tone.Normalize.prototype, 'max', {
- get: function () {
- return this._inputMax;
- },
- set: function (max) {
- this._inputMax = max;
- this._setRange();
- }
- });
/**
- * set the values
+ * Internal start method
+ * @param {Time} time
+ * @param {Time} offset
* @private
*/
- Tone.Normalize.prototype._setRange = function () {
- this._sub.value = -this._inputMin;
- this._div.value = 1 / (this._inputMax - this._inputMin);
+ Tone.GrainPlayer.prototype._start = function (time, offset) {
+ offset = this.defaultArg(offset, 0);
+ offset = this.toSeconds(offset);
+ time = this.toSeconds(time);
+ this._offset = offset;
+ this._clock.start(time);
};
/**
- * clean up
- * @returns {Tone.Normalize} this
+ * Internal start method
+ * @param {Time} time
+ * @private
*/
- Tone.Normalize.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._sub.dispose();
- this._sub = null;
- this._div.dispose();
- this._div = null;
- return this;
+ Tone.GrainPlayer.prototype._stop = function (time) {
+ this._clock.stop(time);
+ this._player.stop(this.buffer, time);
+ this._offset = 0;
};
- return Tone.Normalize;
- });
- Module(function (Tone) {
-
/**
- * @class Route a single input to the specified output.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {number} [outputCount=2] the number of inputs the switch accepts
- * @example
- * var route = new Tone.Route(4);
- * var signal = new Tone.Signal(3).connect(route);
- * route.select(0);
- * //signal is routed through output 0
- * route.select(3);
- * //signal is now routed through output 3
- */
- Tone.Route = function (outputCount) {
- outputCount = this.defaultArg(outputCount, 2);
- Tone.call(this, 1, outputCount);
- /**
- * The control signal.
- * @type {Number}
- * @signal
- */
- this.gate = new Tone.Signal(0);
- this._readOnly('gate');
- //make all the inputs and connect them
- for (var i = 0; i < outputCount; i++) {
- var routeGate = new RouteGate(i);
- this.output[i] = routeGate;
- this.gate.connect(routeGate.selecter);
- this.input.connect(routeGate);
+ * Invoked on each clock tick. scheduled a new
+ * grain at this time.
+ * @param {Time} time
+ * @private
+ */
+ Tone.GrainPlayer.prototype._tick = function (time) {
+ var bufferDuration = this.buffer.duration;
+ if (this.loop && this._loopEnd > 0) {
+ bufferDuration = this._loopEnd;
+ }
+ var drift = (Math.random() * 2 - 1) * this.drift;
+ var offset = this._offset - this._overlap + drift;
+ var detune = this.detune / 100;
+ var originalFadeIn = this._player.fadeIn;
+ if (this.loop && this._offset > bufferDuration) {
+ //play the end
+ var endSegmentDuration = this._offset - bufferDuration;
+ this._player.start(this.buffer, time, offset, endSegmentDuration + this._overlap, detune);
+ //and play the beginning
+ offset = this._offset % bufferDuration;
+ this._offset = this._loopStart;
+ this._player.fadeIn = 0;
+ this._player.start(this.buffer, time + endSegmentDuration, this._offset, offset + this._overlap, detune);
+ } else if (this._offset > bufferDuration) {
+ //set the state to stopped.
+ this.stop(time);
+ } else {
+ if (offset < 0) {
+ this._player.fadeIn = Math.max(this._player.fadeIn + offset, 0);
+ offset = 0;
+ }
+ this._player.start(this.buffer, time, offset, this.grainSize + this._overlap, detune);
}
+ this._player.fadeIn = originalFadeIn;
+ //increment the offset
+ var duration = this._clock._nextTick - time;
+ this._offset += duration * this._playbackRate;
};
- Tone.extend(Tone.Route, Tone.SignalBase);
/**
- * Routes the signal to one of the outputs and close the others.
- * @param {number} [which=0] Open one of the gates (closes the other).
- * @param {Time} [time=now] The time when the switch will open.
- * @returns {Tone.Route} this
+ * Jump to a specific time and play it.
+ * @param {Time} offset The offset to jump to.
+ * @param {Time=} time When to make the jump.
+ * @return {[type]} [description]
*/
- Tone.Route.prototype.select = function (which, time) {
- //make sure it's an integer
- which = Math.floor(which);
- this.gate.setValueAtTime(which, this.toSeconds(time));
+ Tone.GrainPlayer.prototype.scrub = function (offset, time) {
+ this._offset = this.toSeconds(offset);
+ this._tick(this.toSeconds(time));
return this;
};
/**
- * Clean up.
- * @returns {Tone.Route} this
+ * The playback rate of the sample
+ * @memberOf Tone.GrainPlayer#
+ * @type {Positive}
+ * @name playbackRate
*/
- Tone.Route.prototype.dispose = function () {
- this._writable('gate');
- this.gate.dispose();
- this.gate = null;
- for (var i = 0; i < this.output.length; i++) {
- this.output[i].dispose();
- this.output[i] = null;
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'playbackRate', {
+ get: function () {
+ return this._playbackRate;
+ },
+ set: function (rate) {
+ this._playbackRate = rate;
+ this.grainSize = this._grainSize;
}
- Tone.prototype.dispose.call(this);
- return this;
- };
- ////////////START HELPER////////////
+ });
/**
- * helper class for Tone.Route representing a single gate
- * @constructor
- * @extends {Tone}
- * @private
+ * The loop start time.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name loopStart
*/
- var RouteGate = function (num) {
- /**
- * the selector
- * @type {Tone.Equal}
- */
- this.selecter = new Tone.Equal(num);
- /**
- * the gate
- * @type {GainNode}
- */
- this.gate = this.input = this.output = this.context.createGain();
- //connect the selecter to the gate gain
- this.selecter.connect(this.gate.gain);
- };
- Tone.extend(RouteGate);
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'loopStart', {
+ get: function () {
+ return this._loopStart;
+ },
+ set: function (time) {
+ this._loopStart = this.toSeconds(time);
+ }
+ });
/**
- * clean up
- * @private
+ * The loop end time.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name loopEnd
*/
- RouteGate.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this.selecter.dispose();
- this.selecter = null;
- this.gate.disconnect();
- this.gate = null;
- };
- ////////////END HELPER////////////
- //return Tone.Route
- return Tone.Route;
- });
- Module(function (Tone) {
-
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'loopEnd', {
+ get: function () {
+ return this._loopEnd;
+ },
+ set: function (time) {
+ this._loopEnd = this.toSeconds(time);
+ }
+ });
/**
- * @class When the gate is set to 0, the input signal does not pass through to the output.
- * If the gate is set to 1, the input signal passes through.
- * the gate is initially closed.
- *
- * @constructor
- * @extends {Tone.SignalBase}
- * @param {Boolean} [open=false] If the gate is initially open or closed.
- * @example
- * var sigSwitch = new Tone.Switch();
- * var signal = new Tone.Signal(2).connect(sigSwitch);
- * //initially no output from sigSwitch
- * sigSwitch.gate.value = 1;
- * //open the switch and allow the signal through
- * //the output of sigSwitch is now 2.
- */
- Tone.Switch = function (open) {
- open = this.defaultArg(open, false);
- Tone.call(this);
- /**
- * The control signal for the switch.
- * When this value is 0, the input signal will NOT pass through,
- * when it is high (1), the input signal will pass through.
- *
- * @type {Number}
- * @signal
- */
- this.gate = new Tone.Signal(0);
- this._readOnly('gate');
- /**
- * thresh the control signal to either 0 or 1
- * @type {Tone.GreaterThan}
- * @private
- */
- this._thresh = new Tone.GreaterThan(0.5);
- this.input.connect(this.output);
- this.gate.chain(this._thresh, this.output.gain);
- //initially open
- if (open) {
- this.open();
+ * The direction the buffer should play in
+ * @memberOf Tone.GrainPlayer#
+ * @type {boolean}
+ * @name reverse
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'reverse', {
+ get: function () {
+ return this.buffer.reverse;
+ },
+ set: function (rev) {
+ this.buffer.reverse = rev;
}
- };
- Tone.extend(Tone.Switch, Tone.SignalBase);
+ });
/**
- * Open the switch at a specific time.
- *
- * @param {Time} [time=now] The time when the switch will be open.
- * @returns {Tone.Switch} this
- * @example
- * //open the switch to let the signal through
- * sigSwitch.open();
+ * The size of each chunk of audio that the
+ * buffer is chopped into and played back at.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name grainSize
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'grainSize', {
+ get: function () {
+ return this._grainSize;
+ },
+ set: function (size) {
+ this._grainSize = this.toSeconds(size);
+ this._clock.frequency.value = this._playbackRate / this._grainSize;
+ }
+ });
+ /**
+ * This is the duration of the cross-fade between
+ * sucessive grains.
+ * @memberOf Tone.GrainPlayer#
+ * @type {Time}
+ * @name overlap
+ */
+ Object.defineProperty(Tone.GrainPlayer.prototype, 'overlap', {
+ get: function () {
+ return this._overlap;
+ },
+ set: function (time) {
+ time = this.toSeconds(time);
+ this._overlap = time;
+ if (this._overlap < 0) {
+ this._player.fadeIn = 0.01;
+ this._player.fadeOut = 0.01;
+ } else {
+ this._player.fadeIn = time;
+ this._player.fadeOut = time;
+ }
+ }
+ });
+ /**
+ * Clean up
+ * @return {Tone.GrainPlayer} this
*/
- Tone.Switch.prototype.open = function (time) {
- this.gate.setValueAtTime(1, this.toSeconds(time));
+ Tone.GrainPlayer.prototype.dispose = function () {
+ Tone.Source.prototype.dispose.call(this);
+ this.buffer.dispose();
+ this.buffer = null;
+ this._player.dispose();
+ this._player = null;
+ this._clock.dispose();
+ this._clock = null;
return this;
};
+ return Tone.GrainPlayer;
+ });
+ Module(function (Tone) {
+
/**
- * Close the switch at a specific time.
+ * @class Opens up the default source (typically the microphone).
*
- * @param {Time} [time=now] The time when the switch will be closed.
- * @returns {Tone.Switch} this
+ * @constructor
+ * @extends {Tone.ExternalInput}
* @example
- * //close the switch a half second from now
- * sigSwitch.close("+0.5");
+ * //mic will feedback if played through master
+ * var mic = new Tone.Microphone();
+ * mic.open(function(){
+ * //start the mic at ten seconds
+ * mic.start(10);
+ * });
+ * //stop the mic
+ * mic.stop(20);
*/
- Tone.Switch.prototype.close = function (time) {
- this.gate.setValueAtTime(0, this.toSeconds(time));
- return this;
+ Tone.Microphone = function () {
+ Tone.ExternalInput.call(this, 0);
};
+ Tone.extend(Tone.Microphone, Tone.ExternalInput);
/**
- * Clean up.
- * @returns {Tone.Switch} this
+ * If getUserMedia is supported by the browser.
+ * @type {Boolean}
+ * @memberOf Tone.Microphone#
+ * @name supported
+ * @static
+ * @readOnly
*/
- Tone.Switch.prototype.dispose = function () {
- Tone.prototype.dispose.call(this);
- this._writable('gate');
- this.gate.dispose();
- this.gate = null;
- this._thresh.dispose();
- this._thresh = null;
- return this;
- };
- return Tone.Switch;
+ Object.defineProperty(Tone.Microphone, 'supported', {
+ get: function () {
+ return Tone.ExternalInput.supported;
+ }
+ });
+ return Tone.Microphone;
});
- //UMD
- if ( typeof define === "function" && define.amd ) {
- define(function() {
- return Tone;
- });
- } else if (typeof module === "object") {
- module.exports = Tone;
- } else {
- root.Tone = Tone;
- }
-} (this));
\ No newline at end of file
+ return Tone;
+}));
\ No newline at end of file
diff --git a/build/Tone.min.js b/build/Tone.min.js
index a74dcb5ec..9a5017194 100644
--- a/build/Tone.min.js
+++ b/build/Tone.min.js
@@ -1,13 +1,15 @@
-!function(root){"use strict";function Main(t){Tone=t()}function Module(t){t(Tone)}var Tone;/**
+!function(t,e){"function"==typeof define&&define.amd?define(function(){return e()}):"object"==typeof module?module.exports=e():t.Tone=e()}(this,function(){"use strict";function t(t){i=t()}function e(t){t(i)}var i;/**
* Tone.js
* @author Yotam Mann
* @license http://opensource.org/licenses/MIT MIT License
- * @copyright 2014-2015 Yotam Mann
+ * @copyright 2014-2016 Yotam Mann
*/
-Main(function(){function t(t){return void 0===t}function e(t){return"function"==typeof t}var i,s,n,o;if(t(window.AudioContext)&&(window.AudioContext=window.webkitAudioContext),t(window.OfflineAudioContext)&&(window.OfflineAudioContext=window.webkitOfflineAudioContext),t(AudioContext))throw new Error("Web Audio is not supported in this browser");return i=new AudioContext,e(AudioContext.prototype.createGain)||(AudioContext.prototype.createGain=AudioContext.prototype.createGainNode),e(AudioContext.prototype.createDelay)||(AudioContext.prototype.createDelay=AudioContext.prototype.createDelayNode),e(AudioContext.prototype.createPeriodicWave)||(AudioContext.prototype.createPeriodicWave=AudioContext.prototype.createWaveTable),e(AudioBufferSourceNode.prototype.start)||(AudioBufferSourceNode.prototype.start=AudioBufferSourceNode.prototype.noteGrainOn),e(AudioBufferSourceNode.prototype.stop)||(AudioBufferSourceNode.prototype.stop=AudioBufferSourceNode.prototype.noteOff),e(OscillatorNode.prototype.start)||(OscillatorNode.prototype.start=OscillatorNode.prototype.noteOn),e(OscillatorNode.prototype.stop)||(OscillatorNode.prototype.stop=OscillatorNode.prototype.noteOff),e(OscillatorNode.prototype.setPeriodicWave)||(OscillatorNode.prototype.setPeriodicWave=OscillatorNode.prototype.setWaveTable),AudioNode.prototype._nativeConnect=AudioNode.prototype.connect,AudioNode.prototype.connect=function(e,i,s){if(e.input)Array.isArray(e.input)?(t(s)&&(s=0),this.connect(e.input[s])):this.connect(e.input,i,s);else try{e instanceof AudioNode?this._nativeConnect(e,i,s):this._nativeConnect(e,i)}catch(n){throw new Error("error connecting to node: "+e)}},s=function(e,i){t(e)||1===e?this.input=this.context.createGain():e>1&&(this.input=new Array(e)),t(i)||1===i?this.output=this.context.createGain():i>1&&(this.output=new Array(e))},s.prototype.set=function(e,i,n){var o,r,a,l,h,u;this.isObject(e)?n=i:this.isString(e)&&(o={},o[e]=i,e=o);for(r in e){if(i=e[r],a=this,-1!==r.indexOf(".")){for(l=r.split("."),h=0;hreturnType
is "byte" which returns values
@@ -41,10 +42,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+