tone 15.5.48 → 15.5.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Tone/core/clock/Clock.test.ts +29 -0
- package/Tone/core/clock/TickSource.test.ts +13 -0
- package/Tone/core/util/Timeline.test.ts +56 -0
- package/Tone/core/util/Timeline.ts +22 -10
- package/Tone/source/Source.test.ts +31 -0
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/core/util/Timeline.js +19 -10
- package/build/esm/core/util/Timeline.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -413,6 +413,35 @@ describe("Clock", () => {
|
|
|
413
413
|
// the post-restart ticks from offset 40
|
|
414
414
|
expect(observedTicks).to.deep.equal([0, 1, 2, 40, 41]);
|
|
415
415
|
});
|
|
416
|
+
|
|
417
|
+
it("emits the stop event when stop and the following start happen at the same time within the stale window", async () => {
|
|
418
|
+
const events: Array<[string, number]> = [];
|
|
419
|
+
let restartTime = 0;
|
|
420
|
+
await Offline(() => {
|
|
421
|
+
const clock = new Clock(noOp, 200);
|
|
422
|
+
const boundLoop = (
|
|
423
|
+
clock as unknown as { _boundLoop: () => void }
|
|
424
|
+
)._boundLoop;
|
|
425
|
+
clock.on("stop", (time) => events.push(["stop", time]));
|
|
426
|
+
clock.on("start", (time) => events.push(["start", time]));
|
|
427
|
+
clock.start(0);
|
|
428
|
+
// Same staleness simulation as above. stop() and start() are
|
|
429
|
+
// scheduled at the same instant, as happens when both default
|
|
430
|
+
// to now() within one task: `transport.stop(); transport.start()`
|
|
431
|
+
clock.context.off("tick", boundLoop);
|
|
432
|
+
return atTime(0.02, (time) => {
|
|
433
|
+
restartTime = time;
|
|
434
|
+
clock.stop(time);
|
|
435
|
+
clock.start(time, 40);
|
|
436
|
+
clock.context.on("tick", boundLoop);
|
|
437
|
+
});
|
|
438
|
+
}, 0.03);
|
|
439
|
+
expect(events).to.deep.equal([
|
|
440
|
+
["start", 0],
|
|
441
|
+
["stop", restartTime],
|
|
442
|
+
["start", restartTime],
|
|
443
|
+
]);
|
|
444
|
+
});
|
|
416
445
|
});
|
|
417
446
|
|
|
418
447
|
context("Events", () => {
|
|
@@ -76,6 +76,19 @@ describe("TickSource", () => {
|
|
|
76
76
|
}, 0.5);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
+
it("reports the restart offset just after a stop and start at the same time", () => {
|
|
80
|
+
const source = new TickSource(3);
|
|
81
|
+
source.start(0);
|
|
82
|
+
source.stop(1);
|
|
83
|
+
source.start(1, 10);
|
|
84
|
+
expect(source.getTicksAtTime(1)).to.be.closeTo(10, 0.01);
|
|
85
|
+
// the first tick time of the restarted period is computed from the
|
|
86
|
+
// frequency integral and can land a rounding error after the start
|
|
87
|
+
expect(source.getTicksAtTime(1 + 1e-12)).to.be.closeTo(10, 0.01);
|
|
88
|
+
expect(source.getTicksAtTime(1.5)).to.be.closeTo(11.5, 0.01);
|
|
89
|
+
source.dispose();
|
|
90
|
+
});
|
|
91
|
+
|
|
79
92
|
it("ticks restart at 0 when started after stop", () => {
|
|
80
93
|
const source = new TickSource(3);
|
|
81
94
|
source.start(0).stop(1).start(2);
|
|
@@ -604,5 +604,61 @@ describe("Timeline", () => {
|
|
|
604
604
|
expect(count).to.equal(0);
|
|
605
605
|
sched.dispose();
|
|
606
606
|
});
|
|
607
|
+
|
|
608
|
+
it("includes every event at the start time and excludes every event at the end time", () => {
|
|
609
|
+
const sched = new Timeline<{ time: number; id: string }>();
|
|
610
|
+
sched.add({ time: 0.1, id: "a" });
|
|
611
|
+
sched.add({ time: 0.2, id: "b" });
|
|
612
|
+
sched.add({ time: 0.2, id: "c" });
|
|
613
|
+
sched.add({ time: 0.3, id: "d" });
|
|
614
|
+
sched.add({ time: 0.3, id: "e" });
|
|
615
|
+
const visited = (start: number, end: number): string[] => {
|
|
616
|
+
const ids: string[] = [];
|
|
617
|
+
sched.forEachBetween(start, end, (event) => ids.push(event.id));
|
|
618
|
+
return ids;
|
|
619
|
+
};
|
|
620
|
+
expect(visited(0.1, 0.2)).to.deep.equal(["a"]);
|
|
621
|
+
expect(visited(0.2, 0.3)).to.deep.equal(["b", "c"]);
|
|
622
|
+
expect(visited(0.3, 0.4)).to.deep.equal(["d", "e"]);
|
|
623
|
+
expect(visited(0.15, 0.25)).to.deep.equal(["b", "c"]);
|
|
624
|
+
sched.dispose();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("visits an event added at the boundary after the previous range was iterated", () => {
|
|
628
|
+
// two consecutive ranges [0.1, 0.2) and [0.2, 0.4); an event at 0.2
|
|
629
|
+
// is added between the two iterations, after another event at 0.2
|
|
630
|
+
const sched = new Timeline<{ time: number; id: string }>();
|
|
631
|
+
sched.add({ time: 0.1, id: "a" });
|
|
632
|
+
sched.add({ time: 0.2, id: "b" });
|
|
633
|
+
const ids: string[] = [];
|
|
634
|
+
sched.forEachBetween(0.1, 0.2, (event) => ids.push(event.id));
|
|
635
|
+
sched.add({ time: 0.2, id: "c" });
|
|
636
|
+
sched.forEachBetween(0.2, 0.4, (event) => ids.push(event.id));
|
|
637
|
+
expect(ids).to.deep.equal(["a", "b", "c"]);
|
|
638
|
+
sched.dispose();
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("includes events within rounding error of the start time", () => {
|
|
642
|
+
// _search compares times with an epsilon; the range bounds must too
|
|
643
|
+
const sched = new Timeline<{ time: number; id: string }>();
|
|
644
|
+
sched.add({ time: 0.1, id: "a" });
|
|
645
|
+
sched.add({ time: 0.2, id: "b" });
|
|
646
|
+
sched.add({ time: 0.2 + 1e-12, id: "c" });
|
|
647
|
+
const ids: string[] = [];
|
|
648
|
+
sched.forEachBetween(0.2, 0.3, (event) => ids.push(event.id));
|
|
649
|
+
expect(ids).to.deep.equal(["b", "c"]);
|
|
650
|
+
sched.dispose();
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it("excludes the end time when the start time is before the first event", () => {
|
|
654
|
+
const sched = new Timeline<{ time: number; id: string }>();
|
|
655
|
+
sched.add({ time: 0.1, id: "a" });
|
|
656
|
+
sched.add({ time: 0.2, id: "b" });
|
|
657
|
+
sched.add({ time: 0.2, id: "c" });
|
|
658
|
+
const ids: string[] = [];
|
|
659
|
+
sched.forEachBetween(0, 0.2, (event) => ids.push(event.id));
|
|
660
|
+
expect(ids).to.deep.equal(["a"]);
|
|
661
|
+
sched.dispose();
|
|
662
|
+
});
|
|
607
663
|
});
|
|
608
664
|
});
|
|
@@ -387,20 +387,32 @@ export class Timeline<GenericEvent extends TimelineEvent> extends Tone {
|
|
|
387
387
|
endTime: number,
|
|
388
388
|
callback: (event: GenericEvent) => void
|
|
389
389
|
): this {
|
|
390
|
+
// _search compares times with an epsilon, so do the same here
|
|
390
391
|
let lowerBound = this._search(startTime);
|
|
391
392
|
let upperBound = this._search(endTime);
|
|
392
|
-
if (lowerBound
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
if (lowerBound === -1) {
|
|
394
|
+
// every event is after the start time
|
|
395
|
+
lowerBound = 0;
|
|
396
|
+
} else if (EQ(this._timeline[lowerBound].time, startTime)) {
|
|
397
|
+
// _search returns the last event at the start time,
|
|
398
|
+
// include every event at the start time
|
|
399
|
+
while (
|
|
400
|
+
lowerBound > 0 &&
|
|
401
|
+
EQ(this._timeline[lowerBound - 1].time, startTime)
|
|
402
|
+
) {
|
|
403
|
+
lowerBound -= 1;
|
|
395
404
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
this.
|
|
405
|
+
} else {
|
|
406
|
+
lowerBound += 1;
|
|
407
|
+
}
|
|
408
|
+
// exclusive of the end time
|
|
409
|
+
while (
|
|
410
|
+
upperBound >= 0 &&
|
|
411
|
+
EQ(this._timeline[upperBound].time, endTime)
|
|
412
|
+
) {
|
|
413
|
+
upperBound -= 1;
|
|
403
414
|
}
|
|
415
|
+
this._iterate(callback, lowerBound, upperBound);
|
|
404
416
|
return this;
|
|
405
417
|
}
|
|
406
418
|
|
|
@@ -330,6 +330,37 @@ describe("Source", () => {
|
|
|
330
330
|
expect(output.getValueAtTime(0.5)).to.be.equal(0);
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
+
it("stops the playing source when the transport is stopped and restarted at the same time", async () => {
|
|
334
|
+
const output = await Offline(({ transport }) => {
|
|
335
|
+
const source = new Player(rampBuffer).toDestination();
|
|
336
|
+
source.sync().start(0);
|
|
337
|
+
const clock = (
|
|
338
|
+
transport as unknown as {
|
|
339
|
+
_clock: {
|
|
340
|
+
context: typeof transport.context;
|
|
341
|
+
_boundLoop: () => void;
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
)._clock;
|
|
345
|
+
transport.start(0);
|
|
346
|
+
// hold the clock loop back so that stop/start land ahead of
|
|
347
|
+
// _lastUpdate, as they do in a realtime context where both
|
|
348
|
+
// default to now() = currentTime + lookAhead
|
|
349
|
+
clock.context.off("tick", clock._boundLoop);
|
|
350
|
+
return atTime(0.2, (time) => {
|
|
351
|
+
transport.stop(time);
|
|
352
|
+
transport.start(time, 0.5);
|
|
353
|
+
clock.context.on("tick", clock._boundLoop);
|
|
354
|
+
});
|
|
355
|
+
}, 0.5);
|
|
356
|
+
// before the restart the ramp plays from 0
|
|
357
|
+
expect(output.getValueAtTime(0.1)).to.be.closeTo(0.1, 0.01);
|
|
358
|
+
// after the restart only the source started at offset 0.5 plays;
|
|
359
|
+
// if the first source were orphaned the two ramps would sum
|
|
360
|
+
expect(output.getValueAtTime(0.3)).to.be.closeTo(0.6, 0.01);
|
|
361
|
+
expect(output.getValueAtTime(0.4)).to.be.closeTo(0.7, 0.01);
|
|
362
|
+
});
|
|
363
|
+
|
|
333
364
|
it("gives the correct offset on time on start/stop events when started with an offset", async () => {
|
|
334
365
|
const output = await Offline(({ transport }) => {
|
|
335
366
|
const source = new Player(rampBuffer).toDestination();
|
package/Tone/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "15.5.
|
|
1
|
+
export const version: string = "15.5.49";
|