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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { ALL_CHANNELS_OR_DIFFERENT_ACTION, BasicMIDI, BasicSoundBank, DEFAULT_MASTER_PARAMETERS, DEFAULT_PERCUSSION, KeyModifier, MIDITrack, SoundBankLoader, SpessaSynthCoreUtils, SpessaSynthLogging, SpessaSynthProcessor, SpessaSynthSequencer, SynthesizerSnapshot, SynthesizerSnapshot as LibSynthesizerSnapshot, audioToWav, midiControllers, midiMessageTypes } from "spessasynth_core";
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
- enableEventSystem: true,
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
- SpessaSynthCoreUtils.SpessaSynthWarn("1 sound bank left. Aborting!");
153
+ SpessaLog.warn("1 sound bank left. Aborting!");
154
154
  return;
155
155
  }
156
156
  if (!this.soundBankList.some((s) => s.id === id)) {
157
- SpessaSynthCoreUtils.SpessaSynthWarn(`No sound banks with id of "${id}" found. Aborting!`);
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
- newChannel: /* @__PURE__ */ new Map(),
202
- muteChannel: /* @__PURE__ */ new Map(),
198
+ channelAdded: /* @__PURE__ */ new Map(),
203
199
  presetListChange: /* @__PURE__ */ new Map(),
204
- allControllerReset: /* @__PURE__ */ new Map(),
200
+ reset: /* @__PURE__ */ new Map(),
205
201
  soundBankError: /* @__PURE__ */ new Map(),
206
- synthDisplay: /* @__PURE__ */ new Map(),
207
- masterParameterChange: /* @__PURE__ */ new Map(),
208
- channelPropertyChange: /* @__PURE__ */ new Map(),
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 consoleColors = SpessaSynthCoreUtils.consoleColors;
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
- channelProperties = [];
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
- _outputsAmount = 16;
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
- SpessaSynthCoreUtils.SpessaSynthInfo("%cInitializing SpessaSynth synthesizer...", consoleColors.info);
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 < this.channelsAmount; i++) this.addNewChannelInternal(false);
334
- this.channelProperties[DEFAULT_PERCUSSION].isDrum = true;
335
- this.eventHandler.addEvent("newChannel", `synth-new-channel-${Math.random()}`, () => {
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.eventHandler.addEvent("channelPropertyChange", `synth-channel-property-change-${Math.random()}`, (e) => {
345
- this.channelProperties[e.channel] = e.property;
346
- this._voicesAmount = this.channelProperties.reduce((sum, voices) => sum + voices.voicesAmount, 0);
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
- _voicesAmount = 0;
473
+ _voiceCount = 0;
353
474
  /**
354
475
  * The current number of voices playing.
355
476
  */
356
- get voicesAmount() {
357
- return this._voicesAmount;
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: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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
- * Gets a master parameter from the synthesizer.
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
- setMasterParameter(type, value) {
416
- this.masterParameters[type] = value;
535
+ setSystemParameter(type, value) {
536
+ this._systemParameters[type] = value;
417
537
  this.post({
418
- type: "setMasterParameter",
419
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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(LibSynthesizerSnapshot.copyFrom(s));
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._outputsAmount) throw new Error(`input nodes amount differs from the system's outputs amount!
478
- Expected ${this._outputsAmount} got ${audioNodes.length}`);
479
- for (let channel = 0; channel < this._outputsAmount; channel++) this.connectChannel(audioNodes[channel], channel);
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._outputsAmount) throw new Error(`input nodes amount differs from the system's outputs amount!
487
- Expected ${this._outputsAmount} got ${audioNodes.length}`);
488
- for (let channel = 0; channel < this._outputsAmount; channel++) this.disconnectChannel(audioNodes[channel], channel);
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
- midiMessageTypes.noteOn | ch,
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([midiMessageTypes.noteOff | ch, midiNote], offset, eventOptions);
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: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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 controllerNumber 0-127 the MIDI CC number.
551
- * @param controllerValue 0-127 the controller value.
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, controllerNumber, controllerValue, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS) {
555
- if (controllerNumber > 127 || controllerNumber < 0) throw new Error(`Invalid controller number: ${controllerNumber}`);
556
- controllerValue = Math.floor(controllerValue) % 128;
557
- controllerNumber = Math.floor(controllerNumber) % 128;
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
- midiMessageTypes.controllerChange | ch,
562
- controllerNumber,
563
- controllerValue
669
+ MIDIMessageTypes.controllerChange | ch,
670
+ controller,
671
+ value
564
672
  ], offset, eventOptions);
565
673
  }
566
674
  /**
567
- * Resets all controllers (for every channel)
675
+ * Fully resets the synthesizer.
568
676
  */
569
- resetControllers() {
677
+ reset() {
570
678
  this.post({
571
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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([midiMessageTypes.channelPressure | ch, pressure], offset, eventOptions);
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
- midiMessageTypes.polyPressure | ch,
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
- midiMessageTypes.pitchWheel | ch,
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, midiControllers.registeredParameterMSB, 0, eventOptions);
647
- this.controllerChange(channel, midiControllers.registeredParameterLSB, 0, eventOptions);
648
- this.controllerChange(channel, midiControllers.dataEntryMSB, range);
649
- this.controllerChange(channel, midiControllers.registeredParameterMSB, 127, eventOptions);
650
- this.controllerChange(channel, midiControllers.registeredParameterLSB, 127, eventOptions);
651
- this.controllerChange(channel, midiControllers.dataEntryMSB, 0, eventOptions);
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([midiMessageTypes.programChange | ch, programNumber], offset, eventOptions);
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([midiMessageTypes.systemExclusive, ...Array.from(messageData)], channelOffset, eventOptions);
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.channelsAmount; i++) {
747
- this.controllerChange(i, midiControllers.reverbDepth, 127);
748
- this.lockController(i, midiControllers.reverbDepth, true);
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: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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
- SpessaSynthCoreUtils.SpessaSynthWarn(m.data);
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.channelProperties.push({
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
- enableEventSystem: synthConfig.enableEventSystem
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, { enableEventSystem: false });
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.applySynthesizerSnapshot(snapshot);
1087
+ rendererSynth.applySnapshot(snapshot);
1037
1088
  }
1038
- rendererSynth.setMasterParameter("autoAllocateVoices", true);
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
- compress: false,
1155
+ compressionAction: "keep",
1105
1156
  compressionQuality: 1,
1106
- decompress: false
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 = { ...DEFAULT_BANK_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
- enableEventSystem: synthConfig.enableEventSystem
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) => resolve(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) => resolve(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) => resolve(data.binary));
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
- enableEventSystem;
1435
+ eventsEnabled;
1363
1436
  constructor(sampleRate, options, postMessage) {
1364
1437
  this.synthesizer = new SpessaSynthProcessor(sampleRate, options);
1365
- this.enableEventSystem = options.enableEventSystem ?? false;
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.currentSynthTime
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.enableEventSystem) return;
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.currentSynthTime
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.currentSynthTime
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.currentSynthTime
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.currentSynthTime
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 = void 0;
1513
+ let channelObject;
1437
1514
  if (channel >= 0) {
1438
1515
  channelObject = this.synthesizer.midiChannels[channel];
1439
1516
  if (channelObject === void 0) {
1440
- SpessaSynthCoreUtils.SpessaSynthWarn(`Trying to access channel ${channel} which does not exist... ignoring!`);
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
- if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION) this.synthesizer.resetAllControllers();
1453
- else channelObject?.resetControllers();
1526
+ this.synthesizer.reset();
1454
1527
  break;
1455
1528
  case "stopAll":
1456
- if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION) this.synthesizer.stopAllChannels(m.data === 1);
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 "setMasterParameter":
1466
- this.synthesizer.setMasterParameter(m.data.type, m.data.data);
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
- if (m.data.controllerNumber === ALL_CHANNELS_OR_DIFFERENT_ACTION) channelObject?.setPresetLock(m.data.isLocked);
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.currentSynthTime
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.currentSynthTime
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.currentSynthTime
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 = SynthesizerSnapshot.create(this.synthesizer);
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
- SpessaSynthLogging(m.data.enableInfo, m.data.enableWarning, m.data.enableGroup);
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
- SpessaSynthCoreUtils.SpessaSynthWarn("Unrecognized event!", m);
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.trimSoundBank(sq.midiData);
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: await sf.writeSF2({
1730
+ binary: sf.writeSF2({
1644
1731
  ...opts,
1645
- progressFunction: (sampleName, sampleIndex, sampleCount) => {
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
- async function writeDLSWorker(opts) {
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.trimSoundBank(sq.midiData);
1746
+ sfCopy.trim(sq.midiData.getUsedProgramsAndKeys(sfCopy));
1665
1747
  sf = sfCopy;
1666
1748
  }
1667
1749
  return {
1668
- binary: await sf.writeDLS({
1750
+ binary: sf.writeDLS({
1669
1751
  ...opts,
1670
- progressFunction: (sampleName, sampleIndex, sampleCount) => {
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 = await writeDLSWorker.call(this, opts);
1772
+ const bin = writeDLSWorker.call(this, opts);
1695
1773
  sfBin = bin.binary;
1696
1774
  sf = bin.bank;
1697
1775
  }
1698
- return BasicMIDI.copyFrom(sq.midiData).writeRMIDI(sfBin, {
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
- enableEventSystem: true,
1724
- enableEffects: true,
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).then((data) => {
1772
- this.postReady("workerSynthWriteFile", {
1773
- binary: data.binary,
1774
- fileName: data.bank.soundBankInfo.name + ".dls"
1775
- }, [data.binary]);
1776
- this.startAudioLoop();
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.currentSynthTime
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.currentSynthTime;
1831
- if (this.enableEventSystem && t - this.lastSequencerSync > 1) {
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._songsAmount);
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
- _songsAmount = 0;
2006
- get songsAmount() {
2007
- return this._songsAmount;
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._songsAmount = midiBuffers.length;
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 midiMessageTypes.setTempo:
2209
- this._currentTempo = 6e7 / SpessaSynthCoreUtils.readBytesAsUintBigEndian(event.data, 3);
2299
+ case MIDIMessageTypes.setTempo:
2300
+ this._currentTempo = 6e7 / SpessaSynthCoreUtils.readBigEndian(event.data, 3);
2210
2301
  break;
2211
- case midiMessageTypes.text:
2212
- case midiMessageTypes.lyric:
2213
- case midiMessageTypes.copyright:
2214
- case midiMessageTypes.trackName:
2215
- case midiMessageTypes.marker:
2216
- case midiMessageTypes.cuePoint:
2217
- case midiMessageTypes.instrumentName:
2218
- case midiMessageTypes.programName: {
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 === midiMessageTypes.lyric) lyricsIndex = Math.min(this.midiData.lyrics.findIndex((l) => l.ticks === event.ticks), this.midiData.lyrics.length - 1);
2222
- 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);
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
- midiMessageTypes.controllerChange | i,
2254
- 120,
2344
+ MIDIMessageTypes.controllerChange | i,
2345
+ MIDIControllers.allNotesOff,
2255
2346
  0
2256
2347
  ]);
2257
2348
  this.midiOut.send([
2258
- midiMessageTypes.controllerChange | i,
2259
- 123,
2349
+ MIDIMessageTypes.controllerChange | i,
2350
+ MIDIControllers.resetAllControllers,
2260
2351
  0
2261
2352
  ]);
2262
2353
  }
2263
- this.midiOut.send([midiMessageTypes.reset]);
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: ALL_CHANNELS_OR_DIFFERENT_ACTION,
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
- SpessaSynthCoreUtils.SpessaSynthInfo("%cMIDI handler created!", consoleColors.recognized);
2498
+ SpessaLog.info("%cMIDI handler created!", ConsoleColors.recognized);
2408
2499
  return new MIDIDeviceHandler(response);
2409
2500
  } catch (error) {
2410
- SpessaSynthCoreUtils.SpessaSynthWarn(`Could not get MIDI Devices:`, error);
2501
+ SpessaLog.warn(`Could not get MIDI Devices:`, error);
2411
2502
  throw error;
2412
2503
  }
2413
2504
  else {
2414
- SpessaSynthCoreUtils.SpessaSynthWarn("Web MIDI API is not supported.", consoleColors.unrecognized);
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
- SpessaSynthCoreUtils.SpessaSynthInfo("%cWeb MIDI Link handler created!", consoleColors.recognized);
2531
+ SpessaLog.info("%cWeb MIDI Link handler created!", ConsoleColors.recognized);
2441
2532
  }
2442
2533
  };
2443
2534
  //#endregion