libadlmidi-js 2.0.0 → 2.2.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 (78) hide show
  1. package/README.md +8 -5
  2. package/dist/core.d.ts +39 -0
  3. package/dist/fm_banks/manifest.json +1 -1
  4. package/dist/libadlmidi.d.ts +34 -1
  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 +29 -7
  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 +29 -7
  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 +29 -7
  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 +29 -7
  29. package/dist/libadlmidi.js +74 -3
  30. package/dist/libadlmidi.js.map +2 -2
  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 +29 -7
  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 +29 -7
  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 +29 -7
  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 +29 -7
  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 +3 -1
  64. package/dist/utils/opl3.d.ts +157 -0
  65. package/package.json +36 -11
  66. package/src/core.js +63 -0
  67. package/src/libadlmidi.js +63 -2
  68. package/src/processor.js +24 -0
  69. package/src/profiles/dosbox.js +34 -6
  70. package/src/profiles/dosbox.slim.js +34 -6
  71. package/src/profiles/full.js +35 -7
  72. package/src/profiles/full.slim.js +35 -7
  73. package/src/profiles/light.js +35 -7
  74. package/src/profiles/light.slim.js +35 -7
  75. package/src/profiles/nuked.js +35 -7
  76. package/src/profiles/nuked.slim.js +35 -7
  77. package/src/utils/constants.js +3 -1
  78. package/src/utils/opl3.js +250 -0
@@ -16,9 +16,10 @@ export class AdlMidi extends BaseAdlMidi {
16
16
  *
17
17
  * @param {string} [processorUrl] - Override processor URL (optional)
18
18
  * @param {string} [wasmUrl] - Override WASM URL (optional)
19
+ * @param {object} [defaultSettings] - Override profile's default synth settings
19
20
  * @returns {Promise<void>}
20
21
  */
21
- init(processorUrl?: string, wasmUrl?: string): Promise<void>;
22
+ init(processorUrl?: string, wasmUrl?: string, defaultSettings?: object): Promise<void>;
22
23
  }
23
24
  /**
24
25
  * Pre-configured AdlMidiCore for light slim profile.
@@ -37,14 +38,18 @@ export class AdlMidiCore {
37
38
  /**
38
39
  * Create a new AdlMidiCore instance with this profile's WASM.
39
40
  *
40
- * @param {{corePath?: string}} [options] - Options (corePath is pre-configured)
41
+ * @param {{corePath?: string, defaultEmulator?: number, wasmBinary?: ArrayBuffer}} [options]
42
+ * Override profile defaults. corePath and defaultEmulator are pre-configured.
41
43
  * @returns {Promise<BaseAdlMidiCore>}
42
44
  */
43
45
  static create(options?: {
44
46
  corePath?: string;
47
+ defaultEmulator?: number;
48
+ wasmBinary?: ArrayBuffer;
45
49
  }): Promise<BaseAdlMidiCore>;
46
50
  }
47
51
  import { AdlMidi as BaseAdlMidi } from '../libadlmidi.js';
48
52
  import { AdlMidiCore as BaseAdlMidiCore } from '../core.js';
49
53
  export { encodeInstrument, decodeInstrument, defaultInstrument, encodeOperator, decodeOperator, defaultOperator } from "../utils/struct.js";
50
54
  export { Emulator, TrackOption } from "../utils/constants.js";
55
+ export { CHANNELS_PER_CHIP, CHANNELS_STANDARD, CHANNELS_RHYTHM, OPL3_SAMPLE_RATE, CHANNEL_OPERATORS, channelBank, fnumLoReg, keyOnBlockReg, feedbackConnReg, operatorReg, noteToFnumBlock, encodeFnumBlock, keyOn, keyOff, encodeOperatorRegisters, encodeChannelVoice, channelMask } from "../utils/opl3.js";
@@ -16,9 +16,10 @@ export class AdlMidi extends BaseAdlMidi {
16
16
  *
17
17
  * @param {string} [processorUrl] - Override processor URL (optional)
18
18
  * @param {string} [wasmUrl] - Override WASM URL (optional)
19
+ * @param {object} [defaultSettings] - Override profile's default synth settings
19
20
  * @returns {Promise<void>}
20
21
  */
21
- init(processorUrl?: string, wasmUrl?: string): Promise<void>;
22
+ init(processorUrl?: string, wasmUrl?: string, defaultSettings?: object): Promise<void>;
22
23
  }
23
24
  /**
24
25
  * Pre-configured AdlMidiCore for nuked profile.
@@ -37,14 +38,18 @@ export class AdlMidiCore {
37
38
  /**
38
39
  * Create a new AdlMidiCore instance with this profile's WASM.
39
40
  *
40
- * @param {{corePath?: string}} [options] - Options (corePath is pre-configured)
41
+ * @param {{corePath?: string, defaultEmulator?: number, wasmBinary?: ArrayBuffer}} [options]
42
+ * Override profile defaults. corePath and defaultEmulator are pre-configured.
41
43
  * @returns {Promise<BaseAdlMidiCore>}
42
44
  */
43
45
  static create(options?: {
44
46
  corePath?: string;
47
+ defaultEmulator?: number;
48
+ wasmBinary?: ArrayBuffer;
45
49
  }): Promise<BaseAdlMidiCore>;
46
50
  }
47
51
  import { AdlMidi as BaseAdlMidi } from '../libadlmidi.js';
48
52
  import { AdlMidiCore as BaseAdlMidiCore } from '../core.js';
49
53
  export { encodeInstrument, decodeInstrument, defaultInstrument, encodeOperator, decodeOperator, defaultOperator } from "../utils/struct.js";
50
54
  export { Emulator, TrackOption } from "../utils/constants.js";
55
+ export { CHANNELS_PER_CHIP, CHANNELS_STANDARD, CHANNELS_RHYTHM, OPL3_SAMPLE_RATE, CHANNEL_OPERATORS, channelBank, fnumLoReg, keyOnBlockReg, feedbackConnReg, operatorReg, noteToFnumBlock, encodeFnumBlock, keyOn, keyOff, encodeOperatorRegisters, encodeChannelVoice, channelMask } from "../utils/opl3.js";
@@ -16,9 +16,10 @@ export class AdlMidi extends BaseAdlMidi {
16
16
  *
17
17
  * @param {string} [processorUrl] - Override processor URL (optional)
18
18
  * @param {string} [wasmUrl] - Override WASM URL (optional)
19
+ * @param {object} [defaultSettings] - Override profile's default synth settings
19
20
  * @returns {Promise<void>}
20
21
  */
21
- init(processorUrl?: string, wasmUrl?: string): Promise<void>;
22
+ init(processorUrl?: string, wasmUrl?: string, defaultSettings?: object): Promise<void>;
22
23
  }
23
24
  /**
24
25
  * Pre-configured AdlMidiCore for nuked slim profile.
@@ -37,14 +38,18 @@ export class AdlMidiCore {
37
38
  /**
38
39
  * Create a new AdlMidiCore instance with this profile's WASM.
39
40
  *
40
- * @param {{corePath?: string}} [options] - Options (corePath is pre-configured)
41
+ * @param {{corePath?: string, defaultEmulator?: number, wasmBinary?: ArrayBuffer}} [options]
42
+ * Override profile defaults. corePath and defaultEmulator are pre-configured.
41
43
  * @returns {Promise<BaseAdlMidiCore>}
42
44
  */
43
45
  static create(options?: {
44
46
  corePath?: string;
47
+ defaultEmulator?: number;
48
+ wasmBinary?: ArrayBuffer;
45
49
  }): Promise<BaseAdlMidiCore>;
46
50
  }
47
51
  import { AdlMidi as BaseAdlMidi } from '../libadlmidi.js';
48
52
  import { AdlMidiCore as BaseAdlMidiCore } from '../core.js';
49
53
  export { encodeInstrument, decodeInstrument, defaultInstrument, encodeOperator, decodeOperator, defaultOperator } from "../utils/struct.js";
50
54
  export { Emulator, TrackOption } from "../utils/constants.js";
55
+ export { CHANNELS_PER_CHIP, CHANNELS_STANDARD, CHANNELS_RHYTHM, OPL3_SAMPLE_RATE, CHANNEL_OPERATORS, channelBank, fnumLoReg, keyOnBlockReg, feedbackConnReg, operatorReg, noteToFnumBlock, encodeFnumBlock, keyOn, keyOff, encodeOperatorRegisters, encodeChannelVoice, channelMask } from "../utils/opl3.js";
@@ -17,7 +17,9 @@ export type Emulator = number;
17
17
  export const Emulator: Readonly<{
18
18
  /** Nuked OPL3 v1.8 - Most accurate, higher CPU usage */
19
19
  NUKED: 0;
20
- /** Nuked OPL3 v1.7.4 - Slightly older version */
20
+ /** Optimized Nuked 1.8 fork by tgies with identical output */
21
+ NUKED_FAST: 1;
22
+ /** @deprecated Use NUKED_FAST */
21
23
  NUKED_174: 1;
22
24
  /** DosBox OPL3 - Good accuracy, lower CPU usage */
23
25
  DOSBOX: 2;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Get the register bank bit for a channel.
3
+ *
4
+ * @param {number} channel - Per-chip channel (0-17)
5
+ * @returns {number} 0x000 for channels 0-8, 0x100 for channels 9-17
6
+ */
7
+ export function channelBank(channel: number): number;
8
+ /**
9
+ * Get the frequency-number low register address (0xA0 series).
10
+ *
11
+ * @param {number} channel - Per-chip channel (0-17)
12
+ * @returns {number} Register address
13
+ */
14
+ export function fnumLoReg(channel: number): number;
15
+ /**
16
+ * Get the key-on / block / fnum-high register address (0xB0 series).
17
+ *
18
+ * @param {number} channel - Per-chip channel (0-17)
19
+ * @returns {number} Register address
20
+ */
21
+ export function keyOnBlockReg(channel: number): number;
22
+ /**
23
+ * Get the feedback / connection register address (0xC0 series).
24
+ *
25
+ * @param {number} channel - Per-chip channel (0-17)
26
+ * @returns {number} Register address
27
+ */
28
+ export function feedbackConnReg(channel: number): number;
29
+ /**
30
+ * Get the register address for an operator parameter.
31
+ *
32
+ * @param {number} baseReg - Register group base: 0x20 (AM/VIB/EG/KSR/MULT),
33
+ * 0x40 (KSL/TL), 0x60 (AR/DR), 0x80 (SL/RR), or 0xE0 (waveform)
34
+ * @param {number} channel - Per-chip channel (0-17)
35
+ * @param {number} operatorSlot - 0 for modulator, 1 for carrier
36
+ * @returns {number} Register address (0x000-0x1FF)
37
+ */
38
+ export function operatorReg(baseReg: number, channel: number, operatorSlot: number): number;
39
+ /**
40
+ * Convert a MIDI note number to OPL3 fnum and block values.
41
+ *
42
+ * Uses the standard OPL3 frequency formula:
43
+ * freq = fnum * OPL3_SAMPLE_RATE / 2^(20 - block)
44
+ *
45
+ * Picks the lowest block where fnum fits in 10 bits (maximum resolution).
46
+ * Notes above ~6.2 kHz (above MIDI 110 or so) will saturate.
47
+ *
48
+ * @param {number} note - MIDI note number (0-127)
49
+ * @returns {{fnum: number, block: number}}
50
+ */
51
+ export function noteToFnumBlock(note: number): {
52
+ fnum: number;
53
+ block: number;
54
+ };
55
+ /**
56
+ * Encode fnum and block into the two register byte values.
57
+ *
58
+ * @param {number} fnum - Frequency number (0-1023)
59
+ * @param {number} block - Block / octave (0-7)
60
+ * @returns {{fnumLo: number, fnumHiBlock: number}} Values for A0h and B0h
61
+ * registers. The B0h value does NOT include the key-on bit.
62
+ */
63
+ export function encodeFnumBlock(fnum: number, block: number): {
64
+ fnumLo: number;
65
+ fnumHiBlock: number;
66
+ };
67
+ /**
68
+ * Encode a key-on register write for a channel.
69
+ *
70
+ * @param {number} channel - Per-chip channel (0-17)
71
+ * @param {number} fnum - Frequency number (0-1023)
72
+ * @param {number} block - Block / octave (0-7)
73
+ * @returns {{reg: number, value: number}}
74
+ */
75
+ export function keyOn(channel: number, fnum: number, block: number): {
76
+ reg: number;
77
+ value: number;
78
+ };
79
+ /**
80
+ * Encode a key-off register write for a channel.
81
+ *
82
+ * @param {number} channel - Per-chip channel (0-17)
83
+ * @param {number} fnum - Frequency number (0-1023)
84
+ * @param {number} block - Block / octave (0-7)
85
+ * @returns {{reg: number, value: number}}
86
+ */
87
+ export function keyOff(channel: number, fnum: number, block: number): {
88
+ reg: number;
89
+ value: number;
90
+ };
91
+ /**
92
+ * Encode an Operator's properties into OPL3 register writes.
93
+ *
94
+ * @param {import('./struct.js').Operator} op
95
+ * @param {number} channel - Per-chip channel (0-17)
96
+ * @param {number} operatorSlot - 0 for modulator, 1 for carrier
97
+ * @returns {Array<{reg: number, value: number}>}
98
+ */
99
+ export function encodeOperatorRegisters(op: import("./struct.js").Operator, channel: number, operatorSlot: number): Array<{
100
+ reg: number;
101
+ value: number;
102
+ }>;
103
+ /**
104
+ * Generate all register writes to program a 2-op channel voice.
105
+ *
106
+ * Writes both operators and the feedback/connection register. Follows
107
+ * the ADL_Instrument convention: operators[0] = carrier (slot 1),
108
+ * operators[1] = modulator (slot 0). Sets stereo output bits (L+R)
109
+ * on the C0h register, which is required for audible output in OPL3 mode.
110
+ *
111
+ * Only handles 2-op instruments. Throws on 4-op or pseudo-4-op
112
+ * instruments, which require two paired channels.
113
+ *
114
+ * @param {import('./struct.js').Instrument} instrument
115
+ * @param {number} channel - Per-chip channel (0-17)
116
+ * @returns {Array<{reg: number, value: number}>}
117
+ */
118
+ export function encodeChannelVoice(instrument: import("./struct.js").Instrument, channel: number): Array<{
119
+ reg: number;
120
+ value: number;
121
+ }>;
122
+ /**
123
+ * Create a channel reservation bitmask from channel indices.
124
+ *
125
+ * @param {...number} channels - Per-chip channel indices (0-22)
126
+ * @returns {number} Bitmask for use with reserveChipChannels()
127
+ */
128
+ export function channelMask(...channels: number[]): number;
129
+ /**
130
+ * OPL3 register helpers for raw chip access.
131
+ *
132
+ * Pure-JS utilities for working with the OPL3 register layout. No WASM
133
+ * dependency. Use alongside AdlMidiCore.rawOPL3() or AdlMidi.rawOPL3()
134
+ * to program the chip directly.
135
+ *
136
+ * @module opl3
137
+ */
138
+ /** Total per-chip channels including rhythm-mode percussion */
139
+ export const CHANNELS_PER_CHIP: 23;
140
+ /** Standard 2-op melodic channels per chip */
141
+ export const CHANNELS_STANDARD: 18;
142
+ /** Rhythm-mode percussion channels (indices 18-22) */
143
+ export const CHANNELS_RHYTHM: 5;
144
+ /** OPL3 internal sample rate (Hz), used for fnum calculation. 14.31818 MHz / 288. */
145
+ export const OPL3_SAMPLE_RATE: 49716;
146
+ /**
147
+ * Operator register offsets per channel.
148
+ *
149
+ * Maps per-chip channel index (0-17) to [modulator, carrier] register
150
+ * offsets. These are the low byte of the register address; for channels
151
+ * 9-17, add 0x100 (use {@link channelBank}).
152
+ *
153
+ * Derived from Nuked OPL3's `ad_slot` and `ch_slot` tables.
154
+ *
155
+ * @type {ReadonlyArray<readonly [number, number]>}
156
+ */
157
+ export const CHANNEL_OPERATORS: ReadonlyArray<readonly [number, number]>;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "libadlmidi-js",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "libadlmidi": {
5
5
  "version": "1.6.2",
6
- "commit": "89284b0b63e236ccaf84ac2763574738840d37b4"
6
+ "commit": "ad001c83551779934bacd642fce801c51a5b7b4c"
7
7
  },
8
8
  "description": "WebAssembly build of libADLMIDI - OPL3/FM synthesis for the browser",
9
9
  "main": "dist/libadlmidi.js",
@@ -11,35 +11,47 @@
11
11
  "exports": {
12
12
  ".": "./dist/libadlmidi.js",
13
13
  "./core": {
14
- "import": "./src/core.js",
15
- "types": "./dist/core.d.ts"
14
+ "types": "./dist/core.d.ts",
15
+ "import": "./src/core.js"
16
16
  },
17
17
  "./structs": {
18
- "import": "./src/utils/struct.js",
19
- "types": "./dist/utils/struct.d.ts"
18
+ "types": "./dist/utils/struct.d.ts",
19
+ "import": "./src/utils/struct.js"
20
+ },
21
+ "./opl3": {
22
+ "types": "./dist/utils/opl3.d.ts",
23
+ "import": "./src/utils/opl3.js"
20
24
  },
21
25
  "./nuked": {
26
+ "types": "./dist/profiles/nuked.d.ts",
22
27
  "import": "./src/profiles/nuked.js"
23
28
  },
24
29
  "./nuked/slim": {
30
+ "types": "./dist/profiles/nuked.slim.d.ts",
25
31
  "import": "./src/profiles/nuked.slim.js"
26
32
  },
27
33
  "./dosbox": {
34
+ "types": "./dist/profiles/dosbox.d.ts",
28
35
  "import": "./src/profiles/dosbox.js"
29
36
  },
30
37
  "./dosbox/slim": {
38
+ "types": "./dist/profiles/dosbox.slim.d.ts",
31
39
  "import": "./src/profiles/dosbox.slim.js"
32
40
  },
33
41
  "./light": {
42
+ "types": "./dist/profiles/light.d.ts",
34
43
  "import": "./src/profiles/light.js"
35
44
  },
36
45
  "./light/slim": {
46
+ "types": "./dist/profiles/light.slim.d.ts",
37
47
  "import": "./src/profiles/light.slim.js"
38
48
  },
39
49
  "./full": {
50
+ "types": "./dist/profiles/full.d.ts",
40
51
  "import": "./src/profiles/full.js"
41
52
  },
42
53
  "./full/slim": {
54
+ "types": "./dist/profiles/full.slim.d.ts",
43
55
  "import": "./src/profiles/full.slim.js"
44
56
  },
45
57
  "./dist/*": "./dist/*"
@@ -66,7 +78,10 @@
66
78
  "lint:fix": "eslint . --fix",
67
79
  "check": "npm run lint && npm run typecheck && npm run sync-version -- --check",
68
80
  "sync-version": "node scripts/sync-libadlmidi-version.js",
69
- "prepare": "command -v pre-commit >/dev/null && pre-commit install || echo 'pre-commit not found, run: pipx install pre-commit'"
81
+ "prepare": "husky"
82
+ },
83
+ "lint-staged": {
84
+ "*.js": "eslint --fix"
70
85
  },
71
86
  "types": "dist/libadlmidi.d.ts",
72
87
  "keywords": [
@@ -86,15 +101,22 @@
86
101
  "url": "https://github.com/libadlmidi-js/libadlmidi-js.git"
87
102
  },
88
103
  "devDependencies": {
104
+ "@arethetypeswrong/cli": "^0.18.2",
105
+ "@commitlint/cli": "^21.0.1",
106
+ "@commitlint/config-conventional": "^21.0.1",
107
+ "@emnapi/core": "1.11.1",
108
+ "@emnapi/runtime": "1.11.1",
89
109
  "@eslint/js": "^10.0.1",
90
110
  "@playwright/test": "^1.57.0",
91
111
  "@types/node": "^25.0.3",
92
- "esbuild": "^0.27.2",
112
+ "esbuild": "^0.28.0",
93
113
  "eslint": "^10.0.3",
94
114
  "globals": "^17.4.0",
115
+ "husky": "^9.1.7",
116
+ "lint-staged": "^17.0.5",
95
117
  "playwright": "^1.57.0",
96
- "typescript": "^5.9.3",
97
- "vitest": "^4.0.16"
118
+ "typescript": "^6.0.3",
119
+ "vitest": "^4.1.5"
98
120
  },
99
121
  "files": [
100
122
  "dist/",
@@ -109,5 +131,8 @@
109
131
  "light-slim.js",
110
132
  "full-slim.js",
111
133
  "README.md"
112
- ]
134
+ ],
135
+ "overrides": {
136
+ "fflate": "0.8.2"
137
+ }
113
138
  }
package/src/core.js CHANGED
@@ -51,6 +51,8 @@ export class AdlMidiCore {
51
51
  * @param {Object} options
52
52
  * @param {string} options.corePath - Path to the .core.js WASM loader module
53
53
  * @param {ArrayBuffer} [options.wasmBinary] - Pre-loaded WASM binary (optional)
54
+ * @param {number} [options.defaultEmulator] - Emulator to switch to after init.
55
+ * Profile wrappers use this to default to NUKED_FAST.
54
56
  * @returns {Promise<AdlMidiCore>}
55
57
  */
56
58
  static async create(options) {
@@ -73,6 +75,7 @@ export class AdlMidiCore {
73
75
  core._sampleRate = 44100;
74
76
  core._audioBuffer = null;
75
77
  core._audioBufferPtr = null;
78
+ core._defaultEmulator = options.defaultEmulator;
76
79
 
77
80
  return core;
78
81
  }
@@ -88,6 +91,8 @@ export class AdlMidiCore {
88
91
  this._audioBuffer = null;
89
92
  /** @private @type {number|null} */
90
93
  this._audioBufferPtr = null;
94
+ /** @private @type {number|undefined} */
95
+ this._defaultEmulator = undefined;
91
96
  }
92
97
 
93
98
  /**
@@ -108,6 +113,10 @@ export class AdlMidiCore {
108
113
  throw new Error('Failed to initialize ADL MIDI player');
109
114
  }
110
115
 
116
+ if (this._defaultEmulator !== undefined) {
117
+ this._module._adl_switchEmulator(this._player, this._defaultEmulator);
118
+ }
119
+
111
120
  return true;
112
121
  }
113
122
 
@@ -600,6 +609,60 @@ export class AdlMidiCore {
600
609
  this._module._adl_rt_bankChangeLSB(this._player, channel, lsb);
601
610
  }
602
611
 
612
+ // =========================================================================
613
+ // Raw OPL3 Access
614
+ // =========================================================================
615
+
616
+ /**
617
+ * Write a raw OPL3 register on a specific chip.
618
+ *
619
+ * Bypasses the MIDI voice allocator. The caller is responsible for the
620
+ * state of targeted channels. Reserve channels first via
621
+ * {@link reserveChipChannels} to prevent the MIDI driver from
622
+ * overwriting your register state.
623
+ *
624
+ * @param {number} chipId - Zero-based chip index (0 to getNumChipsObtained()-1)
625
+ * @param {number} reg - OPL3 register address (0x000-0x1FF, bit 8 selects bank)
626
+ * @param {number} value - Register value (0-255)
627
+ * @returns {boolean} True on success, false if chipId is out of range
628
+ */
629
+ rawOPL3(chipId, reg, value) {
630
+ this._ensurePlayer();
631
+ return this._module._adl_rt_rawOPL3(this._player, chipId, reg, value) === 1;
632
+ }
633
+
634
+ /**
635
+ * Reserve chip channels so the MIDI voice allocator will not use them.
636
+ *
637
+ * After reservation, use {@link rawOPL3} to drive those channels
638
+ * directly. Pass channelMask = 0 to release all reservations.
639
+ *
640
+ * @param {number} chipId - Zero-based chip index (0 to getNumChipsObtained()-1)
641
+ * @param {number} channelMask - Bitmask of per-chip channels to reserve
642
+ * (bits 0-22, where bit N reserves per-chip channel N)
643
+ * @returns {boolean} True on success
644
+ * @throws {Error} If the device is invalid
645
+ */
646
+ reserveChipChannels(chipId, channelMask) {
647
+ this._ensurePlayer();
648
+ const result = this._module._adl_reserveChipChannels(this._player, chipId, channelMask);
649
+ if (result === -1) {
650
+ throw new Error('Invalid device');
651
+ }
652
+ return result === 0;
653
+ }
654
+
655
+ /**
656
+ * Read back the chip-channel reservation mask.
657
+ *
658
+ * @param {number} chipId - Zero-based chip index
659
+ * @returns {number} Reservation bitmask (0 if invalid chipId)
660
+ */
661
+ getReservedChipChannels(chipId) {
662
+ this._ensurePlayer();
663
+ return this._module._adl_getReservedChipChannels(this._player, chipId);
664
+ }
665
+
603
666
  /**
604
667
  * Generate audio samples (real-time synthesis).
605
668
  *
package/src/libadlmidi.js CHANGED
@@ -114,9 +114,11 @@ export class AdlMidi {
114
114
  * @param {string} processorUrl - URL to the bundled processor JavaScript file
115
115
  * @param {string | null} [wasmUrl=null] - Optional URL to the .wasm file for split builds.
116
116
  * If not provided, assumes bundled version with embedded WASM.
117
+ * @param {object} [defaultSettings={}] - Initial synth settings applied before ready.
118
+ * Profile wrappers use this to set a default emulator.
117
119
  * @returns {Promise<void>}
118
120
  */
119
- async init(processorUrl, wasmUrl = null) {
121
+ async init(processorUrl, wasmUrl = null, defaultSettings = {}) {
120
122
  if (!this.ctx) {
121
123
  this.ctx = new AudioContext({ sampleRate: 44100 });
122
124
  }
@@ -143,7 +145,8 @@ export class AdlMidi {
143
145
  this.node = new AudioWorkletNode(this.ctx, 'adl-midi-processor', {
144
146
  processorOptions: {
145
147
  sampleRate: this.ctx.sampleRate,
146
- wasmBinary: wasmBinary // null for bundled, ArrayBuffer for split
148
+ wasmBinary: wasmBinary, // null for bundled, ArrayBuffer for split
149
+ settings: defaultSettings
147
150
  }
148
151
  });
149
152
 
@@ -346,6 +349,64 @@ export class AdlMidi {
346
349
  this.#send({ type: 'panic' });
347
350
  }
348
351
 
352
+ // ================== Raw OPL3 API ==================
353
+
354
+ /**
355
+ * Write a raw OPL3 register on a specific chip (fire-and-forget).
356
+ *
357
+ * Bypasses the MIDI voice allocator. Reserve channels first via
358
+ * {@link reserveChipChannels} to prevent the MIDI driver from
359
+ * overwriting your register state.
360
+ *
361
+ * @param {number} chipId - Zero-based chip index (0 to getNumChipsObtained()-1)
362
+ * @param {number} reg - OPL3 register address (0x000-0x1FF, bit 8 selects bank)
363
+ * @param {number} value - Register value (0-255)
364
+ */
365
+ rawOPL3(chipId, reg, value) {
366
+ this.#send({ type: 'rawOPL3', chipId, reg, value });
367
+ }
368
+
369
+ /**
370
+ * Reserve chip channels so the MIDI voice allocator will not use them.
371
+ *
372
+ * After reservation, use {@link rawOPL3} to drive those channels
373
+ * directly. Pass channelMask = 0 to release all reservations.
374
+ *
375
+ * @param {number} chipId - Zero-based chip index (0 to getNumChipsObtained()-1)
376
+ * @param {number} channelMask - Bitmask of per-chip channels to reserve
377
+ * (bits 0-22, where bit N reserves per-chip channel N)
378
+ * @returns {Promise<void>}
379
+ */
380
+ async reserveChipChannels(chipId, channelMask) {
381
+ const reqId = this.#nextRequestId++;
382
+ return new Promise((resolve, reject) => {
383
+ this.#onceCorrelatedMessage('chipChannelsReserved', reqId, /** @param {{success: boolean}} msg */ (msg) => {
384
+ if (msg.success) {
385
+ resolve();
386
+ } else {
387
+ reject(new Error(`Failed to reserve chip channels on chip ${chipId}`));
388
+ }
389
+ });
390
+ this.#send({ type: 'reserveChipChannels', chipId, channelMask, reqId });
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Read back the chip-channel reservation mask.
396
+ *
397
+ * @param {number} chipId - Zero-based chip index
398
+ * @returns {Promise<number>} Reservation bitmask (0 if invalid chipId)
399
+ */
400
+ async getReservedChipChannels(chipId) {
401
+ const reqId = this.#nextRequestId++;
402
+ return new Promise((resolve) => {
403
+ this.#onceCorrelatedMessage('reservedChipChannels', reqId, /** @param {{mask: number}} msg */ (msg) => {
404
+ resolve(msg.mask);
405
+ });
406
+ this.#send({ type: 'getReservedChipChannels', chipId, reqId });
407
+ });
408
+ }
409
+
349
410
  /**
350
411
  * Configure synth settings at runtime
351
412
  * @param {ConfigureSettings} settings - Settings object
package/src/processor.js CHANGED
@@ -45,6 +45,7 @@ class AdlMidiProcessor extends AudioWorkletProcessor {
45
45
  softPan: true, // Soft stereo panning
46
46
  deepVibrato: false, // Deep vibrato
47
47
  deepTremolo: false, // Deep tremolo
48
+ emulator: undefined, // Emulator core (undefined = libADLMIDI default)
48
49
  ...options.processorOptions?.settings
49
50
  };
50
51
 
@@ -104,6 +105,11 @@ class AdlMidiProcessor extends AudioWorkletProcessor {
104
105
  applySettings(settings) {
105
106
  if (!this.midi) return;
106
107
 
108
+ // Switch emulator first; adl_switchEmulator does a partialReset and a
109
+ // wrong-order subsequent setBank etc. would otherwise need re-applying.
110
+ if (settings.emulator !== undefined) {
111
+ this.adl._adl_switchEmulator(this.midi, settings.emulator);
112
+ }
107
113
  if (settings.numChips !== undefined) {
108
114
  this.adl._adl_setNumChips(this.midi, settings.numChips);
109
115
  }
@@ -695,6 +701,24 @@ class AdlMidiProcessor extends AudioWorkletProcessor {
695
701
  break;
696
702
  }
697
703
 
704
+ // ================== Raw OPL3 ==================
705
+
706
+ case 'rawOPL3':
707
+ this.adl._adl_rt_rawOPL3(this.midi, msg.chipId, msg.reg, msg.value);
708
+ break;
709
+
710
+ case 'reserveChipChannels': {
711
+ const result = this.adl._adl_reserveChipChannels(this.midi, msg.chipId, msg.channelMask);
712
+ this.port.postMessage({ type: 'chipChannelsReserved', success: result === 0, chipId: msg.chipId, reqId: msg.reqId });
713
+ break;
714
+ }
715
+
716
+ case 'getReservedChipChannels': {
717
+ const mask = this.adl._adl_getReservedChipChannels(this.midi, msg.chipId);
718
+ this.port.postMessage({ type: 'reservedChipChannels', mask, chipId: msg.chipId, reqId: msg.reqId });
719
+ break;
720
+ }
721
+
698
722
  // ================== Debug / Diagnostics ==================
699
723
 
700
724
  case 'describeChannels': {