Tone.js/Tone/signal/TickSignal.js
2018-06-05 22:56:28 -04:00

228 lines
7.9 KiB
JavaScript

define(["Tone/core/Tone", "Tone/signal/Signal"], function(Tone){
/**
* @class Tone.TickSignal extends Tone.Signal, but adds the capability
* to calculate the number of elapsed ticks. exponential and target curves
* are approximated with multiple linear ramps.
*
* Thank you Bruno Dias, H. Sofia Pinto, and David M. Matos, for your [WAC paper](https://smartech.gatech.edu/bitstream/handle/1853/54588/WAC2016-49.pdf)
* describing integrating timing functions for tempo calculations.
*
* @param {Number} value The initial value of the signal
* @extends {Tone.Signal}
*/
Tone.TickSignal = function(value){
value = Tone.defaultArg(value, 1);
Tone.Signal.call(this, {
"units" : Tone.Type.Ticks,
"value" : value
});
//extend the memory
this._events.memory = Infinity;
//clear the clock from the beginning
this.cancelScheduledValues(0);
//set an initial event
this._events.add({
"type" : Tone.Param.AutomationType.SetValue,
"time" : 0,
"value" : value
});
};
Tone.extend(Tone.TickSignal, Tone.Signal);
/**
* Wraps Tone.Signal methods so that they also
* record the ticks.
* @param {Function} method
* @return {Function}
* @private
*/
function _wrapScheduleMethods(method){
return function(value, time){
time = this.toSeconds(time);
method.apply(this, arguments);
var event = this._events.get(time);
var previousEvent = this._events.previousEvent(event);
var ticksUntilTime = this._getTicksUntilEvent(previousEvent, time);
event.ticks = Math.max(ticksUntilTime, 0);
return this;
};
}
Tone.TickSignal.prototype.setValueAtTime = _wrapScheduleMethods(Tone.Signal.prototype.setValueAtTime);
Tone.TickSignal.prototype.linearRampToValueAtTime = _wrapScheduleMethods(Tone.Signal.prototype.linearRampToValueAtTime);
/**
* Start exponentially approaching the target value at the given time with
* a rate having the given time constant.
* @param {number} value
* @param {Time} startTime
* @param {number} timeConstant
* @returns {Tone.TickSignal} this
*/
Tone.TickSignal.prototype.setTargetAtTime = function(value, time, constant){
//aproximate it with multiple linear ramps
time = this.toSeconds(time);
this.setRampPoint(time);
value = this._fromUnits(value);
//start from previously scheduled value
var prevEvent = this._events.get(time);
var segments = Math.round(Math.max(1 / constant, 1));
for (var i = 0; i <= segments; i++){
var segTime = constant * i + time;
var rampVal = this._exponentialApproach(prevEvent.time, prevEvent.value, value, constant, segTime);
this.linearRampToValueAtTime(this._toUnits(rampVal), segTime);
}
return this;
};
/**
* Schedules an exponential continuous change in parameter value from
* the previous scheduled parameter value to the given value.
* @param {number} value
* @param {Time} endTime
* @returns {Tone.TickSignal} this
*/
Tone.TickSignal.prototype.exponentialRampToValueAtTime = function(value, time){
//aproximate it with multiple linear ramps
time = this.toSeconds(time);
value = this._fromUnits(value);
//start from previously scheduled value
var prevEvent = this._events.get(time);
if (prevEvent === null){
prevEvent = {
"value" : this._initialValue,
"time" : 0
};
}
//approx 10 segments per second
var segments = Math.round(Math.max((time - prevEvent.time)*10, 1));
var segmentDur = ((time - prevEvent.time)/segments);
for (var i = 0; i <= segments; i++){
var segTime = segmentDur * i + prevEvent.time;
var rampVal = this._exponentialInterpolate(prevEvent.time, prevEvent.value, time, value, segTime);
this.linearRampToValueAtTime(this._toUnits(rampVal), segTime);
}
return this;
};
/**
* Returns the tick value at the time. Takes into account
* any automation curves scheduled on the signal.
* @private
* @param {Time} time The time to get the tick count at
* @return {Ticks} The number of ticks which have elapsed at the time
* given any automations.
*/
Tone.TickSignal.prototype._getTicksUntilEvent = function(event, time){
if (event === null){
event = {
"ticks" : 0,
"time" : 0
};
} else if (Tone.isUndef(event.ticks)){
var previousEvent = this._events.previousEvent(event);
event.ticks = this._getTicksUntilEvent(previousEvent, event.time);
}
var val0 = this.getValueAtTime(event.time);
var val1 = this.getValueAtTime(time);
//if it's right on the line, take the previous value
if (this._events.get(time).time === time && this._events.get(time).type === Tone.Param.AutomationType.SetValue){
val1 = this.getValueAtTime(time - this.sampleTime);
}
return 0.5 * (time - event.time) * (val0 + val1) + event.ticks;
};
/**
* Returns the tick value at the time. Takes into account
* any automation curves scheduled on the signal.
* @param {Time} time The time to get the tick count at
* @return {Ticks} The number of ticks which have elapsed at the time
* given any automations.
*/
Tone.TickSignal.prototype.getTicksAtTime = function(time){
time = this.toSeconds(time);
var event = this._events.get(time);
return Math.max(this._getTicksUntilEvent(event, time), 0);
};
/**
* Return the elapsed time of the number of ticks from the given time
* @param {Ticks} ticks The number of ticks to calculate
* @param {Time} time The time to get the next tick from
* @return {Seconds} The duration of the number of ticks from the given time in seconds
*/
Tone.TickSignal.prototype.getDurationOfTicks = function(ticks, time){
time = this.toSeconds(time);
var currentTick = this.getTicksAtTime(time);
return this.getTimeOfTick(currentTick + ticks) - time;
};
/**
* Given a tick, returns the time that tick occurs at.
* @param {Ticks} tick
* @return {Time} The time that the tick occurs.
*/
Tone.TickSignal.prototype.getTimeOfTick = function(tick){
var before = this._events.get(tick, "ticks");
var after = this._events.getAfter(tick, "ticks");
if (before && before.ticks === tick){
return before.time;
} else if (before && after &&
after.type === Tone.Param.AutomationType.Linear &&
before.value !== after.value){
var val0 = this.getValueAtTime(before.time);
var val1 = this.getValueAtTime(after.time);
var delta = (val1 - val0) / (after.time - before.time);
var k = Math.sqrt(Math.pow(val0, 2) - 2 * delta * (before.ticks - tick));
var sol1 = (-val0 + k) / delta;
var sol2 = (-val0 - k) / delta;
return (sol1 > 0 ? sol1 : sol2) + before.time;
} else if (before){
if (before.value === 0){
return Infinity;
} else {
return before.time + (tick - before.ticks) / before.value;
}
} else {
return tick / this._initialValue;
}
};
/**
* Convert some number of ticks their the duration in seconds accounting
* for any automation curves starting at the given time.
* @param {Ticks} ticks The number of ticks to convert to seconds.
* @param {Time} [when=now] When along the automation timeline to convert the ticks.
* @return {Tone.Time} The duration in seconds of the ticks.
*/
Tone.TickSignal.prototype.ticksToTime = function(ticks, when){
when = this.toSeconds(when);
return new Tone.Time(this.getDurationOfTicks(ticks, when));
};
/**
* The inverse of [ticksToTime](#tickstotime). Convert a duration in
* seconds to the corresponding number of ticks accounting for any
* automation curves starting at the given time.
* @param {Time} duration The time interval to convert to ticks.
* @param {Time} [when=now] When along the automation timeline to convert the ticks.
* @return {Tone.Ticks} The duration in ticks.
*/
Tone.TickSignal.prototype.timeToTicks = function(duration, when){
when = this.toSeconds(when);
duration = this.toSeconds(duration);
var startTicks = this.getTicksAtTime(when);
var endTicks = this.getTicksAtTime(when + duration);
return new Tone.Ticks(endTicks - startTicks);
};
return Tone.TickSignal;
});