renamed Schedulable to Timeline

This commit is contained in:
Yotam Mann 2015-08-18 16:28:55 -04:00
parent 5fd98c2064
commit 72ee2dcc37
4 changed files with 298 additions and 183 deletions

View file

@ -1,134 +0,0 @@
define(["Tone/core/Tone"], function (Tone) {
/**
* @class A Schedulable class has two private functions
* for scheduling and maintaining state: addEvent
* and getEvent. A scheduled event is pushed onto
* a private _timeline array. The event must be an
* Object with a 'time' attribute.
* @extends {Tone}
*/
Tone.Schedulable = function(){
/**
* The array of scheduled timeline events
* @type {Array}
* @private
*/
this._timeline = [];
};
Tone.extend(Tone.Schedulable);
/**
* Insert an event into the correct position in the timeline.
* @param {Object} event The event object to insert into the
* timeline. Events must have a "time" attribute.
* @returns {Tone.Schedulable} this
*/
Tone.Schedulable.prototype.addEvent = function(event){
//the event needs to have a time attribute
if (this.isUndef(event.time)){
throw new Error("events must have a time attribute");
}
event.time = this.toSeconds(event.time);
if (this._timeline.length){
var index = this._search(event.time);
this._timeline.splice(index + 1, 0, event);
} else {
this._timeline.push(event);
}
return this;
};
/**
* Get the event whose time is less than or equal to the given time.
* @param {Number} time The time to query.
* @returns {Object} The event object set after that time.
*/
Tone.Schedulable.prototype.getEvent = function(time){
time = this.toSeconds(time);
var index = this._search(time);
if (index !== -1){
return this._timeline[index];
} else {
return null;
}
};
/**
* Get the next event after the current event.
* @param {Number} time The time to query.
* @returns {Object} The event object after the given time
*/
Tone.Schedulable.prototype.getNextEvent = function(time){
time = this.toSeconds(time);
var index = this._search(time);
if (index + 1 < this._timeline.length){
return this._timeline[index + 1];
} else {
return null;
}
};
/**
* Cancel events after the given time
* @param {Time} time The time to query.
* @returns {Tone.Schedulable} this
*/
Tone.Schedulable.prototype.clear = function(after){
after = this.toSeconds(after);
var index = this._search(after);
if (index >= 0){
this._timeline = this._timeline.slice(0, index);
}
return this;
};
/**
* Does a binary serach on the timeline array and returns the
* event which is after or equal to the time.
* @param {Number} time
* @return {Number} the index in the timeline array
* @private
*/
Tone.Schedulable.prototype._search = function(time){
var beginning = 0;
var len = this._timeline.length;
var end = len;
// continue searching while [imin,imax] is not empty
while (beginning <= end && beginning < len){
// calculate the midpoint for roughly equal partition
var midPoint = Math.floor(beginning + (end - beginning) / 2);
var event = this._timeline[midPoint];
if (event.time === time){
//choose the last one that has the same time
for (var i = midPoint; i < this._timeline.length; i++){
var testEvent = this._timeline[i];
if (testEvent.time === time){
midPoint = i;
}
}
return midPoint;
} else if (event.time > time){
//search lower
end = midPoint - 1;
} else if (event.time < time){
//search upper
beginning = midPoint + 1;
}
}
return beginning - 1;
};
/**
* Clean up.
* @return {Tone.Schedulable} this
*/
Tone.Schedulable.prototype.dispose = function(){
Tone.prototype.dispose.call(this);
this._timeline = null;
};
return Tone.Schedulable;
});

233
Tone/core/Timeline.js Normal file
View file

@ -0,0 +1,233 @@
define(["Tone/core/Tone", "Tone/core/Types"], function (Tone) {
/**
* @class A Timeline class has two private functions
* for scheduling and maintaining state: addEvent
* and getEvent. A scheduled event is pushed onto
* a private _timeline array. The event must be an
* Object with a 'time' attribute.
* @extends {Tone}
*/
Tone.Timeline = function(){
/**
* The array of scheduled timeline events
* @type {Array}
* @private
*/
this._timeline = [];
};
Tone.extend(Tone.Timeline);
/**
* The number of items in the timeline.
* @type {Number}
* @memberOf Tone.Timeline#
* @name length
* @readOnly
*/
Object.defineProperty(Tone.Timeline.prototype, "length", {
get : function(){
return this._timeline.length;
}
});
/**
* Insert an event into the correct position in the timeline.
* @param {Object} event The event object to insert into the
* timeline. Events must have a "time" attribute.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.addEvent = function(event){
//the event needs to have a time attribute
if (this.isUndef(event.time)){
throw new Error("events must have a time attribute");
}
event.time = this.toSeconds(event.time);
if (this._timeline.length){
var index = this._search(event.time);
this._timeline.splice(index + 1, 0, event);
} else {
this._timeline.push(event);
}
return this;
};
/**
* Remove an event from the timeline.
* @param {Object} event The event object to remove from the list.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.removeEvent = function(event){
var index = this._timeline.indexOf(event);
if (index !== -1){
this._timeline.splice(index, 1);
}
return this;
};
/**
* Get the event whose time is less than or equal to the given time.
* @param {Number} time The time to query.
* @returns {Object} The event object set after that time.
*/
Tone.Timeline.prototype.getEvent = function(time){
time = this.toSeconds(time);
var index = this._search(time);
if (index !== -1){
return this._timeline[index];
} else {
return null;
}
};
/**
* Get the next event after the current event.
* @param {Number} time The time to query.
* @returns {Object} The event object after the given time
*/
Tone.Timeline.prototype.getNextEvent = function(time){
time = this.toSeconds(time);
var index = this._search(time);
if (index + 1 < this._timeline.length){
return this._timeline[index + 1];
} else {
return null;
}
};
/**
* Cancel events after the given time
* @param {Time} time The time to query.
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.clear = function(after){
after = this.toSeconds(after);
var index = this._search(after);
if (index >= 0){
this._timeline = this._timeline.slice(0, index);
}
return this;
};
/**
* Does a binary serach on the timeline array and returns the
* event which is after or equal to the time.
* @param {Number} time
* @return {Number} the index in the timeline array
* @private
*/
Tone.Timeline.prototype._search = function(time){
var beginning = 0;
var len = this._timeline.length;
var end = len;
// continue searching while [imin,imax] is not empty
while (beginning <= end && beginning < len){
// calculate the midpoint for roughly equal partition
var midPoint = Math.floor(beginning + (end - beginning) / 2);
var event = this._timeline[midPoint];
if (event.time === time){
//choose the last one that has the same time
for (var i = midPoint; i < this._timeline.length; i++){
var testEvent = this._timeline[i];
if (testEvent.time === time){
midPoint = i;
}
}
return midPoint;
} else if (event.time > time){
//search lower
end = midPoint - 1;
} else if (event.time < time){
//search upper
beginning = midPoint + 1;
}
}
return beginning - 1;
};
/**
* Iterate over everything in the array
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEach = function(callback){
// while()
//iterate over the items in reverse so that removing an item doesn't break things
for (var i = this._timeline.length - 1; i >= 0; i--){
callback(this._timeline[i]);
}
return this;
};
/**
* Iterate over everything in the array at or before the given time.
* @param {Time} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachBefore = function(time, callback){
//iterate over the items in reverse so that removing an item doesn't break things
time = this.toSeconds(time);
var startIndex = this._search(time);
if (startIndex !== -1){
for (var i = startIndex; i >= 0; i--){
callback(this._timeline[i]);
}
}
return this;
};
/**
* Iterate over everything in the array at or before the given time.
* @param {Time} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachAfter = function(time, callback){
//iterate over the items in reverse so that removing an item doesn't break things
time = this.toSeconds(time);
var endIndex = this._search(time);
if (endIndex !== -1){
for (var i = this._timeline.length - 1; i > endIndex; i--){
callback(this._timeline[i]);
}
}
return this;
};
/**
* Iterate over everything in the array at the given time
* @param {Time} time The time to check if items are before
* @param {Function} callback The callback to invoke with every item
* @returns {Tone.Timeline} this
*/
Tone.Timeline.prototype.forEachAtTime = function(time, callback){
//iterate over the items in reverse so that removing an item doesn't break things
time = this.toSeconds(time);
var index = this._search(time);
if (index !== -1){
for (var i = index; i >= 0; i--){
var event = this._timeline[i];
if (event.time === time){
callback(event);
} else {
break;
}
}
}
return this;
};
/**
* Clean up.
* @return {Tone.Timeline} this
*/
Tone.Timeline.prototype.dispose = function(){
Tone.prototype.dispose.call(this);
this._timeline = null;
};
return Tone.Timeline;
});

View file

@ -1,16 +1,16 @@
define(["Tone/core/Tone", "Tone/core/Schedulable", "Tone/core/Types"], function (Tone) {
define(["Tone/core/Tone", "Tone/core/Timeline", "Tone/core/Types"], function (Tone) {
/**
* @class A Schedulable State. Provides the methods: <code>setStateAtTime("state", time)</code>
* @class A Timeline State. Provides the methods: <code>setStateAtTime("state", time)</code>
* and <code>getStateAtTime(time)</code>.
*
* @extends {Tone.Schedulable}
* @param {String} initial The initial state of the SchedulableState.
* @extends {Tone.Timeline}
* @param {String} initial The initial state of the TimelineState.
* Defaults to <code>undefined</code>
*/
Tone.SchedulableState = function(initial){
Tone.TimelineState = function(initial){
Tone.Schedulable.call(this);
Tone.Timeline.call(this);
/**
* The initial state
@ -20,7 +20,7 @@ define(["Tone/core/Tone", "Tone/core/Schedulable", "Tone/core/Types"], function
this._initial = initial;
};
Tone.extend(Tone.SchedulableState, Tone.Schedulable);
Tone.extend(Tone.TimelineState, Tone.Timeline);
/**
* Returns the scheduled state scheduled before or at
@ -28,7 +28,7 @@ define(["Tone/core/Tone", "Tone/core/Schedulable", "Tone/core/Types"], function
* @param {Time} time The time to query.
* @return {String} The name of the state input in setStateAtTime.
*/
Tone.SchedulableState.prototype.getStateAtTime = function(time){
Tone.TimelineState.prototype.getStateAtTime = function(time){
var event = this.getEvent(time);
if (event !== null){
return event.state;
@ -43,12 +43,12 @@ define(["Tone/core/Tone", "Tone/core/Schedulable", "Tone/core/Types"], function
* @param {String} state The name of the state to set.
* @param {Time} time The time to query.
*/
Tone.SchedulableState.prototype.setStateAtTime = function(state, time){
Tone.TimelineState.prototype.setStateAtTime = function(state, time){
this.addEvent({
"state" : state,
"time" : this.toSeconds(time)
});
};
return Tone.SchedulableState;
return Tone.TimelineState;
});

View file

@ -1,20 +1,20 @@
define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], function (Tone) {
define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Timeline"], function (Tone) {
/**
* @class A signal which adds the method _getValueAtTime.
* @class A signal which adds the method getValueAtTime.
* Code and inspiration from https://github.com/jsantell/web-audio-automation-timeline
*/
Tone.SchedulableSignal = function(){
Tone.TimelineSignal = function(){
//extend Tone.Signal
Tone.Signal.apply(this, arguments);
/**
* The scheduled events
* @type {Tone.Schedulable}
* @type {Tone.Timeline}
* @private
*/
this._events = new Tone.Schedulable();
this._events = new Tone.Timeline();
/**
* The initial scheduled value
@ -24,19 +24,36 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
this._initial = this._value.value;
};
Tone.extend(Tone.SchedulableSignal, Tone.Signal);
Tone.extend(Tone.TimelineSignal, Tone.Signal);
/**
* The event types of a schedulable signal.
* @enum {String}
*/
Tone.SchedulableSignal.Type = {
Tone.TimelineSignal.Type = {
Linear : "linear",
Exponential : "exponential",
Target : "target",
Set : "set"
};
/**
* The current value of the signal.
* @memberOf Tone.TimelineSignal#
* @type {Number}
* @name value
*/
Object.defineProperty(Tone.TimelineSignal.prototype, "value", {
get : function(){
return this._toUnits(this._value.value);
},
set : function(value){
var convertedVal = this._fromUnits(value);
this._initial = convertedVal;
this._value.value = convertedVal;
}
});
///////////////////////////////////////////////////////////////////////////
// SCHEDULING
///////////////////////////////////////////////////////////////////////////
@ -45,16 +62,16 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* Schedules a parameter value change at the given time.
* @param {*} value The value to set the signal.
* @param {Time} time The time when the change should occur.
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
* @example
* //set the frequency to "G4" in exactly 1 second from now.
* freq.setValueAtTime("G4", "+1");
*/
Tone.SchedulableSignal.prototype.setValueAtTime = function (value, startTime) {
Tone.TimelineSignal.prototype.setValueAtTime = function (value, startTime) {
value = this._fromUnits(value);
startTime = this.toSeconds(startTime);
this._events.addEvent({
"type" : Tone.SchedulableSignal.Type.Set,
"type" : Tone.TimelineSignal.Type.Set,
"value" : value,
"time" : startTime
});
@ -69,13 +86,13 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
*
* @param {number} value
* @param {Time} endTime
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.linearRampToValueAtTime = function (value, endTime) {
Tone.TimelineSignal.prototype.linearRampToValueAtTime = function (value, endTime) {
value = this._fromUnits(value);
endTime = this.toSeconds(endTime);
this._events.addEvent({
"type" : Tone.SchedulableSignal.Type.Linear,
"type" : Tone.TimelineSignal.Type.Linear,
"value" : value,
"time" : endTime
});
@ -89,14 +106,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
*
* @param {number} value
* @param {Time} endTime
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
Tone.TimelineSignal.prototype.exponentialRampToValueAtTime = function (value, endTime) {
value = this._fromUnits(value);
value = Math.max(this._minOutput, value);
endTime = this.toSeconds(endTime);
this._events.addEvent({
"type" : Tone.SchedulableSignal.Type.Exponential,
"type" : Tone.TimelineSignal.Type.Exponential,
"value" : value,
"time" : endTime
});
@ -110,14 +127,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* @param {number} value
* @param {Time} startTime
* @param {number} timeConstant
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
Tone.TimelineSignal.prototype.setTargetAtTime = function (value, startTime, timeConstant) {
value = this._fromUnits(value);
value = Math.max(this._minOutput, value);
startTime = this.toSeconds(startTime);
this._events.addEvent({
"type" : Tone.SchedulableSignal.Type.Target,
"type" : Tone.TimelineSignal.Type.Target,
"value" : value,
"time" : startTime,
"constant" : timeConstant
@ -131,9 +148,9 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* equal to startTime.
*
* @param {Time} startTime
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.cancelScheduledValues = function (after) {
Tone.TimelineSignal.prototype.cancelScheduledValues = function (after) {
this._events.clear(after);
Tone.Signal.prototype.cancelScheduledValues.apply(this, arguments);
return this;
@ -144,12 +161,12 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* a point from which a linear or exponential curve
* can be scheduled after.
* @param {Time} time When to set the ramp point
* @returns {Tone.SchedulableSignal} this
* @returns {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.setRampPoint = function (time) {
Tone.TimelineSignal.prototype.setRampPoint = function (time) {
time = this.toSeconds(time);
//get the value at the given time
var val = this._getValueAtTime(time);
var val = this.getValueAtTime(time);
this.setValueAtTime(val, time);
return this;
};
@ -161,7 +178,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* @param {Time} finish The ending anchor point by which the value of
* the signal will equal the given value.
*/
Tone.SchedulableSignal.prototype.linearRampToValueBetween = function (value, start, finish) {
Tone.TimelineSignal.prototype.linearRampToValueBetween = function (value, start, finish) {
this.setRampPoint(start);
this.linearRampToValueAtTime(value, finish);
return this;
@ -174,7 +191,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* @param {Time} finish The ending anchor point by which the value of
* the signal will equal the given value.
*/
Tone.SchedulableSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) {
Tone.TimelineSignal.prototype.exponentialRampToValueBetween = function (value, start, finish) {
this.setRampPoint(start);
this.exponentialRampToValueAtTime(value, finish);
return this;
@ -190,7 +207,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* @return {Object} The event at or before the given time.
* @private
*/
Tone.SchedulableSignal.prototype._searchBefore = function(time){
Tone.TimelineSignal.prototype._searchBefore = function(time){
return this._events.getEvent(time);
};
@ -199,7 +216,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* @param {Number} time The time to query.
* @return {Object} The next event after the given time
*/
Tone.SchedulableSignal.prototype._searchAfter = function(time){
Tone.TimelineSignal.prototype._searchAfter = function(time){
return this._events.getNextEvent(time);
};
@ -207,15 +224,14 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* Get the scheduled value at the given time.
* @param {Number} time The time in seconds.
* @return {Number} The scheduled value at the given time.
* @private
*/
Tone.SchedulableSignal.prototype._getValueAtTime = function(time){
Tone.TimelineSignal.prototype.getValueAtTime = function(time){
var after = this._searchAfter(time);
var before = this._searchBefore(time);
//if it was set by
if (before === null){
return this._initial;
} else if (before.type === Tone.SchedulableSignal.Type.Target){
} else if (before.type === Tone.TimelineSignal.Type.Target){
var previous = this._searchBefore(before.time - 0.0001);
var previouVal;
if (previous === null){
@ -226,9 +242,9 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
return this._exponentialApproach(before.time, previouVal, before.value, before.constant, time);
} else if (after === null){
return before.value;
} else if (after.type === Tone.SchedulableSignal.Type.Linear){
} else if (after.type === Tone.TimelineSignal.Type.Linear){
return this._linearInterpolate(before.time, before.value, after.time, after.value, time);
} else if (after.type === Tone.SchedulableSignal.Type.Exponential){
} else if (after.type === Tone.TimelineSignal.Type.Exponential){
return this._exponentialInterpolate(before.time, before.value, after.time, after.value, time);
} else {
return before.value;
@ -244,7 +260,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* Calculates the the value along the curve produced by setTargetAtTime
* @private
*/
Tone.SchedulableSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
Tone.TimelineSignal.prototype._exponentialApproach = function (t0, v0, v1, timeConstant, t) {
return v1 + (v0 - v1) * Math.exp(-(t - t0) / timeConstant);
};
@ -252,7 +268,7 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* Calculates the the value along the curve produced by linearRampToValueAtTime
* @private
*/
Tone.SchedulableSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
Tone.TimelineSignal.prototype._linearInterpolate = function (t0, v0, t1, v1, t) {
return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
};
@ -260,20 +276,20 @@ define(["Tone/core/Tone", "Tone/signal/Signal", "Tone/core/Schedulable"], functi
* Calculates the the value along the curve produced by exponentialRampToValueAtTime
* @private
*/
Tone.SchedulableSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
Tone.TimelineSignal.prototype._exponentialInterpolate = function (t0, v0, t1, v1, t) {
v0 = Math.max(this._minOutput, v0);
return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
};
/**
* Clean up.
* @return {Tone.SchedulableSignal} this
* @return {Tone.TimelineSignal} this
*/
Tone.SchedulableSignal.prototype.dispose = function(){
Tone.TimelineSignal.prototype.dispose = function(){
Tone.Signal.prototype.dispose.call(this);
this._events.dispose();
this._events = null;
};
return Tone.SchedulableSignal;
return Tone.TimelineSignal;
});