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.
- package/Tone/core/Global.test.ts +19 -0
- package/Tone/core/Global.ts +11 -32
- package/Tone/core/context/Context.test.ts +10 -0
- package/Tone/core/context/Context.ts +33 -8
- package/Tone/core/context/GlobalContext.ts +34 -0
- package/Tone/core/context/OfflineContext.ts +1 -1
- package/Tone/core/context/ToneAudioBuffer.ts +1 -1
- package/Tone/core/util/Timeline.test.ts +4 -0
- package/Tone/core/util/Timeline.ts +30 -2
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/core/Global.d.ts +2 -5
- package/build/esm/core/Global.js +8 -26
- package/build/esm/core/Global.js.map +1 -1
- package/build/esm/core/context/Context.d.ts +12 -0
- package/build/esm/core/context/Context.js +30 -8
- package/build/esm/core/context/Context.js.map +1 -1
- package/build/esm/core/context/GlobalContext.d.ts +15 -0
- package/build/esm/core/context/GlobalContext.js +30 -0
- package/build/esm/core/context/GlobalContext.js.map +1 -0
- package/build/esm/core/context/OfflineContext.js +1 -1
- package/build/esm/core/context/OfflineContext.js.map +1 -1
- package/build/esm/core/context/ToneAudioBuffer.js +1 -1
- package/build/esm/core/context/ToneAudioBuffer.js.map +1 -1
- package/build/esm/core/util/Timeline.d.ts +4 -0
- package/build/esm/core/util/Timeline.js +31 -2
- package/build/esm/core/util/Timeline.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
+
});
|
package/Tone/core/Global.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
28
|
+
getContext().dispose();
|
|
50
29
|
}
|
|
51
30
|
|
|
52
31
|
if (isAudioContext(context)) {
|
|
53
|
-
|
|
32
|
+
_setContext(new Context(context));
|
|
54
33
|
} else if (isOfflineAudioContext(context)) {
|
|
55
|
-
|
|
34
|
+
_setContext(new OfflineContext(context));
|
|
56
35
|
} else {
|
|
57
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
609
|
-
|
|
610
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1
|
+
export const version: string = "15.5.21";
|