ability to get/set ticks at a given time. more accurate seconds counter using elapsed ticks.

This commit is contained in:
Yotam Mann 2017-12-27 16:23:39 -05:00
parent 60cdeaa5f3
commit 32d5451293
2 changed files with 282 additions and 9 deletions

View file

@ -58,9 +58,9 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
* The number of times the callback was invoked. Starts counting at 0
* and increments after the callback was invoked.
* @type {Ticks}
* @readOnly
* @private
*/
this.ticks = 0;
this._ticks = 0;
/**
* The state timeline
@ -69,6 +69,18 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
*/
this._state = new Tone.TimelineState(Tone.State.Stopped);
/**
* The offset values of the ticks
* @type {Tone.Timeline}
* @private
*/
this._tickOffset = new Tone.Timeline();
//add the first event
this._tickOffset.add({
"time" : 0,
"ticks" : 0
});
/**
* The loop function bound to its context.
* This is necessary to remove the event in the end.
@ -116,8 +128,11 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
Tone.Clock.prototype.start = function(time, offset){
time = this.toSeconds(time);
if (this._state.getValueAtTime(time) !== Tone.State.Started){
var ticksAtTime = Math.floor(this.getTicksAtTime(time));
offset = Tone.defaultArg(offset, ticksAtTime);
this._state.setStateAtTime(Tone.State.Started, time);
this._state.get(time).offset = offset;
this.setTicksAtTime(offset, time);
}
return this;
};
@ -133,6 +148,7 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
time = this.toSeconds(time);
this._state.cancel(time);
this._state.setStateAtTime(Tone.State.Stopped, time);
this.setTicksAtTime(0, time);
return this;
};
@ -144,11 +160,87 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
Tone.Clock.prototype.pause = function(time){
time = this.toSeconds(time);
if (this._state.getValueAtTime(time) === Tone.State.Started){
var pausedTicks = this.getTicksAtTime(time);
this.setTicksAtTime(pausedTicks, time);
this._state.setStateAtTime(Tone.State.Paused, time);
}
return this;
};
/**
* The number of times the callback was invoked. Starts counting at 0
* and increments after the callback was invoked.
* @type {Ticks}
*/
Object.defineProperty(Tone.Clock.prototype, "ticks", {
get : function(){
return this._ticks;
},
set : function(t){
this._ticks = t;
this.setTicksAtTime(t, this.now());
}
});
/**
* The time since ticks=0 that the Clock has been running. Accounts
* for tempo curves
* @type {Seconds}
*/
Object.defineProperty(Tone.Clock.prototype, "seconds", {
get : function(){
var time = this.now();
var ticks = this.getTicksAtTime(time);
var totalTicks = this.frequency.getTicksAtTime(time);
if (totalTicks - ticks > 0){
var tickTime = this.frequency.getTimeOfTick(totalTicks - ticks);
return this.frequency.ticksToTime(ticks, tickTime).toSeconds();
} else {
return this.frequency.ticksToTime(ticks, time).toSeconds();
}
},
set : function(s){
var now = this.now();
var ticks = this.frequency.timeToTicks(s, now-s);
this.setTicksAtTime(ticks, now);
}
});
/**
* Get the elapsed ticks at the given time
* @param {Time} time When to get the ticks
* @return {Ticks} The elapsed ticks at the given time.
*/
Tone.Clock.prototype.getTicksAtTime = function(time){
time = this.toSeconds(time);
var tickEvent = this._tickOffset.get(time);
var offset = tickEvent.ticks;
var elapsedTicks = this.frequency.getTicksAtTime(time) - tickEvent.position;
if (this._state.getValueAtTime(time) !== Tone.State.Started){
return offset;
} else {
return elapsedTicks + offset;
}
};
/**
* Set the clock's ticks at the given time.
* @param {Ticks} ticks The tick value to set
* @param {Time} time When to set the tick value
* @return {Tone.Clock} this
*/
Tone.Clock.prototype.setTicksAtTime = function(ticks, time){
time = this.toSeconds(time);
this._ticks = ticks;
this._tickOffset.cancel(time);
this._tickOffset.add({
"time" : time,
"ticks" : ticks,
"position" : this.frequency.getTicksAtTime(time)
});
return this;
};
/**
* The scheduling loop.
* @private
@ -167,14 +259,12 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
this._lastState = event.state;
switch(event.state){
case Tone.State.Started:
if (!Tone.isUndef(event.offset)){
this.ticks = event.offset;
}
this._ticks = event.offset;
this._nextTick = event.time;
this.emit("start", event.time, this.ticks);
this.emit("start", event.time, this._ticks);
break;
case Tone.State.Stopped:
this.ticks = 0;
this._ticks = 0;
this.emit("stop", event.time);
break;
case Tone.State.Paused:
@ -191,9 +281,9 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
if (event.state === Tone.State.Started){
try {
this.callback(tickTime);
this.ticks++;
this._ticks++;
} catch(e){
this.ticks++;
this._ticks++;
throw e;
}
}
@ -225,6 +315,8 @@ define(["Tone/core/Tone", "Tone/signal/TickSignal", "Tone/core/TimelineState",
this._writable("frequency");
this.frequency.dispose();
this.frequency = null;
this._tickOffset.dispose();
this._tickOffset = null;
this._boundLoop = null;
this._nextTick = Infinity;
this.callback = null;

View file

@ -190,6 +190,66 @@ define(["Test", "Tone/core/Clock", "helper/Offline", "helper/Supports"],
});
});
context("Seconds", function(){
it("can set the current seconds", function(){
return Offline(function(){
var clock = new Clock(function(){}, 10);
expect(clock.seconds).to.equal(0);
clock.seconds = 3;
expect(clock.seconds).to.be.closeTo(3, 0.01);
clock.dispose();
});
});
it("can get the seconds", function(){
return Offline(function(){
var clock = new Clock(function(){}, 10);
expect(clock.seconds).to.equal(0);
clock.start(0.05);
return function(time){
if (time > 0.05){
expect(clock.seconds).to.be.closeTo(time - 0.05, 0.01);
}
};
}, 0.1);
});
it("can get the seconds during a bpm ramp", function(){
return Offline(function(){
var clock = new Clock(function(){}, 10);
expect(clock.seconds).to.equal(0);
clock.start(0.05);
clock.frequency.linearRampTo(60, 0.5, 0.5)
return function(time){
if (time > 0.05){
expect(clock.seconds).to.be.closeTo(time - 0.05, 0.01);
}
};
}, 0.7);
});
it("can set seconds during a bpm ramp", function(){
return Offline(function(){
var clock = new Clock(function(){}, 10);
expect(clock.seconds).to.equal(0);
clock.start(0.05);
clock.frequency.linearRampTo(60, 0.5, 0.5)
var changeSeconds = Test.atTime(0.4, function(){
clock.seconds = 0
});
return function(time){
changeSeconds(time);
if (time > 0.05 && time < 0.4){
expect(clock.seconds).to.be.closeTo(time - 0.05, 0.01);
} else if (time > 0.4){
expect(clock.seconds).to.be.closeTo(time - 0.4, 0.01);
}
};
}, 0.7);
});
});
context("Ticks", function(){
it ("has 0 ticks when first created", function(){
@ -331,5 +391,126 @@ define(["Test", "Tone/core/Clock", "helper/Offline", "helper/Supports"],
});
});
context("[get,set]TicksAtTime", function(){
it ("always reports 0 if not started", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
expect(clock.getTicksAtTime(0)).to.equal(0);
expect(clock.getTicksAtTime(1)).to.equal(0);
expect(clock.getTicksAtTime(2)).to.equal(0);
clock.dispose();
});
});
it ("can get ticks in the future", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(1);
expect(clock.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(1.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(20, 0.01);
clock.dispose();
});
});
it ("pauses on last ticks", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0).pause(1);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(20, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(20, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(20, 0.01);
clock.dispose();
});
});
it ("resumes from paused position", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0).pause(1).start(2);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(20, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(20, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(40, 0.01);
expect(clock.getTicksAtTime(3.5)).to.be.closeTo(50, 0.01);
clock.dispose();
});
});
it ("can set a tick value at the given time", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0);
clock.setTicksAtTime(0, 1);
clock.setTicksAtTime(0, 2);
expect(clock.getTicksAtTime(0)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(1.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(2.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(20, 0.01);
clock.dispose();
});
});
it ("can get a tick position while the frequency is scheduled with setValueAtTime", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0);
clock.frequency.setValueAtTime(2, 1)
clock.setTicksAtTime(0, 1);
clock.setTicksAtTime(0, 2);
expect(clock.getTicksAtTime(0)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(1.5)).to.be.closeTo(1, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(2.5)).to.be.closeTo(1, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(2, 0.01);
clock.dispose();
});
});
it ("can get a tick position while the frequency is scheduled with linearRampTo", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0);
clock.frequency.linearRampTo(2, 1, 1)
clock.setTicksAtTime(0, 1);
clock.setTicksAtTime(10, 2);
expect(clock.getTicksAtTime(0)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(1.5)).to.be.closeTo(7.75, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(2.5)).to.be.closeTo(11, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(12, 0.01);
clock.dispose();
});
});
it ("can get a tick position while the frequency is scheduled with exponentialRampTo", function(){
return Offline(function(){
var clock = new Clock(function(){}, 20);
clock.start(0);
clock.frequency.exponentialRampTo(2, 1, 1)
clock.setTicksAtTime(0, 1);
clock.setTicksAtTime(10, 2);
expect(clock.getTicksAtTime(0)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(0.5)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
expect(clock.getTicksAtTime(1.5)).to.be.closeTo(5.96, 0.01);
expect(clock.getTicksAtTime(2)).to.be.closeTo(10, 0.01);
expect(clock.getTicksAtTime(2.5)).to.be.closeTo(11, 0.01);
expect(clock.getTicksAtTime(3)).to.be.closeTo(12, 0.01);
clock.dispose();
});
});
});
});
});