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
package/src/processor.js
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AudioWorklet Processor for libADLMIDI
|
|
3
|
+
*
|
|
4
|
+
* This processor runs the OPL3 emulator in the audio worklet thread,
|
|
5
|
+
* generating audio samples in real-time from MIDI commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Import the WASM module factory
|
|
9
|
+
// This import path is aliased at bundle time to the correct profile
|
|
10
|
+
import createADLMIDI from 'libadlmidi-wasm';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
SIZEOF_ADL_OPERATOR,
|
|
14
|
+
SIZEOF_ADL_INSTRUMENT,
|
|
15
|
+
SIZEOF_ADL_BANK,
|
|
16
|
+
SIZEOF_ADL_BANK_ID,
|
|
17
|
+
decodeOperator,
|
|
18
|
+
encodeOperator,
|
|
19
|
+
defaultOperator,
|
|
20
|
+
decodeInstrument,
|
|
21
|
+
encodeInstrument,
|
|
22
|
+
} from './utils/struct.js';
|
|
23
|
+
|
|
24
|
+
const SAMPLE_RATE = 44100;
|
|
25
|
+
const CHANNELS = 2;
|
|
26
|
+
const BYTES_PER_SAMPLE = 2; // Int16
|
|
27
|
+
|
|
28
|
+
class AdlMidiProcessor extends AudioWorkletProcessor {
|
|
29
|
+
constructor(options) {
|
|
30
|
+
super();
|
|
31
|
+
|
|
32
|
+
this.adl = null;
|
|
33
|
+
this.midi = null;
|
|
34
|
+
this.bufferPtr = null;
|
|
35
|
+
this.ready = false;
|
|
36
|
+
this.playMode = 'realtime'; // 'realtime' or 'file'
|
|
37
|
+
this.sampleRate = options.processorOptions?.sampleRate || SAMPLE_RATE;
|
|
38
|
+
this.cachedHeapBuffer = null; // Track heap buffer for view caching
|
|
39
|
+
|
|
40
|
+
// Synth settings with defaults (can be overridden via processorOptions or messages)
|
|
41
|
+
this.settings = {
|
|
42
|
+
numChips: 4, // Number of emulated OPL3 chips
|
|
43
|
+
numFourOpChannels: -1, // 4-op channels (-1 = auto)
|
|
44
|
+
bank: 72, // FM bank number
|
|
45
|
+
softPan: true, // Soft stereo panning
|
|
46
|
+
deepVibrato: false, // Deep vibrato
|
|
47
|
+
deepTremolo: false, // Deep tremolo
|
|
48
|
+
...options.processorOptions?.settings
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Pass processorOptions to initWasm for split build support
|
|
52
|
+
this.initWasm(options.processorOptions);
|
|
53
|
+
this.port.onmessage = (e) => this.handleMessage(e.data);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async initWasm(processorOptions) {
|
|
57
|
+
try {
|
|
58
|
+
// For split builds, use instantiateWasm to bypass Emscripten's
|
|
59
|
+
// file-locating code which uses URL (not available in AudioWorklet)
|
|
60
|
+
let moduleConfig;
|
|
61
|
+
if (processorOptions?.wasmBinary) {
|
|
62
|
+
moduleConfig = {
|
|
63
|
+
instantiateWasm: (imports, successCallback) => {
|
|
64
|
+
WebAssembly.instantiate(processorOptions.wasmBinary, imports)
|
|
65
|
+
.then(result => successCallback(result.instance));
|
|
66
|
+
return {}; // indicates async instantiation
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const Module = await createADLMIDI(moduleConfig);
|
|
71
|
+
this.adl = Module;
|
|
72
|
+
|
|
73
|
+
// Initialize the MIDI player with desired sample rate
|
|
74
|
+
this.midi = this.adl._adl_init(this.sampleRate);
|
|
75
|
+
|
|
76
|
+
if (!this.midi) {
|
|
77
|
+
throw new Error('Failed to initialize ADL MIDI player');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Apply initial settings (can be overridden via messages)
|
|
81
|
+
this.applySettings(this.settings);
|
|
82
|
+
|
|
83
|
+
// Allocate buffer for audio generation
|
|
84
|
+
// AudioWorklet uses 128 frames per block
|
|
85
|
+
const FRAMES = 128;
|
|
86
|
+
this.bufferSize = FRAMES * CHANNELS * BYTES_PER_SAMPLE;
|
|
87
|
+
this.bufferPtr = this.adl._malloc(this.bufferSize);
|
|
88
|
+
|
|
89
|
+
// Verify HEAP16 is available (required for audio output)
|
|
90
|
+
if (!this.adl.HEAP16) {
|
|
91
|
+
throw new Error('HEAP16 is not available after initialization');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.ready = true;
|
|
95
|
+
this.port.postMessage({ type: 'ready' });
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Failed to initialize WASM:', error);
|
|
98
|
+
this.port.postMessage({ type: 'error', message: error.message });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Apply synth settings
|
|
103
|
+
*/
|
|
104
|
+
applySettings(settings) {
|
|
105
|
+
if (!this.midi) return;
|
|
106
|
+
|
|
107
|
+
if (settings.numChips !== undefined) {
|
|
108
|
+
this.adl._adl_setNumChips(this.midi, settings.numChips);
|
|
109
|
+
}
|
|
110
|
+
if (settings.numFourOpChannels !== undefined) {
|
|
111
|
+
this.adl._adl_setNumFourOpsChn(this.midi, settings.numFourOpChannels);
|
|
112
|
+
}
|
|
113
|
+
if (settings.bank !== undefined) {
|
|
114
|
+
this.adl._adl_setBank(this.midi, settings.bank);
|
|
115
|
+
}
|
|
116
|
+
if (settings.softPan !== undefined) {
|
|
117
|
+
this.adl._adl_setSoftPanEnabled(this.midi, settings.softPan ? 1 : 0);
|
|
118
|
+
}
|
|
119
|
+
if (settings.deepVibrato !== undefined) {
|
|
120
|
+
this.adl._adl_setHVibrato(this.midi, settings.deepVibrato ? 1 : 0);
|
|
121
|
+
}
|
|
122
|
+
if (settings.deepTremolo !== undefined) {
|
|
123
|
+
this.adl._adl_setHTremolo(this.midi, settings.deepTremolo ? 1 : 0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ================== Instrument Editing API ==================
|
|
128
|
+
|
|
129
|
+
// Structure sizes (imported from shared utils)
|
|
130
|
+
static SIZEOF_ADL_OPERATOR = SIZEOF_ADL_OPERATOR;
|
|
131
|
+
static SIZEOF_ADL_INSTRUMENT = SIZEOF_ADL_INSTRUMENT;
|
|
132
|
+
static SIZEOF_ADL_BANK = SIZEOF_ADL_BANK;
|
|
133
|
+
static SIZEOF_ADL_BANK_ID = SIZEOF_ADL_BANK_ID;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Decode an OPL3 operator from raw register bytes to named properties
|
|
137
|
+
* @param {Uint8Array | number[]} bytes
|
|
138
|
+
*/
|
|
139
|
+
decodeOperator(bytes) {
|
|
140
|
+
return decodeOperator(bytes);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Encode named operator properties to raw register bytes
|
|
145
|
+
* @param {import('./utils/struct.js').Operator} op
|
|
146
|
+
*/
|
|
147
|
+
encodeOperator(op) {
|
|
148
|
+
return encodeOperator(op);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Read ADL_Instrument from WASM memory and decode to JS object
|
|
153
|
+
*/
|
|
154
|
+
readInstrumentFromMemory(ptr) {
|
|
155
|
+
// Copy bytes from WASM heap and delegate to shared decoder
|
|
156
|
+
const bytes = this.adl.HEAPU8.slice(ptr, ptr + SIZEOF_ADL_INSTRUMENT);
|
|
157
|
+
return decodeInstrument(bytes);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Write JS instrument object to WASM memory
|
|
162
|
+
*/
|
|
163
|
+
writeInstrumentToMemory(ptr, inst) {
|
|
164
|
+
// Encode to bytes and copy to WASM heap
|
|
165
|
+
const bytes = encodeInstrument(inst);
|
|
166
|
+
this.adl.HEAPU8.set(bytes, ptr);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Default operator values (silent)
|
|
171
|
+
*/
|
|
172
|
+
defaultOperator() {
|
|
173
|
+
return defaultOperator();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get instrument from bank
|
|
178
|
+
*/
|
|
179
|
+
getInstrument(bankId, programNumber) {
|
|
180
|
+
try {
|
|
181
|
+
// Allocate ADL_BankId struct (3 bytes)
|
|
182
|
+
const bankIdPtr = this.adl._malloc(4); // 4 for alignment
|
|
183
|
+
this.adl.HEAPU8[bankIdPtr] = bankId.percussive ? 1 : 0;
|
|
184
|
+
this.adl.HEAPU8[bankIdPtr + 1] = bankId.msb || 0;
|
|
185
|
+
this.adl.HEAPU8[bankIdPtr + 2] = bankId.lsb || 0;
|
|
186
|
+
|
|
187
|
+
// Allocate ADL_Bank struct
|
|
188
|
+
const bankPtr = this.adl._malloc(AdlMidiProcessor.SIZEOF_ADL_BANK);
|
|
189
|
+
|
|
190
|
+
// Get bank (create if needed)
|
|
191
|
+
const bankResult = this.adl._adl_getBank(this.midi, bankIdPtr, 1, bankPtr);
|
|
192
|
+
|
|
193
|
+
if (bankResult !== 0) {
|
|
194
|
+
this.adl._free(bankIdPtr);
|
|
195
|
+
this.adl._free(bankPtr);
|
|
196
|
+
return { success: false, error: 'Failed to get bank' };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Allocate ADL_Instrument struct
|
|
200
|
+
const instPtr = this.adl._malloc(AdlMidiProcessor.SIZEOF_ADL_INSTRUMENT);
|
|
201
|
+
|
|
202
|
+
// Get instrument
|
|
203
|
+
const instResult = this.adl._adl_getInstrument(this.midi, bankPtr, programNumber, instPtr);
|
|
204
|
+
|
|
205
|
+
let instrument = null;
|
|
206
|
+
if (instResult === 0) {
|
|
207
|
+
instrument = this.readInstrumentFromMemory(instPtr);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.adl._free(bankIdPtr);
|
|
211
|
+
this.adl._free(bankPtr);
|
|
212
|
+
this.adl._free(instPtr);
|
|
213
|
+
|
|
214
|
+
if (instrument) {
|
|
215
|
+
return { success: true, instrument };
|
|
216
|
+
} else {
|
|
217
|
+
return { success: false, error: 'Failed to get instrument' };
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return { success: false, error: error.message };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Set instrument in bank
|
|
226
|
+
*/
|
|
227
|
+
setInstrument(bankId, programNumber, instrument) {
|
|
228
|
+
try {
|
|
229
|
+
// Allocate ADL_BankId struct
|
|
230
|
+
const bankIdPtr = this.adl._malloc(4);
|
|
231
|
+
this.adl.HEAPU8[bankIdPtr] = bankId.percussive ? 1 : 0;
|
|
232
|
+
this.adl.HEAPU8[bankIdPtr + 1] = bankId.msb || 0;
|
|
233
|
+
this.adl.HEAPU8[bankIdPtr + 2] = bankId.lsb || 0;
|
|
234
|
+
|
|
235
|
+
// Allocate ADL_Bank struct
|
|
236
|
+
const bankPtr = this.adl._malloc(AdlMidiProcessor.SIZEOF_ADL_BANK);
|
|
237
|
+
|
|
238
|
+
// Get or create bank
|
|
239
|
+
const bankResult = this.adl._adl_getBank(this.midi, bankIdPtr, 1, bankPtr);
|
|
240
|
+
|
|
241
|
+
if (bankResult !== 0) {
|
|
242
|
+
this.adl._free(bankIdPtr);
|
|
243
|
+
this.adl._free(bankPtr);
|
|
244
|
+
return { success: false, error: 'Failed to get/create bank' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Allocate and write ADL_Instrument struct
|
|
248
|
+
const instPtr = this.adl._malloc(AdlMidiProcessor.SIZEOF_ADL_INSTRUMENT);
|
|
249
|
+
this.writeInstrumentToMemory(instPtr, instrument);
|
|
250
|
+
|
|
251
|
+
// Set instrument
|
|
252
|
+
const setResult = this.adl._adl_setInstrument(this.midi, bankPtr, programNumber, instPtr);
|
|
253
|
+
|
|
254
|
+
// Per libADLMIDI docs: "Is recommended to call adl_reset() to apply changes to real-time"
|
|
255
|
+
if (setResult === 0) {
|
|
256
|
+
this.adl._adl_reset(this.midi);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this.adl._free(bankIdPtr);
|
|
260
|
+
this.adl._free(bankPtr);
|
|
261
|
+
this.adl._free(instPtr);
|
|
262
|
+
|
|
263
|
+
if (setResult === 0) {
|
|
264
|
+
return { success: true };
|
|
265
|
+
} else {
|
|
266
|
+
return { success: false, error: 'Failed to set instrument' };
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return { success: false, error: error.message };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
handleMessage(msg) {
|
|
274
|
+
if (!this.ready && msg.type !== 'ping') return;
|
|
275
|
+
|
|
276
|
+
switch (msg.type) {
|
|
277
|
+
case 'ping':
|
|
278
|
+
this.port.postMessage({ type: 'pong', ready: this.ready });
|
|
279
|
+
break;
|
|
280
|
+
|
|
281
|
+
case 'noteOn':
|
|
282
|
+
this.adl._adl_rt_noteOn(this.midi, msg.channel, msg.note, msg.velocity);
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'noteOff':
|
|
286
|
+
this.adl._adl_rt_noteOff(this.midi, msg.channel, msg.note);
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case 'pitchBend':
|
|
290
|
+
this.adl._adl_rt_pitchBendML(this.midi, msg.channel, msg.lsb, msg.msb);
|
|
291
|
+
break;
|
|
292
|
+
|
|
293
|
+
case 'controlChange':
|
|
294
|
+
this.adl._adl_rt_controllerChange(this.midi, msg.channel, msg.controller, msg.value);
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
case 'programChange':
|
|
298
|
+
this.adl._adl_rt_patchChange(this.midi, msg.channel, msg.program);
|
|
299
|
+
break;
|
|
300
|
+
|
|
301
|
+
case 'resetState':
|
|
302
|
+
this.adl._adl_rt_resetState(this.midi);
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'panic':
|
|
306
|
+
this.adl._adl_panic(this.midi);
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'configure':
|
|
310
|
+
// Update settings at runtime
|
|
311
|
+
Object.assign(this.settings, msg.settings);
|
|
312
|
+
this.applySettings(msg.settings);
|
|
313
|
+
this.port.postMessage({ type: 'configured' });
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'loadBank':
|
|
317
|
+
this.loadBank(msg.data);
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'setBank': {
|
|
321
|
+
const result = this.adl._adl_setBank(this.midi, msg.bank);
|
|
322
|
+
this.port.postMessage({ type: 'bankSet', success: result === 0, bank: msg.bank });
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
case 'getInstrument': {
|
|
327
|
+
const getResult = this.getInstrument(msg.bankId, msg.programNumber);
|
|
328
|
+
this.port.postMessage({ type: 'instrumentLoaded', ...getResult });
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'setInstrument': {
|
|
333
|
+
const setResult = this.setInstrument(msg.bankId, msg.programNumber, msg.instrument);
|
|
334
|
+
this.port.postMessage({ type: 'instrumentSet', ...setResult });
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
case 'setNumChips':
|
|
339
|
+
this.adl._adl_setNumChips(this.midi, msg.chips);
|
|
340
|
+
break;
|
|
341
|
+
|
|
342
|
+
case 'setVolumeModel':
|
|
343
|
+
this.adl._adl_setVolumeRangeModel(this.midi, msg.model);
|
|
344
|
+
break;
|
|
345
|
+
|
|
346
|
+
case 'setPercMode':
|
|
347
|
+
this.adl._adl_setPercMode(this.midi, msg.enabled ? 1 : 0);
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case 'setVibrato':
|
|
351
|
+
this.adl._adl_setHVibrato(this.midi, msg.enabled ? 1 : 0);
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
case 'setTremolo':
|
|
355
|
+
this.adl._adl_setHTremolo(this.midi, msg.enabled ? 1 : 0);
|
|
356
|
+
break;
|
|
357
|
+
|
|
358
|
+
case 'switchEmulator': {
|
|
359
|
+
// Note: adl_switchEmulator internally calls partialReset(), so no extra reset needed
|
|
360
|
+
const result = this.adl._adl_switchEmulator(this.midi, msg.emulator);
|
|
361
|
+
this.port.postMessage({ type: 'emulatorSwitched', success: result === 0, emulator: msg.emulator });
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
case 'getEmulatorName': {
|
|
366
|
+
const namePtr = this.adl._adl_chipEmulatorName(this.midi);
|
|
367
|
+
const name = namePtr ? this.adl.UTF8ToString(namePtr) : 'Unknown';
|
|
368
|
+
this.port.postMessage({ type: 'emulatorName', name });
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// MIDI file playback
|
|
373
|
+
case 'loadMidi':
|
|
374
|
+
this.loadMidiData(msg.data);
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case 'play':
|
|
378
|
+
this.playMode = 'file';
|
|
379
|
+
break;
|
|
380
|
+
|
|
381
|
+
case 'stop':
|
|
382
|
+
this.playMode = 'realtime';
|
|
383
|
+
this.adl._adl_positionRewind(this.midi);
|
|
384
|
+
this.adl._adl_panic(this.midi);
|
|
385
|
+
break;
|
|
386
|
+
|
|
387
|
+
case 'seek':
|
|
388
|
+
this.adl._adl_positionSeek(this.midi, msg.position);
|
|
389
|
+
break;
|
|
390
|
+
|
|
391
|
+
case 'setLoop':
|
|
392
|
+
this.adl._adl_setLoopEnabled(this.midi, msg.enabled ? 1 : 0);
|
|
393
|
+
break;
|
|
394
|
+
|
|
395
|
+
case 'setTempo':
|
|
396
|
+
this.adl._adl_setTempo(this.midi, msg.tempo);
|
|
397
|
+
break;
|
|
398
|
+
|
|
399
|
+
case 'getState':
|
|
400
|
+
this.port.postMessage({
|
|
401
|
+
type: 'state',
|
|
402
|
+
position: this.adl._adl_positionTell(this.midi),
|
|
403
|
+
duration: this.adl._adl_totalTimeLength(this.midi),
|
|
404
|
+
atEnd: this.adl._adl_atEnd(this.midi) !== 0,
|
|
405
|
+
playMode: this.playMode
|
|
406
|
+
});
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
case 'reset':
|
|
410
|
+
this.adl._adl_reset(this.midi);
|
|
411
|
+
this.playMode = 'realtime';
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
loadMidiData(arrayBuffer) {
|
|
417
|
+
try {
|
|
418
|
+
const data = new Uint8Array(arrayBuffer);
|
|
419
|
+
const dataPtr = this.adl._malloc(data.length);
|
|
420
|
+
this.adl.HEAPU8.set(data, dataPtr);
|
|
421
|
+
|
|
422
|
+
const result = this.adl._adl_openData(this.midi, dataPtr, data.length);
|
|
423
|
+
this.adl._free(dataPtr);
|
|
424
|
+
|
|
425
|
+
if (result === 0) {
|
|
426
|
+
const duration = this.adl._adl_totalTimeLength(this.midi);
|
|
427
|
+
this.port.postMessage({
|
|
428
|
+
type: 'midiLoaded',
|
|
429
|
+
success: true,
|
|
430
|
+
duration: duration
|
|
431
|
+
});
|
|
432
|
+
} else {
|
|
433
|
+
this.port.postMessage({
|
|
434
|
+
type: 'midiLoaded',
|
|
435
|
+
success: false,
|
|
436
|
+
error: 'Failed to parse MIDI data'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
this.port.postMessage({
|
|
441
|
+
type: 'midiLoaded',
|
|
442
|
+
success: false,
|
|
443
|
+
error: error.message
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
loadBank(arrayBuffer) {
|
|
449
|
+
try {
|
|
450
|
+
const data = new Uint8Array(arrayBuffer);
|
|
451
|
+
const dataPtr = this.adl._malloc(data.length);
|
|
452
|
+
this.adl.HEAPU8.set(data, dataPtr);
|
|
453
|
+
|
|
454
|
+
const result = this.adl._adl_openBankData(this.midi, dataPtr, data.length);
|
|
455
|
+
this.adl._free(dataPtr);
|
|
456
|
+
|
|
457
|
+
if (result === 0) {
|
|
458
|
+
this.port.postMessage({ type: 'bankLoaded', success: true });
|
|
459
|
+
} else {
|
|
460
|
+
this.port.postMessage({
|
|
461
|
+
type: 'bankLoaded',
|
|
462
|
+
success: false,
|
|
463
|
+
error: 'Failed to load bank data'
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
this.port.postMessage({
|
|
468
|
+
type: 'bankLoaded',
|
|
469
|
+
success: false,
|
|
470
|
+
error: error.message
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
process(_inputs, outputs, _parameters) {
|
|
476
|
+
if (!this.ready || !this.midi || !this.adl || !this.adl.HEAP16) return true;
|
|
477
|
+
|
|
478
|
+
const output = outputs[0];
|
|
479
|
+
if (!output || output.length === 0) return true;
|
|
480
|
+
|
|
481
|
+
const left = output[0];
|
|
482
|
+
const right = output[1] || output[0]; // Mono fallback
|
|
483
|
+
const frames = left.length;
|
|
484
|
+
|
|
485
|
+
try {
|
|
486
|
+
// Generate audio (16-bit stereo interleaved)
|
|
487
|
+
const sampleCount = frames * 2;
|
|
488
|
+
|
|
489
|
+
// Use adl_play for file playback mode, adl_generate for real-time
|
|
490
|
+
if (this.playMode === 'file') {
|
|
491
|
+
this.adl._adl_play(this.midi, sampleCount, this.bufferPtr);
|
|
492
|
+
} else {
|
|
493
|
+
this.adl._adl_generate(this.midi, sampleCount, this.bufferPtr);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Convert from Int16 to Float32
|
|
497
|
+
// Cache the view - only recreate if WASM heap has grown
|
|
498
|
+
const currentBuffer = this.adl.HEAP16.buffer;
|
|
499
|
+
if (this.cachedHeapBuffer !== currentBuffer) {
|
|
500
|
+
this.cachedHeapBuffer = currentBuffer;
|
|
501
|
+
}
|
|
502
|
+
const heap16 = new Int16Array(currentBuffer, this.bufferPtr, sampleCount);
|
|
503
|
+
|
|
504
|
+
for (let i = 0; i < frames; i++) {
|
|
505
|
+
left[i] = heap16[i * 2] / 32768.0;
|
|
506
|
+
right[i] = heap16[i * 2 + 1] / 32768.0;
|
|
507
|
+
}
|
|
508
|
+
} catch (e) {
|
|
509
|
+
// Report errors to main thread instead of silently swallowing
|
|
510
|
+
this.port.postMessage({ type: 'processingError', error: e.message || String(e) });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
registerProcessor('adl-midi-processor', AdlMidiProcessor);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config dosbox profile for libADLMIDI-JS
|
|
3
|
+
*
|
|
4
|
+
* Exports pre-configured AdlMidi and AdlMidiCore with this profile's WASM.
|
|
5
|
+
*
|
|
6
|
+
*
|
|
7
|
+
* @module profiles/dosbox
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AdlMidi as BaseAdlMidi } from '../libadlmidi.js';
|
|
11
|
+
import { AdlMidiCore as BaseAdlMidiCore } from '../core.js';
|
|
12
|
+
|
|
13
|
+
// Resolve paths relative to this module
|
|
14
|
+
const PROCESSOR_URL = new URL('../../dist/libadlmidi.dosbox.processor.js', import.meta.url).href;
|
|
15
|
+
const WASM_URL = new URL('../../dist/libadlmidi.dosbox.core.wasm', import.meta.url).href;
|
|
16
|
+
const CORE_PATH = new URL('../../dist/libadlmidi.dosbox.core.js', import.meta.url).href;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pre-configured AdlMidi for dosbox profile.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```javascript
|
|
23
|
+
* import { AdlMidi } from 'libadlmidi-js/dosbox';
|
|
24
|
+
*
|
|
25
|
+
* const synth = new AdlMidi();
|
|
26
|
+
* await synth.init(); // No paths needed!
|
|
27
|
+
* synth.noteOn(0, 60, 100);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class AdlMidi extends BaseAdlMidi {
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the synthesizer with this profile's WASM.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [processorUrl] - Override processor URL (optional)
|
|
35
|
+
* @param {string} [wasmUrl] - Override WASM URL (optional)
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
async init(processorUrl, wasmUrl) {
|
|
39
|
+
return super.init(
|
|
40
|
+
processorUrl || PROCESSOR_URL,
|
|
41
|
+
wasmUrl || WASM_URL
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pre-configured AdlMidiCore for dosbox profile.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```javascript
|
|
51
|
+
* import { AdlMidiCore } from 'libadlmidi-js/dosbox/core';
|
|
52
|
+
*
|
|
53
|
+
* const synth = await AdlMidiCore.create(); // No paths needed!
|
|
54
|
+
* synth.init(44100);
|
|
55
|
+
* synth.noteOn(0, 60, 100);
|
|
56
|
+
* const samples = synth.generate(4096);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class AdlMidiCore {
|
|
60
|
+
/**
|
|
61
|
+
* Create a new AdlMidiCore instance with this profile's WASM.
|
|
62
|
+
*
|
|
63
|
+
* @param {{corePath?: string}} [options] - Options (corePath is pre-configured)
|
|
64
|
+
* @returns {Promise<BaseAdlMidiCore>}
|
|
65
|
+
*/
|
|
66
|
+
static async create(options = {}) {
|
|
67
|
+
return BaseAdlMidiCore.create({
|
|
68
|
+
...options,
|
|
69
|
+
corePath: options.corePath || CORE_PATH
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Re-export struct utilities for convenience
|
|
75
|
+
export {
|
|
76
|
+
encodeInstrument,
|
|
77
|
+
decodeInstrument,
|
|
78
|
+
defaultInstrument,
|
|
79
|
+
encodeOperator,
|
|
80
|
+
decodeOperator,
|
|
81
|
+
defaultOperator
|
|
82
|
+
} from '../utils/struct.js';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-config dosbox (slim) profile for libADLMIDI-JS
|
|
3
|
+
*
|
|
4
|
+
* Exports pre-configured AdlMidi and AdlMidiCore with this profile's WASM.
|
|
5
|
+
* Slim builds require loading a WOPL bank at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @module profiles/dosbox.slim
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { AdlMidi as BaseAdlMidi } from '../libadlmidi.js';
|
|
11
|
+
import { AdlMidiCore as BaseAdlMidiCore } from '../core.js';
|
|
12
|
+
|
|
13
|
+
// Resolve paths relative to this module
|
|
14
|
+
const PROCESSOR_URL = new URL('../../dist/libadlmidi.dosbox.slim.processor.js', import.meta.url).href;
|
|
15
|
+
const WASM_URL = new URL('../../dist/libadlmidi.dosbox.slim.core.wasm', import.meta.url).href;
|
|
16
|
+
const CORE_PATH = new URL('../../dist/libadlmidi.dosbox.slim.core.js', import.meta.url).href;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pre-configured AdlMidi for dosbox slim profile.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```javascript
|
|
23
|
+
* import { AdlMidi } from 'libadlmidi-js/dosbox.slim';
|
|
24
|
+
*
|
|
25
|
+
* const synth = new AdlMidi();
|
|
26
|
+
* await synth.init(); // No paths needed!
|
|
27
|
+
* synth.noteOn(0, 60, 100);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class AdlMidi extends BaseAdlMidi {
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the synthesizer with this profile's WASM.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [processorUrl] - Override processor URL (optional)
|
|
35
|
+
* @param {string} [wasmUrl] - Override WASM URL (optional)
|
|
36
|
+
* @returns {Promise<void>}
|
|
37
|
+
*/
|
|
38
|
+
async init(processorUrl, wasmUrl) {
|
|
39
|
+
return super.init(
|
|
40
|
+
processorUrl || PROCESSOR_URL,
|
|
41
|
+
wasmUrl || WASM_URL
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pre-configured AdlMidiCore for dosbox slim profile.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```javascript
|
|
51
|
+
* import { AdlMidiCore } from 'libadlmidi-js/dosbox.slim/core';
|
|
52
|
+
*
|
|
53
|
+
* const synth = await AdlMidiCore.create(); // No paths needed!
|
|
54
|
+
* synth.init(44100);
|
|
55
|
+
* synth.noteOn(0, 60, 100);
|
|
56
|
+
* const samples = synth.generate(4096);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export class AdlMidiCore {
|
|
60
|
+
/**
|
|
61
|
+
* Create a new AdlMidiCore instance with this profile's WASM.
|
|
62
|
+
*
|
|
63
|
+
* @param {{corePath?: string}} [options] - Options (corePath is pre-configured)
|
|
64
|
+
* @returns {Promise<BaseAdlMidiCore>}
|
|
65
|
+
*/
|
|
66
|
+
static async create(options = {}) {
|
|
67
|
+
return BaseAdlMidiCore.create({
|
|
68
|
+
...options,
|
|
69
|
+
corePath: options.corePath || CORE_PATH
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Re-export struct utilities for convenience
|
|
75
|
+
export {
|
|
76
|
+
encodeInstrument,
|
|
77
|
+
decodeInstrument,
|
|
78
|
+
defaultInstrument,
|
|
79
|
+
encodeOperator,
|
|
80
|
+
decodeOperator,
|
|
81
|
+
defaultOperator
|
|
82
|
+
} from '../utils/struct.js';
|