spessasynth_lib 4.2.14 → 4.3.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 +6 -0
- package/dist/index.d.ts +165 -182
- package/dist/index.js +388 -297
- package/dist/index.js.map +1 -1
- package/dist/spessasynth_processor.min.js +9 -11
- package/dist/spessasynth_processor.min.js.map +4 -4
- package/package.json +10 -9
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BasicMIDI, BasicSoundBank, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, KeyModifier, MIDIControllers, MIDIMessageTypes, MIDITrack, MIDIUtils, SoundBankLoader, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, audioToWav } from "spessasynth_core";
|
|
2
2
|
//#region src/synthesizer/basic/synth_config.ts
|
|
3
3
|
const DEFAULT_SYNTH_CONFIG = {
|
|
4
|
-
|
|
4
|
+
eventsEnabled: true,
|
|
5
5
|
oneOutput: false,
|
|
6
6
|
audioNodeCreators: void 0
|
|
7
7
|
};
|
|
@@ -150,11 +150,11 @@ var SoundBankManager = class {
|
|
|
150
150
|
*/
|
|
151
151
|
async deleteSoundBank(id) {
|
|
152
152
|
if (this.soundBankList.length < 2) {
|
|
153
|
-
|
|
153
|
+
SpessaLog.warn("1 sound bank left. Aborting!");
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
if (!this.soundBankList.some((s) => s.id === id)) {
|
|
157
|
-
|
|
157
|
+
SpessaLog.warn(`No sound banks with id of "${id}" found. Aborting!`);
|
|
158
158
|
return;
|
|
159
159
|
}
|
|
160
160
|
this.sendToWorklet("deleteSoundBank", id);
|
|
@@ -191,21 +191,17 @@ var SynthEventHandler = class {
|
|
|
191
191
|
events = {
|
|
192
192
|
noteOff: /* @__PURE__ */ new Map(),
|
|
193
193
|
noteOn: /* @__PURE__ */ new Map(),
|
|
194
|
-
pitchWheel: /* @__PURE__ */ new Map(),
|
|
195
194
|
controllerChange: /* @__PURE__ */ new Map(),
|
|
196
195
|
programChange: /* @__PURE__ */ new Map(),
|
|
197
|
-
channelPressure: /* @__PURE__ */ new Map(),
|
|
198
196
|
polyPressure: /* @__PURE__ */ new Map(),
|
|
199
|
-
drumChange: /* @__PURE__ */ new Map(),
|
|
200
197
|
stopAll: /* @__PURE__ */ new Map(),
|
|
201
|
-
|
|
202
|
-
muteChannel: /* @__PURE__ */ new Map(),
|
|
198
|
+
channelAdded: /* @__PURE__ */ new Map(),
|
|
203
199
|
presetListChange: /* @__PURE__ */ new Map(),
|
|
204
|
-
|
|
200
|
+
reset: /* @__PURE__ */ new Map(),
|
|
205
201
|
soundBankError: /* @__PURE__ */ new Map(),
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
displayMessage: /* @__PURE__ */ new Map(),
|
|
203
|
+
globalParamChange: /* @__PURE__ */ new Map(),
|
|
204
|
+
channelParamChange: /* @__PURE__ */ new Map(),
|
|
209
205
|
effectChange: /* @__PURE__ */ new Map()
|
|
210
206
|
};
|
|
211
207
|
/**
|
|
@@ -249,10 +245,138 @@ var SynthEventHandler = class {
|
|
|
249
245
|
* Other.js
|
|
250
246
|
* purpose: contains some useful functions that don't belong in any specific category
|
|
251
247
|
*/
|
|
252
|
-
const
|
|
248
|
+
const ConsoleColors = SpessaSynthCoreUtils.ConsoleColors;
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/synthesizer/basic/lib_midi_channel.ts
|
|
251
|
+
var LibMIDIChannel = class {
|
|
252
|
+
/**
|
|
253
|
+
* This channel number.
|
|
254
|
+
* @private
|
|
255
|
+
*/
|
|
256
|
+
channel;
|
|
257
|
+
synth;
|
|
258
|
+
_systemParameters = { ...DEFAULT_CHANNEL_SYSTEM_PARAMETERS };
|
|
259
|
+
/**
|
|
260
|
+
* @internal
|
|
261
|
+
* @param channel
|
|
262
|
+
* @param synth
|
|
263
|
+
*/
|
|
264
|
+
constructor(channel, synth) {
|
|
265
|
+
this.channel = channel;
|
|
266
|
+
this.synth = synth;
|
|
267
|
+
}
|
|
268
|
+
_patch = {
|
|
269
|
+
bankMSB: 0,
|
|
270
|
+
bankLSB: 0,
|
|
271
|
+
program: 0,
|
|
272
|
+
isDrum: false,
|
|
273
|
+
isGMGSDrum: false,
|
|
274
|
+
name: ""
|
|
275
|
+
};
|
|
276
|
+
/**
|
|
277
|
+
* The currently selected MIDI patch of the channel.
|
|
278
|
+
* Note that the exact matching preset may not be available, but this represents exactly what MIDI asks for.
|
|
279
|
+
*/
|
|
280
|
+
get patch() {
|
|
281
|
+
return this._patch;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* @internal
|
|
285
|
+
* @param patch
|
|
286
|
+
*/
|
|
287
|
+
set patch(patch) {
|
|
288
|
+
this._patch = patch;
|
|
289
|
+
}
|
|
290
|
+
_midiParameters = { ...DEFAULT_CHANNEL_MIDI_PARAMETERS };
|
|
291
|
+
/**
|
|
292
|
+
* The channel MIDI parameters of this channel.
|
|
293
|
+
* These are only editable via MIDI messages.
|
|
294
|
+
*/
|
|
295
|
+
get midiParameters() {
|
|
296
|
+
return this._midiParameters;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The channel system parameters of this channel.
|
|
300
|
+
* These are only editable via the API.
|
|
301
|
+
*/
|
|
302
|
+
get systemParameters() {
|
|
303
|
+
return this._systemParameters;
|
|
304
|
+
}
|
|
305
|
+
_voiceCount = 0;
|
|
306
|
+
/**
|
|
307
|
+
* The channel's current voice count.
|
|
308
|
+
*/
|
|
309
|
+
get voiceCount() {
|
|
310
|
+
return this._voiceCount;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* @internal
|
|
314
|
+
* @param value
|
|
315
|
+
*/
|
|
316
|
+
set voiceCount(value) {
|
|
317
|
+
this._voiceCount = value;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Toggles drums on a given channel.
|
|
321
|
+
* @param isDrum If the channel should be drums.
|
|
322
|
+
*/
|
|
323
|
+
setDrums(isDrum) {
|
|
324
|
+
this.synth.post({
|
|
325
|
+
channelNumber: this.channel,
|
|
326
|
+
type: "setDrums",
|
|
327
|
+
data: isDrum
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Causes the given midi channel to ignore controller messages for the given controller number.
|
|
332
|
+
* @param controller 0-127 MIDI CC number.
|
|
333
|
+
* @param isLocked True if locked, false if unlocked.
|
|
334
|
+
*/
|
|
335
|
+
lockController(controller, isLocked) {
|
|
336
|
+
this.synth.post({
|
|
337
|
+
channelNumber: this.channel,
|
|
338
|
+
type: "lockController",
|
|
339
|
+
data: {
|
|
340
|
+
controller,
|
|
341
|
+
isLocked
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Sets a system parameter of the channel.
|
|
347
|
+
* @param parameter The type of the parameter to set.
|
|
348
|
+
* @param value The value to set for the parameter.
|
|
349
|
+
*/
|
|
350
|
+
setSystemParameter(parameter, value) {
|
|
351
|
+
this._systemParameters[parameter] = value;
|
|
352
|
+
this.synth.post({
|
|
353
|
+
type: "setChannelSystemParameter",
|
|
354
|
+
channelNumber: this.channel,
|
|
355
|
+
data: {
|
|
356
|
+
type: parameter,
|
|
357
|
+
data: value
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* @internal
|
|
363
|
+
* @param parameter
|
|
364
|
+
* @param value
|
|
365
|
+
*/
|
|
366
|
+
setMIDIParameter(parameter, value) {
|
|
367
|
+
this._midiParameters[parameter] = value;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* @internal
|
|
371
|
+
*/
|
|
372
|
+
reset() {
|
|
373
|
+
this._midiParameters = { ...DEFAULT_CHANNEL_MIDI_PARAMETERS };
|
|
374
|
+
}
|
|
375
|
+
};
|
|
253
376
|
//#endregion
|
|
254
377
|
//#region src/synthesizer/basic/basic_synthesizer.ts
|
|
255
378
|
const DEFAULT_SYNTH_METHOD_OPTIONS = { time: 0 };
|
|
379
|
+
const SPESSASYNTH_LIB_HANDLER = (event) => `SPESSASYNTH_LIB_HANDLE_${event}_${Math.random()}`;
|
|
256
380
|
var BasicSynthesizer = class {
|
|
257
381
|
/**
|
|
258
382
|
* Allows managing the sound bank list.
|
|
@@ -273,7 +397,7 @@ var BasicSynthesizer = class {
|
|
|
273
397
|
/**
|
|
274
398
|
* Synth's current channel properties.
|
|
275
399
|
*/
|
|
276
|
-
|
|
400
|
+
midiChannels = [];
|
|
277
401
|
/**
|
|
278
402
|
* The current preset list.
|
|
279
403
|
*/
|
|
@@ -289,16 +413,6 @@ var BasicSynthesizer = class {
|
|
|
289
413
|
*/
|
|
290
414
|
isReady;
|
|
291
415
|
/**
|
|
292
|
-
* Legacy parameter.
|
|
293
|
-
* @deprecated
|
|
294
|
-
*/
|
|
295
|
-
reverbProcessor = void 0;
|
|
296
|
-
/**
|
|
297
|
-
* Legacy parameter.
|
|
298
|
-
* @deprecated
|
|
299
|
-
*/
|
|
300
|
-
chorusProcessor = void 0;
|
|
301
|
-
/**
|
|
302
416
|
* INTERNAL USE ONLY!
|
|
303
417
|
* @internal
|
|
304
418
|
*/
|
|
@@ -309,12 +423,8 @@ var BasicSynthesizer = class {
|
|
|
309
423
|
* what does that mean?
|
|
310
424
|
* e.g., if outputsAmount is 16, then channel's 16 audio data will be sent to channel 0
|
|
311
425
|
*/
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
* The current amount of MIDI channels the synthesizer has.
|
|
315
|
-
*/
|
|
316
|
-
channelsAmount = this._outputsAmount;
|
|
317
|
-
masterParameters = { ...DEFAULT_MASTER_PARAMETERS };
|
|
426
|
+
_outputCount = 16;
|
|
427
|
+
_systemParameters = { ...DEFAULT_GLOBAL_SYSTEM_PARAMETERS };
|
|
318
428
|
resolveMap = /* @__PURE__ */ new Map();
|
|
319
429
|
renderingProgressTracker = /* @__PURE__ */ new Map();
|
|
320
430
|
/**
|
|
@@ -324,37 +434,48 @@ var BasicSynthesizer = class {
|
|
|
324
434
|
* @param config Optional configuration for the synthesizer.
|
|
325
435
|
*/
|
|
326
436
|
constructor(worklet, postFunction, config) {
|
|
327
|
-
|
|
437
|
+
SpessaLog.info("%cInitializing SpessaSynth synthesizer...", ConsoleColors.info);
|
|
328
438
|
this.context = worklet.context;
|
|
329
439
|
this.worklet = worklet;
|
|
330
440
|
this.post = postFunction;
|
|
331
441
|
this.isReady = new Promise((resolve) => this.awaitWorkerResponse("sf3Decoder", resolve));
|
|
332
442
|
this.worklet.port.onmessage = (e) => this.handleMessage(e.data);
|
|
333
|
-
for (let i = 0; i <
|
|
334
|
-
this.
|
|
335
|
-
|
|
336
|
-
this.channelsAmount++;
|
|
337
|
-
});
|
|
338
|
-
this.eventHandler.addEvent("presetListChange", `synth-preset-list-change-${Math.random()}`, (e) => {
|
|
339
|
-
this.presetList = [...e];
|
|
340
|
-
});
|
|
341
|
-
this.eventHandler.addEvent("masterParameterChange", `synth-master-parameter-change-${Math.random()}`, (e) => {
|
|
342
|
-
this.masterParameters[e.parameter] = e.value;
|
|
443
|
+
for (let i = 0; i < 16; i++) this.addNewChannelInternal(false);
|
|
444
|
+
this.registerInternalEvent("channelAdded", () => {
|
|
445
|
+
this.addNewChannelInternal(false);
|
|
343
446
|
});
|
|
344
|
-
this.
|
|
345
|
-
|
|
346
|
-
|
|
447
|
+
this.registerInternalEvent("presetListChange", (e) => this.presetList = [...e]);
|
|
448
|
+
this.registerInternalEvent("globalParamChange", (e) => this._midiParameters[e.parameter] = e.value);
|
|
449
|
+
this.registerInternalEvent("channelParamChange", (e) => this.midiChannels[e.channel].setMIDIParameter(e.parameter, e.value));
|
|
450
|
+
this.registerInternalEvent("programChange", (e) => this.midiChannels[e.channel].patch = { ...e });
|
|
451
|
+
this.registerInternalEvent("reset", () => {
|
|
452
|
+
for (const c of this.midiChannels) c.reset();
|
|
453
|
+
this._midiParameters = { ...DEFAULT_GLOBAL_MIDI_PARAMETERS };
|
|
347
454
|
});
|
|
348
455
|
}
|
|
456
|
+
_midiParameters = { ...DEFAULT_GLOBAL_MIDI_PARAMETERS };
|
|
457
|
+
/**
|
|
458
|
+
* The global MIDI parameters of the synthesizer.
|
|
459
|
+
* These are only editable via MIDI messages.
|
|
460
|
+
*/
|
|
461
|
+
get midiParameters() {
|
|
462
|
+
return this._midiParameters;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* The current channel count of the synthesizer.
|
|
466
|
+
*/
|
|
467
|
+
get channelCount() {
|
|
468
|
+
return this.midiChannels.length;
|
|
469
|
+
}
|
|
349
470
|
/**
|
|
350
471
|
* Current voice amount
|
|
351
472
|
*/
|
|
352
|
-
|
|
473
|
+
_voiceCount = 0;
|
|
353
474
|
/**
|
|
354
475
|
* The current number of voices playing.
|
|
355
476
|
*/
|
|
356
|
-
get
|
|
357
|
-
return this.
|
|
477
|
+
get voiceCount() {
|
|
478
|
+
return this._voiceCount;
|
|
358
479
|
}
|
|
359
480
|
/**
|
|
360
481
|
* The audioContext's current time.
|
|
@@ -363,6 +484,13 @@ var BasicSynthesizer = class {
|
|
|
363
484
|
return this.context.currentTime;
|
|
364
485
|
}
|
|
365
486
|
/**
|
|
487
|
+
* The global system parameters of the synthesizer.
|
|
488
|
+
* These are only editable via the API.
|
|
489
|
+
*/
|
|
490
|
+
get systemParameters() {
|
|
491
|
+
return this._systemParameters;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
366
494
|
* Connects from a given node.
|
|
367
495
|
* @param destinationNode The node to connect to.
|
|
368
496
|
*/
|
|
@@ -390,7 +518,7 @@ var BasicSynthesizer = class {
|
|
|
390
518
|
*/
|
|
391
519
|
setLogLevel(enableInfo, enableWarning, enableGroup) {
|
|
392
520
|
this.post({
|
|
393
|
-
channelNumber:
|
|
521
|
+
channelNumber: -1,
|
|
394
522
|
type: "setLogLevel",
|
|
395
523
|
data: {
|
|
396
524
|
enableInfo,
|
|
@@ -400,23 +528,15 @@ var BasicSynthesizer = class {
|
|
|
400
528
|
});
|
|
401
529
|
}
|
|
402
530
|
/**
|
|
403
|
-
*
|
|
404
|
-
* @param type The parameter to get.
|
|
405
|
-
* @returns The parameter value.
|
|
406
|
-
*/
|
|
407
|
-
getMasterParameter(type) {
|
|
408
|
-
return this.masterParameters[type];
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Sets a master parameter to a given value.
|
|
531
|
+
* Sets a system parameter to a given value.
|
|
412
532
|
* @param type The parameter to set.
|
|
413
533
|
* @param value The value to set.
|
|
414
534
|
*/
|
|
415
|
-
|
|
416
|
-
this.
|
|
535
|
+
setSystemParameter(type, value) {
|
|
536
|
+
this._systemParameters[type] = value;
|
|
417
537
|
this.post({
|
|
418
|
-
type: "
|
|
419
|
-
channelNumber:
|
|
538
|
+
type: "setGlobalSystemParameter",
|
|
539
|
+
channelNumber: -1,
|
|
420
540
|
data: {
|
|
421
541
|
type,
|
|
422
542
|
data: value
|
|
@@ -429,7 +549,7 @@ var BasicSynthesizer = class {
|
|
|
429
549
|
async getSnapshot() {
|
|
430
550
|
return new Promise((resolve) => {
|
|
431
551
|
this.awaitWorkerResponse("synthesizerSnapshot", (s) => {
|
|
432
|
-
resolve(
|
|
552
|
+
resolve(s);
|
|
433
553
|
});
|
|
434
554
|
this.post({
|
|
435
555
|
type: "requestSynthesizerSnapshot",
|
|
@@ -445,11 +565,6 @@ var BasicSynthesizer = class {
|
|
|
445
565
|
this.addNewChannelInternal(true);
|
|
446
566
|
}
|
|
447
567
|
/**
|
|
448
|
-
* DEPRECATED, please don't use it!
|
|
449
|
-
* @deprecated
|
|
450
|
-
*/
|
|
451
|
-
setVibrato(channel, value) {}
|
|
452
|
-
/**
|
|
453
568
|
* Connects a given channel output to the given audio node.
|
|
454
569
|
* Note that this output is only meant for visualization and may be silent when Insertion Effect for this channel is enabled.
|
|
455
570
|
* @param targetNode The node to connect to.
|
|
@@ -474,25 +589,18 @@ var BasicSynthesizer = class {
|
|
|
474
589
|
* @param audioNodes Exactly 16 outputs.
|
|
475
590
|
*/
|
|
476
591
|
connectIndividualOutputs(audioNodes) {
|
|
477
|
-
if (audioNodes.length !== this.
|
|
478
|
-
Expected ${this.
|
|
479
|
-
for (let channel = 0; channel < this.
|
|
592
|
+
if (audioNodes.length !== this._outputCount) throw new Error(`input nodes amount differs from the system's outputs amount!
|
|
593
|
+
Expected ${this._outputCount} got ${audioNodes.length}`);
|
|
594
|
+
for (let channel = 0; channel < this._outputCount; channel++) this.connectChannel(audioNodes[channel], channel);
|
|
480
595
|
}
|
|
481
596
|
/**
|
|
482
597
|
* Disconnects the individual audio outputs from the given audio nodes.
|
|
483
598
|
* @param audioNodes Exactly 16 outputs.
|
|
484
599
|
*/
|
|
485
600
|
disconnectIndividualOutputs(audioNodes) {
|
|
486
|
-
if (audioNodes.length !== this.
|
|
487
|
-
Expected ${this.
|
|
488
|
-
for (let channel = 0; channel < this.
|
|
489
|
-
}
|
|
490
|
-
/**
|
|
491
|
-
* Disables the GS NRPN parameters like vibrato or drum key tuning.
|
|
492
|
-
* @deprecated Deprecated! Please use master parameters
|
|
493
|
-
*/
|
|
494
|
-
disableGSNPRNParams() {
|
|
495
|
-
this.setMasterParameter("nprnParamLock", true);
|
|
601
|
+
if (audioNodes.length !== this._outputCount) throw new Error(`input nodes amount differs from the system's outputs amount!
|
|
602
|
+
Expected ${this._outputCount} got ${audioNodes.length}`);
|
|
603
|
+
for (let channel = 0; channel < this._outputCount; channel++) this.disconnectChannel(audioNodes[channel], channel);
|
|
496
604
|
}
|
|
497
605
|
/**
|
|
498
606
|
* Sends a raw MIDI message to the synthesizer.
|
|
@@ -516,7 +624,7 @@ var BasicSynthesizer = class {
|
|
|
516
624
|
midiNote %= 128;
|
|
517
625
|
velocity %= 128;
|
|
518
626
|
this.sendMessage([
|
|
519
|
-
|
|
627
|
+
MIDIMessageTypes.noteOn | ch,
|
|
520
628
|
midiNote,
|
|
521
629
|
velocity
|
|
522
630
|
], offset, eventOptions);
|
|
@@ -531,7 +639,7 @@ var BasicSynthesizer = class {
|
|
|
531
639
|
midiNote %= 128;
|
|
532
640
|
const ch = channel % 16;
|
|
533
641
|
const offset = channel - ch;
|
|
534
|
-
this._sendInternal([
|
|
642
|
+
this._sendInternal([MIDIMessageTypes.noteOff | ch, midiNote], offset, eventOptions);
|
|
535
643
|
}
|
|
536
644
|
/**
|
|
537
645
|
* Stops all notes.
|
|
@@ -539,7 +647,7 @@ var BasicSynthesizer = class {
|
|
|
539
647
|
*/
|
|
540
648
|
stopAll(force = false) {
|
|
541
649
|
this.post({
|
|
542
|
-
channelNumber:
|
|
650
|
+
channelNumber: -1,
|
|
543
651
|
type: "stopAll",
|
|
544
652
|
data: force ? 1 : 0
|
|
545
653
|
});
|
|
@@ -547,51 +655,33 @@ var BasicSynthesizer = class {
|
|
|
547
655
|
/**
|
|
548
656
|
* Changes the given controller
|
|
549
657
|
* @param channel Usually 0-15: the channel to change the controller.
|
|
550
|
-
* @param
|
|
551
|
-
* @param
|
|
658
|
+
* @param controller 0-127 the MIDI CC number.
|
|
659
|
+
* @param value 0-127 the controller value.
|
|
552
660
|
* @param eventOptions Additional options for this command.
|
|
553
661
|
*/
|
|
554
|
-
controllerChange(channel,
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
662
|
+
controllerChange(channel, controller, value, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS) {
|
|
663
|
+
if (controller > 127 || controller < 0) throw new Error(`Invalid controller number: ${controller}`);
|
|
664
|
+
value = Math.floor(value) % 128;
|
|
665
|
+
controller = Math.floor(controller) % 128;
|
|
558
666
|
const ch = channel % 16;
|
|
559
667
|
const offset = channel - ch;
|
|
560
668
|
this._sendInternal([
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
669
|
+
MIDIMessageTypes.controllerChange | ch,
|
|
670
|
+
controller,
|
|
671
|
+
value
|
|
564
672
|
], offset, eventOptions);
|
|
565
673
|
}
|
|
566
674
|
/**
|
|
567
|
-
*
|
|
675
|
+
* Fully resets the synthesizer.
|
|
568
676
|
*/
|
|
569
|
-
|
|
677
|
+
reset() {
|
|
570
678
|
this.post({
|
|
571
|
-
channelNumber:
|
|
679
|
+
channelNumber: -1,
|
|
572
680
|
type: "ccReset",
|
|
573
681
|
data: null
|
|
574
682
|
});
|
|
575
683
|
}
|
|
576
684
|
/**
|
|
577
|
-
* Causes the given midi channel to ignore controller messages for the given controller number.
|
|
578
|
-
* @param channel Usually 0-15: the channel to lock.
|
|
579
|
-
* @param controllerNumber 0-127 MIDI CC number.
|
|
580
|
-
* @param isLocked True if locked, false if unlocked.
|
|
581
|
-
* @remarks
|
|
582
|
-
* Controller number -1 locks the preset.
|
|
583
|
-
*/
|
|
584
|
-
lockController(channel, controllerNumber, isLocked) {
|
|
585
|
-
this.post({
|
|
586
|
-
channelNumber: channel,
|
|
587
|
-
type: "lockController",
|
|
588
|
-
data: {
|
|
589
|
-
controllerNumber,
|
|
590
|
-
isLocked
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
685
|
* Applies pressure to a given channel.
|
|
596
686
|
* @param channel Usually 0-15: the channel to change the controller.
|
|
597
687
|
* @param pressure 0-127: the pressure to apply.
|
|
@@ -601,7 +691,7 @@ var BasicSynthesizer = class {
|
|
|
601
691
|
const ch = channel % 16;
|
|
602
692
|
const offset = channel - ch;
|
|
603
693
|
pressure %= 128;
|
|
604
|
-
this.sendMessage([
|
|
694
|
+
this.sendMessage([MIDIMessageTypes.channelPressure | ch, pressure], offset, eventOptions);
|
|
605
695
|
}
|
|
606
696
|
/**
|
|
607
697
|
* Applies pressure to a given note.
|
|
@@ -616,7 +706,7 @@ var BasicSynthesizer = class {
|
|
|
616
706
|
midiNote %= 128;
|
|
617
707
|
pressure %= 128;
|
|
618
708
|
this.sendMessage([
|
|
619
|
-
|
|
709
|
+
MIDIMessageTypes.polyPressure | ch,
|
|
620
710
|
midiNote,
|
|
621
711
|
pressure
|
|
622
712
|
], offset, eventOptions);
|
|
@@ -631,7 +721,7 @@ var BasicSynthesizer = class {
|
|
|
631
721
|
const ch = channel % 16;
|
|
632
722
|
const offset = channel - ch;
|
|
633
723
|
this.sendMessage([
|
|
634
|
-
|
|
724
|
+
MIDIMessageTypes.pitchWheel | ch,
|
|
635
725
|
value & 127,
|
|
636
726
|
value >> 7
|
|
637
727
|
], offset, eventOptions);
|
|
@@ -643,12 +733,12 @@ var BasicSynthesizer = class {
|
|
|
643
733
|
* @param eventOptions Additional options for this command.
|
|
644
734
|
*/
|
|
645
735
|
pitchWheelRange(channel, range, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS) {
|
|
646
|
-
this.controllerChange(channel,
|
|
647
|
-
this.controllerChange(channel,
|
|
648
|
-
this.controllerChange(channel,
|
|
649
|
-
this.controllerChange(channel,
|
|
650
|
-
this.controllerChange(channel,
|
|
651
|
-
this.controllerChange(channel,
|
|
736
|
+
this.controllerChange(channel, MIDIControllers.registeredParameterMSB, 0, eventOptions);
|
|
737
|
+
this.controllerChange(channel, MIDIControllers.registeredParameterLSB, 0, eventOptions);
|
|
738
|
+
this.controllerChange(channel, MIDIControllers.dataEntryMSB, range);
|
|
739
|
+
this.controllerChange(channel, MIDIControllers.registeredParameterMSB, 127, eventOptions);
|
|
740
|
+
this.controllerChange(channel, MIDIControllers.registeredParameterLSB, 127, eventOptions);
|
|
741
|
+
this.controllerChange(channel, MIDIControllers.dataEntryMSB, 0, eventOptions);
|
|
652
742
|
}
|
|
653
743
|
/**
|
|
654
744
|
* Changes the program for a given channel
|
|
@@ -660,35 +750,7 @@ var BasicSynthesizer = class {
|
|
|
660
750
|
const ch = channel % 16;
|
|
661
751
|
const offset = channel - ch;
|
|
662
752
|
programNumber %= 128;
|
|
663
|
-
this.sendMessage([
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Transposes the channel by given number of semitones.
|
|
667
|
-
* @param channel The channel number.
|
|
668
|
-
* @param semitones The transposition of the channel, it can be a float.
|
|
669
|
-
* @param force Defaults to false, if true transposes the channel even if it's a drum channel.
|
|
670
|
-
*/
|
|
671
|
-
transposeChannel(channel, semitones, force = false) {
|
|
672
|
-
this.post({
|
|
673
|
-
channelNumber: channel,
|
|
674
|
-
type: "transposeChannel",
|
|
675
|
-
data: {
|
|
676
|
-
semitones,
|
|
677
|
-
force
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Mutes or unmutes the given channel.
|
|
683
|
-
* @param channel Usually 0-15: the channel to mute.
|
|
684
|
-
* @param isMuted Indicates if the channel is muted.
|
|
685
|
-
*/
|
|
686
|
-
muteChannel(channel, isMuted) {
|
|
687
|
-
this.post({
|
|
688
|
-
channelNumber: channel,
|
|
689
|
-
type: "muteChannel",
|
|
690
|
-
data: isMuted
|
|
691
|
-
});
|
|
753
|
+
this.sendMessage([MIDIMessageTypes.programChange | ch, programNumber], offset, eventOptions);
|
|
692
754
|
}
|
|
693
755
|
/**
|
|
694
756
|
* Sends a MIDI Sysex message to the synthesizer.
|
|
@@ -697,7 +759,7 @@ var BasicSynthesizer = class {
|
|
|
697
759
|
* @param eventOptions Additional options for this command.
|
|
698
760
|
*/
|
|
699
761
|
systemExclusive(messageData, channelOffset = 0, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS) {
|
|
700
|
-
this._sendInternal([
|
|
762
|
+
this._sendInternal([MIDIMessageTypes.systemExclusive, ...Array.from(messageData)], channelOffset, eventOptions);
|
|
701
763
|
}
|
|
702
764
|
/**
|
|
703
765
|
* Tune MIDI keys of a given program using the MIDI Tuning Standard.
|
|
@@ -728,24 +790,12 @@ var BasicSynthesizer = class {
|
|
|
728
790
|
this.systemExclusive(systemExclusive);
|
|
729
791
|
}
|
|
730
792
|
/**
|
|
731
|
-
* Toggles drums on a given channel.
|
|
732
|
-
* @param channel The channel number.
|
|
733
|
-
* @param isDrum If the channel should be drums.
|
|
734
|
-
*/
|
|
735
|
-
setDrums(channel, isDrum) {
|
|
736
|
-
this.post({
|
|
737
|
-
channelNumber: channel,
|
|
738
|
-
type: "setDrums",
|
|
739
|
-
data: isDrum
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
793
|
* Yes please!
|
|
744
794
|
*/
|
|
745
795
|
reverbateEverythingBecauseWhyNot() {
|
|
746
|
-
for (let i = 0; i < this.
|
|
747
|
-
this.controllerChange(i,
|
|
748
|
-
this.lockController(
|
|
796
|
+
for (let i = 0; i < this.midiChannels.length; i++) {
|
|
797
|
+
this.controllerChange(i, MIDIControllers.reverbDepth, 127);
|
|
798
|
+
this.midiChannels[i].lockController(MIDIControllers.reverbDepth, true);
|
|
749
799
|
}
|
|
750
800
|
return "That's the spirit!";
|
|
751
801
|
}
|
|
@@ -783,7 +833,7 @@ var BasicSynthesizer = class {
|
|
|
783
833
|
const options = fillWithDefaults(eventOptions, DEFAULT_SYNTH_METHOD_OPTIONS);
|
|
784
834
|
this.post({
|
|
785
835
|
type: "midiMessage",
|
|
786
|
-
channelNumber:
|
|
836
|
+
channelNumber: -1,
|
|
787
837
|
data: {
|
|
788
838
|
messageData: new Uint8Array(message),
|
|
789
839
|
channelOffset,
|
|
@@ -802,26 +852,24 @@ var BasicSynthesizer = class {
|
|
|
802
852
|
case "sequencerReturn":
|
|
803
853
|
this.sequencers[m.data.id]?.(m.data);
|
|
804
854
|
break;
|
|
855
|
+
case "voiceCountChange":
|
|
856
|
+
for (let i = 0; i < m.data.length; i++) {
|
|
857
|
+
this.midiChannels[i].voiceCount = m.data[i];
|
|
858
|
+
this._voiceCount = m.data.reduce((s, v) => s + v, 0);
|
|
859
|
+
}
|
|
860
|
+
break;
|
|
805
861
|
case "isFullyInitialized":
|
|
806
862
|
this.workletResponds(m.data.type, m.data.data);
|
|
807
863
|
break;
|
|
808
864
|
case "soundBankError":
|
|
809
|
-
|
|
865
|
+
SpessaLog.warn(m.data);
|
|
810
866
|
this.eventHandler.callEventInternal("soundBankError", m.data);
|
|
811
867
|
break;
|
|
812
868
|
case "renderingProgress": this.renderingProgressTracker.get(m.data.type)?.(m.data.data);
|
|
813
869
|
}
|
|
814
870
|
}
|
|
815
871
|
addNewChannelInternal(post) {
|
|
816
|
-
this.
|
|
817
|
-
voicesAmount: 0,
|
|
818
|
-
pitchWheel: 0,
|
|
819
|
-
pitchWheelRange: 0,
|
|
820
|
-
isMuted: false,
|
|
821
|
-
isDrum: this.channelsAmount % 16 === DEFAULT_PERCUSSION,
|
|
822
|
-
isEFX: false,
|
|
823
|
-
transposition: 0
|
|
824
|
-
});
|
|
872
|
+
this.midiChannels.push(new LibMIDIChannel(this.midiChannels.length, this));
|
|
825
873
|
if (!post) return;
|
|
826
874
|
this.post({
|
|
827
875
|
channelNumber: 0,
|
|
@@ -833,6 +881,9 @@ var BasicSynthesizer = class {
|
|
|
833
881
|
this.resolveMap.get(type)?.(data);
|
|
834
882
|
this.resolveMap.delete(type);
|
|
835
883
|
}
|
|
884
|
+
registerInternalEvent(event, callback) {
|
|
885
|
+
this.eventHandler.addEvent(event, SPESSASYNTH_LIB_HANDLER(event), callback);
|
|
886
|
+
}
|
|
836
887
|
};
|
|
837
888
|
//#endregion
|
|
838
889
|
//#region src/synthesizer/worklet/worklet_synthesizer.ts
|
|
@@ -862,7 +913,7 @@ var WorkletSynthesizer = class extends BasicSynthesizer {
|
|
|
862
913
|
numberOfOutputs,
|
|
863
914
|
processorOptions: {
|
|
864
915
|
oneOutput: synthConfig.oneOutput,
|
|
865
|
-
|
|
916
|
+
eventsEnabled: synthConfig.eventsEnabled
|
|
866
917
|
}
|
|
867
918
|
});
|
|
868
919
|
} catch (error) {
|
|
@@ -1017,7 +1068,7 @@ const DEFAULT_WORKER_RENDER_AUDIO_OPTIONS = {
|
|
|
1017
1068
|
const RENDER_BLOCKS_PER_PROGRESS = 64;
|
|
1018
1069
|
const BLOCK_SIZE$1 = 128;
|
|
1019
1070
|
function renderAudioWorker(sampleRate, options) {
|
|
1020
|
-
const rendererSynth = new SpessaSynthProcessor(sampleRate, {
|
|
1071
|
+
const rendererSynth = new SpessaSynthProcessor(sampleRate, { eventsEnabled: false });
|
|
1021
1072
|
for (const entry of this.synthesizer.soundBankManager.soundBankList) rendererSynth.soundBankManager.addSoundBank(entry.soundBank, entry.id, entry.bankOffset);
|
|
1022
1073
|
rendererSynth.soundBankManager.priorityOrder = this.synthesizer.soundBankManager.priorityOrder;
|
|
1023
1074
|
this.stopAudioLoop();
|
|
@@ -1033,9 +1084,9 @@ function renderAudioWorker(sampleRate, options) {
|
|
|
1033
1084
|
if (options.preserveSynthParams) {
|
|
1034
1085
|
rendererSeq.playbackRate = seq.playbackRate;
|
|
1035
1086
|
const snapshot = this.synthesizer.getSnapshot();
|
|
1036
|
-
rendererSynth.
|
|
1087
|
+
rendererSynth.applySnapshot(snapshot);
|
|
1037
1088
|
}
|
|
1038
|
-
rendererSynth.
|
|
1089
|
+
rendererSynth.setSystemParameter("autoAllocateVoices", true);
|
|
1039
1090
|
rendererSeq.loadNewSongList([parsedMid]);
|
|
1040
1091
|
rendererSeq.play();
|
|
1041
1092
|
const wetL = new Float32Array(sampleDuration);
|
|
@@ -1101,19 +1152,23 @@ const DEFAULT_SF2_WRITE_OPTIONS = {
|
|
|
1101
1152
|
...DEFAULT_BANK_WRITE_OPTIONS,
|
|
1102
1153
|
writeDefaultModulators: true,
|
|
1103
1154
|
writeExtendedLimits: true,
|
|
1104
|
-
|
|
1155
|
+
compressionAction: "keep",
|
|
1105
1156
|
compressionQuality: 1,
|
|
1106
|
-
|
|
1157
|
+
software: "SpessaSynth"
|
|
1107
1158
|
};
|
|
1108
1159
|
const DEFAULT_RMIDI_WRITE_OPTIONS = {
|
|
1109
1160
|
...DEFAULT_BANK_WRITE_OPTIONS,
|
|
1161
|
+
applySnapshot: false,
|
|
1110
1162
|
bankOffset: 0,
|
|
1111
1163
|
correctBankOffset: true,
|
|
1112
1164
|
metadata: {},
|
|
1113
1165
|
format: "sf2",
|
|
1114
1166
|
...DEFAULT_SF2_WRITE_OPTIONS
|
|
1115
1167
|
};
|
|
1116
|
-
const DEFAULT_DLS_WRITE_OPTIONS = {
|
|
1168
|
+
const DEFAULT_DLS_WRITE_OPTIONS = {
|
|
1169
|
+
...DEFAULT_BANK_WRITE_OPTIONS,
|
|
1170
|
+
software: "SpessaSynth"
|
|
1171
|
+
};
|
|
1117
1172
|
/**
|
|
1118
1173
|
* This synthesizer uses a Worker containing the processor and an audio worklet node for playback.
|
|
1119
1174
|
*/
|
|
@@ -1141,7 +1196,7 @@ var WorkerSynthesizer = class extends BasicSynthesizer {
|
|
|
1141
1196
|
numberOfOutputs: 18,
|
|
1142
1197
|
processorOptions: {
|
|
1143
1198
|
oneOutput: synthConfig.oneOutput,
|
|
1144
|
-
|
|
1199
|
+
eventsEnabled: synthConfig.eventsEnabled
|
|
1145
1200
|
}
|
|
1146
1201
|
});
|
|
1147
1202
|
} catch (error) {
|
|
@@ -1193,7 +1248,10 @@ var WorkerSynthesizer = class extends BasicSynthesizer {
|
|
|
1193
1248
|
...writeOptions,
|
|
1194
1249
|
progressFunction: null
|
|
1195
1250
|
};
|
|
1196
|
-
this.awaitWorkerResponse("workerSynthWriteFile", (data) =>
|
|
1251
|
+
this.awaitWorkerResponse("workerSynthWriteFile", (data) => {
|
|
1252
|
+
this.revokeProgressTracker("workerSynthWriteFile");
|
|
1253
|
+
resolve(data);
|
|
1254
|
+
});
|
|
1197
1255
|
this.post({
|
|
1198
1256
|
type: "writeDLS",
|
|
1199
1257
|
data: postOptions,
|
|
@@ -1216,7 +1274,10 @@ var WorkerSynthesizer = class extends BasicSynthesizer {
|
|
|
1216
1274
|
...writeOptions,
|
|
1217
1275
|
progressFunction: null
|
|
1218
1276
|
};
|
|
1219
|
-
this.awaitWorkerResponse("workerSynthWriteFile", (data) =>
|
|
1277
|
+
this.awaitWorkerResponse("workerSynthWriteFile", (data) => {
|
|
1278
|
+
this.revokeProgressTracker("workerSynthWriteFile");
|
|
1279
|
+
resolve(data);
|
|
1280
|
+
});
|
|
1220
1281
|
this.post({
|
|
1221
1282
|
type: "writeSF2",
|
|
1222
1283
|
data: postOptions,
|
|
@@ -1239,7 +1300,10 @@ var WorkerSynthesizer = class extends BasicSynthesizer {
|
|
|
1239
1300
|
...writeOptions,
|
|
1240
1301
|
progressFunction: null
|
|
1241
1302
|
};
|
|
1242
|
-
this.awaitWorkerResponse("workerSynthWriteFile", (data) =>
|
|
1303
|
+
this.awaitWorkerResponse("workerSynthWriteFile", (data) => {
|
|
1304
|
+
this.revokeProgressTracker("workerSynthWriteFile");
|
|
1305
|
+
resolve(data.binary);
|
|
1306
|
+
});
|
|
1243
1307
|
this.post({
|
|
1244
1308
|
type: "writeRMIDI",
|
|
1245
1309
|
data: postOptions,
|
|
@@ -1318,13 +1382,17 @@ var MIDIDataTrack = class extends MIDITrack {
|
|
|
1318
1382
|
/**
|
|
1319
1383
|
* A simplified version of the MIDI, accessible at all times from the Sequencer.
|
|
1320
1384
|
* Use getMIDI() to get the actual sequence.
|
|
1321
|
-
* This class contains all properties that MIDI does, except for tracks and the embedded sound bank.
|
|
1385
|
+
* This class contains all properties that MIDI does, except for tracks, timeline and the embedded sound bank.
|
|
1322
1386
|
*/
|
|
1323
1387
|
var MIDIData = class MIDIData extends BasicMIDI {
|
|
1324
1388
|
tracks;
|
|
1325
1389
|
/**
|
|
1326
1390
|
* THIS DATA WILL BE EMPTY! USE sequencer.getMIDI() TO GET THE ACTUAL DATA!
|
|
1327
1391
|
*/
|
|
1392
|
+
timeline = [];
|
|
1393
|
+
/**
|
|
1394
|
+
* THIS DATA WILL BE EMPTY! USE sequencer.getMIDI() TO GET THE ACTUAL DATA!
|
|
1395
|
+
*/
|
|
1328
1396
|
embeddedSoundBank = void 0;
|
|
1329
1397
|
/**
|
|
1330
1398
|
* The byte length of the sound bank if it exists.
|
|
@@ -1355,20 +1423,29 @@ var BasicSynthesizerCore = class {
|
|
|
1355
1423
|
post;
|
|
1356
1424
|
lastSequencerSync = 0;
|
|
1357
1425
|
/**
|
|
1426
|
+
* For syncing voice counts, implemented separately in the `process()` method.
|
|
1427
|
+
* @protected
|
|
1428
|
+
*/
|
|
1429
|
+
voiceCounts = new Array(16).fill(0);
|
|
1430
|
+
/**
|
|
1358
1431
|
* Indicates if the processor is alive.
|
|
1359
1432
|
* @protected
|
|
1360
1433
|
*/
|
|
1361
1434
|
alive = false;
|
|
1362
|
-
|
|
1435
|
+
eventsEnabled;
|
|
1363
1436
|
constructor(sampleRate, options, postMessage) {
|
|
1364
1437
|
this.synthesizer = new SpessaSynthProcessor(sampleRate, options);
|
|
1365
|
-
this.
|
|
1438
|
+
this.eventsEnabled = options.eventsEnabled ?? false;
|
|
1366
1439
|
this.post = postMessage;
|
|
1367
1440
|
this.synthesizer.onEventCall = (event) => {
|
|
1441
|
+
if (event.type === "channelAdded") {
|
|
1442
|
+
const l = this.synthesizer.midiChannels.length;
|
|
1443
|
+
for (let i = this.voiceCounts.length; i < l; i++) this.voiceCounts.push(0);
|
|
1444
|
+
}
|
|
1368
1445
|
this.post({
|
|
1369
1446
|
type: "eventCall",
|
|
1370
1447
|
data: event,
|
|
1371
|
-
currentTime: this.synthesizer.
|
|
1448
|
+
currentTime: this.synthesizer.currentTime
|
|
1372
1449
|
});
|
|
1373
1450
|
};
|
|
1374
1451
|
}
|
|
@@ -1377,7 +1454,7 @@ var BasicSynthesizerCore = class {
|
|
|
1377
1454
|
const sequencerID = this.sequencers.length;
|
|
1378
1455
|
this.sequencers.push(sequencer);
|
|
1379
1456
|
sequencer.onEventCall = (e) => {
|
|
1380
|
-
if (!this.
|
|
1457
|
+
if (!this.eventsEnabled) return;
|
|
1381
1458
|
if (e.type === "songListChange") {
|
|
1382
1459
|
const midiDatas = e.data.newSongList.map((s) => {
|
|
1383
1460
|
return new MIDIData(s);
|
|
@@ -1392,7 +1469,7 @@ var BasicSynthesizerCore = class {
|
|
|
1392
1469
|
},
|
|
1393
1470
|
id: sequencerID
|
|
1394
1471
|
},
|
|
1395
|
-
currentTime: this.synthesizer.
|
|
1472
|
+
currentTime: this.synthesizer.currentTime
|
|
1396
1473
|
});
|
|
1397
1474
|
return;
|
|
1398
1475
|
}
|
|
@@ -1402,7 +1479,7 @@ var BasicSynthesizerCore = class {
|
|
|
1402
1479
|
...e,
|
|
1403
1480
|
id: sequencerID
|
|
1404
1481
|
},
|
|
1405
|
-
currentTime: this.synthesizer.
|
|
1482
|
+
currentTime: this.synthesizer.currentTime
|
|
1406
1483
|
});
|
|
1407
1484
|
};
|
|
1408
1485
|
}
|
|
@@ -1413,7 +1490,7 @@ var BasicSynthesizerCore = class {
|
|
|
1413
1490
|
type,
|
|
1414
1491
|
data
|
|
1415
1492
|
},
|
|
1416
|
-
currentTime: this.synthesizer.
|
|
1493
|
+
currentTime: this.synthesizer.currentTime
|
|
1417
1494
|
}, transferable);
|
|
1418
1495
|
}
|
|
1419
1496
|
postProgress(type, data) {
|
|
@@ -1423,7 +1500,7 @@ var BasicSynthesizerCore = class {
|
|
|
1423
1500
|
type,
|
|
1424
1501
|
data
|
|
1425
1502
|
},
|
|
1426
|
-
currentTime: this.synthesizer.
|
|
1503
|
+
currentTime: this.synthesizer.currentTime
|
|
1427
1504
|
});
|
|
1428
1505
|
}
|
|
1429
1506
|
destroy() {
|
|
@@ -1433,11 +1510,11 @@ var BasicSynthesizerCore = class {
|
|
|
1433
1510
|
}
|
|
1434
1511
|
handleMessage(m) {
|
|
1435
1512
|
const channel = m.channelNumber;
|
|
1436
|
-
let channelObject
|
|
1513
|
+
let channelObject;
|
|
1437
1514
|
if (channel >= 0) {
|
|
1438
1515
|
channelObject = this.synthesizer.midiChannels[channel];
|
|
1439
1516
|
if (channelObject === void 0) {
|
|
1440
|
-
|
|
1517
|
+
SpessaLog.warn(`Trying to access channel ${channel} which does not exist... ignoring!`);
|
|
1441
1518
|
return;
|
|
1442
1519
|
}
|
|
1443
1520
|
}
|
|
@@ -1445,38 +1522,27 @@ var BasicSynthesizerCore = class {
|
|
|
1445
1522
|
case "midiMessage":
|
|
1446
1523
|
this.synthesizer.processMessage(m.data.messageData, m.data.channelOffset, m.data.options);
|
|
1447
1524
|
break;
|
|
1448
|
-
case "customCcChange":
|
|
1449
|
-
channelObject?.setCustomController(m.data.ccNumber, m.data.ccValue);
|
|
1450
|
-
break;
|
|
1451
1525
|
case "ccReset":
|
|
1452
|
-
|
|
1453
|
-
else channelObject?.resetControllers();
|
|
1526
|
+
this.synthesizer.reset();
|
|
1454
1527
|
break;
|
|
1455
1528
|
case "stopAll":
|
|
1456
|
-
if (channel ===
|
|
1529
|
+
if (channel === -1) this.synthesizer.stopAllChannels(m.data === 1);
|
|
1457
1530
|
else channelObject?.stopAllNotes(m.data === 1);
|
|
1458
1531
|
break;
|
|
1459
|
-
case "muteChannel":
|
|
1460
|
-
channelObject?.muteChannel(m.data);
|
|
1461
|
-
break;
|
|
1462
1532
|
case "addNewChannel":
|
|
1463
1533
|
this.synthesizer.createMIDIChannel();
|
|
1464
1534
|
break;
|
|
1465
|
-
case "
|
|
1466
|
-
this.synthesizer.
|
|
1535
|
+
case "setGlobalSystemParameter":
|
|
1536
|
+
this.synthesizer.setSystemParameter(m.data.type, m.data.data);
|
|
1537
|
+
break;
|
|
1538
|
+
case "setChannelSystemParameter":
|
|
1539
|
+
channelObject?.setSystemParameter(m.data.type, m.data.data);
|
|
1467
1540
|
break;
|
|
1468
1541
|
case "setDrums":
|
|
1469
1542
|
channelObject?.setDrums(m.data);
|
|
1470
1543
|
break;
|
|
1471
|
-
case "transposeChannel":
|
|
1472
|
-
channelObject?.transposeChannel(m.data.semitones, m.data.force);
|
|
1473
|
-
break;
|
|
1474
1544
|
case "lockController":
|
|
1475
|
-
|
|
1476
|
-
else {
|
|
1477
|
-
if (!channelObject) return;
|
|
1478
|
-
channelObject.lockedControllers[m.data.controllerNumber] = m.data.isLocked;
|
|
1479
|
-
}
|
|
1545
|
+
channelObject?.lockController(m.data.controller, m.data.isLocked);
|
|
1480
1546
|
break;
|
|
1481
1547
|
case "sequencerSpecific": {
|
|
1482
1548
|
const seq = this.sequencers[m.data.id];
|
|
@@ -1500,7 +1566,7 @@ var BasicSynthesizerCore = class {
|
|
|
1500
1566
|
data: error,
|
|
1501
1567
|
id: m.data.id
|
|
1502
1568
|
},
|
|
1503
|
-
currentTime: this.synthesizer.
|
|
1569
|
+
currentTime: this.synthesizer.currentTime
|
|
1504
1570
|
});
|
|
1505
1571
|
}
|
|
1506
1572
|
break;
|
|
@@ -1544,7 +1610,7 @@ var BasicSynthesizerCore = class {
|
|
|
1544
1610
|
data: seq.midiData,
|
|
1545
1611
|
id: m.data.id
|
|
1546
1612
|
},
|
|
1547
|
-
currentTime: this.synthesizer.
|
|
1613
|
+
currentTime: this.synthesizer.currentTime
|
|
1548
1614
|
});
|
|
1549
1615
|
break;
|
|
1550
1616
|
case "setSkipToFirstNote":
|
|
@@ -1576,7 +1642,7 @@ var BasicSynthesizerCore = class {
|
|
|
1576
1642
|
this.post({
|
|
1577
1643
|
type: "soundBankError",
|
|
1578
1644
|
data: error,
|
|
1579
|
-
currentTime: this.synthesizer.
|
|
1645
|
+
currentTime: this.synthesizer.currentTime
|
|
1580
1646
|
});
|
|
1581
1647
|
}
|
|
1582
1648
|
break;
|
|
@@ -1596,7 +1662,7 @@ var BasicSynthesizerCore = class {
|
|
|
1596
1662
|
break;
|
|
1597
1663
|
}
|
|
1598
1664
|
case "requestSynthesizerSnapshot": {
|
|
1599
|
-
const snapshot =
|
|
1665
|
+
const snapshot = this.synthesizer.getSnapshot();
|
|
1600
1666
|
this.postReady("synthesizerSnapshot", snapshot);
|
|
1601
1667
|
break;
|
|
1602
1668
|
}
|
|
@@ -1604,7 +1670,7 @@ var BasicSynthesizerCore = class {
|
|
|
1604
1670
|
this.createNewSequencer();
|
|
1605
1671
|
break;
|
|
1606
1672
|
case "setLogLevel":
|
|
1607
|
-
|
|
1673
|
+
SpessaLog.setLogLevel(m.data.enableInfo, m.data.enableWarning, m.data.enableGroup);
|
|
1608
1674
|
break;
|
|
1609
1675
|
case "destroyWorklet":
|
|
1610
1676
|
this.alive = false;
|
|
@@ -1612,7 +1678,7 @@ var BasicSynthesizerCore = class {
|
|
|
1612
1678
|
this.destroy();
|
|
1613
1679
|
break;
|
|
1614
1680
|
default:
|
|
1615
|
-
|
|
1681
|
+
SpessaLog.warn("Unrecognized event!", m);
|
|
1616
1682
|
break;
|
|
1617
1683
|
}
|
|
1618
1684
|
}
|
|
@@ -1621,58 +1687,70 @@ var BasicSynthesizerCore = class {
|
|
|
1621
1687
|
//#region src/synthesizer/worker/write_sf_worker.ts
|
|
1622
1688
|
async function writeSF2Worker(opts) {
|
|
1623
1689
|
let sf = this.getBank(opts);
|
|
1624
|
-
if (opts.compress && !this.compressionFunction) {
|
|
1625
|
-
const e = /* @__PURE__ */ new Error(`Compression enabled but no compression has been provided to WorkerSynthesizerCore.`);
|
|
1626
|
-
this.post({
|
|
1627
|
-
type: "soundBankError",
|
|
1628
|
-
data: e,
|
|
1629
|
-
currentTime: this.synthesizer.currentSynthTime
|
|
1630
|
-
});
|
|
1631
|
-
throw e;
|
|
1632
|
-
}
|
|
1633
1690
|
const sq = this.sequencers[opts.sequencerID];
|
|
1634
1691
|
if (opts.trim) {
|
|
1635
1692
|
if (!sq.midiData) throw new Error("Sound bank MIDI trimming is enabled but no MIDI is loaded!");
|
|
1636
1693
|
const sfCopy = BasicSoundBank.copyFrom(sf);
|
|
1637
|
-
sfCopy.
|
|
1694
|
+
sfCopy.trim(sq.midiData.getUsedProgramsAndKeys(sfCopy));
|
|
1638
1695
|
sf = sfCopy;
|
|
1639
1696
|
}
|
|
1640
1697
|
let compressionFunction;
|
|
1641
1698
|
if (this.compressionFunction !== void 0) compressionFunction = (audioData, sampleRate) => this.compressionFunction(audioData, sampleRate, opts.compressionQuality);
|
|
1699
|
+
switch (opts.compressionAction) {
|
|
1700
|
+
case "keep":
|
|
1701
|
+
default: break;
|
|
1702
|
+
case "compress":
|
|
1703
|
+
if (!compressionFunction) {
|
|
1704
|
+
const e = /* @__PURE__ */ new Error(`Compression enabled but no compression function has been provided to WorkerSynthesizerCore.`);
|
|
1705
|
+
this.post({
|
|
1706
|
+
type: "soundBankError",
|
|
1707
|
+
data: e,
|
|
1708
|
+
currentTime: this.synthesizer.currentTime
|
|
1709
|
+
});
|
|
1710
|
+
throw e;
|
|
1711
|
+
}
|
|
1712
|
+
await sf.setSampleFormat({
|
|
1713
|
+
compressionFunction,
|
|
1714
|
+
format: "compressed",
|
|
1715
|
+
progressFunction: (progress) => {
|
|
1716
|
+
this.postProgress("workerSynthWriteFile", progress);
|
|
1717
|
+
return new Promise((r) => r());
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
break;
|
|
1721
|
+
case "decompress": await sf.setSampleFormat({
|
|
1722
|
+
format: "pcm",
|
|
1723
|
+
progressFunction: (progress) => {
|
|
1724
|
+
this.postProgress("workerSynthWriteFile", progress);
|
|
1725
|
+
return new Promise((r) => r());
|
|
1726
|
+
}
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1642
1729
|
return {
|
|
1643
|
-
binary:
|
|
1730
|
+
binary: sf.writeSF2({
|
|
1644
1731
|
...opts,
|
|
1645
|
-
progressFunction: (
|
|
1646
|
-
this.postProgress("workerSynthWriteFile",
|
|
1647
|
-
sampleCount,
|
|
1648
|
-
sampleIndex,
|
|
1649
|
-
sampleName
|
|
1650
|
-
});
|
|
1732
|
+
progressFunction: (progress) => {
|
|
1733
|
+
this.postProgress("workerSynthWriteFile", progress);
|
|
1651
1734
|
return new Promise((r) => r());
|
|
1652
|
-
}
|
|
1653
|
-
compressionFunction
|
|
1735
|
+
}
|
|
1654
1736
|
}),
|
|
1655
1737
|
bank: sf
|
|
1656
1738
|
};
|
|
1657
1739
|
}
|
|
1658
|
-
|
|
1740
|
+
function writeDLSWorker(opts) {
|
|
1659
1741
|
let sf = this.getBank(opts);
|
|
1660
1742
|
const sq = this.sequencers[opts.sequencerID];
|
|
1661
1743
|
if (opts.trim) {
|
|
1662
1744
|
if (!sq.midiData) throw new Error("Sound bank MIDI trimming is enabled but no MIDI is loaded!");
|
|
1663
1745
|
const sfCopy = BasicSoundBank.copyFrom(sf);
|
|
1664
|
-
sfCopy.
|
|
1746
|
+
sfCopy.trim(sq.midiData.getUsedProgramsAndKeys(sfCopy));
|
|
1665
1747
|
sf = sfCopy;
|
|
1666
1748
|
}
|
|
1667
1749
|
return {
|
|
1668
|
-
binary:
|
|
1750
|
+
binary: sf.writeDLS({
|
|
1669
1751
|
...opts,
|
|
1670
|
-
progressFunction: (
|
|
1671
|
-
this.postProgress("workerSynthWriteFile",
|
|
1672
|
-
sampleCount,
|
|
1673
|
-
sampleIndex,
|
|
1674
|
-
sampleName
|
|
1675
|
-
});
|
|
1752
|
+
progressFunction: (progress) => {
|
|
1753
|
+
this.postProgress("workerSynthWriteFile", progress);
|
|
1676
1754
|
return new Promise((r) => r());
|
|
1677
1755
|
}
|
|
1678
1756
|
}),
|
|
@@ -1691,11 +1769,13 @@ async function writeRMIDIWorker(opts) {
|
|
|
1691
1769
|
sfBin = bin.binary;
|
|
1692
1770
|
sf = bin.bank;
|
|
1693
1771
|
} else {
|
|
1694
|
-
const bin =
|
|
1772
|
+
const bin = writeDLSWorker.call(this, opts);
|
|
1695
1773
|
sfBin = bin.binary;
|
|
1696
1774
|
sf = bin.bank;
|
|
1697
1775
|
}
|
|
1698
|
-
|
|
1776
|
+
const mid = BasicMIDI.copyFrom(sq.midiData);
|
|
1777
|
+
if (opts.applySnapshot) mid.applySnapshot(this.synthesizer.getSnapshot());
|
|
1778
|
+
return mid.writeRMIDI(sfBin, {
|
|
1699
1779
|
soundBank: sf,
|
|
1700
1780
|
...opts
|
|
1701
1781
|
});
|
|
@@ -1720,8 +1800,8 @@ var WorkerSynthesizerCore = class extends BasicSynthesizerCore {
|
|
|
1720
1800
|
*/
|
|
1721
1801
|
constructor(synthesizerConfiguration, workletMessagePort, mainThreadCallback, compressionFunction) {
|
|
1722
1802
|
super(synthesizerConfiguration.sampleRate, {
|
|
1723
|
-
|
|
1724
|
-
|
|
1803
|
+
effectsEnabled: true,
|
|
1804
|
+
eventsEnabled: true,
|
|
1725
1805
|
initialTime: synthesizerConfiguration.initialTime
|
|
1726
1806
|
}, mainThreadCallback);
|
|
1727
1807
|
this.workletMessagePort = workletMessagePort;
|
|
@@ -1766,16 +1846,16 @@ var WorkerSynthesizerCore = class extends BasicSynthesizerCore {
|
|
|
1766
1846
|
this.startAudioLoop();
|
|
1767
1847
|
});
|
|
1768
1848
|
break;
|
|
1769
|
-
case "writeDLS":
|
|
1849
|
+
case "writeDLS": {
|
|
1770
1850
|
this.stopAudioLoop();
|
|
1771
|
-
writeDLSWorker.call(this, m.data)
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
});
|
|
1851
|
+
const data = writeDLSWorker.call(this, m.data);
|
|
1852
|
+
this.postReady("workerSynthWriteFile", {
|
|
1853
|
+
binary: data.binary,
|
|
1854
|
+
fileName: data.bank.soundBankInfo.name + ".dls"
|
|
1855
|
+
}, [data.binary]);
|
|
1856
|
+
this.startAudioLoop();
|
|
1778
1857
|
break;
|
|
1858
|
+
}
|
|
1779
1859
|
default: super.handleMessage(m);
|
|
1780
1860
|
}
|
|
1781
1861
|
}
|
|
@@ -1787,7 +1867,7 @@ var WorkerSynthesizerCore = class extends BasicSynthesizerCore {
|
|
|
1787
1867
|
this.post({
|
|
1788
1868
|
type: "soundBankError",
|
|
1789
1869
|
data: e,
|
|
1790
|
-
currentTime: this.synthesizer.
|
|
1870
|
+
currentTime: this.synthesizer.currentTime
|
|
1791
1871
|
});
|
|
1792
1872
|
throw e;
|
|
1793
1873
|
}
|
|
@@ -1827,8 +1907,8 @@ var WorkerSynthesizerCore = class extends BasicSynthesizerCore {
|
|
|
1827
1907
|
for (const seq of this.sequencers) seq.processTick();
|
|
1828
1908
|
this.synthesizer.processSplit(dry, wetL, wetR);
|
|
1829
1909
|
this.workletMessagePort.postMessage(data, [data.buffer]);
|
|
1830
|
-
const t = this.synthesizer.
|
|
1831
|
-
if (this.
|
|
1910
|
+
const t = this.synthesizer.currentTime;
|
|
1911
|
+
if (this.eventsEnabled && t - this.lastSequencerSync > 1) {
|
|
1832
1912
|
for (let id = 0; id < this.sequencers.length; id++) this.post({
|
|
1833
1913
|
type: "sequencerReturn",
|
|
1834
1914
|
data: {
|
|
@@ -1840,6 +1920,18 @@ var WorkerSynthesizerCore = class extends BasicSynthesizerCore {
|
|
|
1840
1920
|
});
|
|
1841
1921
|
this.lastSequencerSync = t;
|
|
1842
1922
|
}
|
|
1923
|
+
const c = this.synthesizer.midiChannels;
|
|
1924
|
+
const cv = this.voiceCounts;
|
|
1925
|
+
let updateChannels = false;
|
|
1926
|
+
for (let i = 0; i < c.length; i++) {
|
|
1927
|
+
updateChannels ||= c[i].voiceCount !== cv[i];
|
|
1928
|
+
cv[i] = c[i].voiceCount;
|
|
1929
|
+
}
|
|
1930
|
+
if (updateChannels) this.post({
|
|
1931
|
+
type: "voiceCountChange",
|
|
1932
|
+
currentTime: t,
|
|
1933
|
+
data: cv
|
|
1934
|
+
});
|
|
1843
1935
|
}
|
|
1844
1936
|
};
|
|
1845
1937
|
//#endregion
|
|
@@ -1980,7 +2072,7 @@ var Sequencer = class {
|
|
|
1980
2072
|
/**
|
|
1981
2073
|
* Sets the song index in the playlist.
|
|
1982
2074
|
*/
|
|
1983
|
-
const clamped = Math.max(0, value % this.
|
|
2075
|
+
const clamped = Math.max(0, value % this._songCount);
|
|
1984
2076
|
if (clamped === this._songIndex) return;
|
|
1985
2077
|
this.isLoading = true;
|
|
1986
2078
|
this.midiData = void 0;
|
|
@@ -2002,9 +2094,9 @@ var Sequencer = class {
|
|
|
2002
2094
|
get duration() {
|
|
2003
2095
|
return this.midiData?.duration ?? 0;
|
|
2004
2096
|
}
|
|
2005
|
-
|
|
2006
|
-
get
|
|
2007
|
-
return this.
|
|
2097
|
+
_songCount = 0;
|
|
2098
|
+
get songCount() {
|
|
2099
|
+
return this._songCount;
|
|
2008
2100
|
}
|
|
2009
2101
|
_skipToFirstNoteOn;
|
|
2010
2102
|
/**
|
|
@@ -2133,7 +2225,7 @@ var Sequencer = class {
|
|
|
2133
2225
|
this.midiData = void 0;
|
|
2134
2226
|
this.sendMessage("loadNewSongList", midiBuffers);
|
|
2135
2227
|
this._songIndex = 0;
|
|
2136
|
-
this.
|
|
2228
|
+
this._songCount = midiBuffers.length;
|
|
2137
2229
|
}
|
|
2138
2230
|
/**
|
|
2139
2231
|
* Connects a given output to the sequencer.
|
|
@@ -2143,7 +2235,6 @@ var Sequencer = class {
|
|
|
2143
2235
|
this.resetMIDIOutput();
|
|
2144
2236
|
this.midiOut = output;
|
|
2145
2237
|
this.sendMessage("changeMIDIMessageSending", output !== void 0);
|
|
2146
|
-
this.currentTime -= .1;
|
|
2147
2238
|
}
|
|
2148
2239
|
/**
|
|
2149
2240
|
* Pauses the playback.
|
|
@@ -2205,21 +2296,21 @@ var Sequencer = class {
|
|
|
2205
2296
|
case "metaEvent": {
|
|
2206
2297
|
const event = m.data.event;
|
|
2207
2298
|
switch (event.statusByte) {
|
|
2208
|
-
case
|
|
2209
|
-
this._currentTempo = 6e7 / SpessaSynthCoreUtils.
|
|
2299
|
+
case MIDIMessageTypes.setTempo:
|
|
2300
|
+
this._currentTempo = 6e7 / SpessaSynthCoreUtils.readBigEndian(event.data, 3);
|
|
2210
2301
|
break;
|
|
2211
|
-
case
|
|
2212
|
-
case
|
|
2213
|
-
case
|
|
2214
|
-
case
|
|
2215
|
-
case
|
|
2216
|
-
case
|
|
2217
|
-
case
|
|
2218
|
-
case
|
|
2302
|
+
case MIDIMessageTypes.text:
|
|
2303
|
+
case MIDIMessageTypes.lyric:
|
|
2304
|
+
case MIDIMessageTypes.copyright:
|
|
2305
|
+
case MIDIMessageTypes.trackName:
|
|
2306
|
+
case MIDIMessageTypes.marker:
|
|
2307
|
+
case MIDIMessageTypes.cuePoint:
|
|
2308
|
+
case MIDIMessageTypes.instrumentName:
|
|
2309
|
+
case MIDIMessageTypes.programName: {
|
|
2219
2310
|
if (!this.midiData) break;
|
|
2220
2311
|
let lyricsIndex = -1;
|
|
2221
|
-
if (event.statusByte ===
|
|
2222
|
-
if (this.midiData.isKaraokeFile && (event.statusByte ===
|
|
2312
|
+
if (event.statusByte === MIDIMessageTypes.lyric) lyricsIndex = Math.min(this.midiData.lyrics.findIndex((l) => l.ticks === event.ticks), this.midiData.lyrics.length - 1);
|
|
2313
|
+
if (this.midiData.isKaraokeFile && (event.statusByte === MIDIMessageTypes.text || event.statusByte === MIDIMessageTypes.lyric)) lyricsIndex = Math.min(this.midiData.lyrics.findIndex((l) => l.ticks === event.ticks), this.midiData.lyrics.length);
|
|
2223
2314
|
this.callEventInternal("textEvent", {
|
|
2224
2315
|
event,
|
|
2225
2316
|
lyricsIndex
|
|
@@ -2250,17 +2341,17 @@ var Sequencer = class {
|
|
|
2250
2341
|
if (!this.midiOut) return;
|
|
2251
2342
|
for (let i = 0; i < 16; i++) {
|
|
2252
2343
|
this.midiOut.send([
|
|
2253
|
-
|
|
2254
|
-
|
|
2344
|
+
MIDIMessageTypes.controllerChange | i,
|
|
2345
|
+
MIDIControllers.allNotesOff,
|
|
2255
2346
|
0
|
|
2256
2347
|
]);
|
|
2257
2348
|
this.midiOut.send([
|
|
2258
|
-
|
|
2259
|
-
|
|
2349
|
+
MIDIMessageTypes.controllerChange | i,
|
|
2350
|
+
MIDIControllers.resetAllControllers,
|
|
2260
2351
|
0
|
|
2261
2352
|
]);
|
|
2262
2353
|
}
|
|
2263
|
-
this.midiOut.send([
|
|
2354
|
+
this.midiOut.send([MIDIMessageTypes.systemExclusive, ...MIDIUtils.gsData(64, 0, 127, [0])]);
|
|
2264
2355
|
}
|
|
2265
2356
|
recalculateStartTime(time) {
|
|
2266
2357
|
this.absoluteStartTime = this.synth.currentTime - time / this._playbackRate;
|
|
@@ -2269,7 +2360,7 @@ var Sequencer = class {
|
|
|
2269
2360
|
}
|
|
2270
2361
|
sendMessage(messageType, messageData) {
|
|
2271
2362
|
this.synth.post({
|
|
2272
|
-
channelNumber:
|
|
2363
|
+
channelNumber: -1,
|
|
2273
2364
|
type: "sequencerSpecific",
|
|
2274
2365
|
data: {
|
|
2275
2366
|
type: messageType,
|
|
@@ -2404,14 +2495,14 @@ var MIDIDeviceHandler = class MIDIDeviceHandler {
|
|
|
2404
2495
|
sysex: true,
|
|
2405
2496
|
software: true
|
|
2406
2497
|
});
|
|
2407
|
-
|
|
2498
|
+
SpessaLog.info("%cMIDI handler created!", ConsoleColors.recognized);
|
|
2408
2499
|
return new MIDIDeviceHandler(response);
|
|
2409
2500
|
} catch (error) {
|
|
2410
|
-
|
|
2501
|
+
SpessaLog.warn(`Could not get MIDI Devices:`, error);
|
|
2411
2502
|
throw error;
|
|
2412
2503
|
}
|
|
2413
2504
|
else {
|
|
2414
|
-
|
|
2505
|
+
SpessaLog.warn("Web MIDI API is not supported.", ConsoleColors.unrecognized);
|
|
2415
2506
|
throw new Error("Web MIDI API is not supported.");
|
|
2416
2507
|
}
|
|
2417
2508
|
}
|
|
@@ -2437,7 +2528,7 @@ var WebMIDILinkHandler = class {
|
|
|
2437
2528
|
const midiData = data.map((byte) => Number.parseInt(byte, 16));
|
|
2438
2529
|
synth.sendMessage(midiData);
|
|
2439
2530
|
});
|
|
2440
|
-
|
|
2531
|
+
SpessaLog.info("%cWeb MIDI Link handler created!", ConsoleColors.recognized);
|
|
2441
2532
|
}
|
|
2442
2533
|
};
|
|
2443
2534
|
//#endregion
|