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);
}
});