tone 15.5.18 → 15.5.19
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/TickSource.test.ts +59 -0
- package/Tone/core/clock/TickSource.ts +17 -7
- package/Tone/core/clock/Transport.test.ts +14 -0
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/core/clock/TickSource.js +14 -5
- package/build/esm/core/clock/TickSource.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -544,6 +544,65 @@ describe("TickSource", () => {
|
|
|
544
544
|
});
|
|
545
545
|
source.dispose();
|
|
546
546
|
});
|
|
547
|
+
|
|
548
|
+
it("does not fire duplicate ticks across consecutive windows at a tick boundary", () => {
|
|
549
|
+
// Reproduces the double-trigger bug from issues #1080/#1098/#1175.
|
|
550
|
+
// At 400 ticks/sec (equivalent to BPM=125, PPQ=192), tick 7680 falls
|
|
551
|
+
// at exactly t=19.2s.
|
|
552
|
+
const source = new TickSource(400);
|
|
553
|
+
source.start(0);
|
|
554
|
+
|
|
555
|
+
const firedTicks: number[] = [];
|
|
556
|
+
// Window 1 ends just above the tick-7680 boundary
|
|
557
|
+
source.forEachTickBetween(
|
|
558
|
+
19.19709750566994,
|
|
559
|
+
19.20000000000101,
|
|
560
|
+
(_, tick) => firedTicks.push(tick)
|
|
561
|
+
);
|
|
562
|
+
// Window 2 starts at exactly that same boundary
|
|
563
|
+
source.forEachTickBetween(
|
|
564
|
+
19.20000000000101,
|
|
565
|
+
19.202902494332076,
|
|
566
|
+
(_, tick) => firedTicks.push(tick)
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
const occurrences = firedTicks.filter((t) => t === 7680).length;
|
|
570
|
+
expect(occurrences).to.equal(
|
|
571
|
+
1,
|
|
572
|
+
`tick 7680 should fire exactly once, but fired ${occurrences} times`
|
|
573
|
+
);
|
|
574
|
+
source.dispose();
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("fires every tick exactly once across many consecutive windows", () => {
|
|
578
|
+
// At 2 Hz, ticks fall at 0, 0.5, 1.0, 1.5, 2.0.
|
|
579
|
+
// Window boundaries are placed just above each tick time (tick + eps)
|
|
580
|
+
// so every tick lands right at the end of one window and the start of the next.
|
|
581
|
+
const source = new TickSource(2);
|
|
582
|
+
source.start(0);
|
|
583
|
+
|
|
584
|
+
const tickCount = new Map<number, number>();
|
|
585
|
+
const record = (_: number, tick: number) => {
|
|
586
|
+
tickCount.set(tick, (tickCount.get(tick) ?? 0) + 1);
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// Non-overlapping consecutive windows whose boundaries sit just above
|
|
590
|
+
// the tick times (0, 0.5, 1.0, 1.5, 2.0).
|
|
591
|
+
const eps = 1e-10;
|
|
592
|
+
source.forEachTickBetween(0, 0.5 + eps, record); // tick 0 (at 0.0) and tick 1 (at 0.5)
|
|
593
|
+
source.forEachTickBetween(0.5 + eps, 1.0 + eps, record); // tick 2 (at 1.0)
|
|
594
|
+
source.forEachTickBetween(1.0 + eps, 1.5 + eps, record); // tick 3 (at 1.5)
|
|
595
|
+
source.forEachTickBetween(1.5 + eps, 2.0 + eps, record); // tick 4 (at 2.0)
|
|
596
|
+
source.forEachTickBetween(2.0 + eps, 2.5, record); // tick 5 (at 2.5, not included)
|
|
597
|
+
|
|
598
|
+
for (const [tick, count] of tickCount) {
|
|
599
|
+
expect(count).to.equal(
|
|
600
|
+
1,
|
|
601
|
+
`tick ${tick} fired ${count} times instead of once`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
source.dispose();
|
|
605
|
+
});
|
|
547
606
|
});
|
|
548
607
|
|
|
549
608
|
context("Seconds", () => {
|
|
@@ -415,18 +415,28 @@ export class TickSource<
|
|
|
415
415
|
|
|
416
416
|
if (lastStateEvent && lastStateEvent.state === "started") {
|
|
417
417
|
const maxStartTime = Math.max(lastStateEvent.time, startTime);
|
|
418
|
-
//
|
|
418
|
+
// Figure out how far past the last whole-tick boundary maxStartTime
|
|
419
|
+
// sits, so we can compute the time of the next tick at or after it.
|
|
419
420
|
const startTicks = this.frequency.getTicksAtTime(maxStartTime);
|
|
420
421
|
const ticksAtStart = this.frequency.getTicksAtTime(
|
|
421
422
|
lastStateEvent.time
|
|
422
423
|
);
|
|
423
424
|
const diff = startTicks - ticksAtStart;
|
|
424
|
-
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
startTicks
|
|
429
|
-
|
|
425
|
+
const offset = Math.ceil(diff) - diff;
|
|
426
|
+
// Guard against floating-point issues: when startTicks is just barely
|
|
427
|
+
// above an integer tick boundary (offset ≈ 1), snap back to that integer
|
|
428
|
+
const firstTick = EQ(offset, 1)
|
|
429
|
+
? Math.floor(startTicks)
|
|
430
|
+
: startTicks + offset;
|
|
431
|
+
let nextTickTime = this.frequency.getTimeOfTick(firstTick);
|
|
432
|
+
// Advance past any ticks that land before the start of this window
|
|
433
|
+
// to avoid any tick that was already processed.
|
|
434
|
+
if (nextTickTime < maxStartTime) {
|
|
435
|
+
nextTickTime += this.frequency.getDurationOfTicks(
|
|
436
|
+
1,
|
|
437
|
+
nextTickTime
|
|
438
|
+
);
|
|
439
|
+
}
|
|
430
440
|
while (nextTickTime < endTime) {
|
|
431
441
|
try {
|
|
432
442
|
callback(
|
|
@@ -75,6 +75,20 @@ describe("Transport", () => {
|
|
|
75
75
|
expect(invocations).to.equal(5);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
it("does not double-fire events when looping many times", async () => {
|
|
79
|
+
let invocations = 0;
|
|
80
|
+
await Offline((context) => {
|
|
81
|
+
const transport = new TransportInstance({ context });
|
|
82
|
+
transport.schedule(() => {
|
|
83
|
+
invocations++;
|
|
84
|
+
}, 0);
|
|
85
|
+
transport.setLoopPoints(0, 1).start(0);
|
|
86
|
+
transport.loop = true;
|
|
87
|
+
}, 120.1);
|
|
88
|
+
// Should fire exactly 121 times: at t=0,1,2,...,120
|
|
89
|
+
expect(invocations).to.equal(121);
|
|
90
|
+
});
|
|
91
|
+
|
|
78
92
|
it("jumps to the loopStart after the loopEnd point", async () => {
|
|
79
93
|
let looped = false;
|
|
80
94
|
await Offline((context) => {
|
package/Tone/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "15.5.
|
|
1
|
+
export const version: string = "15.5.19";
|