diff --git a/Tone/core/Transport.js b/Tone/core/Transport.js index beb12dfd..d252b7cb 100644 --- a/Tone/core/Transport.js +++ b/Tone/core/Transport.js @@ -1,4 +1,5 @@ -define(["Tone/core/Tone", "Tone/core/Clock", "Tone/signal/Signal", "Tone/signal/Multiply"], +define(["Tone/core/Tone", "Tone/core/Clock", "Tone/signal/Signal", + "Tone/signal/Multiply", "Tone/core/Types", "Tone/core/EventEmitter"], function(Tone){ "use strict"; @@ -12,7 +13,7 @@ function(Tone){ * you're scheduling.

* A single transport is created for you when the library is initialized. * - * @extends {Tone} + * @extends {Tone.EventEmitter} * @singleton * @example * //repeated event every 8th note @@ -32,15 +33,9 @@ function(Tone){ */ Tone.Transport = function(){ - /** - * watches the main oscillator for timing ticks - * initially starts at 120bpm - * - * @private - * @type {Tone.Clock} - */ - this._clock = new Tone.Clock(0, this._processTick.bind(this)); - this._clock.onended = this._onended.bind(this); + /////////////////////////////////////////////////////////////////////// + // LOOPING + ////////////////////////////////////////////////////////////////////// /** * If the transport loops or not. @@ -48,6 +43,40 @@ function(Tone){ */ this.loop = false; + /** + * The loop start position in ticks + * @type {Ticks} + * @private + */ + this._loopStart = 0; + + /** + * The loop end position in ticks + * @type {Ticks} + * @private + */ + this._loopEnd = 0; + + /////////////////////////////////////////////////////////////////////// + // CLOCK/TEMPO + ////////////////////////////////////////////////////////////////////// + + /** + * Pulses per quarter is the number of ticks per quarter note. + * @private + * @type {Number} + */ + this._ppq = TransportConstructor.defaults.PPQ; + + /** + * watches the main oscillator for timing ticks + * initially starts at 120bpm + * @private + * @type {Tone.Clock} + */ + this._clock = new Tone.Clock(0, this._processTick.bind(this)); + this._clock.onended = this._onended.bind(this); + /** * The Beats Per Minute of the Transport. * @type {BPM} @@ -57,26 +86,111 @@ function(Tone){ * //ramp the bpm to 120 over 10 seconds * Tone.Transport.bpm.rampTo(120, 10); */ - this.bpm = new Tone.Signal(120, Tone.Type.BPM); + this.bpm = new Tone.Signal(TransportConstructor.defaults.bpm, Tone.Type.BPM); /** * the signal scalar * @type {Tone.Multiply} * @private */ - this._bpmMult = new Tone.Multiply(1/60 * tatum); + this._bpmMult = new Tone.Multiply(1/60 * this._ppq); /** - * The state of the transport. READ ONLY. - * @type {Tone.State} + * The time signature, or more accurately the numerator + * of the time signature over a denominator of 4. + * @type {Number} + * @private */ - this.state = Tone.State.Stopped; + this._timeSignature = TransportConstructor.defaults.timeSignature; //connect it all up this.bpm.chain(this._bpmMult, this._clock.frequency); + + /////////////////////////////////////////////////////////////////////// + // TIMELINE EVENTS + ////////////////////////////////////////////////////////////////////// + + /** + * The scheduled events. + * @type {Array} + * @private + */ + this._timeline = []; + + /** + * The current position along the scheduledEvents array. + * @type {Number} + * @private + */ + this._timelinePosition = 0; + + /** + * Repeated events + * @type {Array} + * @private + */ + this._repeatedEvents = []; + + /** + * Events that occur once + * @type {Array} + * @private + */ + this._onceEvents = []; + + /** + * The elapsed ticks. + * @type {Ticks} + * @private + */ + this._ticks = 0; + + /////////////////////////////////////////////////////////////////////// + // STATE TIMING + ////////////////////////////////////////////////////////////////////// + + /** + * The next time the state is started. + * @type {Number} + * @private + */ + this._nextStart = Infinity; + + /** + * The next time the state is stopped. + * @type {Number} + * @private + */ + this._nextStop = Infinity; + + /** + * The next time the state is paused. + * @type {Number} + * @private + */ + this._nextPause = Infinity; + + /////////////////////////////////////////////////////////////////////// + // SWING + ////////////////////////////////////////////////////////////////////// + + /** + * The subdivision of the swing + * @type {Ticks} + * @private + */ + this._swingTicks = this.toTicks(TransportConstructor.defaults.swingSubdivision, TransportConstructor.defaults.bpm, TransportConstructor.defaults.timeSignature); + + /** + * The swing amount + * @type {NormalRange} + * @private + */ + this._swingAmount = 0; + }; - Tone.extend(Tone.Transport); + Tone.extend(Tone.Transport, Tone.EventEmitter); /** * the defaults @@ -90,99 +204,10 @@ function(Tone){ "swingSubdivision" : "16n", "timeSignature" : 4, "loopStart" : 0, - "loopEnd" : "4m" + "loopEnd" : "4m", + "PPQ" : 48 }; - /** - * @private - * @type {number} - */ - var tatum = 12; - - /** - * @private - * @type {number} - */ - var timelineTicks = 0; - - /** - * @private - * @type {number} - */ - var transportTicks = 0; - - /** - * Which subdivision the swing is applied to. - * defaults to an 16th note - * @private - * @type {number} - */ - var swingSubdivision = "16n"; - - /** - * controls which beat the swing is applied to - * defaults to an 16th note - * @private - * @type {number} - */ - var swingTatum = 3; - - /** - * controls which beat the swing is applied to - * @private - * @type {number} - */ - var swingAmount = 0; - - /** - * @private - * @type {number} - */ - var transportTimeSignature = 4; - - /** - * @private - * @type {number} - */ - var loopStart = 0; - - /** - * @private - * @type {number} - */ - var loopEnd = tatum * 4; - - /** - * @private - * @type {Array} - */ - var intervals = []; - - /** - * @private - * @type {Array} - */ - var timeouts = []; - - /** - * @private - * @type {Array} - */ - var transportTimeline = []; - - /** - * @private - * @type {number} - */ - var timelineProgress = 0; - - /** - * All of the synced components - * @private - * @type {Array} - */ - var SyncedSources = []; - /** * All of the synced Signals * @private @@ -200,22 +225,45 @@ function(Tone){ * @private */ Tone.Transport.prototype._processTick = function(tickTime){ - if (this.state === Tone.State.Started){ - if (swingAmount > 0 && - timelineTicks % tatum !== 0 && //not on a downbeat - timelineTicks % swingTatum === 0){ - //add some swing - tickTime += this.ticksToSeconds(swingTatum) * swingAmount; - } - processIntervals(tickTime); - processTimeouts(tickTime); - processTimeline(tickTime); - transportTicks += 1; - timelineTicks += 1; - if (this.loop){ - if (timelineTicks === loopEnd){ - this._setTicks(loopStart); + //handle swing + if (this._swingAmount > 0 && + this._ticks % this._ppq !== 0 && //not on a downbeat + this._ticks % this._swingTicks === 0){ + //add some swing + tickTime += this.ticksToSeconds(this._swingTicks) * this._swingAmount; + } + //fire the next tick events if their time has come + for (var i = this._timelinePosition; i < this._timeline.length; i++){ + var evnt = this._timeline[i]; + if (evnt.tick <= this._ticks){ + this._timelinePosition++; + if (evnt.tick === this._ticks){ + evnt.callback(tickTime); } + } else if (evnt.tick > this._ticks){ + break; + } + } + //process the repeated events + for (var j = this._repeatedEvents.length - 1; j >= 0; j--) { + var repeatEvnt = this._repeatedEvents[j]; + if (this._ticks >= repeatEvnt.startTick){ + if ((this._ticks - repeatEvnt.startTick) % repeatEvnt.interval === 0){ + repeatEvnt.callback(tickTime); + } + } + } + //process the single occurrence events + while(this._onceEvents.length > 0 && this._onceEvents[0].tick > this._ticks){ + if (this._onceEvents[0].tick === this._ticks){ + this._onceEvents.shift().callback(tickTime); + } + } + //increment the tick counter and check for loops + this._ticks++; + if (this.loop){ + if (this._ticks === this._loopEnd){ + this._setTicks(this._loopStart); } } }; @@ -228,224 +276,104 @@ function(Tone){ * @private */ Tone.Transport.prototype._setTicks = function(ticks){ - timelineTicks = ticks; - for (var i = 0; i < transportTimeline.length; i++){ - var timeout = transportTimeline[i]; - if (timeout.callbackTick() >= ticks){ - timelineProgress = i; + this._ticks = ticks; + for (var i = 0; i < this._timeline.length; i++){ + var evnt = this._timeline[i]; + if (evnt.tick >= ticks){ + this._timelinePosition = i; break; } } }; /////////////////////////////////////////////////////////////////////////////// - // EVENT PROCESSING + // SCHEDULE /////////////////////////////////////////////////////////////////////////////// /** - * process the intervals - * @param {number} time - */ - var processIntervals = function(time){ - for (var i = 0, len = intervals.length; i transportTicks){ - break; - } - } - //remove the timeouts off the front of the array after they've been called - timeouts.splice(0, removeTimeouts); - }; - - /** - * process the transportTimeline events - * @param {number} time - */ - var processTimeline = function(time){ - for (var i = timelineProgress, len = transportTimeline.length; i timelineTicks){ - break; - } - } - }; - - /////////////////////////////////////////////////////////////////////////////// - // INTERVAL - /////////////////////////////////////////////////////////////////////////////// - - /** - * Set a callback for a recurring event. - * @param {function} callback - * @param {Time} interval - * @return {number} the id of the interval + * Schedule an event along the timeline. + * @param {TimelineEvent} event + * @param {Time} time + * @return {Number} The id of the event which can be used for canceling the event. * @example - * //triggers a callback every 8th note with the exact time of the event - * Tone.Transport.setInterval(function(time){ + * //trigger the callback when the Transport reaches the desired time + * Tone.Transport.schedule(function(time){ * envelope.triggerAttack(time); - * }, "8n"); + * }, "128i"); */ - Tone.Transport.prototype.setInterval = function(callback, interval, ctx){ - var tickTime = this.toTicks(interval); - var timeout = new TimelineEvent(callback, ctx, tickTime, transportTicks); - intervals.push(timeout); - return timeout.id; - }; - - /** - * Stop and ongoing interval. - * @param {number} intervalID The ID of interval to remove. The interval - * ID is given as the return value in Tone.Transport.setInterval. - * @return {boolean} true if the event was removed - */ - 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; - }; - - /** - * Removes all of the intervals that are currently set. - * @return {boolean} true if the event was removed - */ - Tone.Transport.prototype.clearIntervals = function(){ - var willRemove = intervals.length > 0; - intervals = []; - return willRemove; - }; - - /////////////////////////////////////////////////////////////////////////////// - // TIMEOUT - /////////////////////////////////////////////////////////////////////////////// - - /** - * Set a timeout to occur after time from now. NB: the transport must be - * running for this to be triggered. All timeout events are cleared when the - * transport is stopped. - * - * @param {function} callback - * @param {Time} time The time (from now) that the callback will be invoked. - * @return {number} The id of the timeout. - * @example - * //trigger an event to happen 1 second from now - * Tone.Transport.setTimeout(function(time){ - * player.start(time); - * }, 1) - */ - Tone.Transport.prototype.setTimeout = function(callback, time, ctx){ - var ticks = this.toTicks(time); - var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0); + Tone.Transport.prototype.schedule = function(callback, time){ + var event = new TimelineEvent(callback, this.toTicks(time)); //put it in the right spot - for (var i = 0, len = timeouts.length; i timeout.callbackTick()){ - timeouts.splice(i, 0, timeout); - return timeout.id; + for (var i = 0, len = this._timeline.length; i event.tick){ + this._timeline.splice(i, 0, event); + return event; } } //otherwise push it on the end - timeouts.push(timeout); - return timeout.id; + this._timeline.push(event); + return event.id; }; /** - * Clear a timeout using it's ID. - * @param {number} intervalID The ID of timeout to remove. The timeout - * ID is given as the return value in Tone.Transport.setTimeout. - * @return {boolean} true if the timeout was removed + * Schedule a repeated event along the timeline. + * @param {Function} callback The callback to invoke. + * @param {Time} interval The duration between successive + * callbacks. + * @param {Time=} startTime When along the timeline the events should + * start being invoked. + * @return {Number} The ID of the scheduled event. Use this to cancel + * the event. */ - Tone.Transport.prototype.clearTimeout = function(timeoutID){ - for (var i = 0; i < timeouts.length; i++){ - var testTimeout = timeouts[i]; - if (testTimeout.id === timeoutID){ - timeouts.splice(i, 1); - return true; - } - } - return false; + Tone.Transport.prototype.scheduleRepeat = function(callback, interval, startTime){ + var event = new RepeatEvent(callback, this.toTicks(interval), this.toTicks(startTime)); + this._repeatedEvents.push(event); + return event.id; }; /** - * Removes all of the timeouts that are currently set. - * @return {boolean} true if the event was removed + * Schedule an event that will be removed after it is invoked */ - Tone.Transport.prototype.clearTimeouts = function(){ - var willRemove = timeouts.length > 0; - timeouts = []; - return willRemove; - }; - - /////////////////////////////////////////////////////////////////////////////// - // TIMELINE - /////////////////////////////////////////////////////////////////////////////// - - /** - * Timeline events are synced to the timeline of the Tone.Transport. - * Unlike Timeout, Timeline events will restart after the - * Tone.Transport has been stopped and restarted. - * - * @param {function} callback - * @param {Time} timeout - * @return {number} the id for clearing the transportTimeline event - * @example - * //trigger the start of a part on the 16th measure - * Tone.Transport.setTimeline(function(time){ - * part.start(time); - * }, "16m"); - */ - Tone.Transport.prototype.setTimeline = function(callback, timeout, ctx){ - var ticks = this.toTicks(timeout); - var timelineEvnt = new TimelineEvent(callback, ctx, ticks, 0); + Tone.Transport.prototype.scheduleOnce = function(callback, time){ + var event = new TimelineEvent(callback, this.toTicks(time)); //put it in the right spot - for (var i = timelineProgress, len = transportTimeline.length; i timelineEvnt.callbackTick()){ - transportTimeline.splice(i, 0, timelineEvnt); - return timelineEvnt.id; + for (var i = 0, len = this._onceEvents.length; i event.tick){ + this._onceEvents.splice(i, 0, event); + return event; } } //otherwise push it on the end - transportTimeline.push(timelineEvnt); - return timelineEvnt.id; + this._onceEvents.push(event); + return event.id; }; /** - * Clear the timeline event. - * @param {number} timelineID - * @return {boolean} true if it was removed + * Cancel the passed in event. + * @param {TimelineEvent} event The event to cancel. + * @returns {Boolean} true if the event was removed, false otherwise. */ - Tone.Transport.prototype.clearTimeline = function(timelineID){ - for (var i = 0; i < transportTimeline.length; i++){ - var testTimeline = transportTimeline[i]; - if (testTimeline.id === timelineID){ - transportTimeline.splice(i, 1); + Tone.Transport.prototype.cancel = function(eventId){ + for (var i = 0; i < this._timeline.length; i++){ + if (this._timeline[i].id === eventId){ + this._timeline[i].callback = null; + this._timeline.splice(i, 1); + return true; + } + } + for (var j = 0; j < this._repeatedEvents.length; j++){ + if (this._repeatedEvents[j].id === eventId){ + this._repeatedEvents[j].callback = null; + this._repeatedEvents.splice(j, 1); + return true; + } + } + for (var k = 0; k < this._onceEvents.length; k++){ + if (this._onceEvents[k].id === eventId){ + this._onceEvents[k].callback = null; + this._onceEvents.splice(k, 1); return true; } } @@ -453,18 +381,59 @@ function(Tone){ }; /** - * Remove all events from the timeline. - * @returns {boolean} true if the events were removed + * Remove scheduled events from the timeline after + * the given time. Repeated events will be removed + * if their startTime is after the given time + * @param {Time} [after=0] Clear all events after + * this time. + * @returns {Tone.Transport} this */ - Tone.Transport.prototype.clearTimelines = function(){ - timelineProgress = 0; - var willRemove = transportTimeline.length > 0; - transportTimeline = []; - return willRemove; + Tone.Transport.prototype.clear = function(after){ + after = this.defaultArg(after, 0); + after = this.toTicks(after); + for (var i = 0, len = this._timeline.length; i after){ + this._timeline = this._timeline.slice(0, i); + break; + } + } + //remove all of the repeat events after the + return this; + }; + + /* + * @static + * @private + * @type {Number} + */ + var EventIds = 0; + + /** + * @class A Transport Event + */ + var TimelineEvent = function(callback, tick){ + //add this to the transport timeline + this.id = EventIds++; + this.callback = callback; + this.tick = tick; + this.stopTick = Infinity; + }; + + /** + * @class A repeating event + */ + var RepeatEvent = function(callback, interval, startTick){ + //add this to the transport timeline + this.id = EventIds++; + this.callback = callback; + this.startTick = startTick; + this.interval = interval; + this.stopTick = Infinity; }; /////////////////////////////////////////////////////////////////////////////// - // TIME CONVERSIONS + // QUANTIZATION /////////////////////////////////////////////////////////////////////////////// /** @@ -476,11 +445,6 @@ function(Tone){ subdivision = this.defaultArg(subdivision, "4n"); var tickNum = this.toTicks(subdivision); var remainingTicks = (transportTicks % tickNum); - var nextTick = remainingTicks; - if (remainingTicks > 0){ - nextTick = tickNum - remainingTicks; - } - return this.ticksToSeconds(nextTick); }; @@ -488,6 +452,37 @@ function(Tone){ // START/STOP/PAUSE /////////////////////////////////////////////////////////////////////////////// + /** + * Returns the playback state of the source, either "started", "stopped", or "paused" + * @type {String} + * @readOnly + * @memberOf Tone.State# + * @name state + */ + Object.defineProperty(Tone.Transport.prototype, "state", { + get : function(){ + return this._stateAtTime(this.now()); + } + }); + + /** + * Get the state of the source at the specified time. + * @param {Time} time + * @return {Tone.Transport} + * @private + */ + Tone.Transport.prototype._stateAtTime = function(time){ + if (this._nextStart <= time && this._nextStop > time && this._nextPause > time){ + return Tone.State.Started; + } else if (this._nextStop <= time){ + return Tone.State.Stopped; + } else if (this._nextPause <= time){ + return Tone.State.Paused; + } else { + return Tone.State.Stopped; + } + }; + /** * Start the transport and all sources synced to the transport. * @param {Time} [time=now] The time when the transport should start. @@ -498,24 +493,22 @@ function(Tone){ * Tone.Transport.start("+1", "4:0:0"); */ Tone.Transport.prototype.start = function(time, offset){ - if (this.state === Tone.State.Stopped || this.state === Tone.State.Paused){ - if (!this.isUndef(offset)){ - this._setTicks(this.toTicks(offset)); - } - this.state = Tone.State.Started; - var startTime = this.toSeconds(time); - this._clock.start(startTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - var delay = SyncedSources[i].delay; - source.start(startTime + delay); - } + time = this.toSeconds(time); + if (this._stateAtTime(time) !== Tone.State.Started){ + this._nextStart = time; + this._nextStop = Infinity; + this._nextPause = Infinity; + offset = this.defaultArg(offset, this._ticks); + //set the offset + this._setTicks(this.toTicks(offset)); + //call start on each of the synced structures + // this.trigger("start", time, offset); + //start the clock + this._clock.start(time); } return this; }; - /** * Stop the transport and all sources synced to the transport. * @param {Time} [time=now] The time when the transport should stop. @@ -524,14 +517,13 @@ function(Tone){ * Tone.Transport.stop(); */ Tone.Transport.prototype.stop = function(time){ - if (this.state === Tone.State.Started || this.state === Tone.State.Paused){ - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call start on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - source.stop(stopTime); - } + time = this.toSeconds(time); + if (this._stateAtTime(time) !== Tone.State.Stopped){ + this._nextStop = time; + this._clock.stop(time); + //clear the tick events + this.clear(time); + // this.trigger("stop", time); } else { this._onended(); } @@ -543,10 +535,7 @@ function(Tone){ * @private */ Tone.Transport.prototype._onended = function(){ - transportTicks = 0; this._setTicks(0); - this.clearTimeouts(); - this.state = Tone.State.Stopped; }; /** @@ -555,15 +544,11 @@ function(Tone){ * @returns {Tone.Transport} this */ Tone.Transport.prototype.pause = function(time){ - if (this.state === Tone.State.Started){ - this.state = Tone.State.Paused; - var stopTime = this.toSeconds(time); - this._clock.stop(stopTime); - //call pause on each of the synced sources - for (var i = 0; i < SyncedSources.length; i++){ - var source = SyncedSources[i].source; - source.pause(stopTime); - } + time = this.toSeconds(time); + if (this._stateAtTime(time) === Tone.State.Started){ + this._nextPause = time; + this._clock.stop(time); + // this.trigger("pause", time); } return this; }; @@ -586,10 +571,13 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "timeSignature", { get : function(){ - return transportTimeSignature; + return this._timeSignature; }, - set : function(numerator){ - transportTimeSignature = numerator; + set : function(timeSig){ + if (Array.isArray(timeSig)){ + timeSig = (timeSig[0] / timeSig[1]) * 4; + } + this._timeSignature = timeSig; } }); @@ -602,10 +590,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "loopStart", { get : function(){ - return this.ticksToSeconds(loopStart); + return this.ticksToSeconds(this._loopStart); }, set : function(startPosition){ - loopStart = this.toTicks(startPosition); + this._loopStart = this.toTicks(startPosition); } }); @@ -617,10 +605,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "loopEnd", { get : function(){ - return this.ticksToSeconds(loopEnd); + return this.ticksToSeconds(this._loopEnd); }, set : function(endPosition){ - loopEnd = this.toTicks(endPosition); + this._loopEnd = this.toTicks(endPosition); } }); @@ -649,11 +637,11 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swing", { get : function(){ - return swingAmount * 2; + return this._swingAmount * 2; }, set : function(amount){ //scale the values to a normal range - swingAmount = amount * 0.5; + this._swingAmount = amount * 0.5; } }); @@ -668,12 +656,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "swingSubdivision", { get : function(){ - return swingSubdivision; + return this.toNotation(this._swingTicks + "i"); }, set : function(subdivision){ - //scale the values to a normal range - swingSubdivision = subdivision; - swingTatum = this.toTicks(subdivision); + this._swingTicks = this.toTicks(subdivision); } }); @@ -687,10 +673,10 @@ function(Tone){ */ Object.defineProperty(Tone.Transport.prototype, "position", { get : function(){ - var quarters = timelineTicks / tatum; - var measures = Math.floor(quarters / transportTimeSignature); - var sixteenths = Math.floor((quarters % 1) * 4); - quarters = Math.floor(quarters) % transportTimeSignature; + var quarters = this.ticks / this._ppq; + var measures = Math.floor(quarters / this._timeSignature); + var sixteenths = ((quarters % 1) * 4).toFixed(3); + quarters = Math.floor(quarters) % this._timeSignature; var progress = [measures, quarters, sixteenths]; return progress.join(":"); }, @@ -700,10 +686,64 @@ function(Tone){ } }); + /** + * The transports current tick position. + * + * @memberOf Tone.Transport# + * @type {Ticks} + * @name ticks + */ + Object.defineProperty(Tone.Transport.prototype, "ticks", { + get : function(){ + return this._ticks; + }, + set : function(t){ + //should also trigger whatever is on this tick + this._setTicks(t); + //clear everything after that tick + //trigger a tick to get everyone on the same page + // this._trigger("scrub", this._ticks); + } + }); + + /** + * Pulses Per Quarter note. This is the smallest resolution + * the Transport timing supports. This should be set once + * on initialization and not set again. Changing this value + * after other objects have been created can cause problems. + * + * @memberOf Tone.Transport# + * @type {Number} + * @name PPQ + */ + Object.defineProperty(Tone.Transport.prototype, "PPQ", { + get : function(){ + return this._ppq; + }, + set : function(ppq){ + this._ppq = ppq; + } + }); + /////////////////////////////////////////////////////////////////////////////// // SYNCING /////////////////////////////////////////////////////////////////////////////// + /** + * Sync a source to the transport so that + * @param {Tone.Source} source the source to sync to the transport + * @param {Time} delay (optionally) start the source with a delay from the transport + * @returns {Tone.Transport} this + * @example + * Tone.Transport.syncSource(player, "1m"); + * Tone.Transport.start(); + * //the player will start 1 measure after the transport starts + */ + Tone.Transport.prototype.syncStructure = function(struct){ + this._structures.push(struct); + return this; + }; + /** * Sync a source to the transport so that * @param {Tone.Source} source the source to sync to the transport @@ -803,322 +843,106 @@ function(Tone){ }; /////////////////////////////////////////////////////////////////////////////// - // TIMELINE EVENT + // DEPRECATED FUNCTIONS + // (will be removed in r7) /////////////////////////////////////////////////////////////////////////////// /** - * @static - * @type {number} + * @deprecated Use Tone.scheduleRepeat instead. + * Set a callback for a recurring event. + * @param {function} callback + * @param {Time} interval + * @return {number} the id of the interval + * @example + * //triggers a callback every 8th note with the exact time of the event + * Tone.Transport.setInterval(function(time){ + * envelope.triggerAttack(time); + * }, "8n"); */ - var TimelineEventIDCounter = 0; + Tone.Transport.prototype.setInterval = function(callback, interval){ + console.warn("This method is deprecated. Use Tone.Transport.scheduleRepeat instead."); + return Tone.Transport.scheduleRepeat(callback, interval); + }; /** - * A Timeline event + * @deprecated Use Tone.cancel instead. + * Stop and ongoing interval. + * @param {number} intervalID The ID of interval to remove. The interval + * ID is given as the return value in Tone.Transport.setInterval. + * @return {boolean} true if the event was removed + */ + Tone.Transport.prototype.clearInterval = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.cancel instead."); + return Tone.Transport.cancel(id); + }; + + /** + * @deprecated Use Tone.Note instead. + * Set a timeout to occur after time from now. NB: the transport must be + * running for this to be triggered. All timeout events are cleared when the + * transport is stopped. * - * @constructor - * @private - * @param {function(number)} callback - * @param {Object} context - * @param {number} tickTime - * @param {number} startTicks + * @param {function} callback + * @param {Time} time The time (from now) that the callback will be invoked. + * @return {number} The id of the timeout. + * @example + * //trigger an event to happen 1 second from now + * Tone.Transport.setTimeout(function(time){ + * player.start(time); + * }, 1) */ - 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); + Tone.Transport.prototype.setTimeout = function(callback, timeout){ + console.warn("This method is deprecated. Use Tone.Transport.scheduleOnce instead."); + return Tone.Transport.scheduleOnce(callback, timeout); }; /** - * get the tick which the callback is supposed to occur on - * - * @return {number} + * @deprecated Use Tone.Note instead. + * Clear a timeout using it's ID. + * @param {number} intervalID The ID of timeout to remove. The timeout + * ID is given as the return value in Tone.Transport.setTimeout. + * @return {boolean} true if the timeout was removed */ - TimelineEvent.prototype.callbackTick = function(){ - return this.startTicks + this.tickTime; + Tone.Transport.prototype.clearTimeout = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.cancel instead."); + return Tone.Transport.cancel(id); }; /** - * test if the tick occurs on the interval - * - * @param {number} tick - * @return {boolean} + * @deprecated Use Tone.Note instead. + * Timeline events are synced to the timeline of the Tone.Transport. + * Unlike Timeout, Timeline events will restart after the + * Tone.Transport has been stopped and restarted. + * + * @param {function} callback + * @param {Time} time + * @return {number} the id for clearing the transportTimeline event + * @example + * //trigger the start of a part on the 16th measure + * Tone.Transport.setTimeline(function(time){ + * part.start(time); + * }, "16m"); */ - TimelineEvent.prototype.testInterval = function(tick){ - return (tick - this.startTicks) % this.tickTime === 0; + Tone.Transport.prototype.setTimeline = function(callback, time){ + console.warn("This method is deprecated. Use Tone.Transport.schedule instead."); + return Tone.Transport.schedule(callback, time); }; + /** + * @deprecated Use Tone.Note instead. + * Clear the timeline event. + * @param {number} id + * @return {boolean} true if it was removed + */ + Tone.Transport.prototype.clearTimeline = function(id){ + console.warn("This method is deprecated. Use Tone.Transport.cancel instead."); + return Tone.Transport.cancel(id); + }; /////////////////////////////////////////////////////////////////////////////// - // AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING + // INITIALIZATION /////////////////////////////////////////////////////////////////////////////// - /** - * Tests if a string is in Tick notation. - * - * @param {string} str The string to test - * @return {boolean} - * @method isTick - * @lends Tone.prototype.isTick - */ - Tone.prototype.isTicks = (function(){ - var tickFormat = new RegExp(/^\d+i$/i); - return function(note){ - return tickFormat.test(note); - }; - })(); - - /** - * tests if a string is musical notation. - * i.e.: - *
    - *
  • 4n = quarter note
  • - *
  • 2m = two measures
  • - *
  • 8t = eighth-note triplet
  • - *
- * - * @param {string} str The string to test - * @return {boolean} - * @method isNotation - * @lends Tone.prototype.isNotation - */ - Tone.prototype.isNotation = (function(){ - var notationFormat = new RegExp(/[0-9]+[mnt]$/i); - return function(note){ - return notationFormat.test(note); - }; - })(); - - /** - * tests if a string is transportTime - * i.e. : - * 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes - * - * @return {boolean} - * - * @method isTransportTime - * @lends Tone.prototype.isTransportTime - */ - Tone.prototype.isTransportTime = (function(){ - var transportTimeFormat = new RegExp(/^\d+(\.\d+)?:\d+(\.\d+)?(:\d+(\.\d+)?)?$/i); - return function(transportTime){ - return transportTimeFormat.test(transportTime); - }; - })(); - - /** - * - * 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.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var beatTime = (60 / bpm); - var subdivision = parseInt(notation, 10); - var beats = 0; - if (subdivision === 0){ - beats = 0; - } - var lastLetter = notation.slice(-1); - if (lastLetter === "t"){ - beats = (4 / subdivision) * 2/3; - } else if (lastLetter === "n"){ - beats = 4 / subdivision; - } else if (lastLetter === "m"){ - beats = subdivision * timeSignature; - } else { - beats = 0; - } - return beatTime * beats; - }; - - /** - * convert transportTime into seconds. - * - * ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths - * - * @param {string} transportTime - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * - * @lends Tone.prototype.transportTimeToSeconds - */ - Tone.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){ - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var measures = 0; - var quarters = 0; - var sixteenths = 0; - var split = transportTime.split(":"); - if (split.length === 2){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - } else if (split.length === 1){ - quarters = parseFloat(split[0]); - } else if (split.length === 3){ - measures = parseFloat(split[0]); - quarters = parseFloat(split[1]); - sixteenths = parseFloat(split[2]); - } - var beats = (measures * timeSignature + quarters + sixteenths / 4); - return beats * this.notationToSeconds("4n"); - }; - - /** - * convert ticks into seconds - * - * @param {number} ticks - * @param {number=} bpm - * @param {number=} timeSignature - * @return {number} seconds - * @private - */ - Tone.prototype.ticksToSeconds = function(ticks, bpm, timeSignature){ - ticks = parseInt(ticks); - var quater = this.notationToSeconds("4n", bpm, timeSignature); - return (quater * ticks) / (tatum); - }; - - /** - * Convert seconds to the closest transportTime in the form - * measures:quarters:sixteenths - * - * @method toTransportTime - * - * @param {Time} seconds - * @param {number=} bpm - * @param {number=} timeSignature - * @return {string} - * - * @lends Tone.prototype.toTransportTime - */ - Tone.prototype.toTransportTime = function(time, bpm, timeSignature){ - var seconds = this.toSeconds(time, bpm, timeSignature); - bpm = this.defaultArg(bpm, Tone.Transport.bpm.value); - timeSignature = this.defaultArg(timeSignature, transportTimeSignature); - var quarterTime = this.notationToSeconds("4n"); - var quarters = seconds / quarterTime; - var measures = Math.floor(quarters / timeSignature); - var sixteenths = Math.floor((quarters % 1) * 4); - quarters = Math.floor(quarters) % timeSignature; - var progress = [measures, quarters, sixteenths]; - return progress.join(":"); - }; - - /** - * Convert a frequency representation into a number. - * - * @param {Frequency} freq - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} the frequency in hertz - */ - Tone.prototype.toFrequency = function(freq, now){ - if (this.isFrequency(freq)){ - return parseFloat(freq); - } else if (this.isNotation(freq) || this.isTransportTime(freq)) { - return this.secondsToFrequency(this.toSeconds(freq, now)); - } else { - return freq; - } - }; - - /** - * turns the time into - * @param {Time} time - * @return {number} - * @private - */ - Tone.prototype.toTicks = function(time){ - //get the seconds - var seconds = this.toSeconds(time); - var quarter = this.notationToSeconds("4n"); - var quarters = seconds / quarter; - var tickNum = quarters * tatum; - //quantize to tick value - return Math.round(tickNum); - }; - - - /** - * Convert Time into seconds. - * - * Unlike the method which it overrides, this takes into account - * transporttime and musical notation. - * - * Time : 1.40 - * Notation: 4n|1m|2t - * TransportTime: 2:4:1 (measure:quarters:sixteens) - * Now Relative: +3n - * Math: 3n+16n or even very complicated expressions ((3n*2)/6 + 1) - * - * @override - * @param {Time} time - * @param {number=} now if passed in, this number will be - * used for all 'now' relative timings - * @return {number} - */ - Tone.prototype.toSeconds = function(time, now){ - now = this.defaultArg(now, this.now()); - if (typeof time === "number"){ - return time; //assuming that it's seconds - } else if (typeof time === "string"){ - var plusTime = 0; - if(time.charAt(0) === "+") { - plusTime = now; - time = time.slice(1); - } - var components = time.split(/[\(\)\-\+\/\*]/); - if (components.length > 1){ - var originalTime = time; - for(var i = 0; i < components.length; i++){ - var symb = components[i].trim(); - if (symb !== ""){ - var val = this.toSeconds(symb); - time = time.replace(symb, val); - } - } - try { - //i know eval is evil, but i think it's safe here - time = eval(time); // jshint ignore:line - } catch (e){ - throw new EvalError("problem evaluating Tone.Type.Time: "+originalTime); - } - } else if (this.isNotation(time)){ - time = this.notationToSeconds(time); - } else if (this.isTransportTime(time)){ - time = this.transportTimeToSeconds(time); - } else if (this.isFrequency(time)){ - time = this.frequencyToSeconds(time); - } else if (this.isTicks(time)){ - time = this.ticksToSeconds(time); - } else { - time = parseFloat(time); - } - return time + plusTime; - } else { - return now; - } - }; - var TransportConstructor = Tone.Transport; Tone._initAudioContext(function(){ @@ -1128,14 +952,14 @@ function(Tone){ } else { //stop the clock Tone.Transport.stop(); - //get the previous bpm - var bpm = Tone.Transport.bpm.value; - //destory the old clock - Tone.Transport._clock.dispose(); + //get the previous values + var prevSettings = Tone.Transport.get(); + //destory the old transport + Tone.Transport.dispose(); //make new Transport insides TransportConstructor.call(Tone.Transport); - //set the bpm - Tone.Transport.bpm.value = bpm; + //set the previous config + Tone.Transport.set(prevSettings); } });