1 | import { __awaiter } from "tslib";
|
2 | import { Ticker } from "../clock/Ticker.js";
|
3 | import { isAudioContext } from "../util/AdvancedTypeCheck.js";
|
4 | import { optionsFromArguments } from "../util/Defaults.js";
|
5 | import { Timeline } from "../util/Timeline.js";
|
6 | import { isDefined } from "../util/TypeCheck.js";
|
7 | import { createAudioContext, createAudioWorkletNode, } from "./AudioContext.js";
|
8 | import { closeContext, initializeContext } from "./ContextInitialization.js";
|
9 | import { BaseContext } from "./BaseContext.js";
|
10 | import { assert } from "../util/Debug.js";
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | export class Context extends BaseContext {
|
16 | constructor() {
|
17 | var _a, _b;
|
18 | super();
|
19 | this.name = "Context";
|
20 | |
21 |
|
22 |
|
23 | this._constants = new Map();
|
24 | |
25 |
|
26 |
|
27 | this._timeouts = new Timeline();
|
28 | |
29 |
|
30 |
|
31 | this._timeoutIds = 0;
|
32 | |
33 |
|
34 |
|
35 | this._initialized = false;
|
36 | |
37 |
|
38 |
|
39 | this._closeStarted = false;
|
40 | |
41 |
|
42 |
|
43 | this.isOffline = false;
|
44 |
|
45 |
|
46 |
|
47 | |
48 |
|
49 |
|
50 | this._workletPromise = null;
|
51 | const options = optionsFromArguments(Context.getDefaults(), arguments, [
|
52 | "context",
|
53 | ]);
|
54 | if (options.context) {
|
55 | this._context = options.context;
|
56 |
|
57 | this._latencyHint = ((_a = arguments[0]) === null || _a === void 0 ? void 0 : _a.latencyHint) || "";
|
58 | }
|
59 | else {
|
60 | this._context = createAudioContext({
|
61 | latencyHint: options.latencyHint,
|
62 | });
|
63 | this._latencyHint = options.latencyHint;
|
64 | }
|
65 | this._ticker = new Ticker(this.emit.bind(this, "tick"), options.clockSource, options.updateInterval, this._context.sampleRate);
|
66 | this.on("tick", this._timeoutLoop.bind(this));
|
67 |
|
68 | this._context.onstatechange = () => {
|
69 | this.emit("statechange", this.state);
|
70 | };
|
71 |
|
72 | this[((_b = arguments[0]) === null || _b === void 0 ? void 0 : _b.hasOwnProperty("updateInterval"))
|
73 | ? "_lookAhead"
|
74 | : "lookAhead"] = options.lookAhead;
|
75 | }
|
76 | static getDefaults() {
|
77 | return {
|
78 | clockSource: "worker",
|
79 | latencyHint: "interactive",
|
80 | lookAhead: 0.1,
|
81 | updateInterval: 0.05,
|
82 | };
|
83 | }
|
84 | |
85 |
|
86 |
|
87 | initialize() {
|
88 | if (!this._initialized) {
|
89 |
|
90 | initializeContext(this);
|
91 | this._initialized = true;
|
92 | }
|
93 | return this;
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 | createAnalyser() {
|
99 | return this._context.createAnalyser();
|
100 | }
|
101 | createOscillator() {
|
102 | return this._context.createOscillator();
|
103 | }
|
104 | createBufferSource() {
|
105 | return this._context.createBufferSource();
|
106 | }
|
107 | createBiquadFilter() {
|
108 | return this._context.createBiquadFilter();
|
109 | }
|
110 | createBuffer(numberOfChannels, length, sampleRate) {
|
111 | return this._context.createBuffer(numberOfChannels, length, sampleRate);
|
112 | }
|
113 | createChannelMerger(numberOfInputs) {
|
114 | return this._context.createChannelMerger(numberOfInputs);
|
115 | }
|
116 | createChannelSplitter(numberOfOutputs) {
|
117 | return this._context.createChannelSplitter(numberOfOutputs);
|
118 | }
|
119 | createConstantSource() {
|
120 | return this._context.createConstantSource();
|
121 | }
|
122 | createConvolver() {
|
123 | return this._context.createConvolver();
|
124 | }
|
125 | createDelay(maxDelayTime) {
|
126 | return this._context.createDelay(maxDelayTime);
|
127 | }
|
128 | createDynamicsCompressor() {
|
129 | return this._context.createDynamicsCompressor();
|
130 | }
|
131 | createGain() {
|
132 | return this._context.createGain();
|
133 | }
|
134 | createIIRFilter(feedForward, feedback) {
|
135 |
|
136 | return this._context.createIIRFilter(feedForward, feedback);
|
137 | }
|
138 | createPanner() {
|
139 | return this._context.createPanner();
|
140 | }
|
141 | createPeriodicWave(real, imag, constraints) {
|
142 | return this._context.createPeriodicWave(real, imag, constraints);
|
143 | }
|
144 | createStereoPanner() {
|
145 | return this._context.createStereoPanner();
|
146 | }
|
147 | createWaveShaper() {
|
148 | return this._context.createWaveShaper();
|
149 | }
|
150 | createMediaStreamSource(stream) {
|
151 | assert(isAudioContext(this._context), "Not available if OfflineAudioContext");
|
152 | const context = this._context;
|
153 | return context.createMediaStreamSource(stream);
|
154 | }
|
155 | createMediaElementSource(element) {
|
156 | assert(isAudioContext(this._context), "Not available if OfflineAudioContext");
|
157 | const context = this._context;
|
158 | return context.createMediaElementSource(element);
|
159 | }
|
160 | createMediaStreamDestination() {
|
161 | assert(isAudioContext(this._context), "Not available if OfflineAudioContext");
|
162 | const context = this._context;
|
163 | return context.createMediaStreamDestination();
|
164 | }
|
165 | decodeAudioData(audioData) {
|
166 | return this._context.decodeAudioData(audioData);
|
167 | }
|
168 | |
169 |
|
170 |
|
171 | get currentTime() {
|
172 | return this._context.currentTime;
|
173 | }
|
174 | |
175 |
|
176 |
|
177 | get state() {
|
178 | return this._context.state;
|
179 | }
|
180 | |
181 |
|
182 |
|
183 | get sampleRate() {
|
184 | return this._context.sampleRate;
|
185 | }
|
186 | |
187 |
|
188 |
|
189 | get listener() {
|
190 | this.initialize();
|
191 | return this._listener;
|
192 | }
|
193 | set listener(l) {
|
194 | assert(!this._initialized, "The listener cannot be set after initialization.");
|
195 | this._listener = l;
|
196 | }
|
197 | |
198 |
|
199 |
|
200 | get transport() {
|
201 | this.initialize();
|
202 | return this._transport;
|
203 | }
|
204 | set transport(t) {
|
205 | assert(!this._initialized, "The transport cannot be set after initialization.");
|
206 | this._transport = t;
|
207 | }
|
208 | |
209 |
|
210 |
|
211 | get draw() {
|
212 | this.initialize();
|
213 | return this._draw;
|
214 | }
|
215 | set draw(d) {
|
216 | assert(!this._initialized, "Draw cannot be set after initialization.");
|
217 | this._draw = d;
|
218 | }
|
219 | |
220 |
|
221 |
|
222 | get destination() {
|
223 | this.initialize();
|
224 | return this._destination;
|
225 | }
|
226 | set destination(d) {
|
227 | assert(!this._initialized, "The destination cannot be set after initialization.");
|
228 | this._destination = d;
|
229 | }
|
230 | |
231 |
|
232 |
|
233 |
|
234 | createAudioWorkletNode(name, options) {
|
235 | return createAudioWorkletNode(this.rawContext, name, options);
|
236 | }
|
237 | |
238 |
|
239 |
|
240 |
|
241 | addAudioWorkletModule(url) {
|
242 | return __awaiter(this, void 0, void 0, function* () {
|
243 | assert(isDefined(this.rawContext.audioWorklet), "AudioWorkletNode is only available in a secure context (https or localhost)");
|
244 | if (!this._workletPromise) {
|
245 | this._workletPromise = this.rawContext.audioWorklet.addModule(url);
|
246 | }
|
247 | yield this._workletPromise;
|
248 | });
|
249 | }
|
250 | |
251 |
|
252 |
|
253 | workletsAreReady() {
|
254 | return __awaiter(this, void 0, void 0, function* () {
|
255 | (yield this._workletPromise) ? this._workletPromise : Promise.resolve();
|
256 | });
|
257 | }
|
258 |
|
259 |
|
260 |
|
261 | |
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 | get updateInterval() {
|
269 | return this._ticker.updateInterval;
|
270 | }
|
271 | set updateInterval(interval) {
|
272 | this._ticker.updateInterval = interval;
|
273 | }
|
274 | |
275 |
|
276 |
|
277 |
|
278 | get clockSource() {
|
279 | return this._ticker.type;
|
280 | }
|
281 | set clockSource(type) {
|
282 | this._ticker.type = type;
|
283 | }
|
284 | |
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 | get lookAhead() {
|
291 | return this._lookAhead;
|
292 | }
|
293 | set lookAhead(time) {
|
294 | this._lookAhead = time;
|
295 |
|
296 | this.updateInterval = time ? time / 2 : 0.01;
|
297 | }
|
298 | |
299 |
|
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 | get latencyHint() {
|
314 | return this._latencyHint;
|
315 | }
|
316 | |
317 |
|
318 |
|
319 | get rawContext() {
|
320 | return this._context;
|
321 | }
|
322 | |
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | now() {
|
330 | return this._context.currentTime + this._lookAhead;
|
331 | }
|
332 | |
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | immediate() {
|
340 | return this._context.currentTime;
|
341 | }
|
342 | |
343 |
|
344 |
|
345 |
|
346 |
|
347 | resume() {
|
348 | if (isAudioContext(this._context)) {
|
349 | return this._context.resume();
|
350 | }
|
351 | else {
|
352 | return Promise.resolve();
|
353 | }
|
354 | }
|
355 | |
356 |
|
357 |
|
358 |
|
359 | close() {
|
360 | return __awaiter(this, void 0, void 0, function* () {
|
361 | if (isAudioContext(this._context) &&
|
362 | this.state !== "closed" &&
|
363 | !this._closeStarted) {
|
364 | this._closeStarted = true;
|
365 | yield this._context.close();
|
366 | }
|
367 | if (this._initialized) {
|
368 | closeContext(this);
|
369 | }
|
370 | });
|
371 | }
|
372 | |
373 |
|
374 |
|
375 | getConstant(val) {
|
376 | if (this._constants.has(val)) {
|
377 | return this._constants.get(val);
|
378 | }
|
379 | else {
|
380 | const buffer = this._context.createBuffer(1, 128, this._context.sampleRate);
|
381 | const arr = buffer.getChannelData(0);
|
382 | for (let i = 0; i < arr.length; i++) {
|
383 | arr[i] = val;
|
384 | }
|
385 | const constant = this._context.createBufferSource();
|
386 | constant.channelCount = 1;
|
387 | constant.channelCountMode = "explicit";
|
388 | constant.buffer = buffer;
|
389 | constant.loop = true;
|
390 | constant.start(0);
|
391 | this._constants.set(val, constant);
|
392 | return constant;
|
393 | }
|
394 | }
|
395 | |
396 |
|
397 |
|
398 | dispose() {
|
399 | super.dispose();
|
400 | this._ticker.dispose();
|
401 | this._timeouts.dispose();
|
402 | Object.keys(this._constants).map((val) => this._constants[val].disconnect());
|
403 | this.close();
|
404 | return this;
|
405 | }
|
406 |
|
407 |
|
408 |
|
409 | |
410 |
|
411 |
|
412 |
|
413 | _timeoutLoop() {
|
414 | const now = this.now();
|
415 | let firstEvent = this._timeouts.peek();
|
416 | while (this._timeouts.length && firstEvent && firstEvent.time <= now) {
|
417 |
|
418 | firstEvent.callback();
|
419 |
|
420 | this._timeouts.shift();
|
421 |
|
422 | firstEvent = this._timeouts.peek();
|
423 | }
|
424 | }
|
425 | |
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 | setTimeout(fn, timeout) {
|
433 | this._timeoutIds++;
|
434 | const now = this.now();
|
435 | this._timeouts.add({
|
436 | callback: fn,
|
437 | id: this._timeoutIds,
|
438 | time: now + timeout,
|
439 | });
|
440 | return this._timeoutIds;
|
441 | }
|
442 | |
443 |
|
444 |
|
445 |
|
446 | clearTimeout(id) {
|
447 | this._timeouts.forEach((event) => {
|
448 | if (event.id === id) {
|
449 | this._timeouts.remove(event);
|
450 | }
|
451 | });
|
452 | return this;
|
453 | }
|
454 | |
455 |
|
456 |
|
457 | clearInterval(id) {
|
458 | return this.clearTimeout(id);
|
459 | }
|
460 | |
461 |
|
462 |
|
463 | setInterval(fn, interval) {
|
464 | const id = ++this._timeoutIds;
|
465 | const intervalFn = () => {
|
466 | const now = this.now();
|
467 | this._timeouts.add({
|
468 | callback: () => {
|
469 |
|
470 | fn();
|
471 |
|
472 | intervalFn();
|
473 | },
|
474 | id,
|
475 | time: now + interval,
|
476 | });
|
477 | };
|
478 |
|
479 | intervalFn();
|
480 | return id;
|
481 | }
|
482 | }
|
483 |
|
\ | No newline at end of file |