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.
- package/README.md +8 -5
- package/dist/core.d.ts +39 -0
- package/dist/fm_banks/manifest.json +1 -1
- package/dist/libadlmidi.d.ts +34 -1
- 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 +29 -7
- 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 +29 -7
- 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 +29 -7
- 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 +29 -7
- package/dist/libadlmidi.js +74 -3
- package/dist/libadlmidi.js.map +2 -2
- 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 +29 -7
- 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 +29 -7
- 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 +29 -7
- 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 +29 -7
- package/dist/profiles/dosbox.d.ts +7 -2
- package/dist/profiles/dosbox.slim.d.ts +7 -2
- package/dist/profiles/full.d.ts +7 -2
- package/dist/profiles/full.slim.d.ts +7 -2
- package/dist/profiles/light.d.ts +7 -2
- package/dist/profiles/light.slim.d.ts +7 -2
- package/dist/profiles/nuked.d.ts +7 -2
- package/dist/profiles/nuked.slim.d.ts +7 -2
- package/dist/utils/constants.d.ts +3 -1
- package/dist/utils/opl3.d.ts +157 -0
- package/package.json +36 -11
- package/src/core.js +63 -0
- package/src/libadlmidi.js +63 -2
- package/src/processor.js +24 -0
- package/src/profiles/dosbox.js +34 -6
- package/src/profiles/dosbox.slim.js +34 -6
- package/src/profiles/full.js +35 -7
- package/src/profiles/full.slim.js +35 -7
- package/src/profiles/light.js +35 -7
- package/src/profiles/light.slim.js +35 -7
- package/src/profiles/nuked.js +35 -7
- package/src/profiles/nuked.slim.js +35 -7
- package/src/utils/constants.js +3 -1
- 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
|
+
}
|