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.
Files changed (86) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +126 -0
  3. package/dist/core.d.ts +276 -0
  4. package/dist/fm_banks/ail/MonopolyDeluxe.wopl +0 -0
  5. package/dist/fm_banks/ail/master_of_magic.wopl +0 -0
  6. package/dist/fm_banks/manifest.json +60 -0
  7. package/dist/fm_banks/wopl/Apogee-IMF-90.wopl +0 -0
  8. package/dist/fm_banks/wopl/DMXOPL3-by-sneakernets-GS.wopl +0 -0
  9. package/dist/fm_banks/wopl/GM-By-J.A.Nguyen-and-Wohlstand.wopl +0 -0
  10. package/dist/fm_banks/wopl/Wohlstand's-modded-FatMan.wopl +0 -0
  11. package/dist/fm_banks/wopl/fatman-2op.wopl +0 -0
  12. package/dist/fm_banks/wopl/fatman-4op.wopl +0 -0
  13. package/dist/fm_banks/wopl/msadlib.wopl +0 -0
  14. package/dist/libadlmidi.d.ts +453 -0
  15. package/dist/libadlmidi.dosbox.browser.js +2 -0
  16. package/dist/libadlmidi.dosbox.browser.wasm +0 -0
  17. package/dist/libadlmidi.dosbox.core.js +2 -0
  18. package/dist/libadlmidi.dosbox.core.wasm +0 -0
  19. package/dist/libadlmidi.dosbox.js +0 -0
  20. package/dist/libadlmidi.dosbox.processor.js +3226 -0
  21. package/dist/libadlmidi.dosbox.slim.browser.js +2 -0
  22. package/dist/libadlmidi.dosbox.slim.browser.wasm +0 -0
  23. package/dist/libadlmidi.dosbox.slim.core.js +2 -0
  24. package/dist/libadlmidi.dosbox.slim.core.wasm +0 -0
  25. package/dist/libadlmidi.dosbox.slim.js +0 -0
  26. package/dist/libadlmidi.dosbox.slim.processor.js +3226 -0
  27. package/dist/libadlmidi.full.browser.js +2 -0
  28. package/dist/libadlmidi.full.browser.wasm +0 -0
  29. package/dist/libadlmidi.full.core.js +2 -0
  30. package/dist/libadlmidi.full.core.wasm +0 -0
  31. package/dist/libadlmidi.full.js +0 -0
  32. package/dist/libadlmidi.full.processor.js +3226 -0
  33. package/dist/libadlmidi.full.slim.browser.js +2 -0
  34. package/dist/libadlmidi.full.slim.browser.wasm +0 -0
  35. package/dist/libadlmidi.full.slim.core.js +2 -0
  36. package/dist/libadlmidi.full.slim.core.wasm +0 -0
  37. package/dist/libadlmidi.full.slim.js +0 -0
  38. package/dist/libadlmidi.full.slim.processor.js +3226 -0
  39. package/dist/libadlmidi.js +445 -0
  40. package/dist/libadlmidi.js.map +7 -0
  41. package/dist/libadlmidi.light.browser.js +2 -0
  42. package/dist/libadlmidi.light.browser.wasm +0 -0
  43. package/dist/libadlmidi.light.core.js +2 -0
  44. package/dist/libadlmidi.light.core.wasm +0 -0
  45. package/dist/libadlmidi.light.js +0 -0
  46. package/dist/libadlmidi.light.processor.js +3226 -0
  47. package/dist/libadlmidi.light.slim.browser.js +2 -0
  48. package/dist/libadlmidi.light.slim.browser.wasm +0 -0
  49. package/dist/libadlmidi.light.slim.core.js +2 -0
  50. package/dist/libadlmidi.light.slim.core.wasm +0 -0
  51. package/dist/libadlmidi.light.slim.js +0 -0
  52. package/dist/libadlmidi.light.slim.processor.js +3226 -0
  53. package/dist/libadlmidi.nuked.browser.js +2 -0
  54. package/dist/libadlmidi.nuked.browser.wasm +0 -0
  55. package/dist/libadlmidi.nuked.core.js +2 -0
  56. package/dist/libadlmidi.nuked.core.wasm +0 -0
  57. package/dist/libadlmidi.nuked.js +0 -0
  58. package/dist/libadlmidi.nuked.processor.js +3226 -0
  59. package/dist/libadlmidi.nuked.slim.browser.js +2 -0
  60. package/dist/libadlmidi.nuked.slim.browser.wasm +0 -0
  61. package/dist/libadlmidi.nuked.slim.core.js +2 -0
  62. package/dist/libadlmidi.nuked.slim.core.wasm +0 -0
  63. package/dist/libadlmidi.nuked.slim.js +0 -0
  64. package/dist/libadlmidi.nuked.slim.processor.js +3226 -0
  65. package/dist/profiles/dosbox.d.ts +49 -0
  66. package/dist/profiles/dosbox.slim.d.ts +49 -0
  67. package/dist/profiles/full.d.ts +49 -0
  68. package/dist/profiles/full.slim.d.ts +49 -0
  69. package/dist/profiles/light.d.ts +49 -0
  70. package/dist/profiles/light.slim.d.ts +49 -0
  71. package/dist/profiles/nuked.d.ts +49 -0
  72. package/dist/profiles/nuked.slim.d.ts +49 -0
  73. package/dist/utils/struct.d.ts +209 -0
  74. package/package.json +103 -0
  75. package/src/core.js +591 -0
  76. package/src/libadlmidi.js +524 -0
  77. package/src/processor.js +517 -0
  78. package/src/profiles/dosbox.js +82 -0
  79. package/src/profiles/dosbox.slim.js +82 -0
  80. package/src/profiles/full.js +82 -0
  81. package/src/profiles/full.slim.js +82 -0
  82. package/src/profiles/light.js +82 -0
  83. package/src/profiles/light.slim.js +82 -0
  84. package/src/profiles/nuked.js +82 -0
  85. package/src/profiles/nuked.slim.js +82 -0
  86. package/src/utils/struct.js +288 -0
@@ -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';