libadlmidi-js 1.2.0 → 2.1.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 (76) hide show
  1. package/README.md +8 -5
  2. package/dist/core.d.ts +191 -4
  3. package/dist/fm_banks/manifest.json +1 -1
  4. package/dist/libadlmidi.d.ts +146 -66
  5. package/dist/libadlmidi.dosbox.browser.js +1 -1
  6. package/dist/libadlmidi.dosbox.browser.wasm +0 -0
  7. package/dist/libadlmidi.dosbox.core.js +1 -1
  8. package/dist/libadlmidi.dosbox.core.wasm +0 -0
  9. package/dist/libadlmidi.dosbox.js +0 -0
  10. package/dist/libadlmidi.dosbox.processor.js +247 -74
  11. package/dist/libadlmidi.dosbox.slim.browser.js +1 -1
  12. package/dist/libadlmidi.dosbox.slim.browser.wasm +0 -0
  13. package/dist/libadlmidi.dosbox.slim.core.js +1 -1
  14. package/dist/libadlmidi.dosbox.slim.core.wasm +0 -0
  15. package/dist/libadlmidi.dosbox.slim.js +0 -0
  16. package/dist/libadlmidi.dosbox.slim.processor.js +247 -74
  17. package/dist/libadlmidi.full.browser.js +1 -1
  18. package/dist/libadlmidi.full.browser.wasm +0 -0
  19. package/dist/libadlmidi.full.core.js +1 -1
  20. package/dist/libadlmidi.full.core.wasm +0 -0
  21. package/dist/libadlmidi.full.js +0 -0
  22. package/dist/libadlmidi.full.processor.js +247 -74
  23. package/dist/libadlmidi.full.slim.browser.js +1 -1
  24. package/dist/libadlmidi.full.slim.browser.wasm +0 -0
  25. package/dist/libadlmidi.full.slim.core.js +1 -1
  26. package/dist/libadlmidi.full.slim.core.wasm +0 -0
  27. package/dist/libadlmidi.full.slim.js +0 -0
  28. package/dist/libadlmidi.full.slim.processor.js +247 -74
  29. package/dist/libadlmidi.js +473 -24
  30. package/dist/libadlmidi.js.map +3 -3
  31. package/dist/libadlmidi.light.browser.js +1 -1
  32. package/dist/libadlmidi.light.browser.wasm +0 -0
  33. package/dist/libadlmidi.light.core.js +1 -1
  34. package/dist/libadlmidi.light.core.wasm +0 -0
  35. package/dist/libadlmidi.light.js +0 -0
  36. package/dist/libadlmidi.light.processor.js +247 -74
  37. package/dist/libadlmidi.light.slim.browser.js +1 -1
  38. package/dist/libadlmidi.light.slim.browser.wasm +0 -0
  39. package/dist/libadlmidi.light.slim.core.js +1 -1
  40. package/dist/libadlmidi.light.slim.core.wasm +0 -0
  41. package/dist/libadlmidi.light.slim.js +0 -0
  42. package/dist/libadlmidi.light.slim.processor.js +247 -74
  43. package/dist/libadlmidi.nuked.browser.js +1 -1
  44. package/dist/libadlmidi.nuked.browser.wasm +0 -0
  45. package/dist/libadlmidi.nuked.core.js +1 -1
  46. package/dist/libadlmidi.nuked.core.wasm +0 -0
  47. package/dist/libadlmidi.nuked.js +0 -0
  48. package/dist/libadlmidi.nuked.processor.js +247 -74
  49. package/dist/libadlmidi.nuked.slim.browser.js +1 -1
  50. package/dist/libadlmidi.nuked.slim.browser.wasm +0 -0
  51. package/dist/libadlmidi.nuked.slim.core.js +1 -1
  52. package/dist/libadlmidi.nuked.slim.core.wasm +0 -0
  53. package/dist/libadlmidi.nuked.slim.js +0 -0
  54. package/dist/libadlmidi.nuked.slim.processor.js +247 -74
  55. package/dist/profiles/dosbox.d.ts +7 -2
  56. package/dist/profiles/dosbox.slim.d.ts +7 -2
  57. package/dist/profiles/full.d.ts +7 -2
  58. package/dist/profiles/full.slim.d.ts +7 -2
  59. package/dist/profiles/light.d.ts +7 -2
  60. package/dist/profiles/light.slim.d.ts +7 -2
  61. package/dist/profiles/nuked.d.ts +7 -2
  62. package/dist/profiles/nuked.slim.d.ts +7 -2
  63. package/dist/utils/constants.d.ts +61 -0
  64. package/package.json +30 -9
  65. package/src/core.js +361 -4
  66. package/src/libadlmidi.js +379 -58
  67. package/src/processor.js +210 -12
  68. package/src/profiles/dosbox.js +20 -10
  69. package/src/profiles/dosbox.slim.js +20 -10
  70. package/src/profiles/full.js +21 -11
  71. package/src/profiles/full.slim.js +21 -11
  72. package/src/profiles/light.js +21 -11
  73. package/src/profiles/light.slim.js +21 -11
  74. package/src/profiles/nuked.js +21 -11
  75. package/src/profiles/nuked.slim.js +21 -11
  76. package/src/utils/constants.js +53 -0
@@ -6,12 +6,22 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
+ var __privateWrapper = (obj, member, setter, getter) => ({
10
+ set _(value) {
11
+ __privateSet(obj, member, value, setter);
12
+ },
13
+ get _() {
14
+ return __privateGet(obj, member, getter);
15
+ }
16
+ });
9
17
 
10
- // src/libadlmidi.js
18
+ // src/utils/constants.js
11
19
  var Emulator = Object.freeze({
12
20
  /** Nuked OPL3 v1.8 - Most accurate, higher CPU usage */
13
21
  NUKED: 0,
14
- /** Nuked OPL3 v1.7.4 - Slightly older version */
22
+ /** Optimized Nuked 1.8 fork by tgies with identical output */
23
+ NUKED_FAST: 1,
24
+ /** @deprecated Use NUKED_FAST */
15
25
  NUKED_174: 1,
16
26
  /** DosBox OPL3 - Good accuracy, lower CPU usage */
17
27
  DOSBOX: 2,
@@ -20,7 +30,7 @@ var Emulator = Object.freeze({
20
30
  /** Java OPL3 - Port of emu8950 */
21
31
  JAVA: 4,
22
32
  /** ESFMu - ESFM chip emulator */
23
- ESFMU: 5,
33
+ ESFMu: 5,
24
34
  /** MAME OPL2 */
25
35
  MAME_OPL2: 6,
26
36
  /** YMFM OPL2 */
@@ -34,7 +44,17 @@ var Emulator = Object.freeze({
34
44
  /** Nuked OPL2 Lite - Lightweight OPL2 emulation for AdLib-era music */
35
45
  NUKED_OPL2_LITE: 11
36
46
  });
37
- var _ready, _messageHandlers, _AdlMidi_instances, handleMessage_fn, onceMessage_fn, send_fn;
47
+ var TrackOption = Object.freeze({
48
+ /** Enable the track (default state) */
49
+ ON: 1,
50
+ /** Mute/disable the track */
51
+ OFF: 2,
52
+ /** Solo the track (mute all others) */
53
+ SOLO: 3
54
+ });
55
+
56
+ // src/libadlmidi.js
57
+ var _ready, _messageHandlers, _nextRequestId, _AdlMidi_instances, handleMessage_fn, onceMessage_fn, onceCorrelatedMessage_fn, send_fn;
38
58
  var AdlMidi = class {
39
59
  /**
40
60
  * Create a new AdlMidi instance
@@ -46,6 +66,8 @@ var AdlMidi = class {
46
66
  __privateAdd(this, _ready, false);
47
67
  /** @type {Map<string, Set<Function>>} */
48
68
  __privateAdd(this, _messageHandlers, /* @__PURE__ */ new Map());
69
+ /** @type {number} */
70
+ __privateAdd(this, _nextRequestId, 0);
49
71
  this.ctx = context || null;
50
72
  this.node = null;
51
73
  }
@@ -68,9 +90,11 @@ var AdlMidi = class {
68
90
  * @param {string} processorUrl - URL to the bundled processor JavaScript file
69
91
  * @param {string | null} [wasmUrl=null] - Optional URL to the .wasm file for split builds.
70
92
  * If not provided, assumes bundled version with embedded WASM.
93
+ * @param {object} [defaultSettings={}] - Initial synth settings applied before ready.
94
+ * Profile wrappers use this to set a default emulator.
71
95
  * @returns {Promise<void>}
72
96
  */
73
- async init(processorUrl, wasmUrl = null) {
97
+ async init(processorUrl, wasmUrl = null, defaultSettings = {}) {
74
98
  if (!this.ctx) {
75
99
  this.ctx = new AudioContext({ sampleRate: 44100 });
76
100
  }
@@ -87,8 +111,9 @@ var AdlMidi = class {
87
111
  this.node = new AudioWorkletNode(this.ctx, "adl-midi-processor", {
88
112
  processorOptions: {
89
113
  sampleRate: this.ctx.sampleRate,
90
- wasmBinary
114
+ wasmBinary,
91
115
  // null for bundled, ArrayBuffer for split
116
+ settings: defaultSettings
92
117
  }
93
118
  });
94
119
  this.node.connect(this.ctx.destination);
@@ -228,7 +253,7 @@ var AdlMidi = class {
228
253
  * @param {ArrayBuffer} arrayBuffer - Bank file data
229
254
  * @returns {Promise<void>}
230
255
  */
231
- async loadBank(arrayBuffer) {
256
+ async loadBankData(arrayBuffer) {
232
257
  return new Promise((resolve, reject) => {
233
258
  __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
234
259
  this,
@@ -242,7 +267,7 @@ var AdlMidi = class {
242
267
  }
243
268
  }
244
269
  );
245
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "loadBank", data: arrayBuffer });
270
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "loadBankData", data: arrayBuffer });
246
271
  });
247
272
  }
248
273
  /**
@@ -345,6 +370,23 @@ var AdlMidi = class {
345
370
  __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getNumFourOpChannels" });
346
371
  });
347
372
  }
373
+ /**
374
+ * Get the number of 4-operator channels obtained
375
+ * @returns {Promise<number>}
376
+ */
377
+ async getNumFourOpChannelsObtained() {
378
+ return new Promise((resolve) => {
379
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
380
+ this,
381
+ "numFourOpChannelsObtained",
382
+ /** @param {{channels: number}} msg */
383
+ (msg) => {
384
+ resolve(msg.channels);
385
+ }
386
+ );
387
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getNumFourOpChannelsObtained" });
388
+ });
389
+ }
348
390
  /**
349
391
  * Enable/disable scaling of modulators by volume
350
392
  * @param {boolean} enabled
@@ -408,32 +450,66 @@ var AdlMidi = class {
408
450
  });
409
451
  }
410
452
  /**
411
- * Set the volume model
453
+ * Set the volume range model
412
454
  * @param {number} model - Volume model number
413
455
  */
414
- setVolumeModel(model) {
415
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setVolumeModel", model });
456
+ setVolumeRangeModel(model) {
457
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setVolumeRangeModel", model });
416
458
  }
417
459
  /**
418
- * Enable/disable rhythm mode (percussion)
460
+ * Enable/disable soft stereo panning
419
461
  * @param {boolean} enabled
420
462
  */
421
- setPercussionMode(enabled) {
422
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setPercMode", enabled });
463
+ setSoftPanEnabled(enabled) {
464
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setSoftPanEnabled", enabled });
423
465
  }
424
466
  /**
425
467
  * Enable/disable deep vibrato
426
468
  * @param {boolean} enabled
427
469
  */
428
- setVibrato(enabled) {
429
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setVibrato", enabled });
470
+ setDeepVibrato(enabled) {
471
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setDeepVibrato", enabled });
472
+ }
473
+ /**
474
+ * Get deep vibrato state
475
+ * @returns {Promise<boolean>}
476
+ */
477
+ async getDeepVibrato() {
478
+ return new Promise((resolve) => {
479
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
480
+ this,
481
+ "deepVibrato",
482
+ /** @param {{enabled: boolean}} msg */
483
+ (msg) => {
484
+ resolve(msg.enabled);
485
+ }
486
+ );
487
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getDeepVibrato" });
488
+ });
430
489
  }
431
490
  /**
432
491
  * Enable/disable deep tremolo
433
492
  * @param {boolean} enabled
434
493
  */
435
- setTremolo(enabled) {
436
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setTremolo", enabled });
494
+ setDeepTremolo(enabled) {
495
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setDeepTremolo", enabled });
496
+ }
497
+ /**
498
+ * Get deep tremolo state
499
+ * @returns {Promise<boolean>}
500
+ */
501
+ async getDeepTremolo() {
502
+ return new Promise((resolve) => {
503
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
504
+ this,
505
+ "deepTremolo",
506
+ /** @param {{enabled: boolean}} msg */
507
+ (msg) => {
508
+ resolve(msg.enabled);
509
+ }
510
+ );
511
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getDeepTremolo" });
512
+ });
437
513
  }
438
514
  /**
439
515
  * Run emulator with PCM rate to reduce CPU usage
@@ -449,7 +525,7 @@ var AdlMidi = class {
449
525
  * - nuked profile: NUKED only
450
526
  * - dosbox profile: DOSBOX only
451
527
  * - light profile: NUKED, DOSBOX
452
- * - full profile: NUKED, DOSBOX, OPAL, JAVA, ESFMU, YMFM_OPL2, YMFM_OPL3
528
+ * - full profile: NUKED, DOSBOX, OPAL, JAVA, ESFMu, YMFM_OPL2, YMFM_OPL3
453
529
  *
454
530
  * @param {number} emulator - Emulator ID from the Emulator enum
455
531
  * @returns {Promise<void>} Resolves when emulator is switched, rejects if unavailable
@@ -494,6 +570,23 @@ var AdlMidi = class {
494
570
  __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getEmulatorName" });
495
571
  });
496
572
  }
573
+ /**
574
+ * Get the last error info for the player instance
575
+ * @returns {Promise<string>}
576
+ */
577
+ async getErrorInfo() {
578
+ return new Promise((resolve) => {
579
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
580
+ this,
581
+ "errorInfo",
582
+ /** @param {{info: string}} msg */
583
+ (msg) => {
584
+ resolve(msg.info);
585
+ }
586
+ );
587
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getErrorInfo" });
588
+ });
589
+ }
497
590
  /**
498
591
  * Get the version string of the linked libADLMIDI library
499
592
  * @returns {Promise<string>}
@@ -566,17 +659,17 @@ var AdlMidi = class {
566
659
  * Get the volume range model
567
660
  * @returns {Promise<number>}
568
661
  */
569
- async getVolumeModel() {
662
+ async getVolumeRangeModel() {
570
663
  return new Promise((resolve) => {
571
664
  __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
572
665
  this,
573
- "volumeModel",
666
+ "volumeRangeModel",
574
667
  /** @param {{model: number}} msg */
575
668
  (msg) => {
576
669
  resolve(msg.model);
577
670
  }
578
671
  );
579
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getVolumeModel" });
672
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getVolumeRangeModel" });
580
673
  });
581
674
  }
582
675
  /**
@@ -600,6 +693,146 @@ var AdlMidi = class {
600
693
  __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getEmbeddedBanks" });
601
694
  });
602
695
  }
696
+ // ================== Bank Management API ==================
697
+ /**
698
+ * Reserve a number of banks
699
+ * @param {number} count - Number of banks to reserve
700
+ * @returns {Promise<void>} Resolves on success, rejects on failure
701
+ */
702
+ async reserveBanks(count) {
703
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
704
+ return new Promise((resolve, reject) => {
705
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
706
+ this,
707
+ "banksReserved",
708
+ reqId,
709
+ /** @param {{success: boolean}} msg */
710
+ (msg) => {
711
+ if (msg.success) {
712
+ resolve();
713
+ } else {
714
+ reject(new Error("Failed to reserve banks"));
715
+ }
716
+ }
717
+ );
718
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "reserveBanks", count, reqId });
719
+ });
720
+ }
721
+ /**
722
+ * Get the bank ID for a given bank identifier
723
+ * @param {BankId} bankId - Bank identifier
724
+ * @returns {Promise<{percussive: number, msb: number, lsb: number}|null>} Bank ID or null if not found
725
+ */
726
+ async getBankId(bankId) {
727
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
728
+ return new Promise((resolve) => {
729
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
730
+ this,
731
+ "bankId",
732
+ reqId,
733
+ /** @param {{id: {percussive: number, msb: number, lsb: number}|null}} msg */
734
+ (msg) => {
735
+ resolve(msg.id);
736
+ }
737
+ );
738
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getBankId", bankId, reqId });
739
+ });
740
+ }
741
+ /**
742
+ * Remove a bank by its identifier
743
+ * @param {BankId} bankId - Bank identifier
744
+ * @returns {Promise<void>} Resolves on success, rejects on failure
745
+ */
746
+ async removeBank(bankId) {
747
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
748
+ return new Promise((resolve, reject) => {
749
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
750
+ this,
751
+ "bankRemoved",
752
+ reqId,
753
+ /** @param {{success: boolean}} msg */
754
+ (msg) => {
755
+ if (msg.success) {
756
+ resolve();
757
+ } else {
758
+ reject(new Error("Failed to remove bank"));
759
+ }
760
+ }
761
+ );
762
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "removeBank", bankId, reqId });
763
+ });
764
+ }
765
+ /**
766
+ * Load an embedded bank into a custom bank slot
767
+ * @param {BankId} bankId - Target bank identifier
768
+ * @param {number} num - Embedded bank number to load
769
+ * @returns {Promise<void>} Resolves on success, rejects on failure
770
+ */
771
+ async loadEmbeddedBank(bankId, num) {
772
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
773
+ return new Promise((resolve, reject) => {
774
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
775
+ this,
776
+ "embeddedBankLoaded",
777
+ reqId,
778
+ /** @param {{success: boolean}} msg */
779
+ (msg) => {
780
+ if (msg.success) {
781
+ resolve();
782
+ } else {
783
+ reject(new Error("Failed to load embedded bank"));
784
+ }
785
+ }
786
+ );
787
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "loadEmbeddedBank", bankId, num, reqId });
788
+ });
789
+ }
790
+ // ================== SysEx API ==================
791
+ /**
792
+ * Send a System Exclusive (SysEx) message
793
+ * @param {Uint8Array|ArrayBuffer} data - SysEx message data
794
+ * @returns {Promise<void>} Resolves on success, rejects on failure
795
+ */
796
+ async systemExclusive(data) {
797
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
798
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
799
+ return new Promise((resolve, reject) => {
800
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
801
+ this,
802
+ "systemExclusiveSent",
803
+ reqId,
804
+ /** @param {{success: boolean}} msg */
805
+ (msg) => {
806
+ if (msg.success) {
807
+ resolve();
808
+ } else {
809
+ reject(new Error("Failed to send system exclusive message"));
810
+ }
811
+ }
812
+ );
813
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "systemExclusive", data: Array.from(bytes), reqId });
814
+ });
815
+ }
816
+ // ================== Debug / Diagnostics API ==================
817
+ /**
818
+ * Describe the current state of all channels (debug utility)
819
+ * @returns {Promise<{text: string, attr: Uint8Array}>} Channel state text and raw per-channel attribute bytes
820
+ */
821
+ async describeChannels() {
822
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
823
+ return new Promise((resolve) => {
824
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
825
+ this,
826
+ "channelsDescribed",
827
+ reqId,
828
+ /** @param {{text: string, attr: number[]}} msg */
829
+ (msg) => {
830
+ resolve({ text: msg.text, attr: new Uint8Array(msg.attr) });
831
+ }
832
+ );
833
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "describeChannels", reqId });
834
+ });
835
+ }
603
836
  /**
604
837
  * Reset the synthesizer
605
838
  * @returns {void}
@@ -664,6 +897,60 @@ var AdlMidi = class {
664
897
  __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getMusicCopyright" });
665
898
  });
666
899
  }
900
+ /**
901
+ * Get the number of track titles in the loaded MIDI file
902
+ * @returns {Promise<number>}
903
+ */
904
+ async getTrackTitleCount() {
905
+ return new Promise((resolve) => {
906
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
907
+ this,
908
+ "trackTitleCount",
909
+ /** @param {{count: number}} msg */
910
+ (msg) => {
911
+ resolve(msg.count);
912
+ }
913
+ );
914
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getTrackTitleCount" });
915
+ });
916
+ }
917
+ /**
918
+ * Get a track title by index
919
+ * @param {number} index - Track title index
920
+ * @returns {Promise<string>}
921
+ */
922
+ async getTrackTitle(index) {
923
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
924
+ return new Promise((resolve) => {
925
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
926
+ this,
927
+ "trackTitle",
928
+ reqId,
929
+ /** @param {{title: string}} msg */
930
+ (msg) => {
931
+ resolve(msg.title);
932
+ }
933
+ );
934
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getTrackTitle", index, reqId });
935
+ });
936
+ }
937
+ /**
938
+ * Get the number of MIDI markers in the loaded file
939
+ * @returns {Promise<number>}
940
+ */
941
+ async getMarkerCount() {
942
+ return new Promise((resolve) => {
943
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
944
+ this,
945
+ "markerCount",
946
+ /** @param {{count: number}} msg */
947
+ (msg) => {
948
+ resolve(msg.count);
949
+ }
950
+ );
951
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getMarkerCount" });
952
+ });
953
+ }
667
954
  /**
668
955
  * Start or resume MIDI file playback
669
956
  * @returns {void}
@@ -691,8 +978,149 @@ var AdlMidi = class {
691
978
  * @param {boolean} enabled - Whether to loop
692
979
  * @returns {void}
693
980
  */
694
- setLoop(enabled) {
695
- __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setLoop", enabled });
981
+ setLoopEnabled(enabled) {
982
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setLoopEnabled", enabled });
983
+ }
984
+ /**
985
+ * Set the number of loop repetitions
986
+ * @param {number} count - Loop count (-1 = infinite, 0 = no loops, 1+ = number of loops)
987
+ */
988
+ setLoopCount(count) {
989
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setLoopCount", count });
990
+ }
991
+ /**
992
+ * Enable/disable loop hooks only mode
993
+ * @param {boolean} enabled
994
+ */
995
+ setLoopHooksOnly(enabled) {
996
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setLoopHooksOnly", enabled });
997
+ }
998
+ /**
999
+ * Get the loop start time in seconds
1000
+ * @returns {Promise<number>}
1001
+ */
1002
+ async getLoopStartTime() {
1003
+ return new Promise((resolve) => {
1004
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
1005
+ this,
1006
+ "loopStartTime",
1007
+ /** @param {{time: number}} msg */
1008
+ (msg) => {
1009
+ resolve(msg.time);
1010
+ }
1011
+ );
1012
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getLoopStartTime" });
1013
+ });
1014
+ }
1015
+ /**
1016
+ * Get the loop end time in seconds
1017
+ * @returns {Promise<number>}
1018
+ */
1019
+ async getLoopEndTime() {
1020
+ return new Promise((resolve) => {
1021
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
1022
+ this,
1023
+ "loopEndTime",
1024
+ /** @param {{time: number}} msg */
1025
+ (msg) => {
1026
+ resolve(msg.time);
1027
+ }
1028
+ );
1029
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getLoopEndTime" });
1030
+ });
1031
+ }
1032
+ /**
1033
+ * Select a song number for multi-song MIDI files
1034
+ * @param {number} num - Song number (0-based)
1035
+ */
1036
+ selectSongNum(num) {
1037
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "selectSongNum", num });
1038
+ }
1039
+ /**
1040
+ * Get the number of songs in the loaded MIDI file
1041
+ * @returns {Promise<number>}
1042
+ */
1043
+ async getSongsCount() {
1044
+ return new Promise((resolve) => {
1045
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
1046
+ this,
1047
+ "songsCount",
1048
+ /** @param {{count: number}} msg */
1049
+ (msg) => {
1050
+ resolve(msg.count);
1051
+ }
1052
+ );
1053
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getSongsCount" });
1054
+ });
1055
+ }
1056
+ /**
1057
+ * Get the number of tracks in the loaded MIDI file
1058
+ * @returns {Promise<number>}
1059
+ */
1060
+ async getTrackCount() {
1061
+ return new Promise((resolve) => {
1062
+ __privateMethod(this, _AdlMidi_instances, onceMessage_fn).call(
1063
+ this,
1064
+ "trackCount",
1065
+ /** @param {{count: number}} msg */
1066
+ (msg) => {
1067
+ resolve(msg.count);
1068
+ }
1069
+ );
1070
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "getTrackCount" });
1071
+ });
1072
+ }
1073
+ /**
1074
+ * Set track options (enable, mute, or solo)
1075
+ * Use the TrackOption enum: TrackOption.ON (1), TrackOption.OFF (2), TrackOption.SOLO (3).
1076
+ * Note: Passing 0 is a silent no-op that resolves without changing state.
1077
+ * @param {number} track - Track index
1078
+ * @param {number} options - Track option from TrackOption enum
1079
+ * @returns {Promise<void>} Resolves on success, rejects on failure
1080
+ */
1081
+ async setTrackOptions(track, options) {
1082
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
1083
+ return new Promise((resolve, reject) => {
1084
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
1085
+ this,
1086
+ "trackOptionsSet",
1087
+ reqId,
1088
+ /** @param {{success: boolean}} msg */
1089
+ (msg) => {
1090
+ if (msg.success) {
1091
+ resolve();
1092
+ } else {
1093
+ reject(new Error(`Failed to set track options for track ${track}`));
1094
+ }
1095
+ }
1096
+ );
1097
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setTrackOptions", track, options, reqId });
1098
+ });
1099
+ }
1100
+ /**
1101
+ * Enable or disable a MIDI channel
1102
+ * @param {number} channel - MIDI channel (0-15)
1103
+ * @param {boolean} enabled - Whether to enable the channel
1104
+ * @returns {Promise<void>} Resolves on success, rejects on failure
1105
+ */
1106
+ async setChannelEnabled(channel, enabled) {
1107
+ const reqId = __privateWrapper(this, _nextRequestId)._++;
1108
+ return new Promise((resolve, reject) => {
1109
+ __privateMethod(this, _AdlMidi_instances, onceCorrelatedMessage_fn).call(
1110
+ this,
1111
+ "channelEnabledSet",
1112
+ reqId,
1113
+ /** @param {{success: boolean}} msg */
1114
+ (msg) => {
1115
+ if (msg.success) {
1116
+ resolve();
1117
+ } else {
1118
+ reject(new Error(`Failed to set channel ${channel} enabled state`));
1119
+ }
1120
+ }
1121
+ );
1122
+ __privateMethod(this, _AdlMidi_instances, send_fn).call(this, { type: "setChannelEnabled", channel, enabled, reqId });
1123
+ });
696
1124
  }
697
1125
  /**
698
1126
  * Set the playback tempo multiplier
@@ -785,6 +1213,7 @@ var AdlMidi = class {
785
1213
  };
786
1214
  _ready = new WeakMap();
787
1215
  _messageHandlers = new WeakMap();
1216
+ _nextRequestId = new WeakMap();
788
1217
  _AdlMidi_instances = new WeakSet();
789
1218
  /**
790
1219
  * Internal message handler
@@ -814,6 +1243,25 @@ onceMessage_fn = function(type, handler) {
814
1243
  };
815
1244
  __privateGet(this, _messageHandlers).get(type)?.add(wrappedHandler);
816
1245
  };
1246
+ /**
1247
+ * Register a one-time handler correlated by request ID.
1248
+ * Allows concurrent operations of the same type without reply misrouting.
1249
+ * @param {string} type - Message type
1250
+ * @param {number} reqId - Request ID to match against
1251
+ * @param {Function} handler - Handler function
1252
+ */
1253
+ onceCorrelatedMessage_fn = function(type, reqId, handler) {
1254
+ if (!__privateGet(this, _messageHandlers).has(type)) {
1255
+ __privateGet(this, _messageHandlers).set(type, /* @__PURE__ */ new Set());
1256
+ }
1257
+ const filteredHandler = (msg) => {
1258
+ if (msg.reqId === reqId) {
1259
+ __privateGet(this, _messageHandlers).get(type)?.delete(filteredHandler);
1260
+ handler(msg);
1261
+ }
1262
+ };
1263
+ __privateGet(this, _messageHandlers).get(type)?.add(filteredHandler);
1264
+ };
817
1265
  /**
818
1266
  * Send a message to the processor
819
1267
  * @param {Object} msg - Message to send
@@ -827,6 +1275,7 @@ var libadlmidi_default = AdlMidi;
827
1275
  export {
828
1276
  AdlMidi,
829
1277
  Emulator,
1278
+ TrackOption,
830
1279
  libadlmidi_default as default
831
1280
  };
832
1281
  //# sourceMappingURL=libadlmidi.js.map