libadlmidi-js 1.1.1 → 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 -63
- 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 +468 -22
- 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 -54
- 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,43 +76,13 @@
|
|
|
73
76
|
* @property {boolean} [deepTremolo] - Enable deep tremolo
|
|
74
77
|
*/
|
|
75
78
|
|
|
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
79
|
export class AdlMidi {
|
|
109
80
|
/** @type {boolean} */
|
|
110
81
|
#ready = false;
|
|
111
82
|
/** @type {Map<string, Set<Function>>} */
|
|
112
83
|
#messageHandlers = new Map();
|
|
84
|
+
/** @type {number} */
|
|
85
|
+
#nextRequestId = 0;
|
|
113
86
|
|
|
114
87
|
/**
|
|
115
88
|
* Create a new AdlMidi instance
|
|
@@ -229,6 +202,29 @@ export class AdlMidi {
|
|
|
229
202
|
this.#messageHandlers.get(type)?.add(wrappedHandler);
|
|
230
203
|
}
|
|
231
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
|
+
|
|
232
228
|
/**
|
|
233
229
|
* Send a message to the processor
|
|
234
230
|
* @param {Object} msg - Message to send
|
|
@@ -367,7 +363,7 @@ export class AdlMidi {
|
|
|
367
363
|
* @param {ArrayBuffer} arrayBuffer - Bank file data
|
|
368
364
|
* @returns {Promise<void>}
|
|
369
365
|
*/
|
|
370
|
-
async
|
|
366
|
+
async loadBankData(arrayBuffer) {
|
|
371
367
|
return new Promise((resolve, reject) => {
|
|
372
368
|
this.#onceMessage('bankLoaded', /** @param {{success: boolean, error?: string}} msg */(msg) => {
|
|
373
369
|
if (msg.success) {
|
|
@@ -377,7 +373,7 @@ export class AdlMidi {
|
|
|
377
373
|
}
|
|
378
374
|
});
|
|
379
375
|
|
|
380
|
-
this.#send({ type: '
|
|
376
|
+
this.#send({ type: 'loadBankData', data: arrayBuffer });
|
|
381
377
|
});
|
|
382
378
|
}
|
|
383
379
|
|
|
@@ -470,6 +466,19 @@ export class AdlMidi {
|
|
|
470
466
|
});
|
|
471
467
|
}
|
|
472
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
|
+
|
|
473
482
|
/**
|
|
474
483
|
* Enable/disable scaling of modulators by volume
|
|
475
484
|
* @param {boolean} enabled
|
|
@@ -529,35 +538,61 @@ export class AdlMidi {
|
|
|
529
538
|
}
|
|
530
539
|
|
|
531
540
|
/**
|
|
532
|
-
* Set the volume model
|
|
541
|
+
* Set the volume range model
|
|
533
542
|
* @param {number} model - Volume model number
|
|
534
543
|
*/
|
|
535
|
-
|
|
536
|
-
this.#send({ type: '
|
|
544
|
+
setVolumeRangeModel(model) {
|
|
545
|
+
this.#send({ type: 'setVolumeRangeModel', model });
|
|
537
546
|
}
|
|
538
547
|
|
|
539
548
|
/**
|
|
540
|
-
* Enable/disable
|
|
549
|
+
* Enable/disable soft stereo panning
|
|
541
550
|
* @param {boolean} enabled
|
|
542
551
|
*/
|
|
543
|
-
|
|
544
|
-
this.#send({ type: '
|
|
552
|
+
setSoftPanEnabled(enabled) {
|
|
553
|
+
this.#send({ type: 'setSoftPanEnabled', enabled });
|
|
545
554
|
}
|
|
546
555
|
|
|
547
556
|
/**
|
|
548
557
|
* Enable/disable deep vibrato
|
|
549
558
|
* @param {boolean} enabled
|
|
550
559
|
*/
|
|
551
|
-
|
|
552
|
-
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
|
+
});
|
|
553
575
|
}
|
|
554
576
|
|
|
555
577
|
/**
|
|
556
578
|
* Enable/disable deep tremolo
|
|
557
579
|
* @param {boolean} enabled
|
|
558
580
|
*/
|
|
559
|
-
|
|
560
|
-
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
|
+
});
|
|
561
596
|
}
|
|
562
597
|
|
|
563
598
|
/**
|
|
@@ -575,7 +610,7 @@ export class AdlMidi {
|
|
|
575
610
|
* - nuked profile: NUKED only
|
|
576
611
|
* - dosbox profile: DOSBOX only
|
|
577
612
|
* - light profile: NUKED, DOSBOX
|
|
578
|
-
* - full profile: NUKED, DOSBOX, OPAL, JAVA,
|
|
613
|
+
* - full profile: NUKED, DOSBOX, OPAL, JAVA, ESFMu, YMFM_OPL2, YMFM_OPL3
|
|
579
614
|
*
|
|
580
615
|
* @param {number} emulator - Emulator ID from the Emulator enum
|
|
581
616
|
* @returns {Promise<void>} Resolves when emulator is switched, rejects if unavailable
|
|
@@ -612,6 +647,19 @@ export class AdlMidi {
|
|
|
612
647
|
});
|
|
613
648
|
}
|
|
614
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
|
+
|
|
615
663
|
/**
|
|
616
664
|
* Get the version string of the linked libADLMIDI library
|
|
617
665
|
* @returns {Promise<string>}
|
|
@@ -668,12 +716,12 @@ export class AdlMidi {
|
|
|
668
716
|
* Get the volume range model
|
|
669
717
|
* @returns {Promise<number>}
|
|
670
718
|
*/
|
|
671
|
-
async
|
|
719
|
+
async getVolumeRangeModel() {
|
|
672
720
|
return new Promise((resolve) => {
|
|
673
|
-
this.#onceMessage('
|
|
721
|
+
this.#onceMessage('volumeRangeModel', /** @param {{model: number}} msg */(msg) => {
|
|
674
722
|
resolve(msg.model);
|
|
675
723
|
});
|
|
676
|
-
this.#send({ type: '
|
|
724
|
+
this.#send({ type: 'getVolumeRangeModel' });
|
|
677
725
|
});
|
|
678
726
|
}
|
|
679
727
|
|
|
@@ -694,6 +742,119 @@ export class AdlMidi {
|
|
|
694
742
|
});
|
|
695
743
|
}
|
|
696
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
|
+
|
|
697
858
|
/**
|
|
698
859
|
* Reset the synthesizer
|
|
699
860
|
* @returns {void}
|
|
@@ -749,6 +910,47 @@ export class AdlMidi {
|
|
|
749
910
|
});
|
|
750
911
|
}
|
|
751
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
|
+
|
|
752
954
|
/**
|
|
753
955
|
* Start or resume MIDI file playback
|
|
754
956
|
* @returns {void}
|
|
@@ -779,8 +981,126 @@ export class AdlMidi {
|
|
|
779
981
|
* @param {boolean} enabled - Whether to loop
|
|
780
982
|
* @returns {void}
|
|
781
983
|
*/
|
|
782
|
-
|
|
783
|
-
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
|
+
});
|
|
784
1104
|
}
|
|
785
1105
|
|
|
786
1106
|
/**
|