libadlmidi-js 1.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/LICENSE +165 -0
- package/README.md +126 -0
- package/dist/core.d.ts +276 -0
- package/dist/fm_banks/ail/MonopolyDeluxe.wopl +0 -0
- package/dist/fm_banks/ail/master_of_magic.wopl +0 -0
- package/dist/fm_banks/manifest.json +60 -0
- package/dist/fm_banks/wopl/Apogee-IMF-90.wopl +0 -0
- package/dist/fm_banks/wopl/DMXOPL3-by-sneakernets-GS.wopl +0 -0
- package/dist/fm_banks/wopl/GM-By-J.A.Nguyen-and-Wohlstand.wopl +0 -0
- package/dist/fm_banks/wopl/Wohlstand's-modded-FatMan.wopl +0 -0
- package/dist/fm_banks/wopl/fatman-2op.wopl +0 -0
- package/dist/fm_banks/wopl/fatman-4op.wopl +0 -0
- package/dist/fm_banks/wopl/msadlib.wopl +0 -0
- package/dist/libadlmidi.d.ts +453 -0
- package/dist/libadlmidi.dosbox.browser.js +2 -0
- package/dist/libadlmidi.dosbox.browser.wasm +0 -0
- package/dist/libadlmidi.dosbox.core.js +2 -0
- package/dist/libadlmidi.dosbox.core.wasm +0 -0
- package/dist/libadlmidi.dosbox.js +0 -0
- package/dist/libadlmidi.dosbox.processor.js +3226 -0
- package/dist/libadlmidi.dosbox.slim.browser.js +2 -0
- package/dist/libadlmidi.dosbox.slim.browser.wasm +0 -0
- package/dist/libadlmidi.dosbox.slim.core.js +2 -0
- package/dist/libadlmidi.dosbox.slim.core.wasm +0 -0
- package/dist/libadlmidi.dosbox.slim.js +0 -0
- package/dist/libadlmidi.dosbox.slim.processor.js +3226 -0
- package/dist/libadlmidi.full.browser.js +2 -0
- package/dist/libadlmidi.full.browser.wasm +0 -0
- package/dist/libadlmidi.full.core.js +2 -0
- package/dist/libadlmidi.full.core.wasm +0 -0
- package/dist/libadlmidi.full.js +0 -0
- package/dist/libadlmidi.full.processor.js +3226 -0
- package/dist/libadlmidi.full.slim.browser.js +2 -0
- package/dist/libadlmidi.full.slim.browser.wasm +0 -0
- package/dist/libadlmidi.full.slim.core.js +2 -0
- package/dist/libadlmidi.full.slim.core.wasm +0 -0
- package/dist/libadlmidi.full.slim.js +0 -0
- package/dist/libadlmidi.full.slim.processor.js +3226 -0
- package/dist/libadlmidi.js +445 -0
- package/dist/libadlmidi.js.map +7 -0
- package/dist/libadlmidi.light.browser.js +2 -0
- package/dist/libadlmidi.light.browser.wasm +0 -0
- package/dist/libadlmidi.light.core.js +2 -0
- package/dist/libadlmidi.light.core.wasm +0 -0
- package/dist/libadlmidi.light.js +0 -0
- package/dist/libadlmidi.light.processor.js +3226 -0
- package/dist/libadlmidi.light.slim.browser.js +2 -0
- package/dist/libadlmidi.light.slim.browser.wasm +0 -0
- package/dist/libadlmidi.light.slim.core.js +2 -0
- package/dist/libadlmidi.light.slim.core.wasm +0 -0
- package/dist/libadlmidi.light.slim.js +0 -0
- package/dist/libadlmidi.light.slim.processor.js +3226 -0
- package/dist/libadlmidi.nuked.browser.js +2 -0
- package/dist/libadlmidi.nuked.browser.wasm +0 -0
- package/dist/libadlmidi.nuked.core.js +2 -0
- package/dist/libadlmidi.nuked.core.wasm +0 -0
- package/dist/libadlmidi.nuked.js +0 -0
- package/dist/libadlmidi.nuked.processor.js +3226 -0
- package/dist/libadlmidi.nuked.slim.browser.js +2 -0
- package/dist/libadlmidi.nuked.slim.browser.wasm +0 -0
- package/dist/libadlmidi.nuked.slim.core.js +2 -0
- package/dist/libadlmidi.nuked.slim.core.wasm +0 -0
- package/dist/libadlmidi.nuked.slim.js +0 -0
- package/dist/libadlmidi.nuked.slim.processor.js +3226 -0
- package/dist/profiles/dosbox.d.ts +49 -0
- package/dist/profiles/dosbox.slim.d.ts +49 -0
- package/dist/profiles/full.d.ts +49 -0
- package/dist/profiles/full.slim.d.ts +49 -0
- package/dist/profiles/light.d.ts +49 -0
- package/dist/profiles/light.slim.d.ts +49 -0
- package/dist/profiles/nuked.d.ts +49 -0
- package/dist/profiles/nuked.slim.d.ts +49 -0
- package/dist/utils/struct.d.ts +209 -0
- package/package.json +103 -0
- package/src/core.js +591 -0
- package/src/libadlmidi.js +524 -0
- package/src/processor.js +517 -0
- package/src/profiles/dosbox.js +82 -0
- package/src/profiles/dosbox.slim.js +82 -0
- package/src/profiles/full.js +82 -0
- package/src/profiles/full.slim.js +82 -0
- package/src/profiles/light.js +82 -0
- package/src/profiles/light.slim.js +82 -0
- package/src/profiles/nuked.js +82 -0
- package/src/profiles/nuked.slim.js +82 -0
- package/src/utils/struct.js +288 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OPL3 struct serialization utilities
|
|
3
|
+
* Shared between processor and tests
|
|
4
|
+
*
|
|
5
|
+
* @module utils/struct
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Structure Sizes (verified with offsetof() - WASM is 32-bit)
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/** Size of ADL_Operator struct (5 register bytes) */
|
|
13
|
+
export const SIZEOF_ADL_OPERATOR = 5;
|
|
14
|
+
|
|
15
|
+
/** Size of ADL_Instrument struct */
|
|
16
|
+
export const SIZEOF_ADL_INSTRUMENT = 40; // Verified: ops at offset 14, delay at 34/36
|
|
17
|
+
|
|
18
|
+
/** Size of ADL_Bank struct (3 pointers × 4 bytes in 32-bit WASM) */
|
|
19
|
+
export const SIZEOF_ADL_BANK = 12;
|
|
20
|
+
|
|
21
|
+
/** Size of ADL_BankId struct (3 bytes + padding) */
|
|
22
|
+
export const SIZEOF_ADL_BANK_ID = 4;
|
|
23
|
+
|
|
24
|
+
/** Offset where operators start within ADL_Instrument */
|
|
25
|
+
export const OPERATOR_OFFSET = 14;
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Operator Encoding/Decoding
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} Operator
|
|
33
|
+
* @property {boolean} am - Amplitude modulation (tremolo)
|
|
34
|
+
* @property {boolean} vibrato - Vibrato (frequency modulation)
|
|
35
|
+
* @property {boolean} sustaining - Sustaining (EG type)
|
|
36
|
+
* @property {boolean} ksr - Key scale rate
|
|
37
|
+
* @property {number} freqMult - Frequency multiplier (0-15)
|
|
38
|
+
* @property {number} keyScaleLevel - Key scale level (0-3)
|
|
39
|
+
* @property {number} totalLevel - Total level / attenuation (0-63, 0 = loudest)
|
|
40
|
+
* @property {number} attack - Attack rate (0-15)
|
|
41
|
+
* @property {number} decay - Decay rate (0-15)
|
|
42
|
+
* @property {number} sustain - Sustain level (0-15, 0 = loudest)
|
|
43
|
+
* @property {number} release - Release rate (0-15)
|
|
44
|
+
* @property {number} waveform - Waveform select (0-7)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Decode an OPL3 operator from raw register bytes to named properties
|
|
49
|
+
* @param {Uint8Array | number[]} bytes - 5 bytes of operator register data
|
|
50
|
+
* @returns {Operator} Decoded operator with named properties
|
|
51
|
+
*/
|
|
52
|
+
export function decodeOperator(bytes) {
|
|
53
|
+
const avekf = bytes[0];
|
|
54
|
+
const ksl_l = bytes[1];
|
|
55
|
+
const atdec = bytes[2];
|
|
56
|
+
const susrel = bytes[3];
|
|
57
|
+
const waveform = bytes[4];
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
// Register 0x20: AM/Vib/EG-type/KSR/Mult
|
|
61
|
+
am: !!(avekf & 0x80),
|
|
62
|
+
vibrato: !!(avekf & 0x40),
|
|
63
|
+
sustaining: !!(avekf & 0x20),
|
|
64
|
+
ksr: !!(avekf & 0x10),
|
|
65
|
+
freqMult: avekf & 0x0F,
|
|
66
|
+
|
|
67
|
+
// Register 0x40: KSL/TL
|
|
68
|
+
keyScaleLevel: (ksl_l >> 6) & 0x03,
|
|
69
|
+
totalLevel: ksl_l & 0x3F,
|
|
70
|
+
|
|
71
|
+
// Register 0x60: AR/DR
|
|
72
|
+
attack: (atdec >> 4) & 0x0F,
|
|
73
|
+
decay: atdec & 0x0F,
|
|
74
|
+
|
|
75
|
+
// Register 0x80: SL/RR
|
|
76
|
+
sustain: (susrel >> 4) & 0x0F,
|
|
77
|
+
release: susrel & 0x0F,
|
|
78
|
+
|
|
79
|
+
// Register 0xE0: Waveform
|
|
80
|
+
waveform: waveform & 0x07
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Encode named operator properties to raw register bytes
|
|
86
|
+
* @param {Operator} op - Operator with named properties
|
|
87
|
+
* @returns {Uint8Array} 5 bytes of operator register data
|
|
88
|
+
*/
|
|
89
|
+
export function encodeOperator(op) {
|
|
90
|
+
const avekf =
|
|
91
|
+
(op.am ? 0x80 : 0) |
|
|
92
|
+
(op.vibrato ? 0x40 : 0) |
|
|
93
|
+
(op.sustaining ? 0x20 : 0) |
|
|
94
|
+
(op.ksr ? 0x10 : 0) |
|
|
95
|
+
(op.freqMult & 0x0F);
|
|
96
|
+
|
|
97
|
+
const ksl_l = ((op.keyScaleLevel & 0x03) << 6) | (op.totalLevel & 0x3F);
|
|
98
|
+
const atdec = ((op.attack & 0x0F) << 4) | (op.decay & 0x0F);
|
|
99
|
+
const susrel = ((op.sustain & 0x0F) << 4) | (op.release & 0x0F);
|
|
100
|
+
const waveform = op.waveform & 0x07;
|
|
101
|
+
|
|
102
|
+
return new Uint8Array([avekf, ksl_l, atdec, susrel, waveform]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Default operator values (silent)
|
|
107
|
+
* @returns {Operator} A silent operator configuration
|
|
108
|
+
*/
|
|
109
|
+
export function defaultOperator() {
|
|
110
|
+
return {
|
|
111
|
+
am: false,
|
|
112
|
+
vibrato: false,
|
|
113
|
+
sustaining: true,
|
|
114
|
+
ksr: false,
|
|
115
|
+
freqMult: 1,
|
|
116
|
+
keyScaleLevel: 0,
|
|
117
|
+
totalLevel: 63, // Max attenuation (silent)
|
|
118
|
+
attack: 15,
|
|
119
|
+
decay: 0,
|
|
120
|
+
sustain: 0,
|
|
121
|
+
release: 15,
|
|
122
|
+
waveform: 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Instrument Encoding/Decoding
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Complete OPL3 instrument definition
|
|
132
|
+
* @typedef {Object} Instrument
|
|
133
|
+
* @property {number} [version] - Instrument version
|
|
134
|
+
* @property {number} [noteOffset1] - Note offset for voice 1
|
|
135
|
+
* @property {number} [noteOffset2] - Note offset for voice 2
|
|
136
|
+
* @property {number} [velocityOffset] - MIDI velocity offset
|
|
137
|
+
* @property {number} [secondVoiceDetune] - Detune for second voice
|
|
138
|
+
* @property {number} [percussionKey] - Percussion key number
|
|
139
|
+
* @property {boolean} [is4op] - 4-operator mode enabled
|
|
140
|
+
* @property {boolean} [isPseudo4op] - Pseudo 4-op (two 2-op voices)
|
|
141
|
+
* @property {boolean} [isBlank] - Blank/unused instrument
|
|
142
|
+
* @property {number} [rhythmMode] - Rhythm mode (0-7)
|
|
143
|
+
* @property {number} [feedback1] - Feedback for voice 1 (0-7)
|
|
144
|
+
* @property {number} [connection1] - Connection type for voice 1 (0-1)
|
|
145
|
+
* @property {number} [feedback2] - Feedback for voice 2 (0-7)
|
|
146
|
+
* @property {number} [connection2] - Connection type for voice 2 (0-1)
|
|
147
|
+
* @property {[Operator, Operator, Operator, Operator]} operators - Four operators
|
|
148
|
+
* @property {number} [delayOnMs] - Delay before note-on (ms)
|
|
149
|
+
* @property {number} [delayOffMs] - Delay before note-off (ms)
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Decode an ADL_Instrument from raw bytes to JS object
|
|
154
|
+
* @param {Uint8Array} bytes - 40 bytes of instrument data
|
|
155
|
+
* @returns {Instrument} Decoded instrument with named properties
|
|
156
|
+
*/
|
|
157
|
+
export function decodeInstrument(bytes) {
|
|
158
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, SIZEOF_ADL_INSTRUMENT);
|
|
159
|
+
|
|
160
|
+
// int version (4 bytes)
|
|
161
|
+
const version = view.getInt32(0, true);
|
|
162
|
+
|
|
163
|
+
// int16_t note_offset1, note_offset2 (2 bytes each)
|
|
164
|
+
const noteOffset1 = view.getInt16(4, true);
|
|
165
|
+
const noteOffset2 = view.getInt16(6, true);
|
|
166
|
+
|
|
167
|
+
// int8_t midi_velocity_offset, second_voice_detune (1 byte each)
|
|
168
|
+
const velocityOffset = view.getInt8(8);
|
|
169
|
+
const secondVoiceDetune = view.getInt8(9);
|
|
170
|
+
|
|
171
|
+
// uint8_t percussion_key_number, inst_flags, fb_conn1, fb_conn2
|
|
172
|
+
const percussionKey = bytes[10];
|
|
173
|
+
const instFlags = bytes[11];
|
|
174
|
+
const fbConn1 = bytes[12];
|
|
175
|
+
const fbConn2 = bytes[13];
|
|
176
|
+
|
|
177
|
+
// ADL_Operator operators[4] - 5 bytes each at offset 14
|
|
178
|
+
/** @type {[Operator, Operator, Operator, Operator]} */
|
|
179
|
+
const operators = /** @type {[Operator, Operator, Operator, Operator]} */ ([
|
|
180
|
+
decodeOperator(bytes.slice(OPERATOR_OFFSET, OPERATOR_OFFSET + SIZEOF_ADL_OPERATOR)),
|
|
181
|
+
decodeOperator(bytes.slice(OPERATOR_OFFSET + SIZEOF_ADL_OPERATOR, OPERATOR_OFFSET + 2 * SIZEOF_ADL_OPERATOR)),
|
|
182
|
+
decodeOperator(bytes.slice(OPERATOR_OFFSET + 2 * SIZEOF_ADL_OPERATOR, OPERATOR_OFFSET + 3 * SIZEOF_ADL_OPERATOR)),
|
|
183
|
+
decodeOperator(bytes.slice(OPERATOR_OFFSET + 3 * SIZEOF_ADL_OPERATOR, OPERATOR_OFFSET + 4 * SIZEOF_ADL_OPERATOR))
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
// uint16_t delay_on_ms, delay_off_ms at offset 34
|
|
187
|
+
const delayOnMs = view.getUint16(34, true);
|
|
188
|
+
const delayOffMs = view.getUint16(36, true);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
version,
|
|
192
|
+
noteOffset1,
|
|
193
|
+
noteOffset2,
|
|
194
|
+
velocityOffset,
|
|
195
|
+
secondVoiceDetune,
|
|
196
|
+
percussionKey,
|
|
197
|
+
|
|
198
|
+
// Decode flags
|
|
199
|
+
is4op: !!(instFlags & 0x01),
|
|
200
|
+
isPseudo4op: !!(instFlags & 0x02),
|
|
201
|
+
isBlank: !!(instFlags & 0x04),
|
|
202
|
+
rhythmMode: (instFlags >> 3) & 0x07,
|
|
203
|
+
|
|
204
|
+
// Decode feedback/connection
|
|
205
|
+
feedback1: (fbConn1 >> 1) & 0x07,
|
|
206
|
+
connection1: fbConn1 & 0x01,
|
|
207
|
+
feedback2: (fbConn2 >> 1) & 0x07,
|
|
208
|
+
connection2: fbConn2 & 0x01,
|
|
209
|
+
|
|
210
|
+
operators,
|
|
211
|
+
delayOnMs,
|
|
212
|
+
delayOffMs
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Encode a JS instrument object to raw bytes
|
|
218
|
+
* @param {Instrument} inst - Instrument with named properties
|
|
219
|
+
* @returns {Uint8Array} 40 bytes of instrument data
|
|
220
|
+
*/
|
|
221
|
+
export function encodeInstrument(inst) {
|
|
222
|
+
const bytes = new Uint8Array(SIZEOF_ADL_INSTRUMENT);
|
|
223
|
+
const view = new DataView(bytes.buffer);
|
|
224
|
+
|
|
225
|
+
// int version
|
|
226
|
+
view.setInt32(0, inst.version || 0, true);
|
|
227
|
+
|
|
228
|
+
// int16_t note_offset1, note_offset2
|
|
229
|
+
view.setInt16(4, inst.noteOffset1 || 0, true);
|
|
230
|
+
view.setInt16(6, inst.noteOffset2 || 0, true);
|
|
231
|
+
|
|
232
|
+
// int8_t midi_velocity_offset, second_voice_detune
|
|
233
|
+
view.setInt8(8, inst.velocityOffset || 0);
|
|
234
|
+
view.setInt8(9, inst.secondVoiceDetune || 0);
|
|
235
|
+
|
|
236
|
+
// uint8_t percussion_key_number
|
|
237
|
+
bytes[10] = inst.percussionKey || 0;
|
|
238
|
+
|
|
239
|
+
// uint8_t inst_flags
|
|
240
|
+
let flags = 0;
|
|
241
|
+
if (inst.is4op) flags |= 0x01;
|
|
242
|
+
if (inst.isPseudo4op) flags |= 0x02;
|
|
243
|
+
if (inst.isBlank) flags |= 0x04;
|
|
244
|
+
flags |= ((inst.rhythmMode || 0) & 0x07) << 3;
|
|
245
|
+
bytes[11] = flags;
|
|
246
|
+
|
|
247
|
+
// uint8_t fb_conn1, fb_conn2
|
|
248
|
+
bytes[12] = (((inst.feedback1 || 0) & 0x07) << 1) | ((inst.connection1 || 0) & 0x01);
|
|
249
|
+
bytes[13] = (((inst.feedback2 || 0) & 0x07) << 1) | ((inst.connection2 || 0) & 0x01);
|
|
250
|
+
|
|
251
|
+
// ADL_Operator operators[4]
|
|
252
|
+
for (let i = 0; i < 4; i++) {
|
|
253
|
+
const opBytes = encodeOperator(inst.operators?.[i] || defaultOperator());
|
|
254
|
+
bytes.set(opBytes, OPERATOR_OFFSET + i * SIZEOF_ADL_OPERATOR);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// uint16_t delay_on_ms, delay_off_ms
|
|
258
|
+
view.setUint16(34, inst.delayOnMs || 0, true);
|
|
259
|
+
view.setUint16(36, inst.delayOffMs || 0, true);
|
|
260
|
+
|
|
261
|
+
return bytes;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Default instrument values (blank/silent)
|
|
266
|
+
* @returns {Instrument} A blank instrument configuration
|
|
267
|
+
*/
|
|
268
|
+
export function defaultInstrument() {
|
|
269
|
+
return {
|
|
270
|
+
version: 0,
|
|
271
|
+
noteOffset1: 0,
|
|
272
|
+
noteOffset2: 0,
|
|
273
|
+
velocityOffset: 0,
|
|
274
|
+
secondVoiceDetune: 0,
|
|
275
|
+
percussionKey: 0,
|
|
276
|
+
is4op: false,
|
|
277
|
+
isPseudo4op: false,
|
|
278
|
+
isBlank: true,
|
|
279
|
+
rhythmMode: 0,
|
|
280
|
+
feedback1: 0,
|
|
281
|
+
connection1: 0,
|
|
282
|
+
feedback2: 0,
|
|
283
|
+
connection2: 0,
|
|
284
|
+
operators: [defaultOperator(), defaultOperator(), defaultOperator(), defaultOperator()],
|
|
285
|
+
delayOnMs: 0,
|
|
286
|
+
delayOffMs: 0
|
|
287
|
+
};
|
|
288
|
+
}
|