libadlmidi-js 1.0.0

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.
Files changed (86) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +126 -0
  3. package/dist/core.d.ts +276 -0
  4. package/dist/fm_banks/ail/MonopolyDeluxe.wopl +0 -0
  5. package/dist/fm_banks/ail/master_of_magic.wopl +0 -0
  6. package/dist/fm_banks/manifest.json +60 -0
  7. package/dist/fm_banks/wopl/Apogee-IMF-90.wopl +0 -0
  8. package/dist/fm_banks/wopl/DMXOPL3-by-sneakernets-GS.wopl +0 -0
  9. package/dist/fm_banks/wopl/GM-By-J.A.Nguyen-and-Wohlstand.wopl +0 -0
  10. package/dist/fm_banks/wopl/Wohlstand's-modded-FatMan.wopl +0 -0
  11. package/dist/fm_banks/wopl/fatman-2op.wopl +0 -0
  12. package/dist/fm_banks/wopl/fatman-4op.wopl +0 -0
  13. package/dist/fm_banks/wopl/msadlib.wopl +0 -0
  14. package/dist/libadlmidi.d.ts +453 -0
  15. package/dist/libadlmidi.dosbox.browser.js +2 -0
  16. package/dist/libadlmidi.dosbox.browser.wasm +0 -0
  17. package/dist/libadlmidi.dosbox.core.js +2 -0
  18. package/dist/libadlmidi.dosbox.core.wasm +0 -0
  19. package/dist/libadlmidi.dosbox.js +0 -0
  20. package/dist/libadlmidi.dosbox.processor.js +3226 -0
  21. package/dist/libadlmidi.dosbox.slim.browser.js +2 -0
  22. package/dist/libadlmidi.dosbox.slim.browser.wasm +0 -0
  23. package/dist/libadlmidi.dosbox.slim.core.js +2 -0
  24. package/dist/libadlmidi.dosbox.slim.core.wasm +0 -0
  25. package/dist/libadlmidi.dosbox.slim.js +0 -0
  26. package/dist/libadlmidi.dosbox.slim.processor.js +3226 -0
  27. package/dist/libadlmidi.full.browser.js +2 -0
  28. package/dist/libadlmidi.full.browser.wasm +0 -0
  29. package/dist/libadlmidi.full.core.js +2 -0
  30. package/dist/libadlmidi.full.core.wasm +0 -0
  31. package/dist/libadlmidi.full.js +0 -0
  32. package/dist/libadlmidi.full.processor.js +3226 -0
  33. package/dist/libadlmidi.full.slim.browser.js +2 -0
  34. package/dist/libadlmidi.full.slim.browser.wasm +0 -0
  35. package/dist/libadlmidi.full.slim.core.js +2 -0
  36. package/dist/libadlmidi.full.slim.core.wasm +0 -0
  37. package/dist/libadlmidi.full.slim.js +0 -0
  38. package/dist/libadlmidi.full.slim.processor.js +3226 -0
  39. package/dist/libadlmidi.js +445 -0
  40. package/dist/libadlmidi.js.map +7 -0
  41. package/dist/libadlmidi.light.browser.js +2 -0
  42. package/dist/libadlmidi.light.browser.wasm +0 -0
  43. package/dist/libadlmidi.light.core.js +2 -0
  44. package/dist/libadlmidi.light.core.wasm +0 -0
  45. package/dist/libadlmidi.light.js +0 -0
  46. package/dist/libadlmidi.light.processor.js +3226 -0
  47. package/dist/libadlmidi.light.slim.browser.js +2 -0
  48. package/dist/libadlmidi.light.slim.browser.wasm +0 -0
  49. package/dist/libadlmidi.light.slim.core.js +2 -0
  50. package/dist/libadlmidi.light.slim.core.wasm +0 -0
  51. package/dist/libadlmidi.light.slim.js +0 -0
  52. package/dist/libadlmidi.light.slim.processor.js +3226 -0
  53. package/dist/libadlmidi.nuked.browser.js +2 -0
  54. package/dist/libadlmidi.nuked.browser.wasm +0 -0
  55. package/dist/libadlmidi.nuked.core.js +2 -0
  56. package/dist/libadlmidi.nuked.core.wasm +0 -0
  57. package/dist/libadlmidi.nuked.js +0 -0
  58. package/dist/libadlmidi.nuked.processor.js +3226 -0
  59. package/dist/libadlmidi.nuked.slim.browser.js +2 -0
  60. package/dist/libadlmidi.nuked.slim.browser.wasm +0 -0
  61. package/dist/libadlmidi.nuked.slim.core.js +2 -0
  62. package/dist/libadlmidi.nuked.slim.core.wasm +0 -0
  63. package/dist/libadlmidi.nuked.slim.js +0 -0
  64. package/dist/libadlmidi.nuked.slim.processor.js +3226 -0
  65. package/dist/profiles/dosbox.d.ts +49 -0
  66. package/dist/profiles/dosbox.slim.d.ts +49 -0
  67. package/dist/profiles/full.d.ts +49 -0
  68. package/dist/profiles/full.slim.d.ts +49 -0
  69. package/dist/profiles/light.d.ts +49 -0
  70. package/dist/profiles/light.slim.d.ts +49 -0
  71. package/dist/profiles/nuked.d.ts +49 -0
  72. package/dist/profiles/nuked.slim.d.ts +49 -0
  73. package/dist/utils/struct.d.ts +209 -0
  74. package/package.json +103 -0
  75. package/src/core.js +591 -0
  76. package/src/libadlmidi.js +524 -0
  77. package/src/processor.js +517 -0
  78. package/src/profiles/dosbox.js +82 -0
  79. package/src/profiles/dosbox.slim.js +82 -0
  80. package/src/profiles/full.js +82 -0
  81. package/src/profiles/full.slim.js +82 -0
  82. package/src/profiles/light.js +82 -0
  83. package/src/profiles/light.slim.js +82 -0
  84. package/src/profiles/nuked.js +82 -0
  85. package/src/profiles/nuked.slim.js +82 -0
  86. package/src/utils/struct.js +288 -0
@@ -0,0 +1,524 @@
1
+ /**
2
+ * libADLMIDI-JS - Main Thread Interface
3
+ *
4
+ * High-level API for real-time OPL3 FM synthesis in the browser.
5
+ *
6
+ * @example
7
+ * ```javascript
8
+ * import { AdlMidi } from 'libadlmidi-js';
9
+ *
10
+ * const synth = new AdlMidi();
11
+ * await synth.init('/path/to/processor.js');
12
+ *
13
+ * synth.noteOn(0, 60, 100); // Middle C on channel 0
14
+ * synth.noteOff(0, 60);
15
+ * ```
16
+ */
17
+
18
+ /**
19
+ * Bank identifier for instrument access
20
+ * @typedef {Object} BankId
21
+ * @property {boolean} percussive - True for percussion bank, false for melodic
22
+ * @property {number} msb - Bank MSB (0-127)
23
+ * @property {number} lsb - Bank LSB (0-127)
24
+ */
25
+
26
+ /**
27
+ * OPL3 operator parameters
28
+ * @typedef {Object} Operator
29
+ * @property {boolean} am - Amplitude modulation (tremolo)
30
+ * @property {boolean} vibrato - Vibrato (frequency modulation)
31
+ * @property {boolean} sustaining - Sustaining (EG type)
32
+ * @property {boolean} ksr - Key scale rate
33
+ * @property {number} freqMult - Frequency multiplier (0-15)
34
+ * @property {number} keyScaleLevel - Key scale level (0-3)
35
+ * @property {number} totalLevel - Total level / attenuation (0-63, 0 = loudest)
36
+ * @property {number} attack - Attack rate (0-15)
37
+ * @property {number} decay - Decay rate (0-15)
38
+ * @property {number} sustain - Sustain level (0-15, 0 = loudest)
39
+ * @property {number} release - Release rate (0-15)
40
+ * @property {number} waveform - Waveform select (0-7)
41
+ */
42
+
43
+ /**
44
+ * Complete OPL3 instrument definition
45
+ * @typedef {Object} Instrument
46
+ * @property {boolean} is4op - 4-operator mode enabled
47
+ * @property {boolean} isPseudo4op - Pseudo 4-op (two 2-op voices)
48
+ * @property {boolean} isBlank - Blank/unused instrument
49
+ * @property {boolean} isRhythmModeCar - Rhythm mode carrier flag
50
+ * @property {boolean} isRhythmModeMod - Rhythm mode modulator flag
51
+ * @property {number} feedback1 - Voice 1 feedback (0-7)
52
+ * @property {number} connection1 - Voice 1 connection (0 = FM, 1 = additive)
53
+ * @property {number} feedback2 - Voice 2 feedback (0-7, 4-op only)
54
+ * @property {number} connection2 - Voice 2 connection (0 = FM, 1 = additive, 4-op only)
55
+ * @property {number} noteOffset1 - Note offset for voice 1 (semitones)
56
+ * @property {number} noteOffset2 - Note offset for voice 2 (semitones)
57
+ * @property {number} velocityOffset - Velocity offset
58
+ * @property {number} secondVoiceDetune - Second voice detune (cents)
59
+ * @property {number} percussionNote - Percussion note number
60
+ * @property {number} delayOnMs - Delay before note-on (ms)
61
+ * @property {number} delayOffMs - Delay before note-off (ms)
62
+ * @property {[Operator, Operator, Operator, Operator]} operators - Four operators
63
+ */
64
+
65
+ /**
66
+ * Configuration settings for the synthesizer
67
+ * @typedef {Object} ConfigureSettings
68
+ * @property {number} [numChips] - Number of emulated OPL3 chips (1-100)
69
+ * @property {number} [numFourOpChannels] - Number of 4-op channels (-1 = auto)
70
+ * @property {number} [bank] - Embedded bank number
71
+ * @property {boolean} [softPan] - Enable soft stereo panning
72
+ * @property {boolean} [deepVibrato] - Enable deep vibrato
73
+ * @property {boolean} [deepTremolo] - Enable deep tremolo
74
+ */
75
+
76
+ /**
77
+ * Available OPL3 emulator cores.
78
+ * Use with switchEmulator() to change the synthesis engine at runtime.
79
+ * Note: Only emulators compiled into the current profile are available.
80
+ * @readonly
81
+ * @enum {number}
82
+ */
83
+ export const Emulator = Object.freeze({
84
+ /** Nuked OPL3 v1.8 - Most accurate, higher CPU usage */
85
+ NUKED: 0,
86
+ /** Nuked OPL3 v1.7.4 - Slightly older version */
87
+ NUKED_174: 1,
88
+ /** DosBox OPL3 - Good accuracy, lower CPU usage */
89
+ DOSBOX: 2,
90
+ /** Opal - Reality Adlib Tracker emulator */
91
+ OPAL: 3,
92
+ /** Java OPL3 - Port of emu8950 */
93
+ JAVA: 4,
94
+ /** ESFMu - ESFM chip emulator */
95
+ ESFMU: 5,
96
+ /** MAME OPL2 */
97
+ MAME_OPL2: 6,
98
+ /** YMFM OPL2 */
99
+ YMFM_OPL2: 7,
100
+ /** YMFM OPL3 */
101
+ YMFM_OPL3: 8,
102
+ /** Nuked OPL2 LLE - Transistor-level emulation */
103
+ NUKED_OPL2_LLE: 9,
104
+ /** Nuked OPL3 LLE - Transistor-level emulation */
105
+ NUKED_OPL3_LLE: 10,
106
+ });
107
+
108
+ export class AdlMidi {
109
+ /** @type {boolean} */
110
+ #ready = false;
111
+ /** @type {Map<string, Set<Function>>} */
112
+ #messageHandlers = new Map();
113
+
114
+ /**
115
+ * Create a new AdlMidi instance
116
+ * @param {AudioContext} [context] - Optional AudioContext to use. Creates one if not provided.
117
+ */
118
+ constructor(context) {
119
+ this.ctx = context || null;
120
+ this.node = null;
121
+ }
122
+
123
+ /**
124
+ * Get the AudioContext (may be null before init)
125
+ * @returns {AudioContext | null}
126
+ */
127
+ get audioContext() {
128
+ return this.ctx;
129
+ }
130
+
131
+ /**
132
+ * Check if the synth is ready
133
+ * @returns {boolean}
134
+ */
135
+ get ready() {
136
+ return this.#ready;
137
+ }
138
+
139
+ /**
140
+ * Initialize the synthesizer
141
+ * @param {string} processorUrl - URL to the bundled processor JavaScript file
142
+ * @param {string | null} [wasmUrl=null] - Optional URL to the .wasm file for split builds.
143
+ * If not provided, assumes bundled version with embedded WASM.
144
+ * @returns {Promise<void>}
145
+ */
146
+ async init(processorUrl, wasmUrl = null) {
147
+ if (!this.ctx) {
148
+ this.ctx = new AudioContext({ sampleRate: 44100 });
149
+ }
150
+
151
+ // Resume AudioContext if suspended (browser autoplay policy)
152
+ if (this.ctx.state === 'suspended') {
153
+ await this.ctx.resume();
154
+ }
155
+
156
+ // For split builds, fetch the WASM binary from main thread
157
+ // (AudioWorklet doesn't have fetch access)
158
+ // If wasmUrl not provided, derive it from processorUrl
159
+ let wasmBinary = null;
160
+ const effectiveWasmUrl = wasmUrl || processorUrl.replace('.processor.js', '.core.wasm');
161
+ const response = await fetch(effectiveWasmUrl);
162
+ if (!response.ok) {
163
+ throw new Error(`Failed to fetch WASM: ${response.status}`);
164
+ }
165
+ wasmBinary = await response.arrayBuffer();
166
+
167
+ // Add the AudioWorklet module
168
+ await this.ctx.audioWorklet.addModule(processorUrl);
169
+
170
+ // Create the AudioWorkletNode
171
+ this.node = new AudioWorkletNode(this.ctx, 'adl-midi-processor', {
172
+ processorOptions: {
173
+ sampleRate: this.ctx.sampleRate,
174
+ wasmBinary: wasmBinary // null for bundled, ArrayBuffer for split
175
+ }
176
+ });
177
+
178
+ // Connect to destination
179
+ this.node.connect(this.ctx.destination);
180
+
181
+ // Set up message handling
182
+ this.node.port.onmessage = (e) => this.#handleMessage(e.data);
183
+
184
+ // Wait for the processor to be ready
185
+ return new Promise((resolve, reject) => {
186
+ const timeout = setTimeout(() => {
187
+ reject(new Error('Timeout waiting for WASM initialization'));
188
+ }, 10000);
189
+
190
+ this.#onceMessage('ready', () => {
191
+ clearTimeout(timeout);
192
+ this.#ready = true;
193
+ resolve();
194
+ });
195
+
196
+ this.#onceMessage('error', /** @param {{message: string}} msg */(msg) => {
197
+ clearTimeout(timeout);
198
+ reject(new Error(msg.message));
199
+ });
200
+ });
201
+ }
202
+
203
+ /**
204
+ * Internal message handler
205
+ * @param {{type: string}} msg - Message from processor
206
+ */
207
+ #handleMessage(msg) {
208
+ const handlers = this.#messageHandlers.get(msg.type);
209
+ if (handlers) {
210
+ handlers.forEach(/** @param {Function} handler */ handler => handler(msg));
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Register a one-time message handler
216
+ * @param {string} type - Message type
217
+ * @param {Function} handler - Handler function
218
+ */
219
+ #onceMessage(type, handler) {
220
+ if (!this.#messageHandlers.has(type)) {
221
+ this.#messageHandlers.set(type, new Set());
222
+ }
223
+
224
+ /** @param {Object} msg */
225
+ const wrappedHandler = (msg) => {
226
+ this.#messageHandlers.get(type)?.delete(wrappedHandler);
227
+ handler(msg);
228
+ };
229
+
230
+ this.#messageHandlers.get(type)?.add(wrappedHandler);
231
+ }
232
+
233
+ /**
234
+ * Send a message to the processor
235
+ * @param {Object} msg - Message to send
236
+ */
237
+ #send(msg) {
238
+ if (this.node) {
239
+ this.node.port.postMessage(msg);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Play a note
245
+ * @param {number} channel - MIDI channel (0-15)
246
+ * @param {number} note - MIDI note number (0-127)
247
+ * @param {number} velocity - Note velocity (0-127)
248
+ */
249
+ noteOn(channel, note, velocity) {
250
+ this.#send({ type: 'noteOn', channel, note, velocity });
251
+ }
252
+
253
+ /**
254
+ * Stop a note
255
+ * @param {number} channel - MIDI channel (0-15)
256
+ * @param {number} note - MIDI note number (0-127)
257
+ */
258
+ noteOff(channel, note) {
259
+ this.#send({ type: 'noteOff', channel, note });
260
+ }
261
+
262
+ /**
263
+ * Set pitch bend
264
+ * @param {number} channel - MIDI channel (0-15)
265
+ * @param {number} value - Pitch bend value (0-16383, 8192 = center)
266
+ */
267
+ pitchBend(channel, value) {
268
+ const lsb = value & 0x7F;
269
+ const msb = (value >> 7) & 0x7F;
270
+ this.#send({ type: 'pitchBend', channel, lsb, msb });
271
+ }
272
+
273
+ /**
274
+ * Send a control change
275
+ * @param {number} channel - MIDI channel (0-15)
276
+ * @param {number} controller - Controller number (0-127)
277
+ * @param {number} value - Controller value (0-127)
278
+ */
279
+ controlChange(channel, controller, value) {
280
+ this.#send({ type: 'controlChange', channel, controller, value });
281
+ }
282
+
283
+ /**
284
+ * Change program (instrument)
285
+ * @param {number} channel - MIDI channel (0-15)
286
+ * @param {number} program - Program number (0-127)
287
+ */
288
+ programChange(channel, program) {
289
+ this.#send({ type: 'programChange', channel, program });
290
+ }
291
+
292
+ /**
293
+ * Reset the real-time state (stops all notes, resets controllers)
294
+ * @returns {void}
295
+ */
296
+ resetState() {
297
+ this.#send({ type: 'resetState' });
298
+ }
299
+
300
+ /**
301
+ * Panic - stop all sounds immediately
302
+ * @returns {void}
303
+ */
304
+ panic() {
305
+ this.#send({ type: 'panic' });
306
+ }
307
+
308
+ /**
309
+ * Configure synth settings at runtime
310
+ * @param {ConfigureSettings} settings - Settings object
311
+ * @returns {Promise<void>}
312
+ */
313
+ async configure(settings) {
314
+ return new Promise((resolve) => {
315
+ this.#onceMessage('configured', () => resolve());
316
+ this.#send({ type: 'configure', settings });
317
+ });
318
+ }
319
+
320
+ /**
321
+ * Load a custom bank file (WOPL format)
322
+ * @param {ArrayBuffer} arrayBuffer - Bank file data
323
+ * @returns {Promise<void>}
324
+ */
325
+ async loadBank(arrayBuffer) {
326
+ return new Promise((resolve, reject) => {
327
+ this.#onceMessage('bankLoaded', /** @param {{success: boolean, error?: string}} msg */(msg) => {
328
+ if (msg.success) {
329
+ resolve();
330
+ } else {
331
+ reject(new Error(msg.error || 'Failed to load bank'));
332
+ }
333
+ });
334
+
335
+ this.#send({ type: 'loadBank', data: arrayBuffer });
336
+ });
337
+ }
338
+
339
+ /**
340
+ * Set the embedded bank by number
341
+ * @param {number} bank - Bank number
342
+ * @returns {Promise<void>}
343
+ */
344
+ async setBank(bank) {
345
+ return new Promise((resolve, reject) => {
346
+ this.#onceMessage('bankSet', /** @param {{success: boolean}} msg */(msg) => {
347
+ if (msg.success) {
348
+ resolve();
349
+ } else {
350
+ reject(new Error(`Failed to set bank ${bank}`));
351
+ }
352
+ });
353
+
354
+ this.#send({ type: 'setBank', bank });
355
+ });
356
+ }
357
+
358
+ /**
359
+ * Get an instrument from a bank for editing
360
+ * @param {BankId} [bankId] - Bank identifier
361
+ * @param {number} [programNumber] - Program/instrument number (0-127)
362
+ * @returns {Promise<Instrument>} Instrument object with named properties
363
+ */
364
+ async getInstrument(bankId = { percussive: false, msb: 0, lsb: 0 }, programNumber = 0) {
365
+ return new Promise((resolve, reject) => {
366
+ this.#onceMessage('instrumentLoaded', /** @param {{success: boolean, instrument: Instrument, error?: string}} msg */(msg) => {
367
+ if (msg.success) {
368
+ resolve(msg.instrument);
369
+ } else {
370
+ reject(new Error(msg.error || 'Failed to get instrument'));
371
+ }
372
+ });
373
+
374
+ this.#send({ type: 'getInstrument', bankId, programNumber });
375
+ });
376
+ }
377
+
378
+ /**
379
+ * Set an instrument in a bank
380
+ * @param {BankId} bankId - Bank identifier
381
+ * @param {number} programNumber - Program/instrument number (0-127)
382
+ * @param {Instrument} instrument - Instrument object with operator parameters
383
+ * @returns {Promise<void>}
384
+ */
385
+ async setInstrument(bankId = { percussive: false, msb: 0, lsb: 0 }, programNumber, instrument) {
386
+ return new Promise((resolve, reject) => {
387
+ this.#onceMessage('instrumentSet', /** @param {{success: boolean, error?: string}} msg */(msg) => {
388
+ if (msg.success) {
389
+ resolve();
390
+ } else {
391
+ reject(new Error(msg.error || 'Failed to set instrument'));
392
+ }
393
+ });
394
+
395
+ this.#send({ type: 'setInstrument', bankId, programNumber, instrument });
396
+ });
397
+ }
398
+
399
+ /**
400
+ * Set the number of emulated OPL3 chips
401
+ * @param {number} chips - Number of chips (1-100)
402
+ */
403
+ setNumChips(chips) {
404
+ this.#send({ type: 'setNumChips', chips });
405
+ }
406
+
407
+ /**
408
+ * Set the volume model
409
+ * @param {number} model - Volume model number
410
+ */
411
+ setVolumeModel(model) {
412
+ this.#send({ type: 'setVolumeModel', model });
413
+ }
414
+
415
+ /**
416
+ * Enable/disable rhythm mode (percussion)
417
+ * @param {boolean} enabled
418
+ */
419
+ setPercussionMode(enabled) {
420
+ this.#send({ type: 'setPercMode', enabled });
421
+ }
422
+
423
+ /**
424
+ * Enable/disable deep vibrato
425
+ * @param {boolean} enabled
426
+ */
427
+ setVibrato(enabled) {
428
+ this.#send({ type: 'setVibrato', enabled });
429
+ }
430
+
431
+ /**
432
+ * Enable/disable deep tremolo
433
+ * @param {boolean} enabled
434
+ */
435
+ setTremolo(enabled) {
436
+ this.#send({ type: 'setTremolo', enabled });
437
+ }
438
+
439
+ /**
440
+ * Switch the OPL3 emulator core at runtime
441
+ *
442
+ * Only emulators compiled into the current build profile are available:
443
+ * - nuked profile: NUKED only
444
+ * - dosbox profile: DOSBOX only
445
+ * - light profile: NUKED, DOSBOX
446
+ * - full profile: NUKED, DOSBOX, OPAL, JAVA, ESFMU, YMFM_OPL2, YMFM_OPL3
447
+ *
448
+ * @param {number} emulator - Emulator ID from the Emulator enum
449
+ * @returns {Promise<void>} Resolves when emulator is switched, rejects if unavailable
450
+ * @example
451
+ * import { AdlMidi, Emulator } from 'libadlmidi-js';
452
+ * await synth.switchEmulator(Emulator.DOSBOX);
453
+ */
454
+ async switchEmulator(emulator) {
455
+ return new Promise((resolve, reject) => {
456
+ this.#onceMessage('emulatorSwitched', /** @param {{success: boolean, emulator: number}} msg */(msg) => {
457
+ if (msg.success) {
458
+ resolve();
459
+ } else {
460
+ reject(new Error(`Failed to switch to emulator ${emulator}. It may not be available in this build profile.`));
461
+ }
462
+ });
463
+ this.#send({ type: 'switchEmulator', emulator });
464
+ });
465
+ }
466
+
467
+ /**
468
+ * Get the name of the currently active OPL3 emulator
469
+ * @returns {Promise<string>} Human-readable emulator name (e.g., "Nuked OPL3 (v 1.8)")
470
+ * @example
471
+ * const name = await synth.getEmulatorName();
472
+ * console.log(`Using: ${name}`);
473
+ */
474
+ async getEmulatorName() {
475
+ return new Promise((resolve) => {
476
+ this.#onceMessage('emulatorName', /** @param {{name: string}} msg */(msg) => {
477
+ resolve(msg.name);
478
+ });
479
+ this.#send({ type: 'getEmulatorName' });
480
+ });
481
+ }
482
+
483
+ /**
484
+ * Reset the synthesizer
485
+ * @returns {void}
486
+ */
487
+ reset() {
488
+ this.#send({ type: 'reset' });
489
+ }
490
+
491
+ /**
492
+ * Close the synthesizer and release resources
493
+ * @returns {void}
494
+ */
495
+ close() {
496
+ if (this.node) {
497
+ this.node.disconnect();
498
+ this.node = null;
499
+ }
500
+ this._ready = false;
501
+ }
502
+
503
+ /**
504
+ * Suspend the AudioContext (save CPU when not in use)
505
+ * @returns {Promise<void>}
506
+ */
507
+ async suspend() {
508
+ if (this.ctx) {
509
+ await this.ctx.suspend();
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Resume the AudioContext
515
+ * @returns {Promise<void>}
516
+ */
517
+ async resume() {
518
+ if (this.ctx) {
519
+ await this.ctx.resume();
520
+ }
521
+ }
522
+ }
523
+
524
+ export default AdlMidi;