tone 15.5.19 → 15.5.21

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.
@@ -0,0 +1,19 @@
1
+ import { expect } from "chai";
2
+
3
+ import { Context } from "./context/Context.js";
4
+ import { getContext, setContext } from "./Global.js";
5
+
6
+ describe("Global", () => {
7
+ it("can setContext with disposeOld = true", () => {
8
+ const origContext = getContext();
9
+ const toneContext = new Context();
10
+
11
+ setContext(toneContext);
12
+ expect(getContext()).to.equal(toneContext);
13
+
14
+ setContext(origContext, true);
15
+
16
+ expect(getContext()).to.equal(origContext);
17
+ expect(toneContext.disposed).to.be.true;
18
+ });
19
+ });
@@ -1,39 +1,18 @@
1
1
  import { version } from "../version.js";
2
- import {
3
- AnyAudioContext,
4
- hasAudioContext,
5
- theWindow,
6
- } from "./context/AudioContext.js";
2
+ import { AnyAudioContext, theWindow } from "./context/AudioContext.js";
7
3
  import { BaseContext } from "./context/BaseContext.js";
8
4
  import { Context } from "./context/Context.js";
9
- import { DummyContext } from "./context/DummyContext.js";
5
+ import {
6
+ getContext,
7
+ setContext as _setContext,
8
+ } from "./context/GlobalContext.js";
10
9
  import { OfflineContext } from "./context/OfflineContext.js";
11
10
  import {
12
11
  isAudioContext,
13
12
  isOfflineAudioContext,
14
13
  } from "./util/AdvancedTypeCheck.js";
15
14
 
16
- /**
17
- * This dummy context is used to avoid throwing immediate errors when importing in Node.js
18
- */
19
- const dummyContext = new DummyContext();
20
-
21
- /**
22
- * The global audio context which is getable and assignable through
23
- * getContext and setContext
24
- */
25
- let globalContext: BaseContext = dummyContext;
26
-
27
- /**
28
- * Returns the default system-wide {@link Context}
29
- * @category Core
30
- */
31
- export function getContext(): BaseContext {
32
- if (globalContext === dummyContext && hasAudioContext) {
33
- setContext(new Context());
34
- }
35
- return globalContext;
36
- }
15
+ export { getContext };
37
16
 
38
17
  /**
39
18
  * Set the default audio context
@@ -46,15 +25,15 @@ export function setContext(
46
25
  disposeOld = false
47
26
  ): void {
48
27
  if (disposeOld) {
49
- globalContext.dispose();
28
+ getContext().dispose();
50
29
  }
51
30
 
52
31
  if (isAudioContext(context)) {
53
- globalContext = new Context(context);
32
+ _setContext(new Context(context));
54
33
  } else if (isOfflineAudioContext(context)) {
55
- globalContext = new OfflineContext(context);
34
+ _setContext(new OfflineContext(context));
56
35
  } else {
57
- globalContext = context;
36
+ _setContext(context);
58
37
  }
59
38
  }
60
39
 
@@ -72,7 +51,7 @@ export function setContext(
72
51
  * @category Core
73
52
  */
74
53
  export function start(): Promise<void> {
75
- return globalContext.resume();
54
+ return getContext().resume();
76
55
  }
77
56
 
78
57
  /**
@@ -221,6 +221,16 @@ describe("Context", () => {
221
221
  }, 0.05);
222
222
  });
223
223
 
224
+ it("can efficiently add and remove 10k timeouts", () => {
225
+ const ids: number[] = [];
226
+ for (let i = 0; i < 10_000; i++) {
227
+ ids.push(ctx.setTimeout(() => {}, i * 0.001));
228
+ }
229
+ for (const id of ids) {
230
+ ctx.clearTimeout(id);
231
+ }
232
+ }).timeout(100);
233
+
224
234
  it("is robust against altering the timeline within the callback fn", (done) => {
225
235
  let invokeCount = 0;
226
236
  function checkDone(id: number) {
@@ -64,6 +64,11 @@ export class Context extends BaseContext {
64
64
  */
65
65
  private _timeouts: Timeline<ContextTimeoutEvent> = new Timeline();
66
66
 
67
+ /**
68
+ * A Map from timeout ID to event for O(1) lookup in clearTimeout.
69
+ */
70
+ private _timeoutMap = new Map<number, ContextTimeoutEvent>();
71
+
67
72
  /**
68
73
  * The timeout id counter
69
74
  */
@@ -554,6 +559,7 @@ export class Context extends BaseContext {
554
559
  super.dispose();
555
560
  this._ticker.dispose();
556
561
  this._timeouts.dispose();
562
+ this._timeoutMap.clear();
557
563
  Object.keys(this._constants).map((val) =>
558
564
  this._constants[val].disconnect()
559
565
  );
@@ -565,6 +571,26 @@ export class Context extends BaseContext {
565
571
  // TIMEOUTS
566
572
  //---------------------------
567
573
 
574
+ /**
575
+ * Add a timeout event to the timeline and map.
576
+ */
577
+ private _addTimeoutEvent(event: ContextTimeoutEvent): void {
578
+ this._timeouts.add(event);
579
+ this._timeoutMap.set(event.id, event);
580
+ }
581
+
582
+ /**
583
+ * Remove a timeout event from the timeline and map.
584
+ */
585
+ private _removeTimeoutEvent(event: ContextTimeoutEvent): void {
586
+ this._timeouts.remove(event);
587
+ // Skips the map deletion in setInterval case where the same id
588
+ // is reused for the next event.
589
+ if (this._timeoutMap.get(event.id) === event) {
590
+ this._timeoutMap.delete(event.id);
591
+ }
592
+ }
593
+
568
594
  /**
569
595
  * The private loop which keeps track of the context scheduled timeouts
570
596
  * Is invoked from the clock source
@@ -575,7 +601,7 @@ export class Context extends BaseContext {
575
601
  try {
576
602
  event.callback();
577
603
  } finally {
578
- this._timeouts.remove(event);
604
+ this._removeTimeoutEvent(event);
579
605
  }
580
606
  });
581
607
  }
@@ -592,7 +618,7 @@ export class Context extends BaseContext {
592
618
  setTimeout(fn: (...args: any[]) => void, timeout: Seconds): number {
593
619
  this._timeoutIds++;
594
620
  const now = this.now();
595
- this._timeouts.add({
621
+ this._addTimeoutEvent({
596
622
  callback: fn,
597
623
  id: this._timeoutIds,
598
624
  time: now + timeout,
@@ -605,11 +631,10 @@ export class Context extends BaseContext {
605
631
  * @param id The ID returned from {@link setTimeout}.
606
632
  */
607
633
  clearTimeout(id: number): this {
608
- this._timeouts.forEach((event) => {
609
- if (event.id === id) {
610
- this._timeouts.remove(event);
611
- }
612
- });
634
+ const event = this._timeoutMap.get(id);
635
+ if (event) {
636
+ this._removeTimeoutEvent(event);
637
+ }
613
638
  return this;
614
639
  }
615
640
 
@@ -631,7 +656,7 @@ export class Context extends BaseContext {
631
656
  const id = ++this._timeoutIds;
632
657
  const intervalFn = () => {
633
658
  const now = this.now();
634
- this._timeouts.add({
659
+ this._addTimeoutEvent({
635
660
  callback: () => {
636
661
  // invoke the callback
637
662
  fn();
@@ -0,0 +1,34 @@
1
+ import { hasAudioContext } from "./AudioContext.js";
2
+ import { BaseContext } from "./BaseContext.js";
3
+ import { Context } from "./Context.js";
4
+ import { DummyContext } from "./DummyContext.js";
5
+
6
+ /**
7
+ * This dummy context is used to avoid throwing immediate errors when importing in Node.js
8
+ */
9
+ export const dummyContext: BaseContext = new DummyContext();
10
+
11
+ /**
12
+ * The global audio context which is getable and assignable through
13
+ * getContext and setContext
14
+ */
15
+ let globalContext: BaseContext = dummyContext;
16
+
17
+ /**
18
+ * Returns the default system-wide {@link Context}.
19
+ * Auto-initializes a real Context in browser environments.
20
+ */
21
+ export function getContext(): BaseContext {
22
+ if (globalContext === dummyContext && hasAudioContext) {
23
+ globalContext = new Context();
24
+ }
25
+ return globalContext;
26
+ }
27
+
28
+ /**
29
+ * Set the global context directly. Unlike the richer `setContext` in Global.ts,
30
+ * this only accepts a {@link BaseContext} — no native AudioContext wrapping.
31
+ */
32
+ export function setContext(context: BaseContext): void {
33
+ globalContext = context;
34
+ }
@@ -1,8 +1,8 @@
1
1
  import { createOfflineAudioContext } from "../context/AudioContext.js";
2
2
  import { Context } from "../context/Context.js";
3
- import { getContext, setContext } from "../Global.js";
4
3
  import { Seconds } from "../type/Units.js";
5
4
  import { isOfflineAudioContext } from "../util/AdvancedTypeCheck.js";
5
+ import { getContext, setContext } from "./GlobalContext.js";
6
6
  import { ToneAudioBuffer } from "./ToneAudioBuffer.js";
7
7
 
8
8
  /**
@@ -1,10 +1,10 @@
1
- import { getContext } from "../Global.js";
2
1
  import { Tone } from "../Tone.js";
3
2
  import { Samples, Seconds } from "../type/Units.js";
4
3
  import { assert } from "../util/Debug.js";
5
4
  import { optionsFromArguments } from "../util/Defaults.js";
6
5
  import { noOp } from "../util/Interface.js";
7
6
  import { isArray, isNumber, isString } from "../util/TypeCheck.js";
7
+ import { getContext } from "./GlobalContext.js";
8
8
 
9
9
  interface ToneAudioBufferOptions {
10
10
  url?: string | AudioBuffer | ToneAudioBuffer;
@@ -93,6 +93,10 @@ describe("Timeline", () => {
93
93
  sched.remove({ time: 4 });
94
94
  });
95
95
  expect(sched.length).to.equal(1);
96
+ sched.add({ time: 1 });
97
+ sched.add({ time: 3 });
98
+ sched.remove({ time: 2 });
99
+ expect(sched.length).to.equal(3);
96
100
  sched.dispose();
97
101
  });
98
102
 
@@ -115,7 +115,7 @@ export class Timeline<GenericEvent extends TimelineEvent> extends Tone {
115
115
  * @returns {Timeline} this
116
116
  */
117
117
  remove(event: GenericEvent): this {
118
- const index = this._timeline.indexOf(event);
118
+ const index = this._indexOf(event);
119
119
  if (index !== -1) {
120
120
  this._timeline.splice(index, 1);
121
121
  }
@@ -240,7 +240,7 @@ export class Timeline<GenericEvent extends TimelineEvent> extends Tone {
240
240
  * @return The event right before the given event
241
241
  */
242
242
  previousEvent(event: GenericEvent): GenericEvent | null {
243
- const index = this._timeline.indexOf(event);
243
+ const index = this._indexOf(event);
244
244
  if (index > 0) {
245
245
  return this._timeline[index - 1];
246
246
  } else {
@@ -248,6 +248,34 @@ export class Timeline<GenericEvent extends TimelineEvent> extends Tone {
248
248
  }
249
249
  }
250
250
 
251
+ /**
252
+ * Return the index of the given event in the timeline. -1 if it is not found.
253
+ */
254
+ private _indexOf(event: GenericEvent): number {
255
+ // Use _search since internally implemented with binary search.
256
+ const index = this._search(event.time);
257
+ if (index === -1) {
258
+ return -1;
259
+ }
260
+ // There may be multiple events with the same time, so find the exact event
261
+ // Search forward and backward for events with the same time
262
+ for (let i = index; i < this._timeline.length; i++) {
263
+ if (this._timeline[i] === event) {
264
+ return i;
265
+ } else if (this._timeline[i].time !== event.time) {
266
+ break;
267
+ }
268
+ }
269
+ for (let j = index - 1; j >= 0; j--) {
270
+ if (this._timeline[j] === event) {
271
+ return j;
272
+ } else if (this._timeline[j].time !== event.time) {
273
+ break;
274
+ }
275
+ }
276
+ return -1;
277
+ }
278
+
251
279
  /**
252
280
  * Does a binary search on the timeline array and returns the
253
281
  * nearest event index whose time is after or equal to the given time.
package/Tone/version.ts CHANGED
@@ -1 +1 @@
1
- export const version: string = "15.5.19";
1
+ export const version: string = "15.5.21";