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
@@ -0,0 +1,250 @@
1
+ /**
2
+ * OPL3 register helpers for raw chip access.
3
+ *
4
+ * Pure-JS utilities for working with the OPL3 register layout. No WASM
5
+ * dependency. Use alongside AdlMidiCore.rawOPL3() or AdlMidi.rawOPL3()
6
+ * to program the chip directly.
7
+ *
8
+ * @module opl3
9
+ */
10
+
11
+ /** Total per-chip channels including rhythm-mode percussion */
12
+ export const CHANNELS_PER_CHIP = 23;
13
+
14
+ /** Standard 2-op melodic channels per chip */
15
+ export const CHANNELS_STANDARD = 18;
16
+
17
+ /** Rhythm-mode percussion channels (indices 18-22) */
18
+ export const CHANNELS_RHYTHM = 5;
19
+
20
+ /** OPL3 internal sample rate (Hz), used for fnum calculation. 14.31818 MHz / 288. */
21
+ export const OPL3_SAMPLE_RATE = 49716;
22
+
23
+ /**
24
+ * Operator register offsets per channel.
25
+ *
26
+ * Maps per-chip channel index (0-17) to [modulator, carrier] register
27
+ * offsets. These are the low byte of the register address; for channels
28
+ * 9-17, add 0x100 (use {@link channelBank}).
29
+ *
30
+ * Derived from Nuked OPL3's `ad_slot` and `ch_slot` tables.
31
+ *
32
+ * @type {ReadonlyArray<readonly [number, number]>}
33
+ */
34
+ export const CHANNEL_OPERATORS = /** @type {ReadonlyArray<readonly [number, number]>} */ (Object.freeze([
35
+ Object.freeze(/** @type {const} */ ([0x00, 0x03])), Object.freeze(/** @type {const} */ ([0x01, 0x04])), Object.freeze(/** @type {const} */ ([0x02, 0x05])),
36
+ Object.freeze(/** @type {const} */ ([0x08, 0x0B])), Object.freeze(/** @type {const} */ ([0x09, 0x0C])), Object.freeze(/** @type {const} */ ([0x0A, 0x0D])),
37
+ Object.freeze(/** @type {const} */ ([0x10, 0x13])), Object.freeze(/** @type {const} */ ([0x11, 0x14])), Object.freeze(/** @type {const} */ ([0x12, 0x15])),
38
+ Object.freeze(/** @type {const} */ ([0x00, 0x03])), Object.freeze(/** @type {const} */ ([0x01, 0x04])), Object.freeze(/** @type {const} */ ([0x02, 0x05])),
39
+ Object.freeze(/** @type {const} */ ([0x08, 0x0B])), Object.freeze(/** @type {const} */ ([0x09, 0x0C])), Object.freeze(/** @type {const} */ ([0x0A, 0x0D])),
40
+ Object.freeze(/** @type {const} */ ([0x10, 0x13])), Object.freeze(/** @type {const} */ ([0x11, 0x14])), Object.freeze(/** @type {const} */ ([0x12, 0x15])),
41
+ ]));
42
+
43
+ /**
44
+ * Get the register bank bit for a channel.
45
+ *
46
+ * @param {number} channel - Per-chip channel (0-17)
47
+ * @returns {number} 0x000 for channels 0-8, 0x100 for channels 9-17
48
+ */
49
+ export function channelBank(channel) {
50
+ return channel >= 9 ? 0x100 : 0x000;
51
+ }
52
+
53
+ /**
54
+ * Get the frequency-number low register address (0xA0 series).
55
+ *
56
+ * @param {number} channel - Per-chip channel (0-17)
57
+ * @returns {number} Register address
58
+ */
59
+ export function fnumLoReg(channel) {
60
+ return 0xA0 + (channel % 9) + channelBank(channel);
61
+ }
62
+
63
+ /**
64
+ * Get the key-on / block / fnum-high register address (0xB0 series).
65
+ *
66
+ * @param {number} channel - Per-chip channel (0-17)
67
+ * @returns {number} Register address
68
+ */
69
+ export function keyOnBlockReg(channel) {
70
+ return 0xB0 + (channel % 9) + channelBank(channel);
71
+ }
72
+
73
+ /**
74
+ * Get the feedback / connection register address (0xC0 series).
75
+ *
76
+ * @param {number} channel - Per-chip channel (0-17)
77
+ * @returns {number} Register address
78
+ */
79
+ export function feedbackConnReg(channel) {
80
+ return 0xC0 + (channel % 9) + channelBank(channel);
81
+ }
82
+
83
+ /**
84
+ * Get the register address for an operator parameter.
85
+ *
86
+ * @param {number} baseReg - Register group base: 0x20 (AM/VIB/EG/KSR/MULT),
87
+ * 0x40 (KSL/TL), 0x60 (AR/DR), 0x80 (SL/RR), or 0xE0 (waveform)
88
+ * @param {number} channel - Per-chip channel (0-17)
89
+ * @param {number} operatorSlot - 0 for modulator, 1 for carrier
90
+ * @returns {number} Register address (0x000-0x1FF)
91
+ */
92
+ export function operatorReg(baseReg, channel, operatorSlot) {
93
+ return baseReg + CHANNEL_OPERATORS[channel][operatorSlot] + channelBank(channel);
94
+ }
95
+
96
+ /**
97
+ * Convert a MIDI note number to OPL3 fnum and block values.
98
+ *
99
+ * Uses the standard OPL3 frequency formula:
100
+ * freq = fnum * OPL3_SAMPLE_RATE / 2^(20 - block)
101
+ *
102
+ * Picks the lowest block where fnum fits in 10 bits (maximum resolution).
103
+ * Notes above ~6.2 kHz (above MIDI 110 or so) will saturate.
104
+ *
105
+ * @param {number} note - MIDI note number (0-127)
106
+ * @returns {{fnum: number, block: number}}
107
+ */
108
+ export function noteToFnumBlock(note) {
109
+ const freq = 440.0 * Math.pow(2.0, (note - 69) / 12.0);
110
+ for (let block = 0; block <= 7; block++) {
111
+ const fnum = Math.round(freq * Math.pow(2, 20 - block) / OPL3_SAMPLE_RATE);
112
+ if (fnum <= 1023) {
113
+ return { fnum, block };
114
+ }
115
+ }
116
+ return { fnum: 1023, block: 7 };
117
+ }
118
+
119
+ /**
120
+ * Encode fnum and block into the two register byte values.
121
+ *
122
+ * @param {number} fnum - Frequency number (0-1023)
123
+ * @param {number} block - Block / octave (0-7)
124
+ * @returns {{fnumLo: number, fnumHiBlock: number}} Values for A0h and B0h
125
+ * registers. The B0h value does NOT include the key-on bit.
126
+ */
127
+ export function encodeFnumBlock(fnum, block) {
128
+ return {
129
+ fnumLo: fnum & 0xFF,
130
+ fnumHiBlock: ((block & 0x07) << 2) | ((fnum >> 8) & 0x03),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * Encode a key-on register write for a channel.
136
+ *
137
+ * @param {number} channel - Per-chip channel (0-17)
138
+ * @param {number} fnum - Frequency number (0-1023)
139
+ * @param {number} block - Block / octave (0-7)
140
+ * @returns {{reg: number, value: number}}
141
+ */
142
+ export function keyOn(channel, fnum, block) {
143
+ const { fnumHiBlock } = encodeFnumBlock(fnum, block);
144
+ return {
145
+ reg: keyOnBlockReg(channel),
146
+ value: 0x20 | fnumHiBlock,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Encode a key-off register write for a channel.
152
+ *
153
+ * @param {number} channel - Per-chip channel (0-17)
154
+ * @param {number} fnum - Frequency number (0-1023)
155
+ * @param {number} block - Block / octave (0-7)
156
+ * @returns {{reg: number, value: number}}
157
+ */
158
+ export function keyOff(channel, fnum, block) {
159
+ const { fnumHiBlock } = encodeFnumBlock(fnum, block);
160
+ return {
161
+ reg: keyOnBlockReg(channel),
162
+ value: fnumHiBlock,
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Encode an Operator's properties into OPL3 register writes.
168
+ *
169
+ * @param {import('./struct.js').Operator} op
170
+ * @param {number} channel - Per-chip channel (0-17)
171
+ * @param {number} operatorSlot - 0 for modulator, 1 for carrier
172
+ * @returns {Array<{reg: number, value: number}>}
173
+ */
174
+ export function encodeOperatorRegisters(op, channel, operatorSlot) {
175
+ return [
176
+ {
177
+ reg: operatorReg(0x20, channel, operatorSlot),
178
+ value: ((op.am ? 1 : 0) << 7)
179
+ | ((op.vibrato ? 1 : 0) << 6)
180
+ | ((op.sustaining ? 1 : 0) << 5)
181
+ | ((op.ksr ? 1 : 0) << 4)
182
+ | (op.freqMult & 0x0F),
183
+ },
184
+ {
185
+ reg: operatorReg(0x40, channel, operatorSlot),
186
+ value: ((op.keyScaleLevel & 0x03) << 6)
187
+ | (op.totalLevel & 0x3F),
188
+ },
189
+ {
190
+ reg: operatorReg(0x60, channel, operatorSlot),
191
+ value: ((op.attack & 0x0F) << 4)
192
+ | (op.decay & 0x0F),
193
+ },
194
+ {
195
+ reg: operatorReg(0x80, channel, operatorSlot),
196
+ value: ((op.sustain & 0x0F) << 4)
197
+ | (op.release & 0x0F),
198
+ },
199
+ {
200
+ reg: operatorReg(0xE0, channel, operatorSlot),
201
+ value: op.waveform & 0x07,
202
+ },
203
+ ];
204
+ }
205
+
206
+ /**
207
+ * Generate all register writes to program a 2-op channel voice.
208
+ *
209
+ * Writes both operators and the feedback/connection register. Follows
210
+ * the ADL_Instrument convention: operators[0] = carrier (slot 1),
211
+ * operators[1] = modulator (slot 0). Sets stereo output bits (L+R)
212
+ * on the C0h register, which is required for audible output in OPL3 mode.
213
+ *
214
+ * Only handles 2-op instruments. Throws on 4-op or pseudo-4-op
215
+ * instruments, which require two paired channels.
216
+ *
217
+ * @param {import('./struct.js').Instrument} instrument
218
+ * @param {number} channel - Per-chip channel (0-17)
219
+ * @returns {Array<{reg: number, value: number}>}
220
+ */
221
+ export function encodeChannelVoice(instrument, channel) {
222
+ if (instrument.is4op || instrument.isPseudo4op) {
223
+ throw new Error('encodeChannelVoice only supports 2-op instruments');
224
+ }
225
+ const writes = [
226
+ ...encodeOperatorRegisters(instrument.operators[1], channel, 0),
227
+ ...encodeOperatorRegisters(instrument.operators[0], channel, 1),
228
+ {
229
+ reg: feedbackConnReg(channel),
230
+ value: 0x30
231
+ | (((instrument.feedback1 || 0) & 0x07) << 1)
232
+ | ((instrument.connection1 || 0) & 0x01),
233
+ },
234
+ ];
235
+ return writes;
236
+ }
237
+
238
+ /**
239
+ * Create a channel reservation bitmask from channel indices.
240
+ *
241
+ * @param {...number} channels - Per-chip channel indices (0-22)
242
+ * @returns {number} Bitmask for use with reserveChipChannels()
243
+ */
244
+ export function channelMask(...channels) {
245
+ let mask = 0;
246
+ for (const ch of channels) {
247
+ mask |= (1 << ch);
248
+ }
249
+ return mask >>> 0;
250
+ }