From 86ff8a31aa746c3523ec005e4496dde260019c68 Mon Sep 17 00:00:00 2001 From: Jason Sigal Date: Sun, 15 Jun 2014 16:40:41 -0400 Subject: [PATCH 1/2] changed "playing" to "started" in transport example. Continuous range values for panner example --- examples/panner.html | 12 +++++++++--- examples/signalProcessing.html | 4 ++-- examples/transport.html | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/panner.html b/examples/panner.html index 63033a96..d9a8f82e 100644 --- a/examples/panner.html +++ b/examples/panner.html @@ -47,11 +47,17 @@ var range = document.querySelector("input"); - range.onchange = function(){ - var val = range.value; + range.oninput = function(e){ + var val = e.target.value; pan.setPan(val/100, "+.1"); } + var mouseX, mouseY; + window.onmousemove = function(e) { + mouseX = e.x; + mouseY = e.y; + }; + - \ No newline at end of file + diff --git a/examples/signalProcessing.html b/examples/signalProcessing.html index ed1ea109..8fe5cb9f 100644 --- a/examples/signalProcessing.html +++ b/examples/signalProcessing.html @@ -17,13 +17,13 @@ var dryMeter = new Tone.Meter(); signal.connect(dryMeter); - + //the adder var adder = new Tone.Add(100); var addMeter = new Tone.Meter(); adder.connect(addMeter); signal.connect(adder); - + //the scaler var scaler = new Tone.Scale(99, 101, 5, 10); var scaleMeter = new Tone.Meter(); diff --git a/examples/transport.html b/examples/transport.html index 68ece959..8fd40bb5 100644 --- a/examples/transport.html +++ b/examples/transport.html @@ -52,7 +52,7 @@ var button = document.querySelector("button"); button.onclick = function(){ - if (Tone.Transport.state === "playing"){ + if (Tone.Transport.state === "started"){ Tone.Transport.stop(); button.textContent = "start"; } else { From 24c8d2babe071368144d8adb2f80bba390745937 Mon Sep 17 00:00:00 2001 From: Jason Sigal Date: Wed, 18 Jun 2014 15:42:08 -0400 Subject: [PATCH 2/2] added documentation for Player.js and tests for Sources.js --- Tone/source/Player.js | 86 +- build/Tone.js | 3320 +++++++++++++++++++++++++------------- examples/autoPanner.html | 2 +- examples/transport.html | 10 +- test/test.js | 2 +- test/testAudio/kick.mp3 | Bin 0 -> 2191 bytes test/tests/Sources.js | 33 + 7 files changed, 2293 insertions(+), 1160 deletions(-) create mode 100644 test/testAudio/kick.mp3 create mode 100644 test/tests/Sources.js diff --git a/Tone/source/Player.js b/Tone/source/Player.js index 9cb23ef1..531f9882 100644 --- a/Tone/source/Player.js +++ b/Tone/source/Player.js @@ -1,11 +1,14 @@ -/////////////////////////////////////////////////////////////////////////////// -// -// AUDIO PLAYER -// -/////////////////////////////////////////////////////////////////////////////// - define(["Tone/core/Tone"], function(Tone){ + /** + * Audio Player + * + * Audio file player with start, loop, stop. + * + * @constructor + * @extends {Tone.Source} + * @param {string} url + */ Tone.Player = function(url){ //extend Unit Tone.call(this); @@ -16,18 +19,22 @@ define(["Tone/core/Tone"], function(Tone){ this.buffer = null; this.onended = function(){}; - } + }; Tone.extend(Tone.Player, Tone); - //makes an xhr for the buffer at the url - //invokes the callback at the end - //@param {function(Tone.Player)} callback + /** + * Load the audio file as an audio buffer. + * Decodes the audio asynchronously and invokes + * the callback once the audio buffer loads. + * + * @param {function(Tone.Player)} callback + */ Tone.Player.prototype.load = function(callback){ if (!this.buffer){ var request = new XMLHttpRequest(); - request.open('GET', this.url, true); - request.responseType = 'arraybuffer'; + request.open("GET", this.url, true); + request.responseType = "arraybuffer"; // decode asynchronously var self = this; request.onload = function() { @@ -37,7 +44,7 @@ define(["Tone/core/Tone"], function(Tone){ callback(self); } }); - } + }; //send the request request.send(); } else { @@ -45,9 +52,17 @@ define(["Tone/core/Tone"], function(Tone){ callback(this); } } - } + }; - //play the buffer from start to finish at a time + + /** + * Play the buffer from start to finish at a time + * + * @param {Tone.Time} startTime + * @param {Tone.Time} offset + * @param {Tone.Time} duration + * @param {number} volume + */ Tone.Player.prototype.start = function(startTime, offset, duration, volume){ if (this.buffer){ //default args @@ -65,9 +80,18 @@ define(["Tone/core/Tone"], function(Tone){ gain.gain.value = volume; this.chain(this.source, gain, this.output); } - } + }; - //play the buffer from start to finish at a time + /** + * Loop the buffer from start to finish at a time + * + * @param {Tone.Time} startTime + * @param {Tone.Time} loopStart + * @param {Tone.Time} loopEnd + * @param {Tone.Time} offset + * @param {Tone.Time} duration + * @param {Tone.Time} volume + */ Tone.Player.prototype.loop = function(startTime, loopStart, loopEnd, offset, duration, volume){ if (this.buffer){ //default args @@ -82,29 +106,41 @@ define(["Tone/core/Tone"], function(Tone){ this.source.loopStart = this.toSeconds(loopStart); this.source.loopEnd = this.toSeconds(loopEnd); } - } + }; - //stop playback + /** + * Stop playback. + * + * @param {Tone.Time} stopTime + */ Tone.Player.prototype.stop = function(stopTime){ if (this.buffer && this.source){ stopTime = this.defaultArg(stopTime, this.now()); this.source.stop(this.toSeconds(stopTime)); } - } + }; - //@returns {number} the buffer duration + /** + * Get the duration in seconds as a floating point number + * + * @return {number} the buffer duration + */ Tone.Player.prototype.getDuration = function(){ if (this.buffer){ - this.buffer.duration; + return this.buffer.duration; } else { return 0; } - } + }; - //@param {function(Event)} callback + /** + * + * @param {function(Event)} callback + * @private + */ Tone.Player.prototype._onended = function(e){ this.onended(e); - } + }; return Tone.Player; }); diff --git a/build/Tone.js b/build/Tone.js index f2904f7e..07e38930 100644 --- a/build/Tone.js +++ b/build/Tone.js @@ -1,26 +1,27 @@ - /////////////////////////////////////////////////////////////////////////////// // // TONE.js // -// (c) Yotam Mann. 2014. -// The MIT License (MIT) +// @author Yotam Mann +// +// The MIT License (MIT) 2014 /////////////////////////////////////////////////////////////////////////////// + (function (root, factory) { //can run with or without requirejs - if (typeof define === 'function' && define.amd) { + if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. - define('Tone/core/Tone',[],function () { + define("Tone/core/Tone",[],function () { var Tone = factory(root); return Tone; }); - } else if (typeof root.Tone !== 'function') { + } else if (typeof root.Tone !== "function") { //make Tone public root.Tone = factory(root); //define 'define' to invoke the callbacks with Tone root.define = function(name, deps, func){ - func(Tone); - } + func(root.Tone); + }; } } (this, function (global) { @@ -46,6 +47,7 @@ if (typeof audioContext.createDelay !== "function"){ audioContext.createDelay = audioContext.createDelayNode; } + if (typeof AudioBufferSourceNode.prototype.start !== "function"){ AudioBufferSourceNode.prototype.start = AudioBufferSourceNode.prototype.noteGrainOn; } @@ -64,50 +66,91 @@ if (B.input && B.input instanceof GainNode){ this._nativeConnect(B.input); } else { - this._nativeConnect.apply(this, arguments); + try { + this._nativeConnect.apply(this, arguments); + } catch (e) { + throw new Error("trying to connect to a node with no inputs"); + } } - } + }; /////////////////////////////////////////////////////////////////////////// // TONE // @constructor /////////////////////////////////////////////////////////////////////////// + /** + * Tone is the baseclass of all ToneNodes + * From Tone, children inherit timing and math which is used throughout Tone.js + * + * @constructor + */ var Tone = function(){ + /** + * default input of the ToneNode + * + * @type {GainNode} + */ this.input = audioContext.createGain(); + /** + * default output of the ToneNode + * + * @type {GainNode} + */ this.output = audioContext.createGain(); - } + }; /////////////////////////////////////////////////////////////////////////// // CLASS VARS /////////////////////////////////////////////////////////////////////////// + /** + * A pointer to the audio context + * @type {AudioContext} + */ Tone.prototype.context = audioContext; - Tone.prototype.fadeTime = .005; //5ms - Tone.prototype.bufferSize = 2048; //default buffer size - Tone.prototype.waveShaperResolution = 1024; //default buffer size + /** + * A static pointer to the audio context + * @type {[type]} + */ + Tone.context = audioContext; + + /** + * the default buffer size + * @type {number} + */ + Tone.prototype.bufferSize = 2048; + + /** + * the default resolution for WaveShaperNodes + * @type {number} + */ + Tone.prototype.waveShaperResolution = 1024; + /////////////////////////////////////////////////////////////////////////// - // CLASS METHODS + // CONNECTIONS /////////////////////////////////////////////////////////////////////////// - //@returns {number} the currentTime from the AudioContext - Tone.prototype.now = function(){ - return audioContext.currentTime; - } - - //@param {AudioParam | Tone} unit + /** + * connect the output of a ToneNode to an AudioParam, AudioNode, or ToneNode + * @param {Tone | AudioParam | AudioNode} unit + */ Tone.prototype.connect = function(unit){ this.output.connect(unit); - } + }; - //disconnect the output + /** + * disconnect the output + */ Tone.prototype.disconnect = function(){ this.output.disconnect(); - } + }; - //connect together an array of units in series - //@param {...AudioParam | Tone} units + /** + * connect together all of the arguments in series + * @param {...AudioParam|Tone} + */ Tone.prototype.chain = function(){ if (arguments.length > 1){ var currentUnit = arguments[0]; @@ -117,100 +160,92 @@ currentUnit = toUnit; } } - } - - //set the output volume - //@param {number} vol - Tone.prototype.setVolume = function(vol){ - this.output.gain.value = vol; - } - - //fade the output volume - //@param {number} value - //@param {number=} duration (in seconds) - Tone.prototype.fadeTo = function(value, duration){ - this.defaultArg(duration, this.fadeTime); - this.rampToValue(this.output.gain, value, duration); - } - + }; /////////////////////////////////////////////////////////////////////////// - // UTILITIES / HELPERS + // UTILITIES / HELPERS / MATHS /////////////////////////////////////////////////////////////////////////// + //borrowed from underscore.js function isUndef(val){ - return typeof val === "undefined"; + return val === void 0; } - //ramps to value linearly starting now - //@param {AudioParam} audioParam - //@param {number} value - //@param {number=} duration (in seconds) - Tone.prototype.rampToValueNow = function(audioParam, value, duration){ - var currentValue = audioParam.value; - var now = this.now(); - duration = this.defaultArg(duration, this.fadeTime); - audioParam.setValueAtTime(currentValue, now); - audioParam.linearRampToValueAtTime(value, now + this.toSeconds(duration)); - } - - //ramps to value exponentially starting now - //@param {AudioParam} audioParam - //@param {number} value - //@param {number=} duration (in seconds) - Tone.prototype.exponentialRampToValueNow = function(audioParam, value, duration){ - var currentValue = audioParam.value; - var now = this.now(); - audioParam.setValueAtTime(currentValue, now); - audioParam.exponentialRampToValueAtTime(value, now + this.toSeconds(duration)); - } - - //if the given argument is undefined, go with the default - //@param {*} given - //@param {*} fallback - //@returns {*} + /** + * if a the given is undefined, use the fallback + * + * @param {*} given + * @param {*} fallback + * @return {*} + */ Tone.prototype.defaultArg = function(given, fallback){ return isUndef(given) ? fallback : given; - } + }; - //@param {number} percent (0-1) - //@returns {number} the equal power gain (0-1) - //good for cross fades + /** + * equal power gain scale + * good for cross-fading + * + * @param {number} percent (0-1) + * @return {number} output gain (0-1) + */ Tone.prototype.equalPowerScale = function(percent){ - return Math.sin((percent) * 0.5*Math.PI); - } + var piFactor = 0.5 * Math.PI; + return Math.sin(percent * piFactor); + }; - //@param {number} gain - //@returns {number} gain (decibel scale but betwee 0-1) + /** + * @param {number} gain (0-1) + * @return {number} gain (decibel scale but betwee 0-1) + */ Tone.prototype.logScale = function(gain) { return Math.max(this.normalize(this.gainToDb(gain), -100, 0), 0); - } + }; - //@param {number} gain - //@returns {number} gain (decibel scale but betwee 0-1) + /** + * @param {number} gain (0-1) + * @return {number} gain (decibel scale but betwee 0-1) + */ Tone.prototype.expScale = function(gain) { return this.dbToGain(this.interpolate(gain, -100, 0)); - } + }; - //@param {number} db - //@returns {number} gain + /** + * convert db scale to gain scale (0-1) + * @param {number} db + * @return {number} + */ Tone.prototype.dbToGain = function(db) { return Math.pow(2, db / 6); - } + }; - //@param {number} gain - //@returns {number} db + /** + * convert gain scale to decibels + * @param {number} gain (0-1) + * @return {number} + */ Tone.prototype.gainToDb = function(gain) { return 20 * (Math.log(gain) / Math.LN10); - } + }; - //@param {number} input 0 to 1 - //@returns {number} between outputMin and outputMax + /** + * interpolate the input value (0-1) to be between outputMin and outputMax + * @param {number} input + * @param {number} outputMin + * @param {number} outputMax + * @return {number} + */ Tone.prototype.interpolate = function(input, outputMin, outputMax){ return input*(outputMax - outputMin) + outputMin; - } + }; - //@returns {number} 0-1 + /** + * normalize the input to 0-1 from between inputMin to inputMax + * @param {number} input + * @param {number} inputMin + * @param {number} inputMax + * @return {number} + */ Tone.prototype.normalize = function(input, inputMin, inputMax){ //make sure that min < max if (inputMin > inputMax){ @@ -221,30 +256,52 @@ return 0; } return (input - inputMin) / (inputMax - inputMin); - } + }; - //@param {number} samples - //@returns {number} the number of seconds - Tone.prototype.samplesToSeconds = function(samples){ - return samples / audioContext.sampleRate; - } - /////////////////////////////////////////////////////////////////////////// // TIMING - // - // numbers are passed through - // '+' prefixed values will be "now" relative /////////////////////////////////////////////////////////////////////////// - //@typedef {string|number} - Tone.Timing; + /** + * @return {number} the currentTime from the AudioContext + */ + Tone.prototype.now = function(){ + return audioContext.currentTime; + }; - //@param {Tone.Timing} timing - //@param {number=} bpm - //@param {number=} timeSignature - //@returns {number} the time in seconds - Tone.prototype.toSeconds = function(time, bpm, timeSignature){ + /** + * convert a sample count to seconds + * @param {number} samples + * @return {number} + */ + Tone.prototype.samplesToSeconds = function(samples){ + return samples / audioContext.sampleRate; + }; + + /** + * convert a time into samples + * + * @param {Tone.time} time + * @return {number} + */ + Tone.prototype.toSamples = function(time){ + var seconds = this.toSeconds(time); + return seconds * audioContext.sampleRate; + }; + + /** + * convert Tone.Time to seconds + * + * this is a simplified version which only handles numbers and + * 'now' relative numbers. If the Transport is included this + * method is overridden to include many other features including + * notationTime, Frequency, and transportTime + * + * @param {Tone.Time} time + * @return {number} + */ + Tone.prototype.toSeconds = function(time){ if (typeof time === "number"){ return time; //assuming that it's seconds } else if (typeof time === "string"){ @@ -253,384 +310,520 @@ plusTime = this.now(); time = time.slice(1); } - if (this.isNotation(time)){ - time = this.notationToSeconds(time, bpm, timeSignature); - } else if (this.isTransportTime(time)){ - time = this.transportTimeToSeconds(time, bpm, timeSignature); - } else if (this.isFrequency(time)){ - time = this.frequencyToSeconds(time); - } return parseFloat(time) + plusTime; - } - } - - //@param {number|string} timing - //@param {number=} bpm - //@param {number=} timeSignature - //@returns {number} the time in seconds - Tone.prototype.toFrequency = function(time, bpm, timeSignature){ - if (this.isNotation(time) || this.isFrequency(time)){ - return this.secondsToFrequency(this.toSeconds(time, bpm, timeSignature)); } else { - return time; + return this.now(); } - } + }; - //@returns {number} the tempo - //meant to be overriden by Transport - Tone.prototype.getBpm = function(){ - return 120; - } - //@returns {number} the time signature / 4 - //meant to be overriden by Transport - Tone.prototype.getTimeSignature = function(){ - return 4; - } - - /////////////////////////////////////////////////////////////////////////// - // TIMING CONVERSIONS - /////////////////////////////////////////////////////////////////////////// - - //@param {string} note - //@returns {boolean} if the value is in notation form - Tone.prototype.isNotation = (function(){ - var notationFormat = new RegExp(/[0-9]+[mnt]$/i); - return function(note){ - return notationFormat.test(note); - } - })(); - - //@param {string} transportTime - //@returns {boolean} if the value is in notation form - Tone.prototype.isTransportTime = (function(){ - var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/); - return function(transportTime){ - return transportTimeFormat.test(transportTime); - } - })(); - - //@param {string} freq - //@returns {boolean} if the value is in notation form - Tone.prototype.isFrequency = (function(){ - var freqFormat = new RegExp(/[0-9]+hz$/i); - return function(freq){ - return freqFormat.test(freq); - } - })(); - - // 4n == quarter note; 16t == sixteenth note triplet; 1m == 1 measure - //@param {string} notation - //@param {number=} bpm - //@param {number} timeSignature (default 4) - //@returns {number} time duration of notation - Tone.prototype.notationToSeconds = function(notation, bpm, timeSignature){ - bpm = this.defaultArg(bpm, this.getBpm()); - timeSignature = this.defaultArg(timeSignature, this.getTimeSignature()); - var beatTime = (60 / bpm); - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0){ - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === "t"){ - beats = (4 / subdivision) * 2/3 - } else if (lastLetter === 'n'){ - beats = 4 / subdivision - } else if (lastLetter === 'm'){ - beats = subdivision * timeSignature; - } else { - beats = 0; - } - return beatTime * beats; - } - - // 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - //@param {string} transportTime - //@param {number=} bpm - //@param {number=} timeSignature (default 4) - //@returns {number} time duration of notation - Tone.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){ - bpm = this.defaultArg(bpm, this.getBpm()); - timeSignature = this.defaultArg(timeSignature, this.getTimeSignature()); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(":"); - if (split.length === 2){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1){ - quarters = parseFloat(split[0]); - } else if (split.length === 3){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = (measures * timeSignature + quarters + sixteenths / 4); - return beats * this.notationToSeconds("4n", bpm, timeSignature); - } - - //@param {string | number} freq (i.e. 440hz) - //@returns {number} the time of a single cycle + /** + * convert a frequency into seconds + * accepts both numbers and strings + * i.e. 10hz or 10 both equal .1 + * + * @param {number|string} freq + * @return {number} + */ Tone.prototype.frequencyToSeconds = function(freq){ return 1 / parseFloat(freq); - } + }; - //@param {number} seconds - //@param {number=} bpm - //@param {number=} - //@returns {string} the seconds in transportTime - Tone.prototype.secondsToTransportTime = function(seconds, bpm, timeSignature){ - bpm = this.defaultArg(bpm, this.getBpm()); - timeSignature = this.defaultArg(timeSignature, this.getTimeSignature()); - var quarterTime = this.notationToSeconds("4n", bpm, timeSignature); - var quarters = seconds / quarterTime; - var measures = parseInt(quarters / timeSignature, 10); - var sixteenths = parseInt((quarters % 1) * 4, 10); - quarters = parseInt(quarters, 10) % timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); - } - - //@param {number} seconds - //@returns {number} the frequency + /** + * convert a number in seconds to a frequency + * @param {number} seconds + * @return {number} + */ Tone.prototype.secondsToFrequency = function(seconds){ - return 1/seconds - } + return 1/seconds; + }; /////////////////////////////////////////////////////////////////////////// // STATIC METHODS /////////////////////////////////////////////////////////////////////////// - - //based on closure library 'inherit' function + + /** + * have a child inherit all of Tone's (or a parent's) prototype + * to inherit the parent's properties, make sure to call + * Parent.call(this) in the child's constructor + * + * based on closure library's inherit function + * + * @param {function} child + * @param {function=} parent (optional) parent to inherit from + * if no parent is supplied, the child + * will inherit from Tone + */ Tone.extend = function(child, parent){ if (isUndef(parent)){ parent = Tone; } - /** @constructor */ - function tempConstructor() {}; + function tempConstructor(){} tempConstructor.prototype = parent.prototype; child.prototype = new tempConstructor(); /** @override */ child.prototype.constructor = child; - } - - Tone.context = audioContext; + }; return Tone; })); -/////////////////////////////////////////////////////////////////////////////// -// -// SIGNAL -// -// audio-rate value -// useful for controlling AudioParams -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/Signal',["Tone/core/Tone"], function(Tone){ - - //@param {number=} value - Tone.Signal = function(value){ - Tone.call(this); - //components - this.signal = this.context.createWaveShaper(); - this.scalar = this.context.createGain(); - //generator to drive values - this.generator = this.context.createOscillator(); + //all signals share a common constant signal generator + /** + * @static + * @private + * @type {OscillatorNode} + */ + var generator = Tone.context.createOscillator(); - //connections - this.chain(this.generator, this.signal, this.scalar, this.output); - //pass values through - this.input.connect(this.output); + /** + * @static + * @private + * @type {WaveShaperNode} + */ + var constant = Tone.context.createWaveShaper(); - //setup - this.generator.start(0); - this._signalCurve(); - this.setValue(this.defaultArg(value, 0)); - - } - - Tone.extend(Tone.Signal); - - //generates a constant output of 1 - Tone.Signal.prototype._signalCurve = function(){ + //generate the waveshaper table which outputs 1 for any input value + (function(){ var len = 8; var curve = new Float32Array(len); for (var i = 0; i < len; i++){ //all inputs produce the output value curve[i] = 1; } - //console.log(curve); - this.signal.curve = curve; - } + constant.curve = curve; + })(); - //@returns {number} + generator.connect(constant); + generator.start(0); + + /** + * constant audio-rate signal + * + * Tone.Signal is a core component which allows for synchronization of many components. + * A single signal can drive multiple parameters by applying Scaling. + * + * For example: to synchronize two Tone.Oscillators in octaves of each other, + * Signal --> OscillatorA.frequency + * ^--> Tone.Multiply(2) --> OscillatorB.frequency + * + * + * Tone.Signal can be scheduled with all of the functions available to AudioParams + * + * + * @constructor + * @param {number=} value (optional) initial value + */ + Tone.Signal = function(value){ + /** + * scales the constant output to the desired output + * @type {GainNode} + */ + this.scalar = this.context.createGain(); + /** + * the output node + * @type {GainNode} + */ + this.output = this.context.createGain(); + /** + * the ratio of the this value to the control signal value + * + * @private + * @type {number} + */ + this._syncRatio = 1; + + //connect the constant 1 output to the node output + this.chain(constant, this.scalar, this.output); + + //set the default value + this.setValue(this.defaultArg(value, 0)); + + }; + + Tone.extend(Tone.Signal); + + /** + * @return {number} the current value of the signal + */ Tone.Signal.prototype.getValue = function(){ return this.scalar.gain.value; - } + }; - //@param {number} val - Tone.Signal.prototype.setValue = function(val){ - this.scalar.gain.value = val; - } + /** + * set the value of the signal right away + * will be overwritten if there are previously scheduled automation curves + * + * @param {number} value + */ + Tone.Signal.prototype.setValue = function(value){ + if (this._syncRatio === 0){ + value = 0; + } else { + value *= this._syncRatio; + } + this.scalar.gain.value = value; + }; - //all of the automation curves are available - //@param {number} value - //@param {Tone.Timing} time + /** + * Schedules a parameter value change at the given time. + * + * @param {number} value + * @param {Tone.Time} time + */ Tone.Signal.prototype.setValueAtTime = function(value, time){ - + value *= this._syncRatio; this.scalar.gain.setValueAtTime(value, this.toSeconds(time)); - } + }; - //@param {number} value - //@param {Tone.Timing} endTime + /** + * creates a schedule point with the current value at the current time + * + * @returns {number} the current value + */ + Tone.Signal.prototype.setCurrentValueNow = function(){ + var now = this.now(); + var currentVal = this.getValue(); + this.cancelScheduledValues(now); + this.scalar.gain.setValueAtTime(currentVal, now); + return currentVal; + }; + + /** + * Schedules a linear continuous change in parameter value from the + * previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Tone.Time} endTime + */ Tone.Signal.prototype.linearRampToValueAtTime = function(value, endTime){ + value *= this._syncRatio; this.scalar.gain.linearRampToValueAtTime(value, this.toSeconds(endTime)); - } + }; - //@param {number} value - //@param {Tone.Timing} endTime + /** + * Schedules an exponential continuous change in parameter value from + * the previous scheduled parameter value to the given value. + * + * @param {number} value + * @param {Tone.Time} endTime + */ Tone.Signal.prototype.exponentialRampToValueAtTime = function(value, endTime){ + value *= this._syncRatio; this.scalar.gain.exponentialRampToValueAtTime(value, this.toSeconds(endTime)); - } + }; - //@param {number} value - //@param {Tone.Timing} startTime - //@param {number} timeConstant - Tone.Signal.prototype.setTargetAtTime = function(target, startTime, timeConstant){ - this.scalar.gain.setTargetAtTime(target, this.toSeconds(startTime), timeConstant); - } + /** + * Schedules an exponential continuous change in parameter value from + * the current time and current value to the given value. + * + * @param {number} value + * @param {Tone.Time} endTime + */ + Tone.Signal.prototype.exponentialRampToValueNow = function(value, endTime){ + this.setCurrentValueNow(); + value *= this._syncRatio; + //make sure that the endTime doesn't start with + + if (endTime.toString().charAt(0) === "+"){ + endTime = endTime.substr(1); + } + this.scalar.gain.exponentialRampToValueAtTime(value, this.now() + this.toSeconds(endTime)); + }; - //@param {number} value - //@param {Tone.Timing} startTime - //@param {Tone.Timing} duration + /** + * Schedules an linear continuous change in parameter value from + * the current time and current value to the given value at the given time. + * + * @param {number} value + * @param {Tone.Time} endTime + */ + Tone.Signal.prototype.linearRampToValueNow = function(value, endTime){ + this.setCurrentValueNow(); + value *= this._syncRatio; + //make sure that the endTime doesn't start with + + if (endTime.toString().charAt(0) === "+"){ + endTime = endTime.substr(1); + } + this.scalar.gain.linearRampToValueAtTime(value, this.now() + this.toSeconds(endTime)); + }; + + /** + * Start exponentially approaching the target value at the given time with + * a rate having the given time constant. + * + * @param {number} value + * @param {Tone.Time} startTime + * @param {number} timeConstant + */ + Tone.Signal.prototype.setTargetAtTime = function(value, startTime, timeConstant){ + value *= this._syncRatio; + this.output.gain.setTargetAtTime(value, this.toSeconds(startTime), timeConstant); + }; + + /** + * Sets an array of arbitrary parameter values starting at the given time + * for the given duration. + * + * @param {Array} values + * @param {Tone.Time} startTime + * @param {Tone.Time} duration + */ Tone.Signal.prototype.setValueCurveAtTime = function(values, startTime, duration){ + for (var i = 0; i < values.length; i++){ + values[i] *= this._syncRatio; + } this.scalar.gain.setValueCurveAtTime(values, this.toSeconds(startTime), this.toSeconds(duration)); - } + }; - //@param {Tone.Timing} startTime + /** + * Cancels all scheduled parameter changes with times greater than or + * equal to startTime. + * + * @param {Tone.Time} startTime + */ Tone.Signal.prototype.cancelScheduledValues = function(startTime){ this.scalar.gain.cancelScheduledValues(this.toSeconds(startTime)); - } + }; + + /** + * Sync this to another signal and it will always maintain the + * ratio between the two signals until it is unsynced + * + * @param {Tone.Signal} signal to sync to + */ + Tone.Signal.prototype.sync = function(signal){ + //get the sync ratio + if (signal.getValue() !== 0){ + this._syncRatio = this.getValue() / signal.getValue(); + } else { + this._syncRatio = 0; + } + //make a new scalar which is not connected to the constant signal + this.scalar.disconnect(); + this.scalar = this.context.createGain(); + this.chain(signal, this.scalar, this.output); + //set it ot the sync ratio + this.scalar.gain.value = this._syncRatio; + }; + + /** + * unbind the signal control + * + * will leave the signal value as it was without the influence of the control signal + */ + Tone.Signal.prototype.unsync = function(){ + //make a new scalar so that it's disconnected from the control signal + //get the current gain + var currentGain = this.getValue(); + this.scalar.disconnect(); + this.scalar = this.context.createGain(); + this.scalar.gain.value = currentGain / this._syncRatio; + this._syncRatio = 1; + //reconnect things up + this.chain(constant, this.scalar, this.output); + }; return Tone.Signal; }); -/////////////////////////////////////////////////////////////////////////////// -// -// ADD -// -// adds a value to the incoming signal -// can sum two signals or a signal and a constant -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/Add',["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ - //@param {Tone.Signal|number} value + /** + * Adds a value to an incoming signal + * + * @constructor + * @extends {Tone} + * @param {number} value + */ Tone.Add = function(value){ Tone.call(this); - if (typeof value === "number"){ - this.value = new Tone.Signal(value); - } else { - this.value = value; - } + /** + * @private + * @type {Tone} + */ + this._value = new Tone.Signal(value); //connections - this.chain(this.value, this.input, this.output); - } + this.chain(this._value, this.input, this.output); + }; Tone.extend(Tone.Add); - //set the constant value - //@param {number} value + /** + * set the constant + * + * @param {number} value + */ Tone.Add.prototype.setValue = function(value){ - this.value.setValue(value); - } + this._value.setValue(value); + }; return Tone.Add; }); -/////////////////////////////////////////////////////////////////////////////// -// -// MULTIPLY -// -// Multiply the incoming signal by a factor -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/Multiply',["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ - //@param {number} value + /** + * Multiply the incoming signal by some factor + * + * @constructor + * @extends {Tone} + * @param {number=} value constant value to multiple + */ Tone.Multiply = function(value){ - Tone.call(this); - this.input.connect(this.output); - this.input.gain.value = value; - } + /** + * the input node is the same as the output node + * it is also the GainNode which handles the scaling of incoming signal + * + * @type {GainNode} + */ + this.input = this.context.createGain(); + /** @alias */ + this.output = this.input; + + //apply the inital scale factor + this.input.gain.value = this.defaultArg(value, 1); + }; Tone.extend(Tone.Multiply); - //set the constant value - //@param {number} value + /** + * set the constant multiple + * + * @param {number} value + */ Tone.Multiply.prototype.setValue = function(value){ this.input.gain.value = value; - } + }; return Tone.Multiply; -}) -; -/////////////////////////////////////////////////////////////////////////////// -// -// SCALE -// -// performs linear scaling on an input signal between inputMin and inputMax -// to output the range outputMin outputMax -/////////////////////////////////////////////////////////////////////////////// +}); define('Tone/signal/Scale',["Tone/core/Tone", "Tone/signal/Add", "Tone/signal/Multiply"], function(Tone){ - - //@param {number} inputMin - //@param {number} inputMax - //@param {number=} outputMin - //@param {number=} outputMax + + /** + * performs a linear scaling on an input signal + * + * scales from the input range of inputMin to inputMax + * to the output range of outputMin to outputMax + * + * if only two arguments are provided, the inputMin and inputMax are set to -1 and 1 + * + * @constructor + * @extends {Tone} + * @param {number} inputMin + * @param {number} inputMax + * @param {number=} outputMin + * @param {number=} outputMax + */ Tone.Scale = function(inputMin, inputMax, outputMin, outputMax){ Tone.call(this); + //if there are only two args if (arguments.length == 2){ outputMin = inputMin; outputMax = inputMax; inputMin = -1; inputMax = 1; } - //components - this.plusInput = new Tone.Add(-inputMin); - this.scale = new Tone.Multiply((outputMax - outputMin)/(inputMax - inputMin)); - this.plusOutput = new Tone.Add(outputMin); + + /** @private + @type {number} */ + this._inputMin = inputMin; + /** @private + @type {number} */ + this._inputMax = inputMax; + /** @private + @type {number} */ + this._outputMin = outputMin; + /** @private + @type {number} */ + this._outputMax = outputMax; + + + /** @private + @type {Tone.Add} */ + this._plusInput = new Tone.Add(0); + /** @private + @type {Tone.Multiply} */ + this._scale = new Tone.Multiply(1); + /** @private + @type {Tone.Add} */ + this._plusOutput = new Tone.Add(0); //connections - this.chain(this.input, this.plusInput, this.scale, this.plusOutput, this.output); - } + this.chain(this.input, this._plusInput, this._scale, this._plusOutput, this.output); + + //set the scaling values + this._setScalingParameters(); + }; - //extend StereoSplit Tone.extend(Tone.Scale); + /** + * set the scaling parameters + * + * @private + */ + Tone.Scale.prototype._setScalingParameters = function(){ + //components + this._plusInput.setValue(-this._inputMin); + this._scale.setValue((this._outputMax - this._outputMin)/(this._inputMax - this._inputMin)); + this._plusOutput.setValue(this._outputMin); + }; + + /** + * set the input min value + * @param {number} val + */ + Tone.Scale.prototype.setInputMin = function(val){ + this._inputMin = val; + this._setScalingParameters(); + }; + + /** + * set the input max value + * @param {number} val + */ + Tone.Scale.prototype.setInputMax = function(val){ + this._inputMax = val; + this._setScalingParameters(); + }; + + /** + * set the output min value + * @param {number} val + */ + Tone.Scale.prototype.setOutputMin = function(val){ + this._outputMin = val; + this._setScalingParameters(); + }; + + /** + * set the output max value + * @param {number} val + */ + Tone.Scale.prototype.setOutputMax = function(val){ + this._outputMax = val; + this._setScalingParameters(); + }; return Tone.Scale; }); -/////////////////////////////////////////////////////////////////////////////// -// -// DRY/WET KNOB -// -// equal power fading -// control values: -// 0 = 100% dry -// 1 = 100% wet -/////////////////////////////////////////////////////////////////////////////// - define('Tone/component/DryWet',["Tone/core/Tone", "Tone/signal/Signal", "Tone/signal/Scale"], function(Tone){ + /** + * DRY/WET KNOB + * + * equal power fading control values: + * 0 = 100% dry + * 1 = 100% wet + * + * @constructor + * @param {number} initialDry + */ Tone.DryWet = function(initialDry){ Tone.call(this); @@ -654,45 +847,59 @@ define('Tone/component/DryWet',["Tone/core/Tone", "Tone/signal/Signal", "Tone/si this.dry.gain.value = 0; this.wet.gain.value = 0; this.setDry(0); - } + }; Tone.extend(Tone.DryWet); - // @param {number} val - // @param {Tone.Timing} rampTime + /** + * Set the dry value of the knob + * + * @param {number} val + * @param {Tone.Time} rampTime + */ Tone.DryWet.prototype.setDry = function(val, rampTime){ rampTime = this.defaultArg(rampTime, 0); this.control.linearRampToValueAtTime(val*2 - 1, this.toSeconds(rampTime)); - } + }; + /** + * Set the wet value of the knob + * + * @param {number} val + * @param {Tone.Time} rampTime + */ Tone.DryWet.prototype.setWet = function(val, rampTime){ this.setDry(1-val, rampTime); - } + }; return Tone.DryWet; }); -/////////////////////////////////////////////////////////////////////////////// -// -// Envelope -// -// ADR envelope generator attaches to an AudioParam -/////////////////////////////////////////////////////////////////////////////// - define('Tone/component/Envelope',["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){ - + /** + * Envelope + * ADR envelope generator attaches to an AudioParam + * + * @constructor + * @extends {Tone} + * @param {Tone.Time=} attack + * @param {Tone.Time=} decay + * @param {number=} sustain a percentage (0-1) of the full amplitude + * @param {Tone.Time=} release + * @param {number=} minOutput the lowest point of the envelope + * @param {number=} maxOutput the highest point of the envelope + */ Tone.Envelope = function(attack, decay, sustain, release, minOutput, maxOutput){ //extend Unit Tone.call(this); //set the parameters - this.attack = this.defaultArg(attack, .01); - this.decay = this.defaultArg(decay, .1); + this.attack = this.defaultArg(attack, 0.01); + this.decay = this.defaultArg(decay, 0.1); this.release = this.defaultArg(release, 1); - this.sustain = this.defaultArg(sustain, .5); + this.sustain = this.defaultArg(sustain, 0.5); - // this.setSustain(this.defaultArg(sustain, .1)); this.min = this.defaultArg(minOutput, 0); this.max = this.defaultArg(maxOutput, 1); @@ -701,12 +908,14 @@ define('Tone/component/Envelope',["Tone/core/Tone", "Tone/signal/Signal"], funct //connections this.chain(this.control, this.output); - } + }; - Tone.extend(Tone.Envelope, Tone); + Tone.extend(Tone.Envelope); - //attack->decay->sustain - //@param {Tone.Timing} time + /** + * attack->decay->sustain linear ramp + * @param {Tone.Time} time + */ Tone.Envelope.prototype.triggerAttack = function(time){ var startVal = this.min; if (!time){ @@ -721,15 +930,17 @@ define('Tone/component/Envelope',["Tone/core/Tone", "Tone/signal/Signal"], funct this.control.linearRampToValueAtTime(this.max, time + attackTime); var sustainVal = (this.max - this.min) * this.sustain + this.min; this.control.linearRampToValueAtTime(sustainVal, time + attackTime + decayTime); - } + }; - //attack->decay->sustain + /** + * attack->decay->sustain exponential ramp + * @param {Tone.Time} time + */ Tone.Envelope.prototype.triggerAttackExp = function(time){ var startVal = this.min; if (!time){ startVal = this.control.getValue(); } - time = this.defaultArg(time, this.now()); time = this.toSeconds(time); this.control.cancelScheduledValues(time); this.control.setValueAtTime(startVal, time); @@ -738,191 +949,66 @@ define('Tone/component/Envelope',["Tone/core/Tone", "Tone/signal/Signal"], funct this.control.linearRampToValueAtTime(this.max, time + attackTime); var sustainVal = (this.max - this.min) * this.sustain + this.min; this.control.exponentialRampToValueAtTime(sustainVal, time + attackTime + decayTime); - } + }; - //triggers the release of the envelope + + /** + * triggers the release of the envelope with a linear ramp + * @param {Tone.Time} time + */ Tone.Envelope.prototype.triggerRelease = function(time){ var startVal = this.control.getValue(); if (time){ startVal = (this.max - this.min) * this.sustain + this.min; } - time = this.defaultArg(time, this.now()); time = this.toSeconds(time); this.control.cancelScheduledValues(time); this.control.setValueAtTime(startVal, time); this.control.linearRampToValueAtTime(this.min, time + this.toSeconds(this.release)); - } + }; - //triggers the release of the envelope + /** + * triggers the release of the envelope with an exponential ramp + * + * @param {Tone.Time} time + */ Tone.Envelope.prototype.triggerReleaseExp = function(time){ var startVal = this.control.getValue(); if (time){ startVal = (this.max - this.min) * this.sustain + this.min; } - time = this.defaultArg(time, this.now()); time = this.toSeconds(time); this.control.cancelScheduledValues(time); this.control.setValueAtTime(startVal, time); this.control.exponentialRampToValueAtTime(this.min, time + this.toSeconds(this.release)); - } + }; - //@private - //pointer to the parent's connect method + /** + * @private + * pointer to the parent's connect method + */ Tone.Envelope.prototype._connect = Tone.prototype.connect; - //triggers the release of the envelope + /** + * connect the envelope + * + * if the envelope is connected to a param, the params + * value will be set to 0 so that it doesn't interfere with the envelope + * + * @param {number} param + */ Tone.Envelope.prototype.connect = function(param){ if (param instanceof AudioParam){ //set the initial value param.value = 0; } this._connect(param); - } + }; return Tone.Envelope; }); -/////////////////////////////////////////////////////////////////////////////// -// -// OSCILLATOR -// -// just an oscillator, -// but starting and stopping is easier than the native version -/////////////////////////////////////////////////////////////////////////////// - -define('Tone/source/Oscillator',["Tone/core/Tone"], function(Tone){ - - Tone.Oscillator = function(freq, type){ - Tone.call(this); - - this.started = false; - - //components - this.oscillator = this.context.createOscillator(); - this.oscillator.frequency.value = this.defaultArg(this.toFrequency(freq), 440); - this.oscillator.type = this.defaultArg(type, "sine"); - //connections - this.chain(this.oscillator, this.output); - } - - Tone.extend(Tone.Oscillator); - - //@param {number=} time - Tone.Oscillator.prototype.start = function(time){ - if (!this.started){ - var freq = this.oscillator.frequency.value; - var type = this.oscillator.type; - var detune = this.oscillator.frequency.value; - this.oscillator = this.context.createOscillator(); - this.oscillator.frequency.value = freq; - this.oscillator.type = type; - this.oscillator.detune.value = detune; - this.oscillator.connect(this.output); - this.started = true; - time = this.defaultArg(time, this.now()); - this.oscillator.start(time); - } - } - - //@param {number=} time - Tone.Oscillator.prototype.stop = function(time){ - if (this.started){ - time = this.defaultArg(time, this.now()); - this.oscillator.stop(time); - this.started = false; - } - } - - //@param {number} val - //@param {Tone.Timing=} rampTime - Tone.Oscillator.prototype.setFrequency = function(val, rampTime){ - rampTime = this.defaultArg(rampTime, 0); - this.oscillator.frequency.linearRampToValueAtTime(this.toFrequency(val), this.toSeconds(rampTime)); - } - - //@param {string} type - Tone.Oscillator.prototype.setType = function(type){ - this.oscillator.type = type; - } - - return Tone.Oscillator; -}); -/////////////////////////////////////////////////////////////////////////////// -// -// LFO -// -/////////////////////////////////////////////////////////////////////////////// - -define('Tone/component/LFO',["Tone/core/Tone", "Tone/source/Oscillator", "Tone/signal/Scale"], function(Tone){ - - Tone.LFO = function(rate, outputMin, outputMax){ - //extends Unit - Tone.call(this); - - //defaults - rate = this.defaultArg(rate, 1); - min = this.defaultArg(outputMin, -1); - max = this.defaultArg(outputMax, 1); - - //the components - this.oscillator = new Tone.Oscillator(rate, "sine"); - this.scaler = new Tone.Scale(min, max); - - //connect it up - this.chain(this.oscillator, this.scaler, this.output); - } - - Tone.extend(Tone.LFO, Tone); - - - //start the lfo - Tone.LFO.prototype.start = function(time){ - this.oscillator.start(time); - } - - //stop - Tone.LFO.prototype.stop = function(time){ - this.oscillator.stop(time); - } - - - //set the params - Tone.LFO.prototype.setFrequency = function(rate){ - this.oscillator.setFrequency(rate); - } - - //set the params - Tone.LFO.prototype.setMin = function(min){ - this.scaler.setMin(min); - } - - //set the params - Tone.LFO.prototype.setMax = function(max){ - this.scaler.setMax(max); - } - - //set the waveform of the LFO - //@param {string | number} type ('sine', 'square', 'sawtooth', 'triangle', 'custom'); - Tone.LFO.prototype.setType = function(type){ - this.oscillator.setType(type); - } - - //@private - //pointer to the parent's connect method - Tone.LFO.prototype._connect = Tone.prototype.connect; - - //triggers the release of the envelope - Tone.LFO.prototype.connect = function(param){ - if (param instanceof AudioParam){ - //set the initial value - param.value = 0; - } - this._connect(param); - } - - return Tone.LFO; -}); /////////////////////////////////////////////////////////////////////////////// // // MASTER OUTPUT @@ -965,80 +1051,1131 @@ define('Tone/core/Master',["Tone/core/Tone"], function(Tone){ return Tone.Master; }); -/////////////////////////////////////////////////////////////////////////////// -// -// METER -// -// get the rms of the input signal with some averaging -// -// inspired by https://github.com/cwilso/volume-meter/blob/master/volume-meter.js -// The MIT License (MIT) Copyright (c) 2014 Chris Wilson -/////////////////////////////////////////////////////////////////////////////// +define('Tone/core/Transport',["Tone/core/Tone", "Tone/core/Master", "Tone/signal/Signal"], +function(Tone){ + + /** + * oscillator-based transport allows for simple musical timing + * supports tempo curves and time changes + * + * @constructor + */ + Tone.Transport = function(){ + + /** + * watches the main oscillator for timing ticks + * + * @private + * @type {ScriptProcessorNode} + */ + this._jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); + this._jsNode.onaudioprocess = this._processBuffer.bind(this); + + /** @type {boolean} */ + this.loop = false; + + //so it doesn't get garbage collected + this._jsNode.toMaster(); + }; + + Tone.extend(Tone.Transport); + + /** @private @type {number} */ + var transportTicks = 0; + /** @private @type {number} */ + var tatum = 12; + /** @private @type {boolean} */ + var upTick = false; + /** @private @type {number} */ + var transportTimeSignature = 4; + + /** @private @type {number} */ + var loopStart = 0; + /** @private @type {number} */ + var loopEnd = tatum * 4; + + /** @private @type {Array} */ + var intervals = []; + /** @private @type {Array} */ + var timeouts = []; + /** @private @type {Array} */ + var timeline = []; + /** @private @type {number} */ + var timelineProgress = 0; + + /** + * The main oscillator for the system + * @private + * @type {OscillatorNode} + */ + var oscillator = null; + + /** + * controls the oscillator frequency + * starts at 120bpm + * @private + * @type {Tone.Signal} + */ + var controlSignal = new Tone.Signal(120); + + /** + * All of the synced components + * @private @type {Array} + */ + var SyncedComponents = []; + + + /////////////////////////////////////////////////////////////////////////////// + // JS NODE PROCESSING + /////////////////////////////////////////////////////////////////////////////// + + /** + * called when a buffer is ready + * + * @param {AudioProcessingEvent} event + */ + Tone.Transport.prototype._processBuffer = function(event){ + var now = this.defaultArg(event.playbackTime, this.now()); + var bufferSize = this._jsNode.bufferSize; + var incomingBuffer = event.inputBuffer.getChannelData(0); + for (var i = 0; i < bufferSize; i++){ + var sample = incomingBuffer[i]; + if (sample > 0 && !upTick){ + upTick = true; + this._processTick(now + this.samplesToSeconds(i), i); + } else if (sample < 0 && upTick){ + upTick = false; + } + } + }; + + //@param {number} tickTime + Tone.Transport.prototype._processTick = function(tickTime, i){ + if (oscillator !== null){ + transportTicks += 1; + processIntervals(tickTime); + processTimeouts(tickTime, i); + processTimeline(tickTime); + if (this.loop){ + if (transportTicks === loopEnd){ + this._setTicks(this.loopEnd); + } + } + } + }; + + //jump to a specific tick in the timeline + Tone.Transport.prototype._setTicks = function(ticks){ + transportTicks = ticks; + for (var i = 0; i < timeline.length; i++){ + var timeout = timeline[i]; + if (timeout.callbackTick() >= ticks){ + timelineProgress = i; + break; + } + } + }; + + /////////////////////////////////////////////////////////////////////////////// + // EVENT PROCESSING + /////////////////////////////////////////////////////////////////////////////// + + /** + * process the intervals + * @param {number} time + */ + var processIntervals = function(time){ + for (var i = 0, len = intervals.length; i transportTicks){ + break; + } + } + //remove the timeouts off the front of the array after they've been called + timeouts.splice(0, removeTimeouts); + }; + + /** + * process the timeline events + * @param {number} time + */ + var processTimeline = function(time){ + for (var i = timelineProgress, len = timeline.length; i transportTicks){ + break; + } + } + }; + + /** + * clear the timeouts and intervals + */ + function clearTimelineEvents(){ + timeouts = []; + intervals = []; + } + + /////////////////////////////////////////////////////////////////////////////// + // INTERVAL + /////////////////////////////////////////////////////////////////////////////// + + /** + * intervals are recurring events + * + * @param {function} callback + * @param {Tone.Time} interval + * @param {Object} ctx the context the function is invoked in + * @return {number} the id of the interval + */ + Tone.Transport.prototype.setInterval = function(callback, interval, ctx){ + var tickTime = this.toTicks(interval); + var timeout = new TimelineEvent(callback, ctx, tickTime, transportTicks); + intervals.push(timeout); + return timeout.id; + }; + + /** + * clear an interval from the processing array + * @param {number} rmInterval the interval to remove + * @return {boolean} true if the event was removed + */ + Tone.Transport.prototype.clearInterval = function(rmInterval){ + for (var i = 0; i < intervals.length; i++){ + var interval = intervals[i]; + if (interval.id === rmInterval){ + intervals.splice(i, 1); + return true; + } + } + return false; + }; + + /////////////////////////////////////////////////////////////////////////////// + // TIMEOUT + /////////////////////////////////////////////////////////////////////////////// + + /** + * set a timeout to occur after time from now + * + * @param {function} callback + * @param {Tone.Time} time + * @param {Object} ctx the context to invoke the callback in + * @return {number} the id of the timeout for clearing timeouts + */ + Tone.Transport.prototype.setTimeout = function(callback, time, ctx){ + var ticks = this.toTicks(time); + var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0); + //put it in the right spot + for (var i = 0, len = timeouts.length; i timeout.callbackTick()){ + timeouts.splice(i, 0, timeout); + return timeout.id; + } + } + //otherwise push it on the end + timeouts.push(timeout); + return timeout.id; + }; + + /** + * clear the timeout based on it's ID + * @param {number} timeoutID + * @return {boolean} true if the timeout was removed + */ + Tone.Transport.prototype.clearTimeout = function(timeoutID){ + for (var i = 0; i < timeouts.length; i++){ + var testTimeout = timeouts[i]; + if (testTimeout.id === timeoutID){ + timeouts.splice(i, 1); + return true; + } + } + return false; + }; + + /////////////////////////////////////////////////////////////////////////////// + // TIMELINE + /////////////////////////////////////////////////////////////////////////////// + + /** + * Timeline events are synced to the timeline of the Transport + * Unlike Timeout, Timeline events will restart after the + * Transport has been stopped and restarted. + * + * + * @param {function} callback + * @param {Tome.Time} timeout + * @param {Object} ctx the context in which the funtion is called + * @return {number} the id for clearing the timeline event + */ + Tone.Transport.prototype.setTimeline = function(callback, timeout, ctx){ + var ticks = this.toTicks(timeout); + var timelineEvnt = new TimelineEvent(callback, ctx, ticks + transportTicks, 0); + //put it in the right spot + for (var i = timelineProgress, len = timeline.length; i timelineEvnt.callbackTick()){ + timeline.splice(i, 0, timelineEvnt); + return timelineEvnt.id; + } + } + //otherwise push it on the end + timeline.push(timelineEvnt); + return timelineEvnt.id; + }; + + /** + * clear the timeline event from the + * @param {number} timelineID + * @return {boolean} true if it was removed + */ + Tone.Transport.prototype.clearTimeline = function(timelineID){ + for (var i = 0; i < timeline.length; i++){ + var testTimeline = timeline[i]; + if (testTimeline.id === timelineID){ + timeline.splice(i, 1); + return true; + } + } + return false; + }; + + /////////////////////////////////////////////////////////////////////////////// + // TIME CONVERSIONS + /////////////////////////////////////////////////////////////////////////////// + + /** + * turns the time into + * @param {Tone.Time} time + * @return {number} + */ + Tone.Transport.prototype.toTicks = function(time){ + //get the seconds + var seconds = this.toSeconds(time); + var quarter = this.notationToSeconds("4n"); + var quarters = seconds / quarter; + var tickNum = quarters * tatum; + //quantize to tick value + return Math.round(tickNum); + }; + + /** + * get the transport time + * @return {string} in transportTime format (measures:beats:sixteenths) + */ + Tone.Transport.prototype.getTransportTime = function(){ + var quarters = transportTicks / tatum; + var measures = Math.floor(quarters / transportTimeSignature); + var sixteenths = Math.floor((quarters % 1) * 4); + quarters = Math.floor(quarters) % transportTimeSignature; + var progress = [measures, quarters, sixteenths]; + return progress.join(":"); + }; + + /** + * set the transport time, jump to the position right away + * + * @param {Tone.Time} progress + */ + Tone.Transport.prototype.setTransportTime = function(progress){ + var ticks = this.toTicks(progress); + this._setTicks(ticks); + }; + + /////////////////////////////////////////////////////////////////////////////// + // START/STOP/PAUSE + /////////////////////////////////////////////////////////////////////////////// + + /** + * start the transport and all sources synced to the transport + * + * @param {Tone.Time} time + */ + Tone.Transport.prototype.start = function(time){ + if (oscillator === null){ + //reset the oscillator + oscillator = this.context.createOscillator(); + oscillator.type = "square"; + oscillator.connect(this._jsNode); + //connect it up + controlSignal.connect(oscillator.frequency); + oscillator.frequency.value = 0; + } + upTick = false; + oscillator.start(this.toSeconds(time)); + //call start on each of the synced sources + }; + + + /** + * stop the transport and all sources synced to the transport + * + * @param {Tone.Time} time + */ + Tone.Transport.prototype.stop = function(time){ + if (oscillator !== null){ + oscillator.stop(this.toSeconds(time)); + oscillator = null; + } + this._setTicks(0); + clearTimelineEvents(); + //call stop on each of the synced sources + }; + + /** + * pause the transport and all sources synced to the transport + * + * @param {Tone.Time} time + */ + Tone.Transport.prototype.pause = function(time){ + oscillator.stop(this.toSeconds(time)); + oscillator = null; + clearTimelineEvents(); + //call pause on each of the synced sources + }; + + /////////////////////////////////////////////////////////////////////////////// + // SETTERS/GETTERS + /////////////////////////////////////////////////////////////////////////////// + + /** + * set the BPM + * optionally ramp to the bpm over some time + * @param {number} bpm + * @param {Tone.Time=} rampTime + */ + Tone.Transport.prototype.setBpm = function(bpm, rampTime){ + //convert the bpm to frequency + var tatumFreq = this.toFrequency(tatum.toString() + "n", bpm, transportTimeSignature); + var freqVal = 4 * tatumFreq; + if (!rampTime){ + controlSignal.cancelScheduledValues(0); + controlSignal.setValue(freqVal); + } else { + controlSignal.exponentialRampToValueNow(freqVal, rampTime); + } + }; + + /** + * return the current BPM + * + * @return {number} + */ + Tone.Transport.prototype.getBpm = function(){ + //convert the current frequency of the oscillator to bpm + var freq = controlSignal.getValue(); + return 60 * (freq / tatum); + }; + + /** + * set the time signature + * + * @example + * this.setTimeSignature(4); //for 4/4 + * + * @param {number} numerator + * @param {number=} denominator defaults to 4 + */ + Tone.Transport.prototype.setTimeSignature = function(numerator, denominator){ + denominator = this.defaultArg(denominator, 4); + transportTimeSignature = numerator / (denominator / 4); + }; + + /** + * return the time signature as just the numerator + * over 4 is assumed. + * for example 4/4 would return 4 and 6/8 would return 3 + * + * @return {number} + */ + Tone.Transport.prototype.getTimeSignature = function(){ + return transportTimeSignature; + }; + + /** + * set the loop start position + * + * @param {Tone.Time} startPosition + */ + Tone.Transport.prototype.setLoopStart = function(startPosition){ + loopStart = this.toTicks(startPosition); + }; + + /** + * set the loop start position + * + * @param {Tone.Time} endPosition + */ + Tone.Transport.prototype.setLoopEnd = function(endPosition){ + loopEnd = this.toTicks(endPosition); + }; + + /** + * shorthand loop setting + * @param {Tone.Time} startPosition + * @param {Tone.Time} endPosition + */ + Tone.Transport.prototype.setLoopPoint = function(startPosition, endPosition){ + this.setLoopStart(startPosition); + this.setLoopEnd(endPosition); + }; + + /////////////////////////////////////////////////////////////////////////////// + // SYNCING + /////////////////////////////////////////////////////////////////////////////// + + + Tone.Transport.prototype.sync = function(source, controlSignal){ + //create a gain node, attach it to the control signal + // var ratio = new Tone.Multiply(); + // controlSignal.connect(ratio); + // return ratio; + }; + + /** + * remove the source from the list of Synced Sources + * + * @param {[type]} source [description] + * @return {[type]} [description] + */ + Tone.Transport.prototype.unsync = function(source){ + + }; + + + /////////////////////////////////////////////////////////////////////////////// + // TIMELINE EVENT + /////////////////////////////////////////////////////////////////////////////// + + /** + * @static + * @type {number} + */ + var TimelineEventIDCounter = 0; + + /** + * A Timeline event + * + * @constructor + * @param {function(number)} callback + * @param {Object} context + * @param {number} tickTime + * @param {number} startTicks + */ + var TimelineEvent = function(callback, context, tickTime, startTicks){ + this.startTicks = startTicks; + this.tickTime = tickTime; + this.callback = callback; + this.context = context; + this.id = TimelineEventIDCounter++; + }; + + /** + * invoke the callback in the correct context + * passes in the playback time + * + * @param {number} playbackTime + */ + TimelineEvent.prototype.doCallback = function(playbackTime){ + this.callback.call(this.context, playbackTime); + }; + + /** + * get the tick which the callback is supposed to occur on + * + * @return {number} + */ + TimelineEvent.prototype.callbackTick = function(){ + return this.startTicks + this.tickTime; + }; + + /** + * test if the tick occurs on the interval + * + * @param {number} tick + * @return {boolean} + */ + TimelineEvent.prototype.testInterval = function(tick){ + return (tick - this.startTicks) % this.tickTime === 0; + }; + + + /////////////////////////////////////////////////////////////////////////////// + // AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING + /////////////////////////////////////////////////////////////////////////////// + + /** + * tests if a string is musical notation + * i.e.: + * 4n = quarter note + * 2m = two measures + * 8t = eighth-note triplet + * + * @return {boolean} + */ + Tone.prototype.isNotation = (function(){ + var notationFormat = new RegExp(/[0-9]+[mnt]$/i); + return function(note){ + return notationFormat.test(note); + }; + })(); + + /** + * tests if a string is transportTime + * i.e. : + * 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes + * + * @return {boolean} + */ + Tone.prototype.isTransportTime = (function(){ + var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/); + return function(transportTime){ + return transportTimeFormat.test(transportTime); + }; + })(); + + /** + * true if the input is in the format number+hz + * i.e.: 10hz + * + * @param {number} freq + * @return {boolean} + */ + Tone.prototype.isFrequency = (function(){ + var freqFormat = new RegExp(/[0-9]+hz$/i); + return function(freq){ + return freqFormat.test(freq); + }; + })(); + + + /** + * convert notation format strings to seconds + * @param {string} notation + * @param {number=} bpm + * @param {number=} timeSignature + * @return {number} + */ + Tone.prototype.notationToSeconds = function(notation, bpm, timeSignature){ + bpm = this.defaultArg(bpm, Tone.Transport.getBpm()); + timeSignature = this.defaultArg(timeSignature, transportTimeSignature); + var beatTime = (60 / bpm); + var subdivision = parseInt(notation, 10); + var beats = 0; + if (subdivision === 0){ + beats = 0; + } + var lastLetter = notation.slice(-1); + if (lastLetter === "t"){ + beats = (4 / subdivision) * 2/3; + } else if (lastLetter === "n"){ + beats = 4 / subdivision; + } else if (lastLetter === "m"){ + beats = subdivision * timeSignature; + } else { + beats = 0; + } + return beatTime * beats; + }; + + /** + * convert transportTime into seconds + * i.e.: + * 4:2:3 == 4 measures + 2 quarters + 3 sixteenths + * + * @param {string} transportTime + * @param {number=} bpm + * @param {number=} timeSignature + * @return {number} seconds + */ + Tone.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){ + bpm = this.defaultArg(bpm, Tone.Transport.getBpm()); + timeSignature = this.defaultArg(timeSignature, transportTimeSignature); + var measures = 0; + var quarters = 0; + var sixteenths = 0; + var split = transportTime.split(":"); + if (split.length === 2){ + measures = parseFloat(split[0]); + quarters = parseFloat(split[1]); + } else if (split.length === 1){ + quarters = parseFloat(split[0]); + } else if (split.length === 3){ + measures = parseFloat(split[0]); + quarters = parseFloat(split[1]); + sixteenths = parseFloat(split[2]); + } + var beats = (measures * timeSignature + quarters + sixteenths / 4); + return beats * this.notationToSeconds("4n"); + }; + + /** + * Convert seconds to the closest transportTime in the form + * measures:quarters:sixteenths + * + * @param {Tone.Time} seconds + * @param {number=} bpm + * @param {number=} timeSignature + * @return {string} + */ + Tone.prototype.toTransportTime = function(time, bpm, timeSignature){ + var seconds = this.toSeconds(time, bpm, timeSignature); + bpm = this.defaultArg(bpm, Tone.Transport.getBpm()); + timeSignature = this.defaultArg(timeSignature, transportTimeSignature); + var quarterTime = this.notationToSeconds("4n"); + var quarters = seconds / quarterTime; + var measures = Math.floor(quarters / timeSignature); + var sixteenths = Math.floor((quarters % 1) * 4); + quarters = Math.floor(quarters) % timeSignature; + var progress = [measures, quarters, sixteenths]; + return progress.join(":"); + }; + + /** + * convert a time to a frequency + * + * @param {Tone.Time} time + * @return {number} the time in hertz + */ + Tone.prototype.toFrequency = function(time, bpm, timeSignature){ + if (this.isFrequency(time)){ + return parseFloat(time); + } else if (this.isNotation(time) || this.isTransportTime(time)) { + return this.secondsToFrequency(this.toSeconds(time, bpm, timeSignature)); + } else { + return time; + } + }; + + /** + * convert Tone.Time into seconds + * + * unlike the method which it overrides, this takes into account + * transporttime and musical notation + * + * @param {Tone.Time} time + * @param {number=} bpm + * @param {number=} timeSignature + */ + Tone.prototype.toSeconds = function(time, bpm, timeSignature){ + if (typeof time === "number"){ + return time; //assuming that it's seconds + } else if (typeof time === "string"){ + var plusTime = 0; + if(time.charAt(0) === "+") { + plusTime = this.now(); + time = time.slice(1); + } + if (this.isNotation(time)){ + time = this.notationToSeconds(time, bpm, timeSignature); + } else if (this.isTransportTime(time)){ + time = this.transportTimeToSeconds(time, bpm, timeSignature); + } else if (this.isFrequency(time)){ + time = this.frequencyToSeconds(time, bpm, timeSignature); + } else { + time = parseFloat(time); + } + return time + plusTime; + } else { + return this.now(); + } + }; + + //a single transport object + Tone.Transport = new Tone.Transport(); + + return Tone.Transport; +}); + +define('Tone/source/Source',["Tone/core/Tone"], +function(Tone){ + + /** + * base class for sources + * + * @constructor + * @extends {Tone} + */ + Tone.Source = function(){ + /** + * unlike most ToneNodes, Sources only have an output and no input + * + * @type {GainNode} + */ + this.output = this.context.createGain(); + }; + + Tone.extend(Tone.Source); + + /** + * @abstract + * @param {Tone.Time} time + */ + Tone.Source.prototype.start = function(){}; + + /** + * @abstract + * @param {Tone.Time} time + */ + Tone.Source.prototype.stop = function(){}; + + /** + * @abstract + * @param {Tone.Time} time + */ + Tone.Source.prototype.pause = function(){}; + + /** + * @param {number} value + * @param {Tone.Time} time (relative to 'now') + */ + Tone.Source.prototype.fadeTo = function(value, time){ + var currentVolume = this.output.gain.value; + var now = this.now(); + this.output.gain.cancelScheduledValues(now); + this.output.gain.setValueAtTime(currentVolume, now); + this.output.gain.linearRampToValueAtTime(value, this.toSeconds(time)); + }; + + /** + * @param {number} value + */ + Tone.Source.prototype.setVolume = function(value){ + this.output.gain.value = value; + }; + + return Tone.Source; +}); +define('Tone/source/Oscillator',["Tone/core/Tone", "Tone/core/Transport", "Tone/signal/Signal", "Tone/source/Source"], +function(Tone){ + + /** + * Oscillator + * + * Oscilator with start, pause, stop and sync to Transport + * + * @constructor + * @extends {Tone.Source} + * @param {number|string=} freq starting frequency + * @param {string=} type type of oscillator (sine|square|triangle|sawtooth) + */ + Tone.Oscillator = function(freq, type){ + Tone.Source.call(this); + + /** + * the main oscillator + * @type {OscillatorNode} + */ + this.oscillator = this.context.createOscillator(); + /** + * the frequency control signal + * @type {Tone.Signal} + */ + this.frequency = new Tone.Signal(this.defaultArg(this.toFrequency(freq), 440)); + + //connections + this.oscillator.connect(this.output); + //setup + this.oscillator.type = this.defaultArg(type, "sine"); + }; + + Tone.extend(Tone.Oscillator, Tone.Source); + + /** + * start the oscillator + * + * @param {Tone.Time} time + */ + Tone.Oscillator.prototype.start = function(time){ + //get previous values + var type = this.oscillator.type; + var detune = this.oscillator.frequency.value; + //new oscillator with previous values + this.oscillator = this.context.createOscillator(); + this.oscillator.type = type; + this.oscillator.detune.value = detune; + //connect the control signal to the oscillator frequency + this.oscillator.connect(this.output); + this.frequency.connect(this.oscillator.frequency); + this.oscillator.frequency.value = 0; + //start the oscillator + this.oscillator.start(this.toSeconds(time)); + }; + + /** + * Sync the oscillator to the transport + * + * the current ratio between the oscillator and the Transport BPM + * is fixed and any change to the Transport BPM will change this + * oscillator in that same ratio + * + * Transport start/pause/stop will also start/pause/stop the oscillator + */ + Tone.Oscillator.prototype.sync = function(){ + Tone.Transport.sync(this, this.frequency); + }; + + /** + * unsync the oscillator from the Transport + */ + Tone.Oscillator.prototype.unsync = function(){ + Tone.Transport.unsync(this); + this.frequency.unsync(); + }; + + /** + * stop the oscillator + * @param {Tone.Time=} time (optional) timing parameter + */ + Tone.Oscillator.prototype.stop = function(time){ + this.oscillator.stop(this.toSeconds(time)); + }; + + /** + * exponentially ramp the frequency of the oscillator over the rampTime + * + * @param {Tone.Time} val + * @param {Tone.Time=} rampTime when the oscillator will arrive at the frequency + */ + Tone.Oscillator.prototype.setFrequency = function(val, rampTime){ + this.frequency.exponentialRampToValueAtTime(this.toFrequency(val), this.toSeconds(rampTime)); + }; + + /** + * set the oscillator type + * + * @param {string} type (sine|square|triangle|sawtooth) + */ + Tone.Oscillator.prototype.setType = function(type){ + this.oscillator.type = type; + }; + + return Tone.Oscillator; +}); +define('Tone/component/LFO',["Tone/core/Tone", "Tone/source/Oscillator", "Tone/signal/Scale"], function(Tone){ + + /** + * Low Frequency Oscillator + * + * LFO produces an output signal which can be attached to an AudioParam + * for constant control over that parameter + * the LFO can also be synced to the transport + * + * @constructor + * @extends {Tone} + * @param {number} rate + * @param {number=} outputMin + * @param {number=} outputMax + */ + Tone.LFO = function(rate, outputMin, outputMax){ + + Tone.call(this); + + /** @type {Tone.Oscillator} */ + this.oscillator = new Tone.Oscillator(rate, "sine"); + /** @type {Tone.Scale} */ + this.scaler = new Tone.Scale(this.defaultArg(outputMin, 0), this.defaultArg(outputMax, 1)); + + //connect it up + this.chain(this.oscillator, this.scaler, this.output); + }; + + Tone.extend(Tone.LFO); + + + /** + * start the LFO + * @param {Tone.Time} time + */ + Tone.LFO.prototype.start = function(time){ + this.oscillator.start(time); + }; + + /** + * stop the LFO + * @param {Tone.Time} time + */ + Tone.LFO.prototype.stop = function(time){ + this.oscillator.stop(time); + }; + + /** + * Sync the start/stop/pause to the transport + * and the frequency to the bpm of the transport + */ + Tone.LFO.prototype.sync = function(){ + this.oscillator.sync(); + }; + + /** + * unsync the LFO from transport control + */ + Tone.LFO.prototype.unsync = function(){ + this.oscillator.unsync(); + }; + + + /** + * set the frequency + * @param {number} rate + */ + Tone.LFO.prototype.setFrequency = function(rate){ + this.oscillator.setFrequency(rate); + }; + + /** + * set the minimum output of the LFO + * @param {number} min + */ + Tone.LFO.prototype.setMin = function(min){ + this.scaler.setOutputMin(min); + }; + + /** + * set the maximum output of the LFO + * @param {number} min + */ + Tone.LFO.prototype.setMax = function(max){ + this.scaler.setOuputMax(max); + }; + + /** + * set the waveform of the LFO + * @param {string} type + */ + Tone.LFO.prototype.setType = function(type){ + this.oscillator.setType(type); + }; + + /** + * pointer to the parent's connect method + * @private + * @type {[type]} + */ + Tone.LFO.prototype._connect = Tone.prototype.connect; + + /** + * override the connect method so that it 0's out the value + * if attached to an AudioParam + * + * @override + * @param {AudioNode|AudioParam|Tone} param + */ + Tone.LFO.prototype.connect = function(param){ + if (param instanceof AudioParam){ + //set the initial value + param.value = 0; + } + this._connect(param); + }; + + return Tone.LFO; +}); define('Tone/component/Meter',["Tone/core/Tone", "Tone/core/Master"], function(Tone){ - //@param {number=} channels - Tone.Meter = function(channels){ + /** + * get the rms of the input signal with some averaging + * can also just get the value of the signal + * or the value in dB + * + * inspired by https://github.com/cwilso/volume-meter/blob/master/volume-meter.js + * The MIT License (MIT) Copyright (c) 2014 Chris Wilson + * + * @constructor + * @extends {Tone} + * @param {number=} channels (optional) number of channels being metered + * @param {number=} smoothing (optional) amount of smoothing applied to the volume + * @param {number=} clipMemory (optional) number in ms that a "clip" should be remembered + */ + Tone.Meter = function(channels, smoothing, clipMemory){ //extends Unit Tone.call(this); + /** @type {number} */ this.channels = this.defaultArg(channels, 1); - this.volume = new Array(this.channels); - this.values = new Array(this.channels); + + /** @type {number} */ + this.smoothing = this.defaultArg(smoothing, 0.8); + + /** @type {number} */ + this.clipMemory = this.defaultArg(clipMemory, 500); + + /** + * @private + * @type {Array} + * the rms for each of the channels + */ + this._volume = new Array(this.channels); + + /** + * @private + * @type {Array} + * the raw values for each of the channels + */ + this._values = new Array(this.channels); + //zero out the volume array for (var i = 0; i < this.channels; i++){ - this.volume[i] = 0; - this.values[i] = 0; + this._volume[i] = 0; + this._values[i] = 0; } - this.clipTime = 0; + + /** + * @private + * @type {number} + * last time the values clipped + */ + this._lastClip = 0; - //components - this.jsNode = this.context.createScriptProcessor(this.bufferSize, this.channels, this.channels); - this.jsNode.onaudioprocess = this.onprocess.bind(this); + /** + * @private + * @type {ScriptProcessorNode} + */ + this._jsNode = this.context.createScriptProcessor(this.bufferSize, this.channels, 1); + this._jsNode.onaudioprocess = this._onprocess.bind(this); + //so it doesn't get garbage collected + this._jsNode.toMaster(); //signal just passes this.input.connect(this.output); - this.input.connect(this.jsNode); - //so it doesn't get garbage collected - this.jsNode.toMaster(); - } + this.input.connect(this._jsNode); + }; - Tone.extend(Tone.Meter, Tone); + Tone.extend(Tone.Meter); - - //@param {number=} channel - //@returns {number} - Tone.Meter.prototype.getLevel = function(channel){ - channel = this.defaultArg(channel, 0); - var vol = this.volume[channel]; - if (vol < .00001){ - return 0; - } else { - return vol; - } - } - - //@param {number=} channel - //@returns {number} - Tone.Meter.prototype.getValue = function(channel){ - channel = this.defaultArg(channel, 0); - return this.values[channel]; - } - - //@param {number=} channel - //@returns {number} the channel volume in decibels - Tone.Meter.prototype.getDb = function(channel){ - return this.gainToDb(this.getLevel(channel)); - } - - // @returns {boolean} if the audio has clipped in the last 500ms - Tone.Meter.prototype.isClipped = function(){ - return Date.now() - this.clipTime < 500; - } - - //get the max value - Tone.Meter.prototype.onprocess = function(event){ - var bufferSize = this.jsNode.bufferSize; + /** + * called on each processing frame + * @private + * @param {AudioProcessingEvent} event + */ + Tone.Meter.prototype._onprocess = function(event){ + var bufferSize = this._jsNode.bufferSize; + var smoothing = this.smoothing; for (var channel = 0; channel < this.channels; channel++){ var input = event.inputBuffer.getChannelData(channel); var sum = 0; @@ -1047,50 +2184,106 @@ define('Tone/component/Meter',["Tone/core/Tone", "Tone/core/Master"], function(T var clipped = false; for (var i = 0; i < bufferSize; i++){ x = input[i]; - if (!clipped && x > .95){ + if (!clipped && x > 0.95){ clipped = true; - this.clipTime = Date.now(); + this._lastClip = Date.now(); } total += x; sum += x * x; } var average = total / bufferSize; var rms = Math.sqrt(sum / bufferSize); - this.volume[channel] = Math.max(rms, this.volume[channel] * .8); - this.values[channel] = average; + this._volume[channel] = Math.max(rms, this._volume[channel] * smoothing); + this._values[channel] = average; } - } + }; + + /** + * get the rms of the signal + * + * @param {number=} channel which channel + * @return {number} the value + */ + Tone.Meter.prototype.getLevel = function(channel){ + channel = this.defaultArg(channel, 0); + var vol = this._volume[channel]; + if (vol < 0.00001){ + return 0; + } else { + return vol; + } + }; + + /** + * get the value of the signal + * @param {number=} channel + * @return {number} + */ + Tone.Meter.prototype.getValue = function(channel){ + channel = this.defaultArg(channel, 0); + return this._values[channel]; + }; + + /** + * get the volume of the signal in dB + * @param {number=} channel + * @return {number} + */ + Tone.Meter.prototype.getDb = function(channel){ + return this.gainToDb(this.getLevel(channel)); + }; + + // @returns {boolean} if the audio has clipped in the last 500ms + Tone.Meter.prototype.isClipped = function(){ + return Date.now() - this._lastClip < this.clipMemory; + }; return Tone.Meter; }); -/////////////////////////////////////////////////////////////////////////////// -// -// MERGE -// -// Merge a left and a right into a single left/right channel -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/Merge',["Tone/core/Tone"], function(Tone){ + /** + * merge a left and a right channel into a single stereo channel + * + * instead of connecting to the input, connect to either the left, or right input + * + * default input for connect is left input + * + * @constructor + * @extends {Tone} + */ Tone.Merge = function(){ + Tone.call(this); - //components - this.left = this.context.createGain(); + /** + * the left input channel + * also an alias for the input + * @type {GainNode} + */ + this.left = this.input; + /** + * the right input channel + * @type {GainNode} + */ this.right = this.context.createGain(); + /** + * the merger node for the two channels + * @type {ChannelMergerNode} + */ this.merger = this.context.createChannelMerger(2); //connections this.left.connect(this.merger, 0, 0); this.right.connect(this.merger, 0, 1); this.merger.connect(this.output); - } + }; Tone.extend(Tone.Merge); return Tone.Merge; -}) -; +}); + /////////////////////////////////////////////////////////////////////////////// // // PANNER @@ -1141,23 +2334,182 @@ function(Tone){ return Tone.Panner; });; -/////////////////////////////////////////////////////////////////////////////// -// -// BUS -// -// buses are another way of routing audio -// -// adds: send(channelName, amount) -// receive(channelName) -/////////////////////////////////////////////////////////////////////////////// +define('Tone/component/Recorder',["Tone/core/Tone", "Tone/core/Master"], function(Tone){ + /** + * Record an input into an array or AudioBuffer + * + * it is limited in that the recording length needs to be known beforehand + * + * @constructor + * @extends {Tone} + * @param {number} channels + */ + Tone.Recorder = function(channels){ + + Tone.call(this); + + /** + * the number of channels in the recording + * @type {number} + */ + this.channels = this.defaultArg(channels, 1); + + /** + * @private + * @type {ScriptProcessorNode} + */ + this._jsNode = this.context.createScriptProcessor(this.bufferSize, this.channels, 1); + this._jsNode.onaudioprocess = this._audioprocess.bind(this); + + /** + * Float32Array for each channel + * @private + * @type {Array} + */ + this._recordBuffers = new Array(this.channels); + + /** + * @private + * @type {number} + */ + this._recordBufferOffset = 0; + + //connect it up + this.input.connect(this._jsNode); + //pass thru audio + this.input.connect(this.output); + //so it doesn't get garbage collected + this._jsNode.toMaster(); + //clear it to start + this.clear(); + }; + + Tone.extend(Tone.Recorder); + + /** + * internal method called on audio process + * + * @private + * @param {AudioProcessorEvent} event + */ + Tone.Recorder.prototype._audioprocess = function(event){ + if (this._recordBuffers[0] === null || this._recordBuffers[0].length - this._recordBufferOffset === 0){ + return; + } + var input = event.inputBuffer; + var totalWrittenToBuffer = 0; + var recordBufferLength = this._recordBuffers[0].length; + for (var channelNum = 0; channelNum < input.numberOfChannels; channelNum++){ + var bufferOffset = this._recordBufferOffset; + var channel = input.getChannelData(channelNum); + var bufferLen = channel.length; + if (recordBufferLength - bufferOffset > bufferLen){ + this._recordBuffers[channelNum].set(channel, bufferOffset); + totalWrittenToBuffer += bufferLen; + } else { + for (var i = 0; i < bufferLen; i++) { + if (recordBufferLength > bufferOffset){ + this._recordBuffers[channelNum][bufferOffset] = channel[i]; + bufferOffset++; + totalWrittenToBuffer++; + } else { + break; + } + } + } + } + this._recordBufferOffset += totalWrittenToBuffer / input.numberOfChannels; + }; + + /** + * Record for a certain period of time + * + * will clear the internal buffer before starting + * + * @param {Tone.Time} time + */ + Tone.Recorder.prototype.record = function(time){ + this.clear(); + var recordBufferLength = this.toSamples(time); + for (var i = 0; i < this.channels; i++){ + this._recordBuffers[i] = new Float32Array(recordBufferLength); + } + }; + + /** + * clears the recording buffer + */ + Tone.Recorder.prototype.clear = function(){ + for (var i = 0; i < this.channels; i++){ + this._recordBuffers[i] = null; + } + this._recordBufferOffset = 0; + }; + + + /** + * true if there is nothing in the buffers + * @return {boolean} + */ + Tone.Recorder.prototype.isEmpty = function(){ + return this._recordBuffers[0] === null; + }; + + /** + * @return {Array} + */ + Tone.Recorder.prototype.getFloat32Array = function(){ + if (this.isEmpty()){ + return null; + } else { + return this._recordBuffers; + } + }; + + /** + * @return {AudioBuffer} + */ + Tone.Recorder.prototype.getAudioBuffer = function(){ + if (this.isEmpty()){ + return null; + } else { + var audioBuffer = this.context.createBuffer(this.channels, this._recordBuffers[0].length, this.context.sampleRate); + for (var channelNum = 0; channelNum < audioBuffer.numberOfChannels; channelNum++){ + var channel = audioBuffer.getChannelData(channelNum); + channel.set(this._recordBuffers[channelNum]); + } + return audioBuffer; + } + }; + + + return Tone.Recorder; +}); define('Tone/core/Bus',["Tone/core/Tone"], function(Tone){ - var Buses = {} + /** + * @fileOverview + * + * buses are another way of routing audio + * + * augments Tone.prototype to include send and recieve + */ - //@param {string} channelName - //@param {number=} amount - //@returns {GainNode} the send + /** + * All of the routes + * + * @type {Object} + */ + var Buses = {}; + + /** + * send signal to a channel name + * + * @param {string} channelName + * @param {number} amount + * @return {GainNode} + */ Tone.prototype.send = function(channelName, amount){ if (!Buses.hasOwnProperty(channelName)){ Buses[channelName] = this.context.createGain(); @@ -1166,404 +2518,24 @@ define('Tone/core/Bus',["Tone/core/Tone"], function(Tone){ sendKnob.gain.value = this.defaultArg(amount, 1); this.chain(this.output, sendKnob, Buses[channelName]); return sendKnob; - } + }; - //@param {string} channelName + /** + * recieve the input from the desired channelName to the input gain of 'this' node. + * + * @param {string} channelName + */ Tone.prototype.receive = function(channelName){ if (!Buses.hasOwnProperty(channelName)){ Buses[channelName] = this.context.createGain(); } Buses[channelName].connect(this.input); - } + }; - Tone.Buses = Buses; + // Tone.Buses = Buses; - return Tone.Buses; + // return Buses; }); -/////////////////////////////////////////////////////////////////////////////// -// -// TRANSPORT -// -// oscillator-based transport allows for simple musical timing -// supports tempo curves and time changes -// setInterval (repeated events) -// setTimeout (single timeline event) -// -/////////////////////////////////////////////////////////////////////////////// - -define('Tone/core/Transport',["Tone/core/Tone", "Tone/core/Master"], function(Tone){ - - var Transport = function(){ - - //components - this.oscillator = null; - this.jsNode = this.context.createScriptProcessor(this.bufferSize, 1, 1); - this.jsNode.onaudioprocess = this._processBuffer.bind(this); - - - //privates - this._timeSignature = 4;//defaults to 4/4 - this._tatum = 12; //subdivisions of the quarter note - this._ticks = 0; //the number of tatums - this._upTick = false; // if the wave is on the rise or fall - this._bpm = 120; //defaults to 120 - //@type {Array.} - this._intervals = []; - //@type {Array.} - this._timeouts = []; - this._timeoutProgress = 0; - - //public - this._loopStart = 0; - this._loopEnd = this._tatum * 4; - this.loop = false; - this.state = Transport.state.stopped; - - //so it doesn't get garbage collected - this.jsNode.toMaster(); - } - - Tone.extend(Transport); - - /////////////////////////////////////////////////////////////////////////////// - // INTERNAL METHODS - /////////////////////////////////////////////////////////////////////////////// - - Transport.prototype._processBuffer = function(event){ - var now = this.defaultArg(event.playbackTime, this.now()); - var bufferSize = this.jsNode.bufferSize; - var endTime = now + this.samplesToSeconds(bufferSize); - var incomingBuffer = event.inputBuffer.getChannelData(0); - var upTick = this._upTick; - for (var i = 0; i < bufferSize; i++){ - var sample = incomingBuffer[i]; - if (sample > 0 && !upTick){ - upTick = true; - this._processTick(now + this.samplesToSeconds(i)); - } else if (sample < 0 && upTick){ - upTick = false; - } - } - this._upTick = upTick; - } - - //@param {number} tickTime - Transport.prototype._processTick = function(tickTime){ - //do the looping stuff - var ticks = this._ticks; - //do the intervals - this._processIntervals(ticks, tickTime); - this._processTimeouts(ticks, tickTime); - this._ticks = ticks + 1; - if (this.loop){ - if (this._ticks === this._loopEnd){ - this._setTicks(this._loopStart); - } - } - } - - //jump to a specific tick in the timeline - Transport.prototype._setTicks = function(ticks){ - this._ticks = ticks; - for (var i = 0; i < this._timeouts.length; i++){ - var timeout = this._timeouts[i]; - if (timeout.callbackTick() >= ticks){ - this._timeoutProgress = i; - break; - } - } - } - - /////////////////////////////////////////////////////////////////////////////// - // TIMING - /////////////////////////////////////////////////////////////////////////////// - - - //processes and invokes the intervals - Transport.prototype._processIntervals = function(ticks, time){ - for (var i = 0, len = this._intervals.length; i ticks){ - break; - } - } - } - - - //@param {function(number)} callback - //@param {string} interval (01:02:0.2) - //@param {Object=} ctx the 'this' object which the - //@returns {Transport.Event} the event - Transport.prototype.setInterval = function(callback, interval, ctx){ - var ticks = this.toTicks(interval); - ctx = this.defaultArg(ctx, window); - var timeout = new Transport.Timeout(callback, ctx, ticks, this._ticks); - this._intervals.push(timeout); - return timeout; - } - - //@param {number} intervalId - //@param {} - //@returns {boolean} true if the interval was removed - Transport.prototype.clearInterval = function(rmInterval){ - for (var i = 0; i < this._intervals.length; i++){ - var interval = this._intervals[i]; - if (interval === rmInterval){ - this._intervals.splice(i, 1); - return true; - } - } - return false; - } - - //@param {function(number)} callback - //@param {string} timeout colon seperated (bars:beats) - //@param {Object=} ctx the 'this' object which the - //@returns {number} the timeoutID - Transport.prototype.setTimeout = function(callback, timeout, ctx){ - var ticks = this.toTicks(timeout); - ctx = this.defaultArg(ctx, window); - var timeout = new Transport.Timeout(callback, ctx, ticks, this._ticks); - //put it in the right spot - this._addTimeout(timeout); - return timeout; - } - - //@param {function(number)} callback - //@param {string} timeout colon seperated (bars:beats) - //@param {Object=} ctx the 'this' object which the - //@returns {number} the timeoutID - //like setTimeout, but to absolute timeline positions instead of 'now' relative - //events which have passed will not be called - Transport.prototype.setTimeline = function(callback, timeout, ctx){ - var ticks = this.toTicks(timeout); - ctx = this.defaultArg(ctx, window); - var timeout = new Transport.Timeout(callback, ctx, ticks, 0); - //put it in the right spot - this._addTimeout(timeout); - return timeout; - } - - //add an event in the correct position - Transport.prototype._addTimeout = function(event){ - for (var i = this._timeoutProgress, len = this._timeouts.length; i event.callbackTick()){ - this._timeouts.splice(i, 0, event); - return; - } - } - //otherwise push it on the end - this._timeouts.push(event); - } - - //@param {string} timeoutID returned by setTimeout - Transport.prototype.clearTimeout = function(timeoutID){ - for (var i = 0; i < this._timeouts.length; i++){ - var timeout = this._timeouts[i]; - if (timeout.id === timeoutID){ - this._timeouts.splice(i, 1); - return true; - } - } - return false; - } - - //@param {string|number} time - //@returns {number} the the conversion to ticks - Transport.prototype.toTicks = function(time){ - //get the seconds - var seconds = this.toSeconds(time); - var quarter = this.notationToSeconds("4n"); - var quarters = seconds / quarter; - var ticks = quarters * this._tatum; - //quantize to tick value - return Math.round(ticks); - } - - //@param {number} ticks - //@returns {string} progress (measures:beats:sixteenths) - Transport.prototype.ticksToTransportTime = function(ticks){ - var quarters = ticks / this._tatum; - var measures = parseInt(quarters / this._timeSignature, 10); - var sixteenths = parseInt((quarters % 1) * 4, 10); - quarters = parseInt(quarters, 10) % this._timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); - } - - //@returns {string} progress (measures:beats:sixteenths) - Transport.prototype.getTransportTime = function(){ - return this.ticksToTransportTime(this._ticks); - } - - //jump to a specific measure - //@param {string} progress - Transport.prototype.setTransportTime = function(progress){ - var ticks = this.toTicks(progress); - this._setTicks(ticks); - } - - /////////////////////////////////////////////////////////////////////////////// - // START/STOP/PAUSE - /////////////////////////////////////////////////////////////////////////////// - - Transport.prototype.start = function(time){ - if (this.state !== Transport.state.started){ - this.state = Transport.state.started; - this.upTick = false; - time = this.defaultArg(time, this.now()); - this.oscillator = this.context.createOscillator(); - this.oscillator.type = "square"; - this.setBpm(this._bpm); - this.oscillator.connect(this.jsNode); - this.oscillator.start(this.toSeconds(time)); - } - } - - Transport.prototype.stop = function(time){ - if (this.state !== Transport.state.stopped){ - this.state = Transport.state.stopped; - time = this.defaultArg(time, this.now()); - this.oscillator.stop(this.toSeconds(time)); - this._setTicks(0); - } - } - - Transport.prototype.pause = function(time){ - this.state = Transport.state.paused; - time = this.defaultArg(time, this.now()); - this.oscillator.stop(this.toSeconds(time)); - } - - /////////////////////////////////////////////////////////////////////////////// - // SETTERS/GETTERS - /////////////////////////////////////////////////////////////////////////////// - - //@param {number} bpm - //@param {number=} rampTime Optionally speed the tempo up over time - Transport.prototype.setBpm = function(bpm, rampTime){ - this._bpm = bpm; - if (this.state === Transport.state.started){ - //convert the bpm to frequency - var tatumFreq = this.toFrequency(this._tatum.toString() + "n", this._bpm, this._timeSignature); - var freqVal = 4 * tatumFreq; - if (!rampTime){ - this.oscillator.frequency.value = freqVal; - } else { - this.exponentialRampToValueNow(this.oscillator.frequency, freqVal, rampTime); - } - } - } - - //@returns {number} the current bpm - Transport.prototype.getBpm = function(){ - //if the oscillator isn't running, return _bpm - if (this.state === Transport.state.started){ - //convert the current frequency of the oscillator to bpm - var freq = this.oscillator.frequency.value; - return 60 * (freq / this._tatum); - } else { - return this._bpm; - } - } - - //@param {number} numerator - //@param {number=} denominator - Transport.prototype.setTimeSignature = function(numerator, denominator){ - denominator = this.defaultArg(denominator, 4); - this._timeSignature = numerator / (denominator / 4); - } - - //@returns {number} the time signature - Transport.prototype.getTimeSignature = function(){ - return this._timeSignature; - } - - //@param {number|string} startPosition - Transport.prototype.setLoopStart = function(startPosition){ - this._loopStart = this.toTicks(startPosition); - } - - //@param {number|string} endPosition - Transport.prototype.setLoopEnd = function(endPosition){ - this._loopEnd = this.toTicks(endPosition); - } - - //@enum - Transport.state = { - started : "started", - paused : "paused", - stopped : "stopped" - } - - /////////////////////////////////////////////////////////////////////////////// - // - // TRANSPORT EVENT - // - /////////////////////////////////////////////////////////////////////////////// - - //@constructor - //@param {function(number)} callback - //@param {object} context - //@param {number} interval (in ticks) - //@param {number} startTicks - //@param {boolean} repeat - Transport.Timeout = function(callback, context, interval, startTicks){ - this.interval = interval; - this.start = startTicks; - this.callback = callback; - this.context = context; - } - - Transport.Timeout.prototype.doCallback = function(playbackTime){ - this.callback.call(this.context, playbackTime); - } - - Transport.Timeout.prototype.callbackTick = function(){ - return this.start + this.interval; - } - - Transport.Timeout.prototype.testCallback = function(tick){ - return (tick - this.start) % this.interval === 0; - } - - //a single transport object - Tone.Transport = new Transport(); - - /////////////////////////////////////////////////////////////////////////////// - // override Tone's getBpm and getTimeSignature with transport value - /////////////////////////////////////////////////////////////////////////////// - - //@returns {number} - Tone.prototype.getBpm = function(){ - return Tone.Transport.getBpm(); - } - - //@returns {number} - Tone.prototype.getTimeSignature = function(){ - return Tone.Transport.getTimeSignature(); - } - - - return Tone.Transport; -}); - /////////////////////////////////////////////////////////////////////////////// // // EFFECTS UNIT @@ -1622,17 +2594,15 @@ define('Tone/effects/Effect',["Tone/core/Tone", "Tone/component/DryWet"], functi return Tone.Effect; }); -/////////////////////////////////////////////////////////////////////////////// -// -// AUTO PANNER -// -// not a 3d panner. just LR -// -/////////////////////////////////////////////////////////////////////////////// - define('Tone/effects/AutoPanner',["Tone/core/Tone", "Tone/source/Oscillator", "Tone/component/Panner", "Tone/effects/Effect"], function(Tone){ - + /** + * AutoPanner creates a left-right panner effect (not a 3D panner). + * + * @constructor + * @param { number= } rate (optional) rate in HZ of the left-right pan + * @param { number= } amount (optional) of the pan in dB (0 - 1) + */ Tone.AutoPanner = function(rate, amount){ Tone.Effect.call(this); @@ -1648,30 +2618,55 @@ define('Tone/effects/AutoPanner',["Tone/core/Tone", "Tone/source/Oscillator", "T //connections this.connectEffect(this.panner); this.chain(this.osc, this.amount, this.panner.control); - } + }; //extend Effect Tone.extend(Tone.AutoPanner, Tone.Effect); - + + /** + * Start the panner + * + * @param {Tone.Time} Time the panner begins. + */ Tone.AutoPanner.prototype.start = function(time){ this.osc.start(time); - } + }; + /** + * Stop the panner + * + * @param {Tone.Time} time the panner stops. + */ Tone.AutoPanner.prototype.stop = function(time){ this.osc.stop(time); - } + }; + /** + * Set the type of oscillator attached to the AutoPanner. + * + * @param {string} type of oscillator the panner is attached to (sine|sawtooth|triangle|square) + */ Tone.AutoPanner.prototype.setType = function(type){ this.osc.setType(type); - } + }; + /** + * Set frequency of the oscillator attached to the AutoPanner. + * + * @param {number|string} rate in HZ of the oscillator's frequency. + */ Tone.AutoPanner.prototype.setFrequency = function(rate){ this.osc.setFrequency(rate); - } + }; + /** + * Set the amount of the AutoPanner. + * + * @param {number} amount in dB (0 - 1) + */ Tone.AutoPanner.prototype.setAmount = function(amount){ this.amount.gain.value = amount; - } + }; return Tone.AutoPanner; }); @@ -1727,8 +2722,12 @@ define('Tone/effects/FeedbackDelay',["Tone/core/Tone", "Tone/effects/FeedbackEff Tone.extend(Tone.FeedbackDelay, Tone.FeedbackEffect); - Tone.FeedbackDelay.prototype.setDelayTime = function(delayTime){ - this.rampToValueNow(this.delay.delayTime, this.toSeconds(delayTime)); + /** + * sets the delay time + * @param {Tone.Time} time + */ + Tone.FeedbackDelay.prototype.setDelayTime = function(time){ + this.rampToValueNow(this.delay.delayTime, this.toSeconds(time)); } return Tone.FeedbackDelay; @@ -1807,14 +2806,17 @@ define('Tone/instrument/MonoSynth',["Tone/core/Tone", "Tone/component/Envelope", return Tone.MonoSynth; }); -/////////////////////////////////////////////////////////////////////////////// -// -// AUDIO PLAYER -// -/////////////////////////////////////////////////////////////////////////////// - define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ + /** + * Audio Player + * + * Audio file player with start, loop, stop. + * + * @constructor + * @extends {Tone.Source} + * @param {string} url + */ Tone.Player = function(url){ //extend Unit Tone.call(this); @@ -1825,18 +2827,22 @@ define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ this.buffer = null; this.onended = function(){}; - } + }; Tone.extend(Tone.Player, Tone); - //makes an xhr for the buffer at the url - //invokes the callback at the end - //@param {function(Tone.Player)} callback + /** + * Load the audio file as an audio buffer. + * Decodes the audio asynchronously and invokes + * the callback once the audio buffer loads. + * + * @param {function(Tone.Player)} callback + */ Tone.Player.prototype.load = function(callback){ if (!this.buffer){ var request = new XMLHttpRequest(); - request.open('GET', this.url, true); - request.responseType = 'arraybuffer'; + request.open("GET", this.url, true); + request.responseType = "arraybuffer"; // decode asynchronously var self = this; request.onload = function() { @@ -1846,7 +2852,7 @@ define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ callback(self); } }); - } + }; //send the request request.send(); } else { @@ -1854,9 +2860,17 @@ define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ callback(this); } } - } + }; - //play the buffer from start to finish at a time + + /** + * Play the buffer from start to finish at a time + * + * @param {Tone.Time} startTime + * @param {Tone.Time} offset + * @param {Tone.Time} duration + * @param {number} volume + */ Tone.Player.prototype.start = function(startTime, offset, duration, volume){ if (this.buffer){ //default args @@ -1874,9 +2888,18 @@ define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ gain.gain.value = volume; this.chain(this.source, gain, this.output); } - } + }; - //play the buffer from start to finish at a time + /** + * Loop the buffer from start to finish at a time + * + * @param {Tone.Time} startTime + * @param {Tone.Time} loopStart + * @param {Tone.Time} loopEnd + * @param {Tone.Time} offset + * @param {Tone.Time} duration + * @param {Tone.Time} volume + */ Tone.Player.prototype.loop = function(startTime, loopStart, loopEnd, offset, duration, volume){ if (this.buffer){ //default args @@ -1891,29 +2914,41 @@ define('Tone/source/Player',["Tone/core/Tone"], function(Tone){ this.source.loopStart = this.toSeconds(loopStart); this.source.loopEnd = this.toSeconds(loopEnd); } - } + }; - //stop playback + /** + * Stop playback. + * + * @param {Tone.Time} stopTime + */ Tone.Player.prototype.stop = function(stopTime){ if (this.buffer && this.source){ stopTime = this.defaultArg(stopTime, this.now()); this.source.stop(this.toSeconds(stopTime)); } - } + }; - //@returns {number} the buffer duration + /** + * Get the duration in seconds as a floating point number + * + * @return {number} the buffer duration + */ Tone.Player.prototype.getDuration = function(){ if (this.buffer){ - this.buffer.duration; + return this.buffer.duration; } else { return 0; } - } + }; - //@param {function(Event)} callback + /** + * + * @param {function(Event)} callback + * @private + */ Tone.Player.prototype._onended = function(e){ this.onended(e); - } + }; return Tone.Player; }); @@ -1965,46 +3000,55 @@ define('Tone/instrument/Sampler',["Tone/core/Tone", "Tone/component/Envelope", " return Tone.Sampler; }); -/////////////////////////////////////////////////////////////////////////////// -// -// BIT CRUSHER -// -// downsample incoming signal -// inspiration from https://github.com/jaz303/bitcrusher/blob/master/index.js -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/BitCrusher',["Tone/core/Tone"], function(Tone){ - //@param {number=} bits - //@param {number=} frequency + /** + * downsample incoming signal + * inspiration from https://github.com/jaz303/bitcrusher/blob/master/index.js + * + * @constructor + * @extends {Tone} + * @param {number=} bits + * @param {number=} frequency + */ Tone.BitCrusher = function(bits, frequency){ + Tone.call(this); - //the math - this.bits = this.defaultArg(bits, 8); - this.frequency = this.defaultArg(frequency, .5); - this.step = 2 * Math.pow(0.5, this.bits); - this.invStep = 1/this.step; - this.phasor = 0; - this.last = 0; + /** @private @type {number} */ + this._bits = this.defaultArg(bits, 8); + /** @private @type {number} */ + this._frequency = this.defaultArg(frequency, 0.5); + /** @private @type {number} */ + this._step = 2 * Math.pow(0.5, this._bits); + /** @private @type {number} */ + this._invStep = 1/this._step; + /** @private @type {number} */ + this._phasor = 0; + /** @private @type {number} */ + this._last = 0; - //the node - this.crusher = this.context.createScriptProcessor(this.bufferSize, 1, 1); - this.crusher.onaudioprocess = this.audioprocess.bind(this); + /** @private @type {ScriptProcessorNode} */ + this._crusher = this.context.createScriptProcessor(this.bufferSize, 1, 1); + this._crusher.onaudioprocess = this._audioprocess.bind(this); //connect it up - this.chain(this.input, this.crusher, this.output); - } + this.chain(this.input, this._crusher, this.output); + }; Tone.extend(Tone.BitCrusher); - Tone.BitCrusher.prototype.audioprocess = function(event){ - var bufferSize = this.crusher.bufferSize; - var phasor = this.phasor; - var freq = this.frequency; - var invStep = this.invStep; - var last = this.last; - var step = this.step; + /** + * @private + * @param {AudioProcessingEvent} event + */ + Tone.BitCrusher.prototype._audioprocess = function(event){ + //cache the values used in the loop + var phasor = this._phasor; + var freq = this._frequency; + var invStep = this._invStep; + var last = this._last; + var step = this._step; var input = event.inputBuffer.getChannelData(0); var output = event.outputBuffer.getChannelData(0); for (var i = 0, len = output.length; i < len; i++) { @@ -2015,45 +3059,64 @@ define('Tone/signal/BitCrusher',["Tone/core/Tone"], function(Tone){ } output[i] = last; } - this.phasor = phasor; - this.last = last; - } + //set the values for the next loop + this._phasor = phasor; + this._last = last; + }; + /** + * set the bit rate + * + * @param {number} bits + */ Tone.BitCrusher.prototype.setBits = function(bits){ - this.bits = bits; - this.step = 2 * Math.pow(0.5, this.bits); - this.invStep = 1/this.step; - } + this._bits = bits; + this._step = 2 * Math.pow(0.5, this._bits); + this._invStep = 1/this._step; + }; + /** + * set the frequency + * @param {number} freq + */ Tone.BitCrusher.prototype.setFrequency = function(freq){ - this.frequency = freq; - } + this._frequency = freq; + }; return Tone.BitCrusher; }); -/////////////////////////////////////////////////////////////////////////////// -// -// SPLIT -// -// splits the incoming signal into left and right outputs -// one input two outputs -/////////////////////////////////////////////////////////////////////////////// - define('Tone/signal/Split',["Tone/core/Tone"], function(Tone){ + /** + * split the incoming signal into left and right channels + * + * the left channel is the default output + * + * @constructor + * @extends {Tone} + */ Tone.Split = function(){ Tone.call(this); - //components + /** @type {ChannelSplitterNode} */ this.splitter = this.context.createChannelSplitter(2); - this.left = this.context.createGain(); + /** + * left channel output + * @alias for the default output + * @type {GainNode} + */ + this.left = this.output; + /** + * the right channel output + * @type {GainNode} + */ this.right = this.context.createGain(); //connections this.input.connect(this.splitter); this.splitter.connect(this.left, 1, 0); this.splitter.connect(this.right, 0, 0); - } + }; Tone.extend(Tone.Split); @@ -2210,3 +3273,4 @@ define('Tone/source/Noise',["Tone/core/Tone"], function(Tone){ return Tone.Noise; }); +//require(["Tone/component/DryWet", "Tone/component/Envelope", "Tone/component/LFO", "Tone/component/Meter", "Tone/component/Panner", "Tone/component/Recorder", "Tone/core/Bus", "Tone/core/Master", "Tone/core/Tone", "Tone/core/Transport", "Tone/effects/AutoPanner", "Tone/effects/Effect", "Tone/effects/FeedbackDelay", "Tone/effects/FeedbackEffect", "Tone/effects/PingPongDelay", "Tone/instrument/MonoSynth", "Tone/instrument/Sampler", "Tone/signal/Add", "Tone/signal/BitCrusher", "Tone/signal/Merge", "Tone/signal/Multiply", "Tone/signal/Scale", "Tone/signal/Signal", "Tone/signal/Split", "Tone/source/Microphone", "Tone/source/Noise", "Tone/source/Oscillator", "Tone/source/Player", "Tone/source/Source"], function(){}); diff --git a/examples/autoPanner.html b/examples/autoPanner.html index 6bd3c9c7..6a8f9c6a 100644 --- a/examples/autoPanner.html +++ b/examples/autoPanner.html @@ -17,7 +17,7 @@ //input signals var sine = new Tone.Oscillator(); - + console.log(sine); //connect it up sine.connect(pan); pan.toMaster(); diff --git a/examples/transport.html b/examples/transport.html index 9b28fb18..3aa666ea 100644 --- a/examples/transport.html +++ b/examples/transport.html @@ -24,27 +24,27 @@ //setting events var output = document.querySelector("#output"); - Tone.Transport.setTimeout(function(time){ + Tone.Transport.setInterval(function(time){ player.start(time); output.textContent = "a"; }, "0:0"); - Tone.Transport.setTimeout(function(time){ + Tone.Transport.setInterval(function(time){ player.start(time); output.textContent = "series"; }, "0:1"); - Tone.Transport.setTimeout(function(time){ + Tone.Transport.setInterval(function(time){ player.start(time); output.textContent = "of"; }, "2n"); - Tone.Transport.setTimeout(function(time){ + Tone.Transport.setInterval(function(time){ player.start(time); output.textContent = "timed"; }, "0:3"); - Tone.Transport.setTimeout(function(time){ + Tone.Transport.setInterval(function(time){ player.start(time); output.textContent = "events"; }, "0:3:2"); diff --git a/test/test.js b/test/test.js index 13fa7b8a..5dd0faac 100644 --- a/test/test.js +++ b/test/test.js @@ -12,7 +12,7 @@ require.config({ } }); -require(["tests/Timing", "tests/Signal", "tests/Math", "tests/Transport"], function(){ +require(["tests/Timing", "tests/Signal", "tests/Math", "tests/Transport", "tests/Sources"], function(){ if (window.mochaPhantomJS) { mochaPhantomJS.run(); } else { diff --git a/test/testAudio/kick.mp3 b/test/testAudio/kick.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..83283402d42916cfe1f5317fefcf788240240a42 GIT binary patch literal 2191 zcmeZtF=l1}0gsU2U{@e*0K`6yzOD+!MoF0^#R_GqMa7x;^3K|(DB?VUc z`ZfOnjt=}en4F;Kx|}e{QviaBS01^h{()K2a50mF&6^^hb==K9Mr+V zL^xOo2OHtw032L^g9mW%0S^Ad0MITGkWWA^FJhSibC9Tl3R9~i1LG2qJ63-7kby~z z6#D<~4ZsLuIl`R#MSy`xRa)o2*#{2S$f?du3PG0{*jad7&rjjul!=jVKb3m^qxGkD zJO0!2H8^wx{_`*}Z0S0{*(f2+d6%z)iGh)Kq0XW9?w<1W6D}9twy9dRJ@avqxg;sw z5wlT2SwcVCuIEbQt)TfSKdSpZ52bQ`Hrnyck<&_{=BTklDfdz1WsAP$Dc!ijb=CW+ zmQakq_ID3lEt9R&di?isyPD#Q`2%C0S3<4{?rwFx)^HLTW^$#I~U|7C}6$V$ZKD@)3do(7aSEXR@@u@ z`roDqcJuGQ-~D1>Tpw27+ON2-Lt0Hy%H^V<)rm{)J?X#B?d?p0#p|@3StA zm*2N6{nG_K_P>{|Ej=q29slnCHBqLD^p5h(^$ZNWcbc?}J#5=05_Tm{T$I^$p+js{ z=#^Ga?Vvw`_4O@(O8?~7KF~URxOw-(15BrluCTuneLTZw0%u;?0*wPNv^KBYeM@6U zD=ZJ_nTC=JY=C(~pm9N}0Jnn7fl~+rR}lyP{|-2Et%2=^f8^iI3>uHwvQ5{hu;S4Q zDuW~!_#*5f*WD=kgdT8fA&kUhJwd7ezZH)BVQ{;m82eV`(2C2f*|p0gM3HoHEuxM+ zNJg$&g<|CY-vUSeG`Mcj4S%yyA)}=^tLDp$BPhzr=3v-!m+_zruOsV{1O;A(6(I!- z>|`5_TO-InH=7UaVD@BfU{GLb$aL7iz=B&j*)kw|ZZID(VRc~v+QY`M99ZAc%AV`Y x2NW1BfJs?{nL*uwfdOBNC)YzD|6F4}z`*zqXpaH|gZ%+u$xg0ec=XZB9sn&7-tGVZ literal 0 HcmV?d00001 diff --git a/test/tests/Sources.js b/test/tests/Sources.js new file mode 100644 index 00000000..d800eceb --- /dev/null +++ b/test/tests/Sources.js @@ -0,0 +1,33 @@ +define(["chai", "Tone/source/Player"], function(chai, Player){ + var expect = chai.expect; + + describe("Tone.Player", function(){ + this.timeout(100); + var player = new Player("./testAudio/kick.mp3"); + + it("loads a file", function(done){ + player.load(function(){ + done(); + }); + }); + + it("returns correct file duration", function(){ + expect(player.getDuration()).to.equal(0.2361678034067154); + }); + + it("plays a file", function(){ + var ended = false; + player._onended = function(){ + ended = true; + }; + expect(ended).to.equal(false); + player.start(); + setTimeout(function(){ + expect(ended).to.equal(true); + }, 0.5); + }); + + }); + + +}); \ No newline at end of file