Tone.js/Tone/core/Transport.js

1031 lines
27 KiB
JavaScript
Raw Normal View History

2014-07-30 17:54:55 +00:00
define(["Tone/core/Tone", "Tone/core/Clock", "Tone/signal/Signal"],
function(Tone){
"use strict";
2014-09-11 02:37:57 +00:00
/**
* Time can be descibed in a number of ways.
* Any Method which accepts Tone.Time as a parameter will accept:
*
* Numbers, which will be taken literally as the time (in seconds).
*
* Notation, ("4n", "8t") describes time in BPM and time signature relative values.
*
* Transport Time, ("4:3:2") will also provide tempo and time signature relative times
* in the form BARS:QUARTERS:SIXTEENTHS.
*
* Frequency, ("8hz") is converted to the length of the cycle in seconds.
*
* Now-Relative, ("+1") prefix any of the above with "+" and it will be interpreted as
* "the current time plus whatever expression follows".
*
* Expressions, ("3:0 + 2 - (1m / 7)") any of the above can also be combined
* into a mathematical expression which will be evaluated to compute the desired time.
*
* No Argument, for methods which accept time, no argument will be interpreted as
* 0 seconds or "now" (i.e. the currentTime) depending on the context.
*
* @typedef {number|string|undefined} Tone.Time
*/
/**
2014-07-30 17:54:55 +00:00
* @class oscillator-based transport allows for simple musical timing
* supports tempo curves and time changes
*
* @constructor
2014-06-18 19:39:10 +00:00
* @extends {Tone}
*/
2014-09-11 02:37:57 +00:00
Tone.Transport = function(){
/**
* watches the main oscillator for timing ticks
2014-07-30 17:54:55 +00:00
* initially starts at 120bpm
*
* @private
2014-07-30 17:54:55 +00:00
* @type {Tone.Clock}
*/
2014-07-30 17:54:55 +00:00
this._clock = new Tone.Clock(1, this._processTick.bind(this));
2014-06-23 04:53:59 +00:00
/**
* @type {boolean}
*/
2014-04-06 00:47:59 +00:00
this.loop = false;
2014-03-19 21:25:33 +00:00
/**
* @type {TransportState}
*/
this.state = TransportState.STOPPED;
};
2014-09-11 02:37:57 +00:00
Tone.extend(Tone.Transport);
/**
* @private
* @type {number}
*/
var timelineTicks = 0;
2014-07-30 17:54:55 +00:00
/**
* @private
* @type {number}
*/
var transportTicks = 0;
2014-07-30 17:54:55 +00:00
/**
* @private
* @type {number}
*/
var tatum = 12;
2014-07-30 17:54:55 +00:00
2014-09-25 03:46:57 +00:00
/**
* controls which beat the swing is applied to
2014-09-30 03:42:56 +00:00
* defaults to an 16th note
2014-09-25 03:46:57 +00:00
* @private
* @type {number}
*/
2014-09-30 03:42:56 +00:00
var swingTatum = 3;
2014-09-25 03:46:57 +00:00
/**
* controls which beat the swing is applied to
* @private
* @type {number}
*/
var swingAmount = 0;
/**
* @private
* @type {number}
*/
2014-06-18 05:35:34 +00:00
var transportTimeSignature = 4;
/**
* @private
* @type {number}
*/
var loopStart = 0;
2014-09-25 03:46:57 +00:00
/**
* @private
* @type {number}
*/
var loopEnd = tatum * 4;
/**
* @private
* @type {Array}
*/
var intervals = [];
2014-06-18 21:41:39 +00:00
/**
* @private
* @type {Array}
*/
var timeouts = [];
2014-06-18 21:41:39 +00:00
/**
* @private
* @type {Array}
*/
2014-06-18 20:45:25 +00:00
var transportTimeline = [];
2014-06-18 21:41:39 +00:00
/**
* @private
* @type {number}
*/
var timelineProgress = 0;
/**
* All of the synced components
* @private
* @type {Array<Tone>}
*/
2014-06-25 16:47:47 +00:00
var SyncedSources = [];
/**
* @enum
*/
var TransportState = {
STARTED : "started",
PAUSED : "paused",
STOPPED : "stopped"
};
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
2014-07-30 17:54:55 +00:00
// TICKS
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
/**
2014-07-30 17:54:55 +00:00
* called on every tick
* @param {number} tickTime clock relative tick time
* @private
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype._processTick = function(tickTime){
if (this.state === TransportState.STARTED){
2014-09-25 03:46:57 +00:00
if (swingAmount > 0 && timelineTicks % tatum !== 0 && timelineTicks % swingTatum === 0){
//add some swing
tickTime += Tone.Transport.ticksToSeconds(swingTatum) * swingAmount;
}
processIntervals(tickTime);
processTimeouts(tickTime);
processTimeline(tickTime);
transportTicks += 1;
timelineTicks += 1;
if (this.loop){
if (timelineTicks === loopEnd){
this._setTicks(loopStart);
}
2014-04-06 00:47:59 +00:00
}
}
};
2014-03-19 21:25:33 +00:00
2014-07-30 17:54:55 +00:00
/**
* jump to a specific tick in the timeline
* updates the timeline callbacks
*
* @param {number} ticks the tick to jump to
* @private
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype._setTicks = function(ticks){
timelineTicks = ticks;
2014-06-18 20:45:25 +00:00
for (var i = 0; i < transportTimeline.length; i++){
var timeout = transportTimeline[i];
2014-04-06 00:47:59 +00:00
if (timeout.callbackTick() >= ticks){
2014-06-18 05:35:34 +00:00
timelineProgress = i;
2014-04-06 00:47:59 +00:00
break;
}
}
};
2014-03-19 21:25:33 +00:00
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
// EVENT PROCESSING
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
2014-03-19 21:25:33 +00:00
/**
* process the intervals
* @param {number} time
*/
var processIntervals = function(time){
for (var i = 0, len = intervals.length; i<len; i++){
var interval = intervals[i];
if (interval.testInterval(transportTicks)){
2014-04-06 00:47:59 +00:00
interval.doCallback(time);
}
2014-03-20 03:59:38 +00:00
}
};
/**
* process the timeouts
* @param {number} time
*/
var processTimeouts = function(time){
var removeTimeouts = 0;
for (var i = 0, len = timeouts.length; i<len; i++){
var timeout = timeouts[i];
2014-04-06 00:47:59 +00:00
var callbackTick = timeout.callbackTick();
2014-06-18 05:35:34 +00:00
if (callbackTick <= transportTicks){
2014-04-06 00:47:59 +00:00
timeout.doCallback(time);
removeTimeouts++;
} else if (callbackTick > transportTicks){
break;
}
}
//remove the timeouts off the front of the array after they've been called
timeouts.splice(0, removeTimeouts);
};
/**
2014-06-18 20:45:25 +00:00
* process the transportTimeline events
* @param {number} time
*/
var processTimeline = function(time){
2014-06-18 20:45:25 +00:00
for (var i = timelineProgress, len = transportTimeline.length; i<len; i++){
var evnt = transportTimeline[i];
var callbackTick = evnt.callbackTick();
if (callbackTick === timelineTicks){
timelineProgress = i;
evnt.doCallback(time);
} else if (callbackTick > timelineTicks){
2014-04-06 00:47:59 +00:00
break;
}
2014-03-20 03:59:38 +00:00
}
};
2014-03-19 21:25:33 +00:00
///////////////////////////////////////////////////////////////////////////////
// INTERVAL
///////////////////////////////////////////////////////////////////////////////
2014-03-19 21:25:33 +00:00
/**
* intervals are recurring events
2014-09-11 17:38:41 +00:00
*
* @example
* //triggers a callback every 8th note with the exact time of the event
* Tone.Transport.setInterval(function(time){
* envelope.triggerAttack(time);
* }, "8n");
*
* @param {function} callback
* @param {Tone.Time} interval
* @param {Object} ctx the context the function is invoked in
* @return {number} the id of the interval
*/
2014-09-11 02:37:57 +00:00
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
*/
2014-09-11 02:37:57 +00:00
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);
2014-04-06 00:47:59 +00:00
return true;
}
2014-03-19 21:25:33 +00:00
}
2014-04-06 00:47:59 +00:00
return false;
};
2014-04-06 00:47:59 +00:00
2014-06-18 20:45:25 +00:00
/**
* removes all of the intervals that are currently set
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.clearIntervals = function(){
2014-06-18 20:45:25 +00:00
intervals = [];
};
///////////////////////////////////////////////////////////////////////////////
// TIMEOUT
///////////////////////////////////////////////////////////////////////////////
/**
2014-09-11 17:38:41 +00:00
* 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.
*
* @example
* //trigger an event to happen 1 second from now
* Tone.Transport.setTimeout(function(time){
* player.start(time);
* }, 1)
*
* @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
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setTimeout = function(callback, time, ctx){
var ticks = this.toTicks(time);
var timeout = new TimelineEvent(callback, ctx, ticks + transportTicks, 0);
2014-04-06 00:47:59 +00:00
//put it in the right spot
for (var i = 0, len = timeouts.length; i<len; i++){
var testEvnt = timeouts[i];
if (testEvnt.callbackTick() > 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
*/
2014-09-11 02:37:57 +00:00
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;
};
2014-06-18 20:45:25 +00:00
/**
* removes all of the timeouts that are currently set
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.clearTimeouts = function(){
2014-06-18 20:45:25 +00:00
timeouts = [];
};
///////////////////////////////////////////////////////////////////////////////
// TIMELINE
///////////////////////////////////////////////////////////////////////////////
/**
2014-09-11 02:37:57 +00:00
* Timeline events are synced to the transportTimeline of the Tone.Transport
* Unlike Timeout, Timeline events will restart after the
2014-09-11 02:37:57 +00:00
* Tone.Transport has been stopped and restarted.
*
2014-09-11 17:38:41 +00:00
* @example
* //trigger the start of a part on the 16th measure
* Tone.Transport.setTimeline(function(time){
* part.start(time);
* }, "16m");
*
*
* @param {function} callback
* @param {Tome.Time} timeout
* @param {Object} ctx the context in which the funtion is called
2014-06-18 20:45:25 +00:00
* @return {number} the id for clearing the transportTimeline event
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setTimeline = function(callback, timeout, ctx){
var ticks = this.toTicks(timeout);
var timelineEvnt = new TimelineEvent(callback, ctx, ticks, 0);
//put it in the right spot
2014-06-18 20:45:25 +00:00
for (var i = timelineProgress, len = transportTimeline.length; i<len; i++){
var testEvnt = transportTimeline[i];
if (testEvnt.callbackTick() > timelineEvnt.callbackTick()){
2014-06-18 20:45:25 +00:00
transportTimeline.splice(i, 0, timelineEvnt);
return timelineEvnt.id;
2014-04-06 00:47:59 +00:00
}
2014-03-19 21:25:33 +00:00
}
2014-04-06 00:47:59 +00:00
//otherwise push it on the end
2014-06-18 20:45:25 +00:00
transportTimeline.push(timelineEvnt);
return timelineEvnt.id;
};
/**
2014-06-18 20:45:25 +00:00
* clear the transportTimeline event from the
* @param {number} timelineID
* @return {boolean} true if it was removed
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.clearTimeline = function(timelineID){
2014-06-18 20:45:25 +00:00
for (var i = 0; i < transportTimeline.length; i++){
var testTimeline = transportTimeline[i];
if (testTimeline.id === timelineID){
2014-06-18 20:45:25 +00:00
transportTimeline.splice(i, 1);
2014-04-06 00:47:59 +00:00
return true;
}
2014-03-19 21:25:33 +00:00
}
2014-04-06 00:47:59 +00:00
return false;
};
2014-04-06 00:47:59 +00:00
2014-06-18 20:45:25 +00:00
/**
* remove all events from the timeline
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.clearTimelines = function(){
2014-06-18 20:45:25 +00:00
timelineProgress = 0;
transportTimeline = [];
};
///////////////////////////////////////////////////////////////////////////////
// TIME CONVERSIONS
///////////////////////////////////////////////////////////////////////////////
/**
* turns the time into
* @param {Tone.Time} time
* @return {number}
*/
2014-09-11 02:37:57 +00:00
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;
2014-04-06 00:47:59 +00:00
//quantize to tick value
return Math.round(tickNum);
};
2014-03-19 21:25:33 +00:00
2014-06-18 05:35:34 +00:00
/**
* get the transport time
* @return {string} in transportTime format (measures:beats:sixteenths)
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.getTransportTime = function(){
var quarters = timelineTicks / tatum;
2014-06-18 05:35:34 +00:00
var measures = Math.floor(quarters / transportTimeSignature);
var sixteenths = Math.floor((quarters % 1) * 4);
quarters = Math.floor(quarters) % transportTimeSignature;
2014-04-06 00:47:59 +00:00
var progress = [measures, quarters, sixteenths];
return progress.join(":");
};
2014-03-19 21:25:33 +00:00
2014-06-18 05:35:34 +00:00
/**
* set the transport time, jump to the position right away
*
* @param {Tone.Time} progress
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setTransportTime = function(progress){
var ticks = this.toTicks(progress);
2014-04-06 00:47:59 +00:00
this._setTicks(ticks);
};
2014-03-19 21:25:33 +00:00
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
// START/STOP/PAUSE
///////////////////////////////////////////////////////////////////////////////
/**
* start the transport and all sources synced to the transport
*
* @param {Tone.Time} time
* @param {Tone.Time=} offset the offset position to start
*/
Tone.Transport.prototype.start = function(time, offset){
if (this.state === TransportState.STOPPED || this.state === TransportState.PAUSED){
if (!this.isUndef(offset)){
this._setTicks(this.toTicks(offset));
}
this.state = TransportState.STARTED;
2014-06-25 16:47:47 +00:00
var startTime = this.toSeconds(time);
2014-07-30 17:54:55 +00:00
this._clock.start(startTime);
//call start on each of the synced sources
2014-06-25 16:47:47 +00:00
for (var i = 0; i < SyncedSources.length; i++){
var source = SyncedSources[i].source;
var delay = SyncedSources[i].delay;
source.start(startTime + delay);
}
2014-06-18 05:35:34 +00:00
}
};
/**
* stop the transport and all sources synced to the transport
*
* @param {Tone.Time} time
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.stop = function(time){
if (this.state === TransportState.STARTED || this.state === TransportState.PAUSED){
2014-06-25 16:47:47 +00:00
var stopTime = this.toSeconds(time);
this._clock.stop(stopTime, this._onend.bind(this));
2014-06-25 16:47:47 +00:00
//call start on each of the synced sources
for (var i = 0; i < SyncedSources.length; i++){
var source = SyncedSources[i].source;
source.stop(stopTime);
}
} else {
this._onend();
2014-06-18 05:35:34 +00:00
}
};
/**
* invoked when the transport is stopped
* @private
*/
Tone.Transport.prototype._onend = function(){
transportTicks = 0;
this._setTicks(0);
this.clearTimeouts();
this.state = TransportState.STOPPED;
};
/**
* pause the transport and all sources synced to the transport
*
* @param {Tone.Time} time
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.pause = function(time){
if (this.state === TransportState.STARTED){
this.state = TransportState.PAUSED;
2014-06-25 16:47:47 +00:00
var stopTime = this.toSeconds(time);
2014-07-30 17:54:55 +00:00
this._clock.stop(stopTime);
//call pause on each of the synced sources
2014-06-25 16:47:47 +00:00
for (var i = 0; i < SyncedSources.length; i++){
var source = SyncedSources[i].source;
source.pause(stopTime);
}
}
};
2014-03-19 21:25:33 +00:00
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
// SETTERS/GETTERS
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
/**
* set the BPM
* optionally ramp to the bpm over some time
2014-06-18 05:35:34 +00:00
* @param {number} bpm
* @param {Tone.Time=} rampTime
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setBpm = function(bpm, rampTime){
2014-07-30 17:54:55 +00:00
var quarterTime = this.notationToSeconds(tatum.toString() + "n", bpm, transportTimeSignature) / 4;
this._clock.setRate(quarterTime, rampTime);
};
/**
* return the current BPM
*
* @return {number}
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.getBpm = function(){
2014-06-18 05:35:34 +00:00
//convert the current frequency of the oscillator to bpm
2014-07-30 17:54:55 +00:00
var freq = this._clock.getRate();
2014-06-18 05:35:34 +00:00
return 60 * (freq / tatum);
};
/**
* set the time signature
*
* @example
2014-09-11 17:38:41 +00:00
* this.setTimeSignature(3, 8); // 3/8
* this.setTimeSignature(4); // 4/4
*
2014-09-11 17:38:41 +00:00
* @param {number} numerator the numerator of the time signature
* @param {number=} [denominator=4] the denominator of the time signature. this should
* be a multiple of 2.
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setTimeSignature = function(numerator, denominator){
denominator = this.defaultArg(denominator, 4);
2014-06-18 05:35:34 +00:00
transportTimeSignature = numerator / (denominator / 4);
};
/**
2014-09-11 17:38:41 +00:00
* return the time signature as just the numerator over 4.
* for example 4/4 would return 4 and 6/8 would return 3
*
* @return {number}
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.getTimeSignature = function(){
2014-06-18 05:35:34 +00:00
return transportTimeSignature;
};
/**
* set the loop start position
*
* @param {Tone.Time} startPosition
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setLoopStart = function(startPosition){
loopStart = this.toTicks(startPosition);
};
/**
* set the loop start position
*
* @param {Tone.Time} endPosition
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setLoopEnd = function(endPosition){
loopEnd = this.toTicks(endPosition);
};
/**
* shorthand loop setting
* @param {Tone.Time} startPosition
* @param {Tone.Time} endPosition
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.setLoopPoints = function(startPosition, endPosition){
this.setLoopStart(startPosition);
this.setLoopEnd(endPosition);
};
2014-09-25 03:46:57 +00:00
/**
* set the amount of swing which is applied to the subdivision (defaults to 16th notes)
* @param {number} amount a value between 0-1 where 1 equal to the note + half the subdivision
*/
Tone.Transport.prototype.setSwing = function(amount){
//scale the values to a normal range
swingAmount = amount * 0.5;
};
/**
2014-09-30 03:42:56 +00:00
* set the subdivision which the swing will be applied to. the starting values is a 16th note.
*
2014-09-25 03:46:57 +00:00
* @example
* Tone.Transport.setSwingSubdivision("8n"); //the eight note will be swing by the "swing amount"
*
* @param {string} subdivision the subdivision in notation (i.e. 8n, 16n, 8t).
* value must be less than a quarter note.
*/
Tone.Transport.prototype.setSwingSubdivision = function(subdivision){
swingTatum = this.toTicks(subdivision);
};
///////////////////////////////////////////////////////////////////////////////
// SYNCING
///////////////////////////////////////////////////////////////////////////////
2014-06-25 16:47:47 +00:00
/**
* Sync a source to the transport so that
* @param {Tone.Source} source the source to sync to the transport
* @param {Tone.Time} delay (optionally) start the source with a delay from the transport
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.syncSource = function(source, startDelay){
2014-06-25 16:47:47 +00:00
SyncedSources.push({
source : source,
delay : this.toSeconds(this.defaultArg(startDelay, 0))
});
};
/**
* remove the source from the list of Synced Sources
*
* @param {Tone.Source} source [description]
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.unsyncSource = function(source){
2014-06-25 16:47:47 +00:00
for (var i = 0; i < SyncedSources.length; i++){
if (SyncedSources[i].source === source){
SyncedSources.splice(i, 1);
}
}
};
2014-07-30 17:54:55 +00:00
/**
* attaches the signal to the tempo control signal so that
* any changes in the tempo will change the signal in the same
* ratio
*
* @param {Tone.Signal} signal
*/
2014-09-11 02:37:57 +00:00
Tone.Transport.prototype.syncSignal = function(signal){
//overreaching. fix this.
signal.sync(this._clock._controlSignal);
2014-07-30 17:54:55 +00:00
};
2014-03-19 21:25:33 +00:00
2014-09-11 17:38:41 +00:00
/**
* clean up
*/
Tone.Transport.prototype.dispose = function(){
this._clock.dispose();
this._clock = null;
};
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
// TIMELINE EVENT
2014-04-06 00:47:59 +00:00
///////////////////////////////////////////////////////////////////////////////
/**
* @static
* @type {number}
*/
var TimelineEventIDCounter = 0;
/**
* A Timeline event
*
* @constructor
2014-07-30 17:54:55 +00:00
* @internal
* @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;
2014-04-06 00:47:59 +00:00
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){
2014-04-06 00:47:59 +00:00
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;
};
2014-03-20 03:59:38 +00:00
2014-03-19 21:25:33 +00:00
///////////////////////////////////////////////////////////////////////////////
2014-06-16 05:44:00 +00:00
// AUGMENT TONE'S PROTOTYPE TO INCLUDE TRANSPORT TIMING
///////////////////////////////////////////////////////////////////////////////
2014-06-16 05:44:00 +00:00
/**
* tests if a string is musical notation
* i.e.:
* 4n = quarter note
* 2m = two measures
* 8t = eighth-note triplet
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-06-16 05:44:00 +00:00
*
* @return {boolean}
2014-06-23 18:20:16 +00:00
* @method isNotation
2014-06-23 18:52:33 +00:00
* @lends Tone.prototype.isNotation
2014-06-16 05:44:00 +00:00
*/
Tone.prototype.isNotation = (function(){
var notationFormat = new RegExp(/[0-9]+[mnt]$/i);
return function(note){
return notationFormat.test(note);
};
})();
2014-07-30 17:54:55 +00:00
/**
* tests if a string is in Tick notation
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-07-30 17:54:55 +00:00
* @return {boolean}
* @method isTicks
* @lends Tone.prototype.isNotation
*/
Tone.prototype.isTicks = (function(){
var tickFormat = new RegExp(/[0-9]+[i]$/i);
return function(tick){
return tickFormat.test(tick);
};
})();
2014-06-16 05:44:00 +00:00
/**
* tests if a string is transportTime
* i.e. :
* 1:2:0 = 1 measure + two quarter notes + 0 sixteenth notes
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-06-16 05:44:00 +00:00
*
* @return {boolean}
2014-06-24 03:18:17 +00:00
*
* @method isTransportTime
2014-06-23 18:52:33 +00:00
* @lends Tone.prototype.isTransportTime
2014-06-16 05:44:00 +00:00
*/
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
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
*
* @param {number} freq
* @return {boolean}
2014-06-23 18:20:16 +00:00
*
2014-06-24 03:18:17 +00:00
* @method isFrequency
2014-06-23 18:52:33 +00:00
* @lends Tone.prototype.isFrequency
*/
Tone.prototype.isFrequency = (function(){
var freqFormat = new RegExp(/[0-9]+hz$/i);
return function(freq){
return freqFormat.test(freq);
};
})();
2014-06-16 05:44:00 +00:00
/**
2014-06-23 18:20:16 +00:00
*
2014-06-16 05:44:00 +00:00
* convert notation format strings to seconds
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
*
2014-06-18 05:35:34 +00:00
* @param {string} notation
* @param {number=} bpm
* @param {number=} timeSignature
2014-06-23 18:52:33 +00:00
* @return {number}
*
2014-06-16 05:44:00 +00:00
*/
2014-06-18 05:35:34 +00:00
Tone.prototype.notationToSeconds = function(notation, bpm, timeSignature){
bpm = this.defaultArg(bpm, Tone.Transport.getBpm());
timeSignature = this.defaultArg(timeSignature, transportTimeSignature);
2014-06-16 05:44:00 +00:00
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;
};
2014-06-16 05:44:00 +00:00
/**
* convert transportTime into seconds
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-06-23 18:52:33 +00:00
*
* ie: 4:2:3 == 4 measures + 2 quarters + 3 sixteenths
2014-06-23 18:20:16 +00:00
*
2014-06-16 05:44:00 +00:00
* @param {string} transportTime
2014-06-18 05:35:34 +00:00
* @param {number=} bpm
* @param {number=} timeSignature
2014-06-16 05:44:00 +00:00
* @return {number} seconds
2014-06-23 18:52:33 +00:00
*
* @lends Tone.prototype.transportTimeToSeconds
2014-06-16 05:44:00 +00:00
*/
2014-06-18 05:35:34 +00:00
Tone.prototype.transportTimeToSeconds = function(transportTime, bpm, timeSignature){
bpm = this.defaultArg(bpm, Tone.Transport.getBpm());
timeSignature = this.defaultArg(timeSignature, transportTimeSignature);
2014-06-16 05:44:00 +00:00
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");
2014-06-16 05:44:00 +00:00
};
2014-07-30 17:54:55 +00:00
/**
* convert ticks into seconds
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
*
* @param {number} ticks
2014-07-30 17:54:55 +00:00
* @param {number=} bpm
* @param {number=} timeSignature
* @return {number} seconds
*/
Tone.prototype.ticksToSeconds = function(ticks, bpm, timeSignature){
ticks = Math.floor(ticks);
var quater = this.notationToSeconds("4n", bpm, timeSignature);
return (quater * ticks) / (tatum);
2014-07-30 17:54:55 +00:00
};
2014-06-16 05:44:00 +00:00
/**
* Convert seconds to the closest transportTime in the form
* measures:quarters:sixteenths
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-06-23 18:20:16 +00:00
*
* @method toTransportTime
*
2014-06-18 05:35:34 +00:00
* @param {Tone.Time} seconds
* @param {number=} bpm
* @param {number=} timeSignature
2014-06-23 18:52:33 +00:00
* @return {string}
*
* @lends Tone.prototype.toTransportTime
2014-06-16 05:44:00 +00:00
*/
2014-06-18 05:35:34 +00:00
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");
2014-06-16 05:44:00 +00:00
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
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
*
* @param {Tone.Time} time
* @return {number} the time in hertz
*/
Tone.prototype.toFrequency = function(time, now){
if (this.isFrequency(time)){
return parseFloat(time);
} else if (this.isNotation(time) || this.isTransportTime(time)) {
return this.secondsToFrequency(this.toSeconds(time, now));
} else {
return time;
}
};
2014-06-16 05:44:00 +00:00
/**
2014-06-19 17:38:21 +00:00
* convert Tone.Time into seconds.
2014-09-25 03:46:57 +00:00
* defined in "Tone/core/Transport"
2014-06-16 05:44:00 +00:00
*
* unlike the method which it overrides, this takes into account
* transporttime and musical notation
2014-06-23 18:52:33 +00:00
*
2014-07-30 17:54:55 +00:00
* 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)
* Ticks: "146i"
*
2014-06-23 18:52:33 +00:00
* @override
2014-06-18 05:35:34 +00:00
* @param {Tone.Time} time
* @param {number=} now if passed in, this number will be
* used for all 'now' relative timings
2014-06-19 17:38:21 +00:00
* @return {number}
2014-06-16 05:44:00 +00:00
*/
Tone.prototype.toSeconds = function(time, now){
now = this.defaultArg(now, this.now());
2014-06-16 05:44:00 +00:00
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;
2014-06-16 05:44:00 +00:00
time = time.slice(1);
}
2014-07-30 17:54:55 +00:00
var components = time.split(/[\(\)\-\+\/\*]/);
if (components.length > 1){
var oringalTime = time;
for(var i = 0; i < components.length; i++){
var symb = components[i];
if (symb !== ""){
var val = this.toSeconds(symb.trim());
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.Time: "+oringalTime);
2014-07-30 17:54:55 +00:00
}
} else if (this.isNotation(time)){
time = this.notationToSeconds(time);
2014-06-16 05:44:00 +00:00
} else if (this.isTransportTime(time)){
time = this.transportTimeToSeconds(time);
2014-06-16 05:44:00 +00:00
} else if (this.isFrequency(time)){
time = this.frequencyToSeconds(time);
} else {
time = parseFloat(time);
2014-06-16 05:44:00 +00:00
}
return time + plusTime;
2014-06-16 05:44:00 +00:00
} else {
return now;
2014-06-16 05:44:00 +00:00
}
};
var TransportConstructor = Tone.Transport;
//a single transport object
2014-09-11 02:37:57 +00:00
Tone.Transport = new Tone.Transport();
2014-08-29 20:36:31 +00:00
Tone.Transport.setBpm(120);
2014-07-30 17:54:55 +00:00
Tone._initAudioContext(function(){
//stop the clock
Tone.Transport.stop();
2014-08-29 20:36:31 +00:00
//get the previous bpm
var bpm = Tone.Transport.getBpm();
//destory the old clock
Tone.Transport._clock.dispose();
//make new Transport insides
TransportConstructor.call(Tone.Transport);
2014-08-29 20:36:31 +00:00
//set the bpm
Tone.Transport.setBpm(bpm);
2014-07-30 17:54:55 +00:00
});
2014-04-06 00:47:59 +00:00
return Tone.Transport;
});