From f86639fc8eda151509f98c02db658c8117488296 Mon Sep 17 00:00:00 2001 From: Yotam Mann Date: Fri, 26 May 2017 16:45:30 -0400 Subject: [PATCH] Tone.TickSignal calculates elapsed ticks sums area under scheduling curve. approximates exponential and target curves using multiple linear ramps --- Tone/signal/TickSignal.js | 149 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 Tone/signal/TickSignal.js diff --git a/Tone/signal/TickSignal.js b/Tone/signal/TickSignal.js new file mode 100644 index 00000000..5047cbd0 --- /dev/null +++ b/Tone/signal/TickSignal.js @@ -0,0 +1,149 @@ +define(["Tone/core/Tone", "Tone/signal/TimelineSignal"], function (Tone) { + + /** + * @class Tone.TickSignal extends Tone.TimelineSignal, but adds the capability + * to calculate the number of elapsed ticks. exponential and target curves + * are approximated with multiple linear ramps. + * @param {Number} value The initial value of the signal + * @extends {Tone.TimelineSignal} + */ + Tone.TickSignal = function(value){ + + Tone.TimelineSignal.call(this, { + "units" : Tone.Type.Ticks, + "value" : value + }); + + //extend the memory + this._events.memory = Infinity; + }; + + Tone.extend(Tone.TickSignal, Tone.TimelineSignal); + + /** + * Wraps Tone.TimelineSignal 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); + event.ticks = Math.max(this.getTickAtTime(event.time - this.sampleTime), 0); + return this; + } + } + + Tone.TickSignal.prototype.setValueAtTime = _wrapScheduleMethods(Tone.TimelineSignal.prototype.setValueAtTime); + Tone.TickSignal.prototype.linearRampToValueAtTime = _wrapScheduleMethods(Tone.TimelineSignal.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); + //start from previously scheduled value + var prevEvent = this._events.get(time); + var segments = 5; + var segmentDur = constant; + for (var i = 0; i <= segments; i++){ + var segTime = segmentDur * i + time; + var rampVal = this._exponentialApproach(prevEvent.time, prevEvent.value, value, constant, segTime); + this.linearRampToValueAtTime(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); + //start from previously scheduled value + var prevEvent = this._events.get(time); + if (prevEvent === null){ + prevEvent = { + "value" : this._initial, + "time" : 0 + }; + } + var segments = 5; + 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(rampVal, segTime); + } + return this; + }; + + /** + * Calculates the number of ticks elapsed between the given interval + * @param {Number} time0 + * @param {Number} time1 + * @param {Tone.TimelineSignal.Type} curve + * @return {Ticks} + * @private + */ + Tone.TickSignal.prototype._getElapsedTicksBetween = function(time0, time1, curve){ + var val0 = this.getValueAtTime(time0); + var val1 = this.getValueAtTime(time1); + return 0.5 * (time1 - time0) * (val0 + val1); + }; + + /** + * 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.getTickAtTime = function(time){ + time = this.toSeconds(time); + var event = this._events.get(time); + if (event !== null){ + return this._getElapsedTicksBetween(event.time, time, event.type) + event.ticks; + } else { + //from the start + return this._getElapsedTicksBetween(0, time, Tone.TimelineSignal.Type.Set); + } + }; + + /** + * 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 !== null && after !== null && after.type === Tone.TimelineSignal.Type.Linear){ + return this._linearInterpolate(before.ticks, before.time, after.ticks, after.time, tick); + } else if (before !== null){ + if (before.value === 0){ + return Infinity; + } else { + return before.time + (tick - before.ticks) / before.value; + } + } else { + return tick / this._initial; + } + }; + + return Tone.TickSignal; +}); \ No newline at end of file