libadlmidi-js 1.2.0 → 2.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.
- package/dist/core.d.ts +186 -4
- package/dist/fm_banks/manifest.json +1 -1
- package/dist/libadlmidi.d.ts +143 -65
- package/dist/libadlmidi.dosbox.browser.js +1 -1
- package/dist/libadlmidi.dosbox.browser.wasm +0 -0
- package/dist/libadlmidi.dosbox.core.js +1 -1
- package/dist/libadlmidi.dosbox.core.wasm +0 -0
- package/dist/libadlmidi.dosbox.js +0 -0
- package/dist/libadlmidi.dosbox.processor.js +242 -74
- package/dist/libadlmidi.dosbox.slim.browser.js +1 -1
- package/dist/libadlmidi.dosbox.slim.browser.wasm +0 -0
- package/dist/libadlmidi.dosbox.slim.core.js +1 -1
- package/dist/libadlmidi.dosbox.slim.core.wasm +0 -0
- package/dist/libadlmidi.dosbox.slim.js +0 -0
- package/dist/libadlmidi.dosbox.slim.processor.js +242 -74
- package/dist/libadlmidi.full.browser.js +1 -1
- package/dist/libadlmidi.full.browser.wasm +0 -0
- package/dist/libadlmidi.full.core.js +1 -1
- package/dist/libadlmidi.full.core.wasm +0 -0
- package/dist/libadlmidi.full.js +0 -0
- package/dist/libadlmidi.full.processor.js +242 -74
- package/dist/libadlmidi.full.slim.browser.js +1 -1
- package/dist/libadlmidi.full.slim.browser.wasm +0 -0
- package/dist/libadlmidi.full.slim.core.js +1 -1
- package/dist/libadlmidi.full.slim.core.wasm +0 -0
- package/dist/libadlmidi.full.slim.js +0 -0
- package/dist/libadlmidi.full.slim.processor.js +242 -74
- package/dist/libadlmidi.js +465 -21
- package/dist/libadlmidi.js.map +3 -3
- package/dist/libadlmidi.light.browser.js +1 -1
- package/dist/libadlmidi.light.browser.wasm +0 -0
- package/dist/libadlmidi.light.core.js +1 -1
- package/dist/libadlmidi.light.core.wasm +0 -0
- package/dist/libadlmidi.light.js +0 -0
- package/dist/libadlmidi.light.processor.js +242 -74
- package/dist/libadlmidi.light.slim.browser.js +1 -1
- package/dist/libadlmidi.light.slim.browser.wasm +0 -0
- package/dist/libadlmidi.light.slim.core.js +1 -1
- package/dist/libadlmidi.light.slim.core.wasm +0 -0
- package/dist/libadlmidi.light.slim.js +0 -0
- package/dist/libadlmidi.light.slim.processor.js +242 -74
- package/dist/libadlmidi.nuked.browser.js +1 -1
- package/dist/libadlmidi.nuked.browser.wasm +0 -0
- package/dist/libadlmidi.nuked.core.js +1 -1
- package/dist/libadlmidi.nuked.core.wasm +0 -0
- package/dist/libadlmidi.nuked.js +0 -0
- package/dist/libadlmidi.nuked.processor.js +242 -74
- package/dist/libadlmidi.nuked.slim.browser.js +1 -1
- package/dist/libadlmidi.nuked.slim.browser.wasm +0 -0
- package/dist/libadlmidi.nuked.slim.core.js +1 -1
- package/dist/libadlmidi.nuked.slim.core.wasm +0 -0
- package/dist/libadlmidi.nuked.slim.js +0 -0
- package/dist/libadlmidi.nuked.slim.processor.js +242 -74
- package/dist/profiles/dosbox.d.ts +1 -0
- package/dist/profiles/dosbox.slim.d.ts +1 -0
- package/dist/profiles/full.d.ts +1 -0
- package/dist/profiles/full.slim.d.ts +1 -0
- package/dist/profiles/light.d.ts +1 -0
- package/dist/profiles/light.slim.d.ts +1 -0
- package/dist/profiles/nuked.d.ts +1 -0
- package/dist/profiles/nuked.slim.d.ts +1 -0
- package/dist/utils/constants.d.ts +59 -0
- package/package.json +1 -1
- package/src/core.js +352 -4
- package/src/libadlmidi.js +374 -56
- package/src/processor.js +204 -12
- package/src/profiles/dosbox.js +7 -4
- package/src/profiles/dosbox.slim.js +7 -4
- package/src/profiles/full.js +7 -4
- package/src/profiles/full.slim.js +7 -4
- package/src/profiles/light.js +7 -4
- package/src/profiles/light.slim.js +7 -4
- package/src/profiles/nuked.js +7 -4
- package/src/profiles/nuked.slim.js +7 -4
- package/src/utils/constants.js +51 -0
package/src/libadlmidi.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* libADLMIDI-JS - Main Thread Interface
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* High-level API for real-time OPL3 FM synthesis in the browser.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```javascript
|
|
8
8
|
* import { AdlMidi } from 'libadlmidi-js';
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* const synth = new AdlMidi();
|
|
11
11
|
* await synth.init('/path/to/processor.js');
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* synth.noteOn(0, 60, 100); // Middle C on channel 0
|
|
14
14
|
* synth.noteOff(0, 60);
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { Emulator, TrackOption } from './utils/constants.js';
|
|
19
|
+
export { Emulator, TrackOption };
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* Bank identifier for instrument access
|
|
20
23
|
* @typedef {Object} BankId
|
|
@@ -73,45 +76,13 @@
|
|
|
73
76
|
* @property {boolean} [deepTremolo] - Enable deep tremolo
|
|
74
77
|
*/
|
|
75
78
|
|
|
76
|
-
/**
|
|
77
|
-
* Available OPL2/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
|
-
/** Nuked OPL2 Lite - Lightweight OPL2 emulation for AdLib-era music */
|
|
107
|
-
NUKED_OPL2_LITE: 11,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
79
|
export class AdlMidi {
|
|
111
80
|
/** @type {boolean} */
|
|
112
81
|
#ready = false;
|
|
113
82
|
/** @type {Map<string, Set<Function>>} */
|
|
114
83
|
#messageHandlers = new Map();
|
|
84
|
+
/** @type {number} */
|
|
85
|
+
#nextRequestId = 0;
|
|
115
86
|
|
|
116
87
|
/**
|
|
117
88
|
* Create a new AdlMidi instance
|
|
@@ -231,6 +202,29 @@ export class AdlMidi {
|
|
|
231
202
|
this.#messageHandlers.get(type)?.add(wrappedHandler);
|
|
232
203
|
}
|
|
233
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Register a one-time handler correlated by request ID.
|
|
207
|
+
* Allows concurrent operations of the same type without reply misrouting.
|
|
208
|
+
* @param {string} type - Message type
|
|
209
|
+
* @param {number} reqId - Request ID to match against
|
|
210
|
+
* @param {Function} handler - Handler function
|
|
211
|
+
*/
|
|
212
|
+
#onceCorrelatedMessage(type, reqId, handler) {
|
|
213
|
+
if (!this.#messageHandlers.has(type)) {
|
|
214
|
+
this.#messageHandlers.set(type, new Set());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** @param {{reqId?: number}} msg */
|
|
218
|
+
const filteredHandler = (msg) => {
|
|
219
|
+
if (msg.reqId === reqId) {
|
|
220
|
+
this.#messageHandlers.get(type)?.delete(filteredHandler);
|
|
221
|
+
handler(msg);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
this.#messageHandlers.get(type)?.add(filteredHandler);
|
|
226
|
+
}
|
|
227
|
+
|
|
234
228
|
/**
|
|
235
229
|
* Send a message to the processor
|
|
236
230
|
* @param {Object} msg - Message to send
|
|
@@ -369,7 +363,7 @@ export class AdlMidi {
|
|
|
369
363
|
* @param {ArrayBuffer} arrayBuffer - Bank file data
|
|
370
364
|
* @returns {Promise<void>}
|
|
371
365
|
*/
|
|
372
|
-
async
|
|
366
|
+
async loadBankData(arrayBuffer) {
|
|
373
367
|
return new Promise((resolve, reject) => {
|
|
374
368
|
this.#onceMessage('bankLoaded', /** @param {{success: boolean, error?: string}} msg */(msg) => {
|
|
375
369
|
if (msg.success) {
|
|
@@ -379,7 +373,7 @@ export class AdlMidi {
|
|
|
379
373
|
}
|
|
380
374
|
});
|
|
381
375
|
|
|
382
|
-
this.#send({ type: '
|
|
376
|
+
this.#send({ type: 'loadBankData', data: arrayBuffer });
|
|
383
377
|
});
|
|
384
378
|
}
|
|
385
379
|
|
|
@@ -472,6 +466,19 @@ export class AdlMidi {
|
|
|
472
466
|
});
|
|
473
467
|
}
|
|
474
468
|
|
|
469
|
+
/**
|
|
470
|
+
* Get the number of 4-operator channels obtained
|
|
471
|
+
* @returns {Promise<number>}
|
|
472
|
+
*/
|
|
473
|
+
async getNumFourOpChannelsObtained() {
|
|
474
|
+
return new Promise((resolve) => {
|
|
475
|
+
this.#onceMessage('numFourOpChannelsObtained', /** @param {{channels: number}} msg */(msg) => {
|
|
476
|
+
resolve(msg.channels);
|
|
477
|
+
});
|
|
478
|
+
this.#send({ type: 'getNumFourOpChannelsObtained' });
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
475
482
|
/**
|
|
476
483
|
* Enable/disable scaling of modulators by volume
|
|
477
484
|
* @param {boolean} enabled
|
|
@@ -531,35 +538,61 @@ export class AdlMidi {
|
|
|
531
538
|
}
|
|
532
539
|
|
|
533
540
|
/**
|
|
534
|
-
* Set the volume model
|
|
541
|
+
* Set the volume range model
|
|
535
542
|
* @param {number} model - Volume model number
|
|
536
543
|
*/
|
|
537
|
-
|
|
538
|
-
this.#send({ type: '
|
|
544
|
+
setVolumeRangeModel(model) {
|
|
545
|
+
this.#send({ type: 'setVolumeRangeModel', model });
|
|
539
546
|
}
|
|
540
547
|
|
|
541
548
|
/**
|
|
542
|
-
* Enable/disable
|
|
549
|
+
* Enable/disable soft stereo panning
|
|
543
550
|
* @param {boolean} enabled
|
|
544
551
|
*/
|
|
545
|
-
|
|
546
|
-
this.#send({ type: '
|
|
552
|
+
setSoftPanEnabled(enabled) {
|
|
553
|
+
this.#send({ type: 'setSoftPanEnabled', enabled });
|
|
547
554
|
}
|
|
548
555
|
|
|
549
556
|
/**
|
|
550
557
|
* Enable/disable deep vibrato
|
|
551
558
|
* @param {boolean} enabled
|
|
552
559
|
*/
|
|
553
|
-
|
|
554
|
-
this.#send({ type: '
|
|
560
|
+
setDeepVibrato(enabled) {
|
|
561
|
+
this.#send({ type: 'setDeepVibrato', enabled });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Get deep vibrato state
|
|
566
|
+
* @returns {Promise<boolean>}
|
|
567
|
+
*/
|
|
568
|
+
async getDeepVibrato() {
|
|
569
|
+
return new Promise((resolve) => {
|
|
570
|
+
this.#onceMessage('deepVibrato', /** @param {{enabled: boolean}} msg */(msg) => {
|
|
571
|
+
resolve(msg.enabled);
|
|
572
|
+
});
|
|
573
|
+
this.#send({ type: 'getDeepVibrato' });
|
|
574
|
+
});
|
|
555
575
|
}
|
|
556
576
|
|
|
557
577
|
/**
|
|
558
578
|
* Enable/disable deep tremolo
|
|
559
579
|
* @param {boolean} enabled
|
|
560
580
|
*/
|
|
561
|
-
|
|
562
|
-
this.#send({ type: '
|
|
581
|
+
setDeepTremolo(enabled) {
|
|
582
|
+
this.#send({ type: 'setDeepTremolo', enabled });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get deep tremolo state
|
|
587
|
+
* @returns {Promise<boolean>}
|
|
588
|
+
*/
|
|
589
|
+
async getDeepTremolo() {
|
|
590
|
+
return new Promise((resolve) => {
|
|
591
|
+
this.#onceMessage('deepTremolo', /** @param {{enabled: boolean}} msg */(msg) => {
|
|
592
|
+
resolve(msg.enabled);
|
|
593
|
+
});
|
|
594
|
+
this.#send({ type: 'getDeepTremolo' });
|
|
595
|
+
});
|
|
563
596
|
}
|
|
564
597
|
|
|
565
598
|
/**
|
|
@@ -577,7 +610,7 @@ export class AdlMidi {
|
|
|
577
610
|
* - nuked profile: NUKED only
|
|
578
611
|
* - dosbox profile: DOSBOX only
|
|
579
612
|
* - light profile: NUKED, DOSBOX
|
|
580
|
-
* - full profile: NUKED, DOSBOX, OPAL, JAVA,
|
|
613
|
+
* - full profile: NUKED, DOSBOX, OPAL, JAVA, ESFMu, YMFM_OPL2, YMFM_OPL3
|
|
581
614
|
*
|
|
582
615
|
* @param {number} emulator - Emulator ID from the Emulator enum
|
|
583
616
|
* @returns {Promise<void>} Resolves when emulator is switched, rejects if unavailable
|
|
@@ -614,6 +647,19 @@ export class AdlMidi {
|
|
|
614
647
|
});
|
|
615
648
|
}
|
|
616
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Get the last error info for the player instance
|
|
652
|
+
* @returns {Promise<string>}
|
|
653
|
+
*/
|
|
654
|
+
async getErrorInfo() {
|
|
655
|
+
return new Promise((resolve) => {
|
|
656
|
+
this.#onceMessage('errorInfo', /** @param {{info: string}} msg */(msg) => {
|
|
657
|
+
resolve(msg.info);
|
|
658
|
+
});
|
|
659
|
+
this.#send({ type: 'getErrorInfo' });
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
617
663
|
/**
|
|
618
664
|
* Get the version string of the linked libADLMIDI library
|
|
619
665
|
* @returns {Promise<string>}
|
|
@@ -670,12 +716,12 @@ export class AdlMidi {
|
|
|
670
716
|
* Get the volume range model
|
|
671
717
|
* @returns {Promise<number>}
|
|
672
718
|
*/
|
|
673
|
-
async
|
|
719
|
+
async getVolumeRangeModel() {
|
|
674
720
|
return new Promise((resolve) => {
|
|
675
|
-
this.#onceMessage('
|
|
721
|
+
this.#onceMessage('volumeRangeModel', /** @param {{model: number}} msg */(msg) => {
|
|
676
722
|
resolve(msg.model);
|
|
677
723
|
});
|
|
678
|
-
this.#send({ type: '
|
|
724
|
+
this.#send({ type: 'getVolumeRangeModel' });
|
|
679
725
|
});
|
|
680
726
|
}
|
|
681
727
|
|
|
@@ -696,6 +742,119 @@ export class AdlMidi {
|
|
|
696
742
|
});
|
|
697
743
|
}
|
|
698
744
|
|
|
745
|
+
// ================== Bank Management API ==================
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Reserve a number of banks
|
|
749
|
+
* @param {number} count - Number of banks to reserve
|
|
750
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
751
|
+
*/
|
|
752
|
+
async reserveBanks(count) {
|
|
753
|
+
const reqId = this.#nextRequestId++;
|
|
754
|
+
return new Promise((resolve, reject) => {
|
|
755
|
+
this.#onceCorrelatedMessage('banksReserved', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
756
|
+
if (msg.success) {
|
|
757
|
+
resolve();
|
|
758
|
+
} else {
|
|
759
|
+
reject(new Error('Failed to reserve banks'));
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
this.#send({ type: 'reserveBanks', count, reqId });
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Get the bank ID for a given bank identifier
|
|
768
|
+
* @param {BankId} bankId - Bank identifier
|
|
769
|
+
* @returns {Promise<{percussive: number, msb: number, lsb: number}|null>} Bank ID or null if not found
|
|
770
|
+
*/
|
|
771
|
+
async getBankId(bankId) {
|
|
772
|
+
const reqId = this.#nextRequestId++;
|
|
773
|
+
return new Promise((resolve) => {
|
|
774
|
+
this.#onceCorrelatedMessage('bankId', reqId, /** @param {{id: {percussive: number, msb: number, lsb: number}|null}} msg */(msg) => {
|
|
775
|
+
resolve(msg.id);
|
|
776
|
+
});
|
|
777
|
+
this.#send({ type: 'getBankId', bankId, reqId });
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Remove a bank by its identifier
|
|
783
|
+
* @param {BankId} bankId - Bank identifier
|
|
784
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
785
|
+
*/
|
|
786
|
+
async removeBank(bankId) {
|
|
787
|
+
const reqId = this.#nextRequestId++;
|
|
788
|
+
return new Promise((resolve, reject) => {
|
|
789
|
+
this.#onceCorrelatedMessage('bankRemoved', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
790
|
+
if (msg.success) {
|
|
791
|
+
resolve();
|
|
792
|
+
} else {
|
|
793
|
+
reject(new Error('Failed to remove bank'));
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
this.#send({ type: 'removeBank', bankId, reqId });
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Load an embedded bank into a custom bank slot
|
|
802
|
+
* @param {BankId} bankId - Target bank identifier
|
|
803
|
+
* @param {number} num - Embedded bank number to load
|
|
804
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
805
|
+
*/
|
|
806
|
+
async loadEmbeddedBank(bankId, num) {
|
|
807
|
+
const reqId = this.#nextRequestId++;
|
|
808
|
+
return new Promise((resolve, reject) => {
|
|
809
|
+
this.#onceCorrelatedMessage('embeddedBankLoaded', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
810
|
+
if (msg.success) {
|
|
811
|
+
resolve();
|
|
812
|
+
} else {
|
|
813
|
+
reject(new Error('Failed to load embedded bank'));
|
|
814
|
+
}
|
|
815
|
+
});
|
|
816
|
+
this.#send({ type: 'loadEmbeddedBank', bankId, num, reqId });
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ================== SysEx API ==================
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Send a System Exclusive (SysEx) message
|
|
824
|
+
* @param {Uint8Array|ArrayBuffer} data - SysEx message data
|
|
825
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
826
|
+
*/
|
|
827
|
+
async systemExclusive(data) {
|
|
828
|
+
const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
|
|
829
|
+
const reqId = this.#nextRequestId++;
|
|
830
|
+
return new Promise((resolve, reject) => {
|
|
831
|
+
this.#onceCorrelatedMessage('systemExclusiveSent', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
832
|
+
if (msg.success) {
|
|
833
|
+
resolve();
|
|
834
|
+
} else {
|
|
835
|
+
reject(new Error('Failed to send system exclusive message'));
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
this.#send({ type: 'systemExclusive', data: Array.from(bytes), reqId });
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// ================== Debug / Diagnostics API ==================
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Describe the current state of all channels (debug utility)
|
|
846
|
+
* @returns {Promise<{text: string, attr: Uint8Array}>} Channel state text and raw per-channel attribute bytes
|
|
847
|
+
*/
|
|
848
|
+
async describeChannels() {
|
|
849
|
+
const reqId = this.#nextRequestId++;
|
|
850
|
+
return new Promise((resolve) => {
|
|
851
|
+
this.#onceCorrelatedMessage('channelsDescribed', reqId, /** @param {{text: string, attr: number[]}} msg */(msg) => {
|
|
852
|
+
resolve({ text: msg.text, attr: new Uint8Array(msg.attr) });
|
|
853
|
+
});
|
|
854
|
+
this.#send({ type: 'describeChannels', reqId });
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
699
858
|
/**
|
|
700
859
|
* Reset the synthesizer
|
|
701
860
|
* @returns {void}
|
|
@@ -751,6 +910,47 @@ export class AdlMidi {
|
|
|
751
910
|
});
|
|
752
911
|
}
|
|
753
912
|
|
|
913
|
+
/**
|
|
914
|
+
* Get the number of track titles in the loaded MIDI file
|
|
915
|
+
* @returns {Promise<number>}
|
|
916
|
+
*/
|
|
917
|
+
async getTrackTitleCount() {
|
|
918
|
+
return new Promise((resolve) => {
|
|
919
|
+
this.#onceMessage('trackTitleCount', /** @param {{count: number}} msg */(msg) => {
|
|
920
|
+
resolve(msg.count);
|
|
921
|
+
});
|
|
922
|
+
this.#send({ type: 'getTrackTitleCount' });
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
/**
|
|
927
|
+
* Get a track title by index
|
|
928
|
+
* @param {number} index - Track title index
|
|
929
|
+
* @returns {Promise<string>}
|
|
930
|
+
*/
|
|
931
|
+
async getTrackTitle(index) {
|
|
932
|
+
const reqId = this.#nextRequestId++;
|
|
933
|
+
return new Promise((resolve) => {
|
|
934
|
+
this.#onceCorrelatedMessage('trackTitle', reqId, /** @param {{title: string}} msg */(msg) => {
|
|
935
|
+
resolve(msg.title);
|
|
936
|
+
});
|
|
937
|
+
this.#send({ type: 'getTrackTitle', index, reqId });
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Get the number of MIDI markers in the loaded file
|
|
943
|
+
* @returns {Promise<number>}
|
|
944
|
+
*/
|
|
945
|
+
async getMarkerCount() {
|
|
946
|
+
return new Promise((resolve) => {
|
|
947
|
+
this.#onceMessage('markerCount', /** @param {{count: number}} msg */(msg) => {
|
|
948
|
+
resolve(msg.count);
|
|
949
|
+
});
|
|
950
|
+
this.#send({ type: 'getMarkerCount' });
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
754
954
|
/**
|
|
755
955
|
* Start or resume MIDI file playback
|
|
756
956
|
* @returns {void}
|
|
@@ -781,8 +981,126 @@ export class AdlMidi {
|
|
|
781
981
|
* @param {boolean} enabled - Whether to loop
|
|
782
982
|
* @returns {void}
|
|
783
983
|
*/
|
|
784
|
-
|
|
785
|
-
this.#send({ type: '
|
|
984
|
+
setLoopEnabled(enabled) {
|
|
985
|
+
this.#send({ type: 'setLoopEnabled', enabled });
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Set the number of loop repetitions
|
|
990
|
+
* @param {number} count - Loop count (-1 = infinite, 0 = no loops, 1+ = number of loops)
|
|
991
|
+
*/
|
|
992
|
+
setLoopCount(count) {
|
|
993
|
+
this.#send({ type: 'setLoopCount', count });
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Enable/disable loop hooks only mode
|
|
998
|
+
* @param {boolean} enabled
|
|
999
|
+
*/
|
|
1000
|
+
setLoopHooksOnly(enabled) {
|
|
1001
|
+
this.#send({ type: 'setLoopHooksOnly', enabled });
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Get the loop start time in seconds
|
|
1006
|
+
* @returns {Promise<number>}
|
|
1007
|
+
*/
|
|
1008
|
+
async getLoopStartTime() {
|
|
1009
|
+
return new Promise((resolve) => {
|
|
1010
|
+
this.#onceMessage('loopStartTime', /** @param {{time: number}} msg */(msg) => {
|
|
1011
|
+
resolve(msg.time);
|
|
1012
|
+
});
|
|
1013
|
+
this.#send({ type: 'getLoopStartTime' });
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/**
|
|
1018
|
+
* Get the loop end time in seconds
|
|
1019
|
+
* @returns {Promise<number>}
|
|
1020
|
+
*/
|
|
1021
|
+
async getLoopEndTime() {
|
|
1022
|
+
return new Promise((resolve) => {
|
|
1023
|
+
this.#onceMessage('loopEndTime', /** @param {{time: number}} msg */(msg) => {
|
|
1024
|
+
resolve(msg.time);
|
|
1025
|
+
});
|
|
1026
|
+
this.#send({ type: 'getLoopEndTime' });
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Select a song number for multi-song MIDI files
|
|
1032
|
+
* @param {number} num - Song number (0-based)
|
|
1033
|
+
*/
|
|
1034
|
+
selectSongNum(num) {
|
|
1035
|
+
this.#send({ type: 'selectSongNum', num });
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Get the number of songs in the loaded MIDI file
|
|
1040
|
+
* @returns {Promise<number>}
|
|
1041
|
+
*/
|
|
1042
|
+
async getSongsCount() {
|
|
1043
|
+
return new Promise((resolve) => {
|
|
1044
|
+
this.#onceMessage('songsCount', /** @param {{count: number}} msg */(msg) => {
|
|
1045
|
+
resolve(msg.count);
|
|
1046
|
+
});
|
|
1047
|
+
this.#send({ type: 'getSongsCount' });
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Get the number of tracks in the loaded MIDI file
|
|
1053
|
+
* @returns {Promise<number>}
|
|
1054
|
+
*/
|
|
1055
|
+
async getTrackCount() {
|
|
1056
|
+
return new Promise((resolve) => {
|
|
1057
|
+
this.#onceMessage('trackCount', /** @param {{count: number}} msg */(msg) => {
|
|
1058
|
+
resolve(msg.count);
|
|
1059
|
+
});
|
|
1060
|
+
this.#send({ type: 'getTrackCount' });
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* Set track options (enable, mute, or solo)
|
|
1066
|
+
* Use the TrackOption enum: TrackOption.ON (1), TrackOption.OFF (2), TrackOption.SOLO (3).
|
|
1067
|
+
* Note: Passing 0 is a silent no-op that resolves without changing state.
|
|
1068
|
+
* @param {number} track - Track index
|
|
1069
|
+
* @param {number} options - Track option from TrackOption enum
|
|
1070
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
1071
|
+
*/
|
|
1072
|
+
async setTrackOptions(track, options) {
|
|
1073
|
+
const reqId = this.#nextRequestId++;
|
|
1074
|
+
return new Promise((resolve, reject) => {
|
|
1075
|
+
this.#onceCorrelatedMessage('trackOptionsSet', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
1076
|
+
if (msg.success) {
|
|
1077
|
+
resolve();
|
|
1078
|
+
} else {
|
|
1079
|
+
reject(new Error(`Failed to set track options for track ${track}`));
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
this.#send({ type: 'setTrackOptions', track, options, reqId });
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Enable or disable a MIDI channel
|
|
1088
|
+
* @param {number} channel - MIDI channel (0-15)
|
|
1089
|
+
* @param {boolean} enabled - Whether to enable the channel
|
|
1090
|
+
* @returns {Promise<void>} Resolves on success, rejects on failure
|
|
1091
|
+
*/
|
|
1092
|
+
async setChannelEnabled(channel, enabled) {
|
|
1093
|
+
const reqId = this.#nextRequestId++;
|
|
1094
|
+
return new Promise((resolve, reject) => {
|
|
1095
|
+
this.#onceCorrelatedMessage('channelEnabledSet', reqId, /** @param {{success: boolean}} msg */(msg) => {
|
|
1096
|
+
if (msg.success) {
|
|
1097
|
+
resolve();
|
|
1098
|
+
} else {
|
|
1099
|
+
reject(new Error(`Failed to set channel ${channel} enabled state`));
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
this.#send({ type: 'setChannelEnabled', channel, enabled, reqId });
|
|
1103
|
+
});
|
|
786
1104
|
}
|
|
787
1105
|
|
|
788
1106
|
/**
|