spessasynth_core 4.4.5 → 4.4.7

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
@@ -90,7 +90,6 @@ function readLittleEndian(dataArray, bytesAmount, offset = 0) {
90
90
  * @param dataArray the IndexedByteArray to write to.
91
91
  * @param number the number to write.
92
92
  * @param byteTarget the amount of bytes to use. Excess bytes will be set to zero.
93
- * @returns the little endian representation of the number.
94
93
  */
95
94
  function writeLittleEndianIndexed(dataArray, number, byteTarget) {
96
95
  for (let i = 0; i < byteTarget; i++) dataArray[dataArray.currentIndex++] = number >> i * 8 & 255;
@@ -359,8 +358,8 @@ var RIFFChunk = class RIFFChunk {
359
358
  * Writes a RIFF chunk correctly.
360
359
  * @param header the fourCC code of the header.
361
360
  * @param data the binary chunk data.
362
- * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
363
361
  * @param rf64 if the chunk uses a 64-bit size.
362
+ * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
364
363
  * @returns the binary data.
365
364
  */
366
365
  static write(header, data, rf64 = false, isList = false) {
@@ -391,8 +390,8 @@ var RIFFChunk = class RIFFChunk {
391
390
  * This allows avoiding large array allocations and only one writeParts call at the end.
392
391
  * @param header the fourCC code of the header.
393
392
  * @param chunks binary chunk data parts, will be combined in order.
394
- * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
395
393
  * @param rf64 if the chunk uses a 64-bit size.
394
+ * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
396
395
  * @returns the chunk as binary blobs.
397
396
  */
398
397
  static getParts(header, chunks, rf64 = false, isList = false) {
@@ -421,8 +420,8 @@ var RIFFChunk = class RIFFChunk {
421
420
  * It merges them together into data and allocates one large array.
422
421
  * @param header the fourCC code of the header.
423
422
  * @param chunks binary chunk data parts, will be combined in order.
424
- * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
425
423
  * @param rf64 if the chunk uses a 64-bit size.
424
+ * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
426
425
  * @returns the binary data.
427
426
  */
428
427
  static writeParts(header, chunks, rf64 = false, isList = false) {
@@ -673,8 +672,9 @@ var SpessaLog = class {
673
672
  /**
674
673
  * Enables or disables logging at various levels.
675
674
  * All the input variables are booleans corresponding to the things SpessaSynth logs.
676
- * @param enableInfo enables info: all general info such as parsing sound banks, MIDI files, RPN changes, etc.
677
- * @param enableWarn enables warning: all messages unrecognized by the synthesizer, other warnings.
675
+ * @param enableInfo enables info: all general info such as parsing sound banks, MIDI files, RPN changes,
676
+ * messages unrecognized by the synthesizer, etc.
677
+ * @param enableWarn enables warning: unexpected and erroneous behavior, such as invalid data or missing presets.
678
678
  * @param enableGroup enables groups: the groups for parsing the sound banks and MIDI files.
679
679
  */
680
680
  static setLogLevel(enableInfo, enableWarn, enableGroup) {
@@ -831,7 +831,7 @@ const MIDIMessageTypes = {
831
831
  sequenceSpecific: 127
832
832
  };
833
833
  /**
834
- * All MIDI 1.0 controller values.
834
+ * All MIDI 1.0 controller numbers.
835
835
  *
836
836
  * > **Tip**
837
837
  * >
@@ -888,10 +888,10 @@ const MIDIControllers = {
888
888
  effectControl2LSB: 45,
889
889
  undefinedCC14LSB: 46,
890
890
  undefinedCC15LSB: 47,
891
- undefinedCC16LSB: 48,
892
- undefinedCC17LSB: 49,
893
- undefinedCC18LSB: 50,
894
- undefinedCC19LSB: 51,
891
+ generalPurposeController1LSB: 48,
892
+ generalPurposeController2LSB: 49,
893
+ generalPurposeController3LSB: 50,
894
+ generalPurposeController4LSB: 51,
895
895
  undefinedCC20LSB: 52,
896
896
  undefinedCC21LSB: 53,
897
897
  undefinedCC22LSB: 54,
@@ -942,32 +942,120 @@ const MIDIControllers = {
942
942
  nonRegisteredParameterMSB: 99,
943
943
  registeredParameterLSB: 100,
944
944
  registeredParameterMSB: 101,
945
+ undefinedCC102: 102,
946
+ undefinedCC103: 103,
947
+ undefinedCC104: 104,
948
+ undefinedCC105: 105,
949
+ undefinedCC106: 106,
950
+ undefinedCC107: 107,
951
+ undefinedCC108: 108,
952
+ undefinedCC109: 109,
953
+ undefinedCC110: 110,
954
+ undefinedCC111: 111,
955
+ undefinedCC112: 112,
956
+ undefinedCC113: 113,
957
+ undefinedCC114: 114,
958
+ undefinedCC115: 115,
959
+ undefinedCC116: 116,
960
+ undefinedCC117: 117,
961
+ undefinedCC118: 118,
962
+ undefinedCC119: 119,
963
+ allSoundOff: 120,
964
+ resetAllControllers: 121,
965
+ localControlOnOff: 122,
966
+ allNotesOff: 123,
967
+ omniModeOff: 124,
968
+ omniModeOn: 125,
969
+ monoModeOn: 126,
970
+ polyModeOn: 127,
971
+ /**
972
+ * @deprecated Use {@link MIDIControllers.generalPurposeController1LSB} instead.
973
+ */
974
+ undefinedCC16LSB: 48,
975
+ /**
976
+ * @deprecated Use {@link MIDIControllers.generalPurposeController2LSB} instead.
977
+ */
978
+ undefinedCC17LSB: 49,
979
+ /**
980
+ * @deprecated Use {@link MIDIControllers.generalPurposeController3LSB} instead.
981
+ */
982
+ undefinedCC18LSB: 50,
983
+ /**
984
+ * @deprecated Use {@link MIDIControllers.generalPurposeController4LSB} instead.
985
+ */
986
+ undefinedCC19LSB: 51,
987
+ /**
988
+ * @deprecated Use {@link MIDIControllers.undefinedCC102} instead.
989
+ */
945
990
  undefinedCC102LSB: 102,
991
+ /**
992
+ * @deprecated Use {@link MIDIControllers.undefinedCC103} instead.
993
+ */
946
994
  undefinedCC103LSB: 103,
995
+ /**
996
+ * @deprecated Use {@link MIDIControllers.undefinedCC104} instead.
997
+ */
947
998
  undefinedCC104LSB: 104,
999
+ /**
1000
+ * @deprecated Use {@link MIDIControllers.undefinedCC105} instead.
1001
+ */
948
1002
  undefinedCC105LSB: 105,
1003
+ /**
1004
+ * @deprecated Use {@link MIDIControllers.undefinedCC106} instead.
1005
+ */
949
1006
  undefinedCC106LSB: 106,
1007
+ /**
1008
+ * @deprecated Use {@link MIDIControllers.undefinedCC107} instead.
1009
+ */
950
1010
  undefinedCC107LSB: 107,
1011
+ /**
1012
+ * @deprecated Use {@link MIDIControllers.undefinedCC108} instead.
1013
+ */
951
1014
  undefinedCC108LSB: 108,
1015
+ /**
1016
+ * @deprecated Use {@link MIDIControllers.undefinedCC109} instead.
1017
+ */
952
1018
  undefinedCC109LSB: 109,
1019
+ /**
1020
+ * @deprecated Use {@link MIDIControllers.undefinedCC110} instead.
1021
+ */
953
1022
  undefinedCC110LSB: 110,
1023
+ /**
1024
+ * @deprecated Use {@link MIDIControllers.undefinedCC111} instead.
1025
+ */
954
1026
  undefinedCC111LSB: 111,
1027
+ /**
1028
+ * @deprecated Use {@link MIDIControllers.undefinedCC112} instead.
1029
+ */
955
1030
  undefinedCC112LSB: 112,
1031
+ /**
1032
+ * @deprecated Use {@link MIDIControllers.undefinedCC113} instead.
1033
+ */
956
1034
  undefinedCC113LSB: 113,
1035
+ /**
1036
+ * @deprecated Use {@link MIDIControllers.undefinedCC114} instead.
1037
+ */
957
1038
  undefinedCC114LSB: 114,
1039
+ /**
1040
+ * @deprecated Use {@link MIDIControllers.undefinedCC115} instead.
1041
+ */
958
1042
  undefinedCC115LSB: 115,
1043
+ /**
1044
+ * @deprecated Use {@link MIDIControllers.undefinedCC116} instead.
1045
+ */
959
1046
  undefinedCC116LSB: 116,
1047
+ /**
1048
+ * @deprecated Use {@link MIDIControllers.undefinedCC117} instead.
1049
+ */
960
1050
  undefinedCC117LSB: 117,
1051
+ /**
1052
+ * @deprecated Use {@link MIDIControllers.undefinedCC118} instead.
1053
+ */
961
1054
  undefinedCC118LSB: 118,
962
- undefinedCC119LSB: 119,
963
- allSoundOff: 120,
964
- resetAllControllers: 121,
965
- localControlOnOff: 122,
966
- allNotesOff: 123,
967
- omniModeOff: 124,
968
- omniModeOn: 125,
969
- monoModeOn: 126,
970
- polyModeOn: 127
1055
+ /**
1056
+ * @deprecated Use {@link MIDIControllers.undefinedCC119} instead.
1057
+ */
1058
+ undefinedCC119LSB: 119
971
1059
  };
972
1060
  /**
973
1061
  * General MIDI Registered Parameter Numbers.
@@ -1024,7 +1112,7 @@ const NonRegisteredParameterTypesLSB = {
1024
1112
  // src/midi/midi_message.ts
1025
1113
  /**
1026
1114
  * Midi_message.ts
1027
- * purpose: contains enums for midi events and controllers and functions to parse them
1115
+ * purpose: contains the MIDIMessage class and functions to create common messages
1028
1116
  */
1029
1117
  /**
1030
1118
  * This class represents a single MIDI 1.0 message.
@@ -1251,7 +1339,7 @@ const EMBEDDED_SOUND_BANK_ID = `SPESSASYNTH_EMBEDDED_BANK_${Math.random()}_DO_NO
1251
1339
  const GENERATOR_OVERRIDE_NO_CHANGE_VALUE = 32767;
1252
1340
  const DEFAULT_SYNTH_METHOD_OPTIONS = { time: 0 };
1253
1341
  /**
1254
- * If the note is released faster than that, it forced to last that long
1342
+ * If the note is released faster than that, it is forced to last that long
1255
1343
  * This is used mostly for drum channels, where a lot of midis like to send instant note off after a note on
1256
1344
  * @internal
1257
1345
  */
@@ -1900,13 +1988,13 @@ const GeneratorLimits = Object.freeze({
1900
1988
  },
1901
1989
  [GeneratorTypes.vibLfoWaveform]: {
1902
1990
  min: 0,
1903
- max: 1,
1991
+ max: 2,
1904
1992
  def: 0,
1905
1993
  nrpn: 0
1906
1994
  },
1907
1995
  [GeneratorTypes.modLfoWaveform]: {
1908
1996
  min: 0,
1909
- max: 1,
1997
+ max: 2,
1910
1998
  def: 0,
1911
1999
  nrpn: 0
1912
2000
  },
@@ -2051,7 +2139,7 @@ const ModulatorControllerSources = {
2051
2139
  */
2052
2140
  pitchWheel: 14,
2053
2141
  /**
2054
- * The current pitch wheel range (where 1 means 127 semitones) on the channel.
2142
+ * The current pitch wheel range (where 1 means 128 semitones) on the channel.
2055
2143
  */
2056
2144
  pitchWheelRange: 16,
2057
2145
  /**
@@ -2074,7 +2162,7 @@ const ModulatorCurveTypes = {
2074
2162
  */
2075
2163
  concave: 1,
2076
2164
  /**
2077
- * Inverse of concave.
2165
+ * Exponential ramp, the inverse of concave.
2078
2166
  */
2079
2167
  convex: 2,
2080
2168
  /**
@@ -2136,6 +2224,7 @@ DEFAULT_DRUM_REVERB[36] = 0;
2136
2224
  * Reset all controllers for channel.
2137
2225
  * This will reset all controllers to their default values,
2138
2226
  * except for the locked controllers.
2227
+ * @param sendCCEvents If controller change events should be emitted for the reset controllers.
2139
2228
  */
2140
2229
  function resetChannelInternal(sendCCEvents = true) {
2141
2230
  for (let cc = 0; cc < 128; cc++) {
@@ -2157,6 +2246,7 @@ function resetChannelInternal(sendCCEvents = true) {
2157
2246
  this.resetMIDIParameter("modulationDepth");
2158
2247
  this.resetMIDIParameter("rxChannel");
2159
2248
  this.resetMIDIParameter("efxAssign");
2249
+ this.resetMIDIParameter("eqEnabled");
2160
2250
  this.resetMIDIParameter("polyMode");
2161
2251
  this.resetMIDIParameter("keyShift");
2162
2252
  this.resetMIDIParameter("fineTune");
@@ -2170,6 +2260,7 @@ function resetChannelInternal(sendCCEvents = true) {
2170
2260
  this.resetMIDIParameter("dryLevel");
2171
2261
  this.octaveTuning.fill(0);
2172
2262
  this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
2263
+ this.portamentoForce = false;
2173
2264
  this.resetDrumParams();
2174
2265
  this.resetGeneratorOverrides();
2175
2266
  this.resetGeneratorOffsets();
@@ -2293,7 +2384,7 @@ var DrumParameterUtils = class {
2293
2384
  dest.rxNoteOn = source.rxNoteOn;
2294
2385
  }
2295
2386
  /**
2296
- * Checks if this user drum parameter is the default.
2387
+ * Checks if this drum parameter is the default.
2297
2388
  * @param param the param to check.
2298
2389
  * @param midiNote the MIDI note of this parameter.
2299
2390
  */
@@ -2377,6 +2468,7 @@ function applySnapshotInternal(midi, snapshot) {
2377
2468
  gsReverbParams: snapshot.systemParameters.gsReverbLock ? snapshot.gsReverbProcessor : void 0,
2378
2469
  gsChorusParams: snapshot.systemParameters.gsChorusLock ? snapshot.gsChorusProcessor : void 0,
2379
2470
  gsDelayParams: snapshot.systemParameters.gsDelayLock ? snapshot.gsDelayProcessor : void 0,
2471
+ gsEqualizerParams: snapshot.systemParameters.gsEqualizerLock ? snapshot.gsEqualizerProcessor : void 0,
2380
2472
  gsInsertionParams: snapshot.systemParameters.gsInsertionLock ? snapshot.gsInsertionBlock : void 0,
2381
2473
  xgReverbParams: snapshot.systemParameters.xgReverbLock ? snapshot.xgReverbBlock : void 0,
2382
2474
  xgChorusParams: snapshot.systemParameters.xgChorusLock ? snapshot.xgChorusBlock : void 0,
@@ -2389,6 +2481,7 @@ function applySnapshotInternal(midi, snapshot) {
2389
2481
  // src/midi/midi_tools/get_note_times.ts
2390
2482
  /**
2391
2483
  * Calculates all note times in seconds.
2484
+ * MIDI ports are ignored, every port is merged into the same 16 channels.
2392
2485
  * @param midi the midi to use
2393
2486
  * @param minDrumLength the shortest a drum note (channel 10) can be, in seconds.
2394
2487
  * @returns an array of 16 channels, each channel containing its notes,
@@ -2462,26 +2555,32 @@ var MIDIPatchTools = class MIDIPatchTools {
2462
2555
  /**
2463
2556
  * Converts a given {@link MIDIPatch} to a string.
2464
2557
  * The format is:
2465
- * - `DRUM:program` for `isGMGSDrum` set to `true`.
2558
+ * - `DRUM:bankMSB:bankLSB:program` for `isGMGSDrum` set to `true`.
2466
2559
  * - `bankLSB:bankMSB:program` for `isGMGSDrum` set to `false`.
2560
+ * @param patch The patch to convert.
2467
2561
  */
2468
2562
  static toMIDIString(patch) {
2469
- if (patch.isGMGSDrum) return `DRUM:${patch.program}`;
2563
+ if (patch.isGMGSDrum) return `DRUM:${patch.bankMSB}:${patch.bankLSB}:${patch.program}`;
2470
2564
  return `${patch.bankLSB}:${patch.bankMSB}:${patch.program}`;
2471
2565
  }
2472
2566
  /**
2473
2567
  * Gets {@link MIDIPatch} from a given string.
2568
+ * @param string The string in the {@link MIDIPatchTools.toMIDIString} format.
2474
2569
  */
2475
2570
  static fromMIDIString(string) {
2476
2571
  const parts = string.split(":");
2477
- const isDrum = string.startsWith("DRUM");
2478
- if (parts.length !== (isDrum ? 2 : 3)) throw new Error(`Invalid MIDI string: ${string}`);
2479
- return isDrum ? {
2480
- bankMSB: 0,
2481
- bankLSB: 0,
2482
- program: Number.parseInt(parts[1]),
2483
- isGMGSDrum: true
2484
- } : {
2572
+ if (string.startsWith("DRUM")) {
2573
+ if (parts.length !== 4 && parts.length !== 2) throw new Error(`Invalid MIDI string: ${string}`);
2574
+ const long = parts.length === 4;
2575
+ return {
2576
+ bankMSB: long ? Number.parseInt(parts[1]) : 0,
2577
+ bankLSB: long ? Number.parseInt(parts[2]) : 0,
2578
+ program: Number.parseInt(parts[parts.length - 1]),
2579
+ isGMGSDrum: true
2580
+ };
2581
+ }
2582
+ if (parts.length !== 3) throw new Error(`Invalid MIDI string: ${string}`);
2583
+ return {
2485
2584
  bankLSB: Number.parseInt(parts[0]),
2486
2585
  bankMSB: Number.parseInt(parts[1]),
2487
2586
  program: Number.parseInt(parts[2]),
@@ -2493,12 +2592,14 @@ var MIDIPatchTools = class MIDIPatchTools {
2493
2592
  * The format is:
2494
2593
  * - `<MIDIPatch string> D <name>` for `isDrum` set to `true`.
2495
2594
  * - `<MIDIPatch string> M <name>` for `isDrum` set to `false`.
2595
+ * @param patch The patch to convert.
2496
2596
  */
2497
2597
  static toFullMIDIString(patch) {
2498
2598
  return `${this.toMIDIString(patch)} ${patch.isDrum ? "D" : "M"} ${patch.name}`;
2499
2599
  }
2500
2600
  /**
2501
2601
  * Gets {@link MIDIPatchFull} from a given string.
2602
+ * @param string The string in the {@link MIDIPatchTools.toFullMIDIString} format.
2502
2603
  */
2503
2604
  static fromFullMIDIString(string) {
2504
2605
  const firstSpace = string.indexOf(" ");
@@ -2515,14 +2616,18 @@ var MIDIPatchTools = class MIDIPatchTools {
2515
2616
  }
2516
2617
  /**
2517
2618
  * Checks if two MIDI patches represent the same one.
2619
+ * @param patch1 The first patch.
2620
+ * @param patch2 The second patch.
2518
2621
  */
2519
2622
  static matches(patch1, patch2) {
2520
- if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program;
2623
+ if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program && patch1.bankMSB === patch2.bankMSB && patch1.bankLSB === patch2.bankLSB;
2521
2624
  return patch1.program === patch2.program && patch1.bankLSB === patch2.bankLSB && patch1.bankMSB === patch2.bankMSB;
2522
2625
  }
2523
2626
  /**
2524
2627
  * A comparison function for `.sort()` or `.toSorted()`,
2525
- * ordering the patches in ascending order.
2628
+ * ordering the patches in ascending order: by program, then bank MSB, then bank LSB, with GM/GS drums last.
2629
+ * @param a The first patch.
2630
+ * @param b The second patch.
2526
2631
  */
2527
2632
  static compare(a, b) {
2528
2633
  if (a.isGMGSDrum && !b.isGMGSDrum) return 1;
@@ -2533,6 +2638,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2533
2638
  }
2534
2639
  /**
2535
2640
  * Checks if the given {@link MIDIPatchFull} is an XG/GM2 drum patch.
2641
+ * @param p The patch to check.
2536
2642
  */
2537
2643
  static isXGDrum(p) {
2538
2644
  return p.isDrum && !p.isGMGSDrum;
@@ -2544,7 +2650,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2544
2650
  * @param patch The {@link MIDIPatch} to select.
2545
2651
  * @param system The MIDI system to select for.
2546
2652
  * @returns The selected patch.
2547
- * @throws Error An error if the array is empty,
2653
+ * @throws Error An error if the array is empty.
2548
2654
  */
2549
2655
  static selectPatch(patches, patch, system) {
2550
2656
  if (patches.length === 0) throw new Error("No presets!");
@@ -2563,7 +2669,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2563
2669
  SpessaLog.info(`%cPreset %c${MIDIPatchTools.toMIDIString(patch)}%c not found. (${system}) Replaced with %c${this.toFullMIDIString(pres)}`, ConsoleColors.warn, ConsoleColors.unrecognized, ConsoleColors.warn, ConsoleColors.value);
2564
2670
  };
2565
2671
  if (isGMGSDrum) {
2566
- let p = patches.find((p) => p.isGMGSDrum && p.program === program);
2672
+ let p = patches.find((p) => p.isGMGSDrum && p.program === program && p.bankLSB === bankLSB) ?? patches.find((p) => p.isGMGSDrum && p.program === program);
2567
2673
  if (p) {
2568
2674
  returnReplacement(p);
2569
2675
  return p;
@@ -2675,7 +2781,8 @@ const GSInsertionAddressMap = {
2675
2781
  controlSource1: 27,
2676
2782
  controlDepth1: 28,
2677
2783
  controlSource2: 29,
2678
- controlDepth2: 30
2784
+ controlDepth2: 30,
2785
+ sendEQSwitch: 31
2679
2786
  };
2680
2787
  const GSChorusAddressMap = {
2681
2788
  macro: 56,
@@ -2701,6 +2808,12 @@ const GSDelayAddressMap = {
2701
2808
  feedback: 89,
2702
2809
  sendLevelToReverb: 90
2703
2810
  };
2811
+ const GSEqualizerAddressMap = {
2812
+ lowFrequency: 0,
2813
+ lowGain: 1,
2814
+ highFrequency: 2,
2815
+ highGain: 3
2816
+ };
2704
2817
  /**
2705
2818
  * The SysEx addresses of the channel MIDI parameters that GS and XG both set per part.
2706
2819
  * `gs` is the third address byte of GS PATCH PART (`40 1x xx`) and `xg` is the third address byte of
@@ -2771,6 +2884,10 @@ const NRPNDrumParamMap = {
2771
2884
  const NRPNDrumParamByMSB = new Map(Object.entries(NRPNDrumParamMap).map(([parameter, msb]) => [msb, parameter]));
2772
2885
  // src/midi/midi_tools/midi_utils.ts
2773
2886
  /**
2887
+ * The GS equalizer parameters by their address (`40 02 xx`).
2888
+ */
2889
+ const GS_EQUALIZER_PARAMETER_BY_ADDRESS = new Map(Object.entries(GSEqualizerAddressMap).map(([name, address]) => [address, name]));
2890
+ /**
2774
2891
  * The named GS insertion parameters by their address (`40 03 xx`).
2775
2892
  */
2776
2893
  const GS_INSERTION_PARAMETER_BY_ADDRESS = new Map(Object.entries(GSInsertionAddressMap).map(([name, address]) => [address, name]));
@@ -2811,7 +2928,7 @@ var MIDIUtils = class MIDIUtils {
2811
2928
  * @param channel The MIDI channel number.
2812
2929
  * @param rpn The 14-bit RPN number.
2813
2930
  * @param value The 14-bit value for that number.
2814
- * @returns The analysis result(s) of the message.
2931
+ * @returns The analysis result of the message.
2815
2932
  */
2816
2933
  static analyzeRPN(channel, rpn, value) {
2817
2934
  switch (rpn) {
@@ -2850,7 +2967,7 @@ var MIDIUtils = class MIDIUtils {
2850
2967
  * @param value The 14-bit value for that number.
2851
2968
  * @param drumPitchStep The drum pitch coarse resolution in semitones.
2852
2969
  * 0.5 for GS (SC-88 and above), 1 for XG and the SC-55 drum map (bank LSB 1).
2853
- * @returns The analysis result(s) of the message.
2970
+ * @returns The analysis result of the message.
2854
2971
  */
2855
2972
  static analyzeNRPN(channel, nrpn, value, drumPitchStep = .5) {
2856
2973
  const msb = nrpn >> 7;
@@ -2913,20 +3030,33 @@ var MIDIUtils = class MIDIUtils {
2913
3030
  return this.gsMessage(ticks, 64, 1, a3, [value]);
2914
3031
  }
2915
3032
  /**
3033
+ * Returns a MIDI event needed to set the given GS Equalizer Parameter.
3034
+ * @param ticks The MIDI tick time for the output event.
3035
+ * @param parameter The parameter to set.
3036
+ * @param value The value to set it to.
3037
+ * @returns The {@link MIDIMessage} needed to set this GS Equalizer Parameter.
3038
+ */
3039
+ static setGSEqualizerParameter(ticks, parameter, value) {
3040
+ const a3 = GSEqualizerAddressMap[parameter];
3041
+ if (a3 === void 0) throw new Error(`Invalid equalizer parameter: ${parameter}`);
3042
+ return this.gsMessage(ticks, 64, 2, a3, [value]);
3043
+ }
3044
+ /**
2916
3045
  * Returns a MIDI event needed to set the given GS Insertion Parameter.
2917
3046
  * @param ticks The MIDI tick time for the output event.
2918
3047
  * @param parameter The parameter to set: `"type"`, a send level or EFX control name (see {@link GSInsertionParameter}),
2919
3048
  * or a 0-based effect-specific parameter number (0-19).
2920
- * @param value The value to set it to.
3049
+ * @param value The value to set it to. A boolean for `"sendEQSwitch"`.
2921
3050
  * @returns The {@link MIDIMessage} needed to set this GS Insertion Parameter.
2922
3051
  */
2923
3052
  static setGSInsertionParameter(ticks, parameter, value) {
3053
+ const data = typeof value === "boolean" ? value ? 1 : 0 : value;
2924
3054
  switch (parameter) {
2925
- case "type": return this.gsMessage(ticks, 64, 3, 0, [value >> 8 & 127, value & 127]);
3055
+ case "type": return this.gsMessage(ticks, 64, 3, 0, [data >> 8 & 127, data & 127]);
2926
3056
  default:
2927
- if (typeof parameter === "string") return this.gsMessage(ticks, 64, 3, GSInsertionAddressMap[parameter], [value]);
3057
+ if (typeof parameter === "string") return this.gsMessage(ticks, 64, 3, GSInsertionAddressMap[parameter], [data]);
2928
3058
  if (!Number.isInteger(parameter) || parameter < 0 || parameter > 19) throw new Error(`Invalid insertion parameter: ${parameter}`);
2929
- return this.gsMessage(ticks, 64, 3, parameter + 3, [value]);
3059
+ return this.gsMessage(ticks, 64, 3, parameter + 3, [data]);
2930
3060
  }
2931
3061
  }
2932
3062
  /**
@@ -3134,6 +3264,7 @@ var MIDIUtils = class MIDIUtils {
3134
3264
  case "drumMap":
3135
3265
  case "velocitySenseDepth":
3136
3266
  case "velocitySenseOffset": return [MIDIUtils.partParamMessage(ticks, channel, system, parameter, value)];
3267
+ case "eqEnabled": return [MIDIUtils.gsMessage(ticks, 64, 64 | MIDIUtils.channelToGSPart(channel), 32, [value ? 1 : 0])];
3137
3268
  case "efxAssign": return [MIDIUtils.gsMessage(ticks, 64, 64 | MIDIUtils.channelToGSPart(channel), 34, [value ? 1 : 0])];
3138
3269
  case "dryLevel": return [MIDIUtils.xgMessage(ticks, 8, channel, 17, [value])];
3139
3270
  }
@@ -4334,6 +4465,15 @@ var MIDIUtils = class MIDIUtils {
4334
4465
  value
4335
4466
  }];
4336
4467
  }
4468
+ if (a2 === 2) {
4469
+ const parameter = GS_EQUALIZER_PARAMETER_BY_ADDRESS.get(a3);
4470
+ if (parameter === void 0) return [OTHER];
4471
+ return [{
4472
+ type: "GS Equalizer Param",
4473
+ parameter,
4474
+ value
4475
+ }];
4476
+ }
4337
4477
  if (a2 === 3) {
4338
4478
  switch (a3) {
4339
4479
  case 0: return [{
@@ -4343,6 +4483,11 @@ var MIDIUtils = class MIDIUtils {
4343
4483
  }];
4344
4484
  }
4345
4485
  const named = GS_INSERTION_PARAMETER_BY_ADDRESS.get(a3);
4486
+ if (named === "sendEQSwitch") return [{
4487
+ type: "GS Insertion Param",
4488
+ parameter: named,
4489
+ value: value !== 0
4490
+ }];
4346
4491
  if (named !== void 0) return [{
4347
4492
  type: "GS Insertion Param",
4348
4493
  parameter: named,
@@ -4546,6 +4691,12 @@ var MIDIUtils = class MIDIUtils {
4546
4691
  controller: MIDIControllers.bankSelectLSB,
4547
4692
  value
4548
4693
  }];
4694
+ case 32: return [{
4695
+ type: "Channel MIDI Param",
4696
+ channel,
4697
+ parameter: "eqEnabled",
4698
+ value: value === 1
4699
+ }];
4549
4700
  case 34: return [{
4550
4701
  type: "Channel MIDI Param",
4551
4702
  channel,
@@ -4877,7 +5028,7 @@ var MIDIEditor = class {
4877
5028
  * GM -> GS -> note data
4878
5029
  *
4879
5030
  * That GM reset is meaningless, so it can stay unmodified as the actual reset that matters is GS.
4880
- * Meaningful ones are replaced with GS in-place
5031
+ * Meaningful GM ones are replaced with the fallback reset in-place
4881
5032
  * (unless the `system` parameter is explicitly set).
4882
5033
  */
4883
5034
  meaningfulResets = new Array();
@@ -4919,6 +5070,7 @@ var MIDIEditor = class {
4919
5070
  gsReverbParams;
4920
5071
  gsChorusParams;
4921
5072
  gsDelayParams;
5073
+ gsEqualizerParams;
4922
5074
  gsInsertionParams;
4923
5075
  xgReverbParams;
4924
5076
  xgChorusParams;
@@ -4946,10 +5098,11 @@ var MIDIEditor = class {
4946
5098
  constructor(midi, opts) {
4947
5099
  this.midi = midi;
4948
5100
  SpessaLog.groupCollapsed("%cApplying changes to the MIDI file...", ConsoleColors.info);
4949
- const { channels, gsReverbParams, gsChorusParams, gsDelayParams, gsInsertionParams, xgReverbParams, xgChorusParams, xgVariationParams, xgInsertionParams, userDrumParams, midiParams } = opts;
5101
+ const { channels, gsReverbParams, gsChorusParams, gsDelayParams, gsEqualizerParams, gsInsertionParams, xgReverbParams, xgChorusParams, xgVariationParams, xgInsertionParams, userDrumParams, midiParams } = opts;
4950
5102
  this.gsReverbParams = gsReverbParams;
4951
5103
  this.gsChorusParams = gsChorusParams;
4952
5104
  this.gsDelayParams = gsDelayParams;
5105
+ this.gsEqualizerParams = gsEqualizerParams;
4953
5106
  this.gsInsertionParams = gsInsertionParams;
4954
5107
  this.xgReverbParams = xgReverbParams;
4955
5108
  this.xgChorusParams = xgChorusParams;
@@ -5022,12 +5175,12 @@ var MIDIEditor = class {
5022
5175
  }
5023
5176
  if (this.lastReset !== void 0 && replacedEvents.has(this.lastReset)) this.system = this.fallbackReset ?? "gs";
5024
5177
  }
5025
- if (!this.addedReset && this.midiParams?.system !== "clear" && (this.midiParams?.system !== void 0 || this.fallbackReset !== void 0 || this.gsReverbParams && this.gsReverbParams !== "clear" || this.gsChorusParams && this.gsChorusParams !== "clear" || this.gsDelayParams && this.gsDelayParams !== "clear" || this.gsInsertionParams && this.gsInsertionParams !== "clear" || this.xgReverbParams && this.xgReverbParams !== "clear" || this.xgChorusParams && this.xgChorusParams !== "clear" || this.xgVariationParams && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear") || this.userDrumSetParams?.size || [...this.channelChanges.values()].some((c) => c.patch && c.patch !== "clear"))) {
5178
+ if (!this.addedReset && this.midiParams?.system !== "clear" && (this.midiParams?.system !== void 0 || this.fallbackReset !== void 0 || this.gsReverbParams && this.gsReverbParams !== "clear" || this.gsChorusParams && this.gsChorusParams !== "clear" || this.gsDelayParams && this.gsDelayParams !== "clear" || this.gsEqualizerParams && this.gsEqualizerParams !== "clear" || this.gsInsertionParams && this.gsInsertionParams !== "clear" || this.xgReverbParams && this.xgReverbParams !== "clear" || this.xgChorusParams && this.xgChorusParams !== "clear" || this.xgVariationParams && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear") || this.userDrumSetParams?.size || [...this.channelChanges.values()].some((c) => c.patch && c.patch !== "clear"))) {
5026
5179
  let index = 0;
5027
5180
  const firstTrack = this.midi.tracks[0];
5028
5181
  while (index < firstTrack.events.length && firstTrack.events[index].statusByte < MIDIMessageTypes.noteOff) index++;
5029
5182
  const hasXG = this.xgReverbParams !== void 0 && this.xgReverbParams !== "clear" || this.xgChorusParams !== void 0 && this.xgChorusParams !== "clear" || this.xgVariationParams !== void 0 && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear");
5030
- const hasGS = this.gsReverbParams !== void 0 && this.gsReverbParams !== "clear" || this.gsChorusParams !== void 0 && this.gsChorusParams !== "clear" || this.gsDelayParams !== void 0 && this.gsDelayParams !== "clear" || this.gsInsertionParams !== void 0 && this.gsInsertionParams !== "clear";
5183
+ const hasGS = this.gsReverbParams !== void 0 && this.gsReverbParams !== "clear" || this.gsChorusParams !== void 0 && this.gsChorusParams !== "clear" || this.gsDelayParams !== void 0 && this.gsDelayParams !== "clear" || this.gsEqualizerParams !== void 0 && this.gsEqualizerParams !== "clear" || this.gsInsertionParams !== void 0 && this.gsInsertionParams !== "clear";
5031
5184
  const targetSystem = this.midiParams?.system ?? this.fallbackReset ?? (hasXG && !hasGS ? "xg" : "gs");
5032
5185
  const event = MIDIUtils.reset(0, targetSystem);
5033
5186
  firstTrack.addEvents(index, event);
@@ -5267,6 +5420,12 @@ var MIDIEditor = class {
5267
5420
  return;
5268
5421
  }
5269
5422
  break;
5423
+ case "GS Equalizer Param":
5424
+ if (this.gsEqualizerParams) {
5425
+ this.deleteCurrentEvent();
5426
+ return;
5427
+ }
5428
+ break;
5270
5429
  case "GS Insertion Param":
5271
5430
  if (this.gsInsertionParams) {
5272
5431
  this.deleteCurrentEvent();
@@ -5681,6 +5840,10 @@ var MIDIEditor = class {
5681
5840
  const p = this.gsDelayParams;
5682
5841
  output.push(MIDIUtils.setGSDelayParameter(targetTicks, "level", p.level), MIDIUtils.setGSDelayParameter(targetTicks, "preLowpass", p.preLowpass), MIDIUtils.setGSDelayParameter(targetTicks, "timeCenter", p.timeCenter), MIDIUtils.setGSDelayParameter(targetTicks, "timeRatioLeft", p.timeRatioLeft), MIDIUtils.setGSDelayParameter(targetTicks, "timeRatioRight", p.timeRatioRight), MIDIUtils.setGSDelayParameter(targetTicks, "levelCenter", p.levelCenter), MIDIUtils.setGSDelayParameter(targetTicks, "levelLeft", p.levelLeft), MIDIUtils.setGSDelayParameter(targetTicks, "levelRight", p.levelRight), MIDIUtils.setGSDelayParameter(targetTicks, "feedback", p.feedback), MIDIUtils.setGSDelayParameter(targetTicks, "sendLevelToReverb", p.sendLevelToReverb));
5683
5842
  }
5843
+ if (this.gsEqualizerParams && this.gsEqualizerParams !== "clear") {
5844
+ const p = this.gsEqualizerParams;
5845
+ for (const name of Object.keys(GSEqualizerAddressMap)) output.push(MIDIUtils.setGSEqualizerParameter(targetTicks, name, p[name]));
5846
+ }
5684
5847
  if (this.gsInsertionParams && this.gsInsertionParams !== "clear") {
5685
5848
  const p = this.gsInsertionParams;
5686
5849
  output.push(MIDIUtils.setGSInsertionParameter(targetTicks, "type", p.type));
@@ -5946,7 +6109,7 @@ var MIDITrack = class MIDITrack {
5946
6109
  */
5947
6110
  port = 0;
5948
6111
  /**
5949
- * A set that contains the MIDI channel numbers used by this track.
6112
+ * A set that contains the MIDI channel numbers (0-15) that have note on events in this track.
5950
6113
  */
5951
6114
  channels = /* @__PURE__ */ new Set();
5952
6115
  /**
@@ -5975,7 +6138,7 @@ var MIDITrack = class MIDITrack {
5975
6138
  }
5976
6139
  /**
5977
6140
  * Adds events to the track.
5978
- * @param index The index at which to add these event.
6141
+ * @param index The index at which to add these events.
5979
6142
  * @param events The events to add.
5980
6143
  */
5981
6144
  addEvents(index, ...events) {
@@ -7043,7 +7206,7 @@ var BasicSample = class {
7043
7206
  *
7044
7207
  * > **Warning**
7045
7208
  * >
7046
- * > Do not change this value directly. use {@link BasicSample.setSampleType},
7209
+ * > Do not change this value directly. Use {@link BasicSample.setSampleType},
7047
7210
  * > {@link BasicSample.setLinkedSample} or {@link BasicSample.unlinkSample} instead.
7048
7211
  */
7049
7212
  sampleType;
@@ -7105,7 +7268,7 @@ var BasicSample = class {
7105
7268
  * @param sampleRate The sample's rate in Hz.
7106
7269
  * @param originalKey The sample's pitch as a MIDI note number.
7107
7270
  * @param pitchCorrection The sample's pitch correction in cents.
7108
- * @param sampleType The sample's type, an enum that defines the sample type/compression.
7271
+ * @param sampleType The sample's type, one of the {@link SampleTypes}.
7109
7272
  * @param loopStart The sample's loop start relative to the sample start in sample points.
7110
7273
  * @param loopEnd The sample's loop end relative to the sample start in sample points. Exclusive.
7111
7274
  */
@@ -7705,7 +7868,7 @@ var ModulatorSource = class ModulatorSource {
7705
7868
  /**
7706
7869
  * If this field is set true, the direction of the controller should be from the maximum value to the minimum value. So, for
7707
7870
  * example, if the controller source is Key Number, then a Key Number value of 0 corresponds to the maximum possible
7708
- * controller output, and the Key Number value of 127 corresponds to the minimum possible controller input.
7871
+ * controller output, and the Key Number value of 127 corresponds to the minimum possible controller output.
7709
7872
  */
7710
7873
  isNegative;
7711
7874
  /**
@@ -8519,7 +8682,7 @@ var BasicPreset = class BasicPreset {
8519
8682
  * >
8520
8683
  * > Consider reading about [the DLS conversion problem](../../../docs/extra/dls-conversion-problem.md).
8521
8684
  *
8522
- * @returns The instrument containing the flattened zones. If flattening was successful, it should exactly the same as this preset.
8685
+ * @returns The instrument containing the flattened zones. If flattening was successful, it should sound exactly the same as this preset.
8523
8686
  */
8524
8687
  toFlattenedInstrument() {
8525
8688
  const addUnique = (main, adder) => {
@@ -8710,7 +8873,7 @@ var BasicInstrument = class {
8710
8873
  return zone;
8711
8874
  }
8712
8875
  /**
8713
- * Links the instrument ta a given preset.
8876
+ * Links the instrument to a given preset.
8714
8877
  * @param preset The preset to link to.
8715
8878
  * @internal
8716
8879
  */
@@ -10170,8 +10333,8 @@ var DownloadableSoundsArticulation = class extends DLSVerifier {
10170
10333
  for (const modulator of zone.modulators) ConnectionBlock.fromSFModulator(modulator, this);
10171
10334
  }
10172
10335
  /**
10173
- * Chunk list for the region/instrument (containing lar2 or lart)
10174
- * @param chunks
10336
+ * Reads the articulation from a chunk list.
10337
+ * @param chunks Chunk list for the region/instrument (containing lar2 or lart)
10175
10338
  */
10176
10339
  read(chunks) {
10177
10340
  const lart = RIFFChunk.findListType(chunks, "lart");
@@ -10493,7 +10656,6 @@ var DownloadableSoundsRegion = class DownloadableSoundsRegion extends DLSVerifie
10493
10656
  // src/soundbank/downloadable_sounds/instrument.ts
10494
10657
  /**
10495
10658
  * Represents a proper DLS instrument, with regions and articulation.
10496
- * DLS
10497
10659
  */
10498
10660
  var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DLSVerifier {
10499
10661
  articulation = new DownloadableSoundsArticulation();
@@ -10765,7 +10927,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
10765
10927
  for (let i = 0; i < bank.samples.length; i++) {
10766
10928
  const s = bank.samples[i];
10767
10929
  dls.samples.push(DownloadableSoundsSample.fromSFSample(s));
10768
- progressFunc?.(i / bank.samples.length);
10930
+ progressFunc?.((i + 1) / bank.samples.length);
10769
10931
  }
10770
10932
  for (const p of bank.presets) dls.instruments.push(DownloadableSoundsInstrument.fromSFPreset(p, bank.samples));
10771
10933
  SpessaLog.info("%cConversion complete!", ConsoleColors.recognized);
@@ -10799,12 +10961,12 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
10799
10961
  let written = 0;
10800
10962
  for (const s of this.samples) {
10801
10963
  const out = s.write();
10964
+ written++;
10802
10965
  options.progressFunction?.(written / this.samples.length);
10803
10966
  SpessaLog.info(`%cWrote sample %c${written}. ${s.name}%c of %c${this.samples.length}.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.recognized);
10804
10967
  ptblOffsets.push(currentIndex);
10805
10968
  currentIndex += out.reduce((sum, cur) => sum + cur.length, 0);
10806
10969
  samples.push(...out);
10807
- written++;
10808
10970
  }
10809
10971
  const wvpl = RIFFChunk.getParts("wvpl", samples, false, true);
10810
10972
  SpessaLog.info("%cSucceeded!", ConsoleColors.recognized);
@@ -11016,9 +11178,8 @@ var SoundBankCloner = class {
11016
11178
  * This module handles parsing and writing SoundFont2 (`.sf2`, `.sf3` and `.sfogg`) files.
11017
11179
  * It represents a single sound bank.
11018
11180
  *
11019
- * It also contains support for `.dls` files and experimental read and write support for [64-bit SFE](https://github.com/SFe-Team-was-taken/SFE).
11181
+ * It also contains support for `.dls` files and experimental read and write support for [64-bit SFE](https://sfe-team-was-taken.github.io/SFE/).
11020
11182
  *
11021
-
11022
11183
  * **Specifications:**
11023
11184
  *
11024
11185
  * - [SoundFont2 Specification](http://www.synthfont.com/sfspec24.pdf)
@@ -11062,7 +11223,7 @@ var BasicSoundBank = class BasicSoundBank {
11062
11223
  }
11063
11224
  };
11064
11225
  /**
11065
- * An array of all presets in the bank, ordered by bank and preset number.
11226
+ * An array of all presets in the bank, ordered by program and bank number, with GM/GS drum presets last.
11066
11227
  */
11067
11228
  presets = [];
11068
11229
  /**
@@ -11129,8 +11290,9 @@ var BasicSoundBank = class BasicSoundBank {
11129
11290
  return b;
11130
11291
  }
11131
11292
  /**
11132
- * Creates a simple sound bank with a single saw wave preset.
11293
+ * Creates a simple SF2 file with a single saw wave preset.
11133
11294
  * Useful for testing synthesizer's functionality without providing a file.
11295
+ * @returns The binary representation of the SF2 file.
11134
11296
  */
11135
11297
  static getSampleSoundBankFile() {
11136
11298
  const font = new BasicSoundBank();
@@ -11158,12 +11320,17 @@ var BasicSoundBank = class BasicSoundBank {
11158
11320
  return font.writeSF2();
11159
11321
  }
11160
11322
  /**
11161
- * Copies a given sound bank, deeply cloning all presets instruments and samples.
11323
+ * Copies a given sound bank, cloning all presets, instruments and samples.
11324
+ * The sample data is shared with the original, not copied.
11325
+ * Elements sharing a name are all copied, the repeats get a free name like `Piano-1`.
11162
11326
  * @param bank The sound bank to copy.
11163
11327
  */
11164
11328
  static copyFrom(bank) {
11165
11329
  const copied = new BasicSoundBank(bank.type);
11166
- const cloner = new SoundBankCloner(copied);
11330
+ const cloner = new SoundBankCloner(copied, {
11331
+ reuseByName: false,
11332
+ reuseChildrenByName: false
11333
+ });
11167
11334
  for (const p of bank.presets) cloner.clonePreset(p);
11168
11335
  copied.soundBankInfo = { ...bank.soundBankInfo };
11169
11336
  copied.defaultModulators = bank.defaultModulators.map((m) => Modulator.copyFrom(m));
@@ -11189,8 +11356,8 @@ var BasicSoundBank = class BasicSoundBank {
11189
11356
  *
11190
11357
  * > **Warning**
11191
11358
  * >
11192
- * > Note that decompressing (sample format `pcm`) usually results
11193
- * > in permanent sample quality loss!
11359
+ * > Note that compressing (sample format `compressed`) usually results
11360
+ * > in permanent sample quality loss! Decompressing does not restore it.
11194
11361
  * > This method is memory and CPU intensive with large sound banks.
11195
11362
  *
11196
11363
  * @param options Options associated with setting the sample format.
@@ -11234,7 +11401,7 @@ var BasicSoundBank = class BasicSoundBank {
11234
11401
  }
11235
11402
  /**
11236
11403
  * Writes out a DLS Level 2 sound bank. This may not be 100% accurate.
11237
- * Samples data is always written in the S16LE PCM encoding.
11404
+ * Sample data is always written in the S16LE PCM encoding.
11238
11405
  *
11239
11406
  * > **Note**
11240
11407
  * >
@@ -11357,16 +11524,15 @@ var BasicSoundBank = class BasicSoundBank {
11357
11524
  /**
11358
11525
  * Trims the sound bank _in-place_ to only contain samples in a given MIDI file.
11359
11526
  *
11360
-
11361
11527
  * Absent presets will be removed from the sound bank,
11362
11528
  * and samples that don't get activated in the remaining presets will be removed as well.
11363
11529
  *
11364
11530
  * > **Note**
11365
11531
  * >
11366
- * > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}
11532
+ * > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}.
11367
11533
  * > Consider reading the page for more explanation about the parameter.
11368
11534
  *
11369
- * @param presetData - A `Map`: `BasicPreset` -> `Set<"key-velocity">`.
11535
+ * @param presetData A `Map`: `BasicPreset` -> `Map<MIDI note, Set<velocity>>`.
11370
11536
  * Absent presets will be removed from the sound bank,
11371
11537
  * and samples that don't get activated in the remaining presets will be removed as well.
11372
11538
  */
@@ -13766,11 +13932,13 @@ const DEFAULT_RMIDI_WRITE_OPTIONS = {
13766
13932
  */
13767
13933
  function writeRMIDIInternal(mid, soundBankBinary, options) {
13768
13934
  const metadata = { ...options.metadata };
13769
- if (options.correctBankOffset && !options.soundBank) throw new Error("Sound bank must be provided if correcting bank offset.");
13770
13935
  SpessaLog.group("%cWriting the RMIDI File...", ConsoleColors.info);
13771
13936
  SpessaLog.info("metadata", metadata);
13772
13937
  SpessaLog.info("Initial bank offset", mid.bankOffset);
13773
- if (options.correctBankOffset && options.soundBank) correctBankOffsetInternal(mid, options.bankOffset, options.soundBank);
13938
+ if (options.correctBankOffset) {
13939
+ if (options.soundBank) correctBankOffsetInternal(mid, options.bankOffset, options.soundBank);
13940
+ else SpessaLog.warn("No sound bank provided, skipping the bank offset correction.");
13941
+ }
13774
13942
  const newMid = new IndexedByteArray(mid.writeMIDI());
13775
13943
  metadata.name ??= mid.getName();
13776
13944
  metadata.creationDate ??= /* @__PURE__ */ new Date();
@@ -13958,7 +14126,8 @@ var BasicMIDI = class BasicMIDI {
13958
14126
  * The loop points (in ticks) of the sequence, including both start and end points.
13959
14127
  *
13960
14128
  * If there's nothing detected, the loop will start from the first note on event and end will be the last voice message.
13961
- * Current looping detection is: CC 2/4, 116/117 and "start," "loopStart" and "loopEnd" markers.
14129
+ * Current looping detection is: CC 2/4 (Touhou), CC 111 (RPG Maker), CC 116/117 and 118/119 (EMIDI/XMI)
14130
+ * and the "start", "loopStart" and "loopEnd" markers.
13962
14131
  */
13963
14132
  loop = {
13964
14133
  start: 0,
@@ -13976,6 +14145,7 @@ var BasicMIDI = class BasicMIDI {
13976
14145
  format = 0;
13977
14146
  /**
13978
14147
  * The RMID (Resource-Interchangeable MIDI) info data, if the file is RMID formatted.
14148
+ * XMF metadata and converted tracker modules are stored here as well.
13979
14149
  * Otherwise, this object is empty.
13980
14150
  * Info type: Chunk data as a binary array.
13981
14151
  *
@@ -14047,7 +14217,7 @@ var BasicMIDI = class BasicMIDI {
14047
14217
  * This function reads the MIDI file format, extracts the header and track chunks,
14048
14218
  * and populates the BasicMIDI instance with the parsed data.
14049
14219
  * It supports Standard MIDI Files (SMF), RIFF MIDI (RMIDI), and Extensible Music Format (XMF).
14050
- * It also handles embedded soundbanks in RMIDI files.
14220
+ * It also handles embedded soundbanks in RMIDI and XMF files.
14051
14221
  * Tracker modules (ProTracker MOD, Fast Tracker 2 XM, Scream Tracker 3 S3M) are converted
14052
14222
  * into MIDI with an embedded sound bank.
14053
14223
  */
@@ -14086,7 +14256,7 @@ var BasicMIDI = class BasicMIDI {
14086
14256
  return m;
14087
14257
  }
14088
14258
  /**
14089
- * Copies another instance this `BasicMIDI` instance, including track data.
14259
+ * Copies another instance into this `BasicMIDI` instance, including track data.
14090
14260
  * @param mid The MIDI to copy.
14091
14261
  */
14092
14262
  copyFrom(mid) {
@@ -14159,7 +14329,7 @@ var BasicMIDI = class BasicMIDI {
14159
14329
  }
14160
14330
  /**
14161
14331
  * Preloads all voices for this sequence in a given {@link SpessaSynthProcessor}.
14162
- * This caches all the needed voices for playing back this sequencer, resulting in a smooth playback.
14332
+ * This caches all the needed voices for playing back this sequence, resulting in a smooth playback.
14163
14333
  * The sequencer calls this function by default when loading the songs. (it can be disabled: {@link SpessaSynthSequencer.preload}).
14164
14334
  * @param synth The synthesizer to preload.
14165
14335
  */
@@ -14189,6 +14359,12 @@ var BasicMIDI = class BasicMIDI {
14189
14359
  }
14190
14360
  /**
14191
14361
  * Calculates all note times in seconds.
14362
+ *
14363
+ * > **Note**
14364
+ * >
14365
+ * > MIDI ports are ignored, so the channels of every port are merged into the 16 channels.
14366
+ * > For example, channel 1 of port 2 is returned as channel 1.
14367
+ *
14192
14368
  * @param minDrumLength In seconds, represents the minimum allowed time for a drum note,
14193
14369
  * since they sometimes have a length of 0.
14194
14370
  * @returns An array of 16 channels, each channel containing its notes,
@@ -14241,7 +14417,7 @@ var BasicMIDI = class BasicMIDI {
14241
14417
  return writeMIDIInternal(this);
14242
14418
  }
14243
14419
  /**
14244
- * Writes out an RMIDI file (MIDI + SF2).
14420
+ * Writes out an RMIDI file (MIDI + SF2 or DLS).
14245
14421
  * [See more info about this format](https://github.com/spessasus/sf2-rmidi-specification#readme).
14246
14422
  *
14247
14423
  * Note that this method modifies the MIDI file in-place.
@@ -14250,11 +14426,16 @@ var BasicMIDI = class BasicMIDI {
14250
14426
  * The method is called on a `BasicMIDI` instance;
14251
14427
  * that instance is the MIDI file to embed.
14252
14428
  *
14429
+ * > **Warning**
14430
+ * >
14431
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
14432
+ * > Bank offset correction (on by default) needs `soundBank`. Without it, the correction is skipped with a warning.
14433
+ *
14253
14434
  * > **Tip**
14254
14435
  * >
14255
14436
  * > Use {@link BasicSoundBank.trim} to drastically reduce the file size.
14256
- * > consider also using compression (like shown in example) to save even more space.
14257
- * > (using these both methods, I managed to shrink a 1GB sound bank into a 5MB RMIDI!)
14437
+ * > Consider also using compression (like shown in example) to save even more space.
14438
+ * > (using both of these methods, I managed to shrink a 1GB sound bank into a 5MB RMIDI!)
14258
14439
  *
14259
14440
  * @param soundBankBinary The binary sound bank (SF2 or DLS) to embed into the file.
14260
14441
  * @param configuration Extra options for writing the file.
@@ -14292,7 +14473,7 @@ var BasicMIDI = class BasicMIDI {
14292
14473
  *
14293
14474
  * // trim the soundfont
14294
14475
  * soundBank.trim(midi.getUsedProgramsAndKeys(soundBank));
14295
- * // write out with compression to save space (0.5 is medium quality)
14476
+ * // write out with compression to save space
14296
14477
  * await soundBank.setSampleFormat({
14297
14478
  * format: "compressed",
14298
14479
  * compressionFunction: SampleEncodingFunction // Remember to get your compression function
@@ -14330,6 +14511,8 @@ var BasicMIDI = class BasicMIDI {
14330
14511
  * and inserts/deletes messages appropriately.
14331
14512
  *
14332
14513
  * This modifies the MIDI sequence _in-place_.
14514
+ *
14515
+ * @param opts The modifications to apply.
14333
14516
  */
14334
14517
  modify(opts) {
14335
14518
  new MIDIEditor(this, opts).apply();
@@ -14339,8 +14522,10 @@ var BasicMIDI = class BasicMIDI {
14339
14522
  * This means changing the programs and controllers if they are locked.
14340
14523
  *
14341
14524
  * For example, if channel 1 has locked preset on `Drawbar Organ`,
14342
- * this will remove all program changes for channel 1 and add one at the start to change the program to
14525
+ * this will remove all program changes for channel 1 and add one before the channel's first note on
14526
+ * (and again after every system reset) to change the program to
14343
14527
  * `Drawbar organ` (using bank MSB/LSB and program change).
14528
+ * Muted channels are removed from the sequence.
14344
14529
  *
14345
14530
  * > **Warning**
14346
14531
  * >
@@ -14413,11 +14598,31 @@ var BasicMIDI = class BasicMIDI {
14413
14598
  * > **Note**
14414
14599
  * >
14415
14600
  * > This sets the Info encoding to `utf-8`.
14601
+ * > Text chunks that are already present get re-encoded from their old encoding (IENC) to UTF-8,
14602
+ * > so they stay readable. Without IENC, they are assumed to be UTF-8 already, like {@link BasicMIDI.getRMIDInfo} does.
14603
+ *
14604
+ * > **Warning**
14605
+ * >
14606
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
14416
14607
  *
14417
14608
  * @param infoType The type to set.
14418
14609
  * @param infoData The value to set it to.
14419
14610
  */
14420
14611
  setRMIDInfo(infoType, infoData) {
14612
+ const oldEncoding = this.infoEncoding;
14613
+ if (oldEncoding !== void 0 && !/^utf-?8$/i.test(oldEncoding.trim())) try {
14614
+ const decoder = new TextDecoder(oldEncoding.trim());
14615
+ const encoder = new TextEncoder();
14616
+ for (const key of Object.keys(this.rmidiInfo)) {
14617
+ if (key === "picture" || key === "creationDate" || key === "infoEncoding") continue;
14618
+ const data = this.rmidiInfo[key];
14619
+ if (!data) continue;
14620
+ const text = decoder.decode(data[data.length - 1] === 0 ? data.subarray(0, -1) : data);
14621
+ this.rmidiInfo[key] = new Uint8Array([...encoder.encode(text), 0]);
14622
+ }
14623
+ } catch (error) {
14624
+ SpessaLog.warn(`Failed to re-encode the RMIDI info from ${oldEncoding}: ${error}`);
14625
+ }
14421
14626
  this.rmidiInfo.infoEncoding = getStringBytes("utf-8", true);
14422
14627
  if (infoType === "picture") this.rmidiInfo.picture = new Uint8Array(infoData);
14423
14628
  else if (infoType === "creationDate") this.rmidiInfo.creationDate = getStringBytes(toISODateString(infoData), true);
@@ -14428,6 +14633,11 @@ var BasicMIDI = class BasicMIDI {
14428
14633
  }
14429
14634
  /**
14430
14635
  * Gets a given chunk from the RMIDI information.
14636
+ *
14637
+ * > **Warning**
14638
+ * >
14639
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder.
14640
+ *
14431
14641
  * @param infoType The metadata type.
14432
14642
  * @returns `string`, `Date`, `ArrayBuffer` or undefined if the info is not set.
14433
14643
  */
@@ -14455,7 +14665,7 @@ var BasicMIDI = class BasicMIDI {
14455
14665
  * > if you are not editing the MIDI file in your loop.
14456
14666
  * > It is usually a faster solution and allows custom loops.
14457
14667
  *
14458
- * If the track data is being edited, remember to call {@link BasicMIDI.flush} it after editing!
14668
+ * If the track data is being edited, remember to call {@link BasicMIDI.flush} after editing!
14459
14669
  *
14460
14670
  * @param callback The callback function to process each event.
14461
14671
  */
@@ -14562,11 +14772,15 @@ var BasicMIDI = class BasicMIDI {
14562
14772
  case MIDIMessageTypes.controllerChange:
14563
14773
  switch (e.data[0]) {
14564
14774
  case MIDIControllers.breathController:
14565
- case MIDIControllers.undefinedCC111LSB:
14566
14775
  if (e.data[1] === 0) loopStart = e.ticks;
14567
14776
  break;
14568
- case MIDIControllers.undefinedCC116LSB:
14569
- case MIDIControllers.undefinedCC118LSB:
14777
+ case MIDIControllers.undefinedCC111:
14778
+ if (e.data[1] !== 0) break;
14779
+ loopStart = e.ticks;
14780
+ loopType = "soft";
14781
+ break;
14782
+ case MIDIControllers.undefinedCC116:
14783
+ case MIDIControllers.undefinedCC118:
14570
14784
  loopStart = e.ticks;
14571
14785
  break;
14572
14786
  case MIDIControllers.footController:
@@ -14579,8 +14793,8 @@ var BasicMIDI = class BasicMIDI {
14579
14793
  loopType = "hard";
14580
14794
  }
14581
14795
  break;
14582
- case MIDIControllers.undefinedCC117LSB:
14583
- case MIDIControllers.undefinedCC119LSB:
14796
+ case MIDIControllers.undefinedCC117:
14797
+ case MIDIControllers.undefinedCC119:
14584
14798
  loopEnd = loopEnd === null ? e.ticks : 0;
14585
14799
  loopType = "hard";
14586
14800
  break;
@@ -14776,6 +14990,7 @@ const DEFAULT_MIDI_BUILDER_OPTIONS = {
14776
14990
  * > **Warning**
14777
14991
  * >
14778
14992
  * > Remember to call {@link BasicMIDI.flush} after you're done building the file!
14993
+ * > Do not use in audioWorkletGlobalScope as it uses TextEncoder.
14779
14994
  *
14780
14995
  * @group MIDI.Files
14781
14996
  */
@@ -15071,7 +15286,7 @@ function processEventInternal(event, trackIndex) {
15071
15286
  }
15072
15287
  // src/sequencer/process_tick.ts
15073
15288
  /**
15074
- * Processes a single MIDI tick.
15289
+ * Processes a single sequencer tick, playing all MIDI events up to the current time.
15075
15290
  * Call this every rendering quantum to process the sequencer events in real-time.
15076
15291
  */
15077
15292
  function processTick() {
@@ -17958,7 +18173,8 @@ function wordToFloat(word, mode) {
17958
18173
  return mode === 1 ? signed / 128 : signed / 512;
17959
18174
  }
17960
18175
  /**
17961
- * Rounds a scaled coefficient half away from zero, like the DLL's biased truncation.
18176
+ * Adds the DLL's half step bias (away from zero) to a scaled coefficient.
18177
+ * Truncating the result rounds half away from zero, like the DLL's biased truncation.
17962
18178
  * @param value The exact scaled value.
17963
18179
  */
17964
18180
  function biasedRound(value) {
@@ -18487,6 +18703,8 @@ var XGDSPProgram = class {
18487
18703
  var DefaultXGEffect = class {
18488
18704
  /**
18489
18705
  * The currently used processor.
18706
+ * Hidden from the docs as the processor types are internal.
18707
+ * @hidden
18490
18708
  */
18491
18709
  processor;
18492
18710
  outputLeft;
@@ -18506,8 +18724,9 @@ var DefaultXGEffect = class {
18506
18724
  * @param defaultType The default effect type.
18507
18725
  * @param sampleRate The sample rate, in Hertz.
18508
18726
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
18509
- * Attempting to `.process()` more samples than this will result in an error.
18727
+ * Processing more samples than this is not supported and produces undefined output.
18510
18728
  * @param variation True for the variation and insertion blocks.
18729
+ * @hidden
18511
18730
  */
18512
18731
  constructor(effectMap, fallbackProcessor, defaultType, sampleRate, maxBufferSize, variation) {
18513
18732
  let memoryLength = getMemorySize(sampleRate, fallbackProcessor.memorySize);
@@ -18569,11 +18788,17 @@ for (let pan = MIN_PAN$1; pan <= MAX_PAN$1; pan++) {
18569
18788
  var DefaultXGSystemEffect = class extends DefaultXGEffect {
18570
18789
  return = 64;
18571
18790
  pan = 64;
18791
+ /**
18792
+ * Resets this block to default values, including the processor type.
18793
+ */
18572
18794
  reset() {
18573
18795
  super.reset();
18574
18796
  this.return = 64;
18575
18797
  this.pan = 64;
18576
18798
  }
18799
+ /**
18800
+ * Gets a snapshot of this XG effect block.
18801
+ */
18577
18802
  getSnapshot() {
18578
18803
  return {
18579
18804
  ...super.getSnapshot(),
@@ -18581,6 +18806,10 @@ var DefaultXGSystemEffect = class extends DefaultXGEffect {
18581
18806
  pan: this.pan
18582
18807
  };
18583
18808
  }
18809
+ /**
18810
+ * Restores this XG effect block from a snapshot.
18811
+ * @param snapshot The snapshot to restore.
18812
+ */
18584
18813
  applySnapshot(snapshot) {
18585
18814
  super.applySnapshot(snapshot);
18586
18815
  this.return = snapshot.return;
@@ -18721,9 +18950,9 @@ const TIME_OFFSETS = new Int8Array([
18721
18950
  107
18722
18951
  ]);
18723
18952
  /**
18724
- * Damping line feedback coefficient words.
18953
+ * Damping line input gain coefficient words.
18725
18954
  */
18726
- const DAMP_FEEDBACK = new Uint16Array([
18955
+ const DAMP_INPUT = new Uint16Array([
18727
18956
  105,
18728
18957
  105,
18729
18958
  105,
@@ -18982,9 +19211,9 @@ const DAMP_FEEDBACK = new Uint16Array([
18982
19211
  2
18983
19212
  ]);
18984
19213
  /**
18985
- * Damping line filter coefficient words.
19214
+ * Damping line feedback coefficient words, used for both taps of the two-tap feedback filter.
18986
19215
  */
18987
- const DAMP_FILTER = new Uint16Array([
19216
+ const DAMP_FEEDBACK = new Uint16Array([
18988
19217
  1,
18989
19218
  1,
18990
19219
  1,
@@ -19756,7 +19985,7 @@ const LIST_LPF = [
19756
19985
  50
19757
19986
  ];
19758
19987
  /**
19759
- * Diffusion allpass addresses, 4 per line: base, density tap, write, read.
19988
+ * Damped delay line addresses, 4 per line: write, feedback read, left output tap, right output tap.
19760
19989
  */
19761
19990
  const LIST_DIFFUSION = [
19762
19991
  31,
@@ -19777,7 +20006,7 @@ const LIST_DIFFUSION = [
19777
20006
  7
19778
20007
  ];
19779
20008
  /**
19780
- * Damping line coefficients, 3 per line: feedback, filter, filter.
20009
+ * Damping line coefficients, 3 per line: input, read and previous read gains.
19781
20010
  */
19782
20011
  const LIST_TIME = [
19783
20012
  91,
@@ -20059,13 +20288,14 @@ var XGReverb = class extends XGDSPProgram {
20059
20288
  * @param offset The first list index.
20060
20289
  */
20061
20290
  writeDamping(index, list, offset) {
20062
- this.coefficientWords[list[offset]] = DAMP_FEEDBACK[index];
20063
- const filter = index > 202 ? 255 : DAMP_FILTER[index];
20064
- this.coefficientWords[list[offset + 1]] = filter;
20065
- this.coefficientWords[list[offset + 2]] = filter;
20291
+ this.coefficientWords[list[offset]] = DAMP_INPUT[index];
20292
+ const feedback = index > 202 ? 255 : DAMP_FEEDBACK[index];
20293
+ this.coefficientWords[list[offset + 1]] = feedback;
20294
+ this.coefficientWords[list[offset + 2]] = feedback;
20066
20295
  }
20067
20296
  /**
20068
- * Places the diffusion allpasses from the diffusion parameter.
20297
+ * Places the delay line read and output taps from the diffusion parameter.
20298
+ * The diffusion spreads the left and right output taps of each line apart.
20069
20299
  */
20070
20300
  diffusion() {
20071
20301
  const scale = DIFFUSION_SCALE[this.params[1]];
@@ -20221,7 +20451,7 @@ var XGRoomReverb = class extends XGReverb {
20221
20451
  this.writeDamping(extra + base & 255, LIST_TIME, offset);
20222
20452
  }
20223
20453
  /**
20224
- * Places a diffusion allpass from a room dimension.
20454
+ * Places a delay line's length and output taps from a room dimension.
20225
20455
  * @param step The room diffusion step index.
20226
20456
  * @param size The dimension size.
20227
20457
  * @param offset The first diffusion list index.
@@ -20242,7 +20472,7 @@ var XGRoomReverb = class extends XGReverb {
20242
20472
  }
20243
20473
  }
20244
20474
  /**
20245
- * Updates one dimension's damping line and allpass, then pushes them.
20475
+ * Updates one dimension's delay line (damping, length and taps), then pushes it.
20246
20476
  * @param dimension 0 width, 1 height, 2 depth.
20247
20477
  * @param step The room diffusion step index.
20248
20478
  * @param size The dimension size.
@@ -22066,6 +22296,8 @@ const REVERB_MAP = /* @__PURE__ */ new Map([
22066
22296
  /**
22067
22297
  * The default implementation for {@link XGReverbBlock}.
22068
22298
  *
22299
+ * The default type is HALL 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
22300
+ *
22069
22301
  * @group Synthesizer.XG Effects
22070
22302
  * @sealed
22071
22303
  */
@@ -22074,7 +22306,7 @@ var DefaultXGReverb = class extends DefaultXGSystemEffect {
22074
22306
  * Constructs a new default XG reverb processor.
22075
22307
  * @param sampleRate The sample rate, in Hertz.
22076
22308
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
22077
- * Attempting to `.process()` more samples than this will result in an error.
22309
+ * Processing more samples than this is not supported and produces undefined output.
22078
22310
  */
22079
22311
  constructor(sampleRate, maxBufferSize) {
22080
22312
  super(REVERB_MAP, XGNoEffect, 256, sampleRate, maxBufferSize, false);
@@ -22166,7 +22398,7 @@ const LIST_RIGHT_TAPS = [
22166
22398
  25,
22167
22399
  26
22168
22400
  ];
22169
- const LIST_DRY_WET = [
22401
+ const LIST_DRY_WET$4 = [
22170
22402
  95,
22171
22403
  96,
22172
22404
  97,
@@ -22407,7 +22639,7 @@ var XGChorusBase = class extends XGDSPProgram {
22407
22639
  const w = this.coefficientWords;
22408
22640
  w[95] = w[104] = dry & 1023;
22409
22641
  w[96] = w[105] = w[97] = w[106] = wet & 1023;
22410
- this.pushCoefficients(LIST_DRY_WET);
22642
+ this.pushCoefficients(LIST_DRY_WET$4);
22411
22643
  break;
22412
22644
  }
22413
22645
  }
@@ -23679,6 +23911,8 @@ const CHORUS_MAP = /* @__PURE__ */ new Map([
23679
23911
  /**
23680
23912
  * The default implementation for {@link XGChorusBlock}.
23681
23913
  *
23914
+ * The default type is CHORUS 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
23915
+ *
23682
23916
  * @group Synthesizer.XG Effects
23683
23917
  * @sealed
23684
23918
  */
@@ -23688,11 +23922,14 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
23688
23922
  * Constructs a new default XG chorus processor.
23689
23923
  * @param sampleRate The sample rate, in Hertz.
23690
23924
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
23691
- * Attempting to `.process()` more samples than this will result in an error.
23925
+ * Processing more samples than this is not supported and produces undefined output.
23692
23926
  */
23693
23927
  constructor(sampleRate, maxBufferSize) {
23694
23928
  super(CHORUS_MAP, XGNoEffect, 16640, sampleRate, maxBufferSize, false);
23695
23929
  }
23930
+ /**
23931
+ * Resets this block to default values, including the processor type.
23932
+ */
23696
23933
  reset() {
23697
23934
  super.reset();
23698
23935
  this.sendToReverb = 0;
@@ -23707,12 +23944,19 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
23707
23944
  reverbRight[i] += this.outputRight[i] * gain;
23708
23945
  }
23709
23946
  }
23947
+ /**
23948
+ * Gets a snapshot of this XG effect block.
23949
+ */
23710
23950
  getSnapshot() {
23711
23951
  return {
23712
23952
  ...super.getSnapshot(),
23713
23953
  sendToReverb: this.sendToReverb
23714
23954
  };
23715
23955
  }
23956
+ /**
23957
+ * Restores this XG effect block from a snapshot.
23958
+ * @param snapshot The snapshot to restore.
23959
+ */
23716
23960
  applySnapshot(snapshot) {
23717
23961
  super.applySnapshot(snapshot);
23718
23962
  this.sendToReverb = snapshot.sendToReverb;
@@ -24141,6 +24385,16 @@ const PARAMETER_RANGES$15 = {
24141
24385
  13: [4, 124],
24142
24386
  14: [0, 1]
24143
24387
  };
24388
+ const LIST_INPUT_COEFFICIENT = [
24389
+ 170,
24390
+ 180,
24391
+ 185
24392
+ ];
24393
+ const LIST_INPUT_ADDRESS = [
24394
+ 59,
24395
+ 45,
24396
+ 46
24397
+ ];
24144
24398
  /**
24145
24399
  * TREMOLO (`46 00`): amplitude and pitch modulation.
24146
24400
  */
@@ -24328,16 +24582,8 @@ var XGTremolo = class extends XGTremoloBase {
24328
24582
  a[45] = 1025;
24329
24583
  a[46] = 1026;
24330
24584
  }
24331
- this.pushCoefficients([
24332
- 170,
24333
- 180,
24334
- 185
24335
- ]);
24336
- this.pushAddresses([
24337
- 59,
24338
- 45,
24339
- 46
24340
- ]);
24585
+ this.pushCoefficients(LIST_INPUT_COEFFICIENT);
24586
+ this.pushAddresses(LIST_INPUT_ADDRESS);
24341
24587
  this.convert(5);
24342
24588
  this.convert(7);
24343
24589
  break;
@@ -24353,6 +24599,7 @@ const PARAMETER_RANGES$14 = {
24353
24599
  7: [28, 58],
24354
24600
  8: [52, 76]
24355
24601
  };
24602
+ const LIST_OUTPUT$3 = [180, 185];
24356
24603
  /**
24357
24604
  * LFO phases (left, right) per pan direction: L<->R, L->R, L<-R, L turn, R turn.
24358
24605
  */
@@ -24555,7 +24802,7 @@ var XGAutoPan = class extends XGTremoloBase {
24555
24802
  break;
24556
24803
  case 9:
24557
24804
  w[185] = w[180] = this.outputWord(this.insertion ? 362 : 724);
24558
- this.pushCoefficients([180, 185]);
24805
+ this.pushCoefficients(LIST_OUTPUT$3);
24559
24806
  }
24560
24807
  }
24561
24808
  };
@@ -24779,6 +25026,11 @@ const PARAMETER_RANGES$13 = {
24779
25026
  8: [52, 76],
24780
25027
  10: [6, 10]
24781
25028
  };
25029
+ const LIST_DRY_LEFT = [162];
25030
+ const LIST_MIX_RIGHT$1 = [166, 167];
25031
+ const LIST_WET_SECOND = [164];
25032
+ const LIST_WET_FOURTH = [165];
25033
+ const LIST_INPUT_MIX = [132, 133];
24782
25034
  /**
24783
25035
  * The converter index of the input mix level, which depends on the connection.
24784
25036
  */
@@ -25001,14 +25253,14 @@ var XGPhaser1 = class extends XGPhaserBase {
25001
25253
  case 9:
25002
25254
  w[166] = w[162] = PAN_LEFT[mix];
25003
25255
  w[167] = PAN_RIGHT[mix];
25004
- this.pushCoefficients([162]);
25005
- this.pushCoefficients([166, 167]);
25256
+ this.pushCoefficients(LIST_DRY_LEFT);
25257
+ this.pushCoefficients(LIST_MIX_RIGHT$1);
25006
25258
  if (p[10] < 9) {
25007
25259
  w[164] = PAN_RIGHT[mix];
25008
- this.pushCoefficients([164]);
25260
+ this.pushCoefficients(LIST_WET_SECOND);
25009
25261
  } else {
25010
25262
  w[165] = PAN_RIGHT[mix];
25011
- this.pushCoefficients([165]);
25263
+ this.pushCoefficients(LIST_WET_FOURTH);
25012
25264
  }
25013
25265
  break;
25014
25266
  case 10: {
@@ -25032,7 +25284,7 @@ var XGPhaser1 = class extends XGPhaserBase {
25032
25284
  }
25033
25285
  case INPUT_MIX:
25034
25286
  w[132] = w[133] = this.insertion ? 256 : 512;
25035
- this.pushCoefficients([132, 133]);
25287
+ this.pushCoefficients(LIST_INPUT_MIX);
25036
25288
  }
25037
25289
  }
25038
25290
  };
@@ -25046,6 +25298,8 @@ const PARAMETER_RANGES$12 = {
25046
25298
  10: [3, 5],
25047
25299
  12: [4, 124]
25048
25300
  };
25301
+ const LIST_MIX_LEFT = [162, 163];
25302
+ const LIST_MIX_RIGHT = [166, 167];
25049
25303
  /**
25050
25304
  * PHASER 2 (`48 08`): a stereo phaser with 3 to 5 stages and an LFO phase difference between the sides.
25051
25305
  */
@@ -25218,8 +25472,8 @@ var XGPhaser2 = class extends XGPhaserBase {
25218
25472
  const mix = this.mixIndex();
25219
25473
  w[166] = w[162] = PAN_LEFT[mix];
25220
25474
  w[167] = w[163] = PAN_RIGHT[mix];
25221
- this.pushCoefficients([162, 163]);
25222
- this.pushCoefficients([166, 167]);
25475
+ this.pushCoefficients(LIST_MIX_LEFT);
25476
+ this.pushCoefficients(LIST_MIX_RIGHT);
25223
25477
  break;
25224
25478
  }
25225
25479
  case 10: {
@@ -25616,6 +25870,8 @@ const LIST_HIGH_LEFT = [
25616
25870
  131,
25617
25871
  133
25618
25872
  ];
25873
+ const LIST_OUTPUT$2 = [185, 186];
25874
+ const LIST_DRY_WET$3 = [171, 173];
25619
25875
  /**
25620
25876
  * AUTO WAH (`4E 00`): a resonant filter per side, swept by an exponential LFO.
25621
25877
  */
@@ -25869,8 +26125,8 @@ var XGAutoWah = class extends XGFilterVariationProgram {
25869
26125
  const mix = this.mixIndex();
25870
26126
  w[171] = w[185] = PAN_LEFT[mix];
25871
26127
  w[173] = w[186] = PAN_RIGHT[mix];
25872
- this.pushCoefficients([185, 186]);
25873
- this.pushCoefficients([171, 173]);
26128
+ this.pushCoefficients(LIST_OUTPUT$2);
26129
+ this.pushCoefficients(LIST_DRY_WET$3);
25874
26130
  break;
25875
26131
  }
25876
26132
  }
@@ -25897,16 +26153,17 @@ const UNWRITTEN_CATEGORY_2 = [
25897
26153
  128,
25898
26154
  174
25899
26155
  ];
26156
+ const LIST_MONO_INPUT$1 = [109, 110];
25900
26157
  /**
25901
26158
  * The base for the S-YXG50 category 2 variation programs (3-band EQ, distortion, early reflections).
25902
- * They process a mono sum of the input through a two-section low shelf, keeping scaled copies
26159
+ * They process a mono sum of the input through two first-order input sections, keeping scaled copies
25903
26160
  * of the dry input that the routines mix back in.
25904
26161
  * Any of 0x76, 0x7a, 0x7d and 0x80 sets 0x76 to their product.
25905
26162
  */
25906
26163
  var XGMonoVariationProgram = class extends XGVariationProgram {
25907
26164
  unwrittenCoefficients = UNWRITTEN_CATEGORY_2;
25908
26165
  /**
25909
- * The mono input after the low shelf.
26166
+ * The mono input after the input sections.
25910
26167
  */
25911
26168
  mono;
25912
26169
  /**
@@ -26027,7 +26284,7 @@ var XGMonoVariationProgram = class extends XGVariationProgram {
26027
26284
  monoInputLevel() {
26028
26285
  const w = this.coefficientWords;
26029
26286
  w[109] = w[110] = this.insertion ? 256 : 512;
26030
- this.pushCoefficients([109, 110]);
26287
+ this.pushCoefficients(LIST_MONO_INPUT$1);
26031
26288
  }
26032
26289
  };
26033
26290
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/equalizer/three_band_eq.ts
@@ -26387,6 +26644,12 @@ const LIST_MID = [
26387
26644
  166,
26388
26645
  165
26389
26646
  ];
26647
+ const LIST_DRY_WET$2 = [
26648
+ 173,
26649
+ 176,
26650
+ 177,
26651
+ 179
26652
+ ];
26390
26653
  /**
26391
26654
  * The clipping range (the original works on a 16-bit scale, which is 4 times the full scale).
26392
26655
  */
@@ -26480,12 +26743,7 @@ var XGDistortionBase = class extends XGMonoVariationProgram {
26480
26743
  if (this.insertion) wet >>= 1;
26481
26744
  w[173] = w[177] = PAN_LEFT[mix];
26482
26745
  w[176] = w[179] = VARIATION_GAIN * wet >> 6;
26483
- this.pushCoefficients([
26484
- 173,
26485
- 176,
26486
- 177,
26487
- 179
26488
- ]);
26746
+ this.pushCoefficients(LIST_DRY_WET$2);
26489
26747
  }
26490
26748
  };
26491
26749
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/distortion/distortion.ts
@@ -28425,6 +28683,12 @@ const LIST_HIGH_DAMP = [
28425
28683
  119,
28426
28684
  121
28427
28685
  ];
28686
+ const LIST_DRY_WET$1 = [
28687
+ 173,
28688
+ 175,
28689
+ 177,
28690
+ 178
28691
+ ];
28428
28692
  /**
28429
28693
  * The converter index of the mono input level, which depends on the connection.
28430
28694
  */
@@ -28544,12 +28808,7 @@ var XGEarlyReflectionsBase = class extends XGMonoVariationProgram {
28544
28808
  const mix = this.mixIndex();
28545
28809
  w[173] = w[177] = MIX_DRY[mix] & 1023;
28546
28810
  w[175] = w[178] = MIX_WET[mix] & 1023;
28547
- this.pushCoefficients([
28548
- 173,
28549
- 175,
28550
- 177,
28551
- 178
28552
- ]);
28811
+ this.pushCoefficients(LIST_DRY_WET$1);
28553
28812
  break;
28554
28813
  }
28555
28814
  case 10:
@@ -29308,7 +29567,7 @@ const DELAY_LEVEL = new Float32Array([
29308
29567
  ]);
29309
29568
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/delay/xg_delay.ts
29310
29569
  /**
29311
- * The right line input position used by the two-line delays (in the upper half of the memory).
29570
+ * The right line input position used by the two-line delays (past the longest left line).
29312
29571
  */
29313
29572
  const RIGHT_LINE_START = 16384;
29314
29573
  const LIST_DAMP_A = [159, 158];
@@ -29373,7 +29632,7 @@ var XGDelayBase = class extends XGStereoVariationProgram {
29373
29632
  this.pushAddresses([tap]);
29374
29633
  }
29375
29634
  /**
29376
- * Designs a feedback damping filter (a one-pole low-pass) from the high damp parameter.
29635
+ * Designs a feedback damping filter (a two-tap low-pass) from the high damp parameter.
29377
29636
  * @param damp The high damp value (1-10).
29378
29637
  * @param list The coefficient indices for the input and feedback coefficients.
29379
29638
  */
@@ -29402,6 +29661,14 @@ const PARAMETER_RANGES$4 = {
29402
29661
  14: [28, 58],
29403
29662
  15: [52, 76]
29404
29663
  };
29664
+ const LIST_MIX$3 = [
29665
+ 178,
29666
+ 181,
29667
+ 184,
29668
+ 185,
29669
+ 187,
29670
+ 182
29671
+ ];
29405
29672
  /**
29406
29673
  * DELAY L,C,R (`05 00`): left, right and center taps with a separate feedback tap.
29407
29674
  */
@@ -29575,14 +29842,7 @@ var XGDelayLCR = class extends XGDelayBase {
29575
29842
  if (this.insertion) wet >>= 1;
29576
29843
  w[185] = w[181] = wet & 1023;
29577
29844
  w[187] = w[182] = Math.trunc(wet * DELAY_LEVEL[p[5]]) & 1023;
29578
- this.pushCoefficients([
29579
- 178,
29580
- 181,
29581
- 184,
29582
- 185,
29583
- 187,
29584
- 182
29585
- ]);
29845
+ this.pushCoefficients(LIST_MIX$3);
29586
29846
  break;
29587
29847
  }
29588
29848
  case 6:
@@ -29611,6 +29871,12 @@ const PARAMETER_RANGES$3 = {
29611
29871
  14: [28, 58],
29612
29872
  15: [52, 76]
29613
29873
  };
29874
+ const LIST_MIX$2 = [
29875
+ 178,
29876
+ 181,
29877
+ 184,
29878
+ 187
29879
+ ];
29614
29880
  /**
29615
29881
  * DELAY L,R (`06 00`): left and right taps with two feedback taps.
29616
29882
  */
@@ -29786,12 +30052,7 @@ var XGDelayLR = class extends XGDelayBase {
29786
30052
  let wet = MIX_WET[mix];
29787
30053
  if (this.insertion) wet >>= 1;
29788
30054
  w[187] = w[181] = wet & 1023;
29789
- this.pushCoefficients([
29790
- 178,
29791
- 181,
29792
- 184,
29793
- 187
29794
- ]);
30055
+ this.pushCoefficients(LIST_MIX$2);
29795
30056
  break;
29796
30057
  }
29797
30058
  case 12:
@@ -29818,6 +30079,14 @@ const PARAMETER_RANGES$2 = {
29818
30079
  14: [28, 58],
29819
30080
  15: [52, 76]
29820
30081
  };
30082
+ const LIST_MIX$1 = [
30083
+ 178,
30084
+ 180,
30085
+ 181,
30086
+ 184,
30087
+ 185,
30088
+ 187
30089
+ ];
29821
30090
  /**
29822
30091
  * ECHO (`07 00`): two independent delay lines with feedback and a second tap each.
29823
30092
  */
@@ -29999,14 +30268,7 @@ var XGEcho = class extends XGDelayBase {
29999
30268
  const wet = MIX_WET[mix];
30000
30269
  w[185] = w[180] = wet & 1023;
30001
30270
  w[187] = w[181] = Math.trunc(wet * DELAY_LEVEL[p[7]]) & 1023;
30002
- this.pushCoefficients([
30003
- 178,
30004
- 180,
30005
- 181,
30006
- 184,
30007
- 185,
30008
- 187
30009
- ]);
30271
+ this.pushCoefficients(LIST_MIX$1);
30010
30272
  break;
30011
30273
  }
30012
30274
  case 12:
@@ -30031,6 +30293,12 @@ const PARAMETER_RANGES$1 = {
30031
30293
  14: [28, 58],
30032
30294
  15: [52, 76]
30033
30295
  };
30296
+ const LIST_MIX = [
30297
+ 178,
30298
+ 180,
30299
+ 184,
30300
+ 185
30301
+ ];
30034
30302
  /**
30035
30303
  * CROSS DELAY (`08 00`): two delay lines feeding back into each other.
30036
30304
  */
@@ -30221,12 +30489,7 @@ var XGCrossDelay = class extends XGDelayBase {
30221
30489
  let wet = MIX_WET[mix];
30222
30490
  if (p[3] < 2) wet >>= 1;
30223
30491
  w[185] = w[180] = wet & 1023;
30224
- this.pushCoefficients([
30225
- 178,
30226
- 180,
30227
- 184,
30228
- 185
30229
- ]);
30492
+ this.pushCoefficients(LIST_MIX);
30230
30493
  break;
30231
30494
  }
30232
30495
  case 12:
@@ -30421,6 +30684,10 @@ const LIST_DENSITY_ADDRESS = [
30421
30684
  54,
30422
30685
  51
30423
30686
  ];
30687
+ const LIST_DRY = [185];
30688
+ const LIST_WET = [187];
30689
+ const LIST_DRY_WET = [174, 172];
30690
+ const LIST_MONO_INPUT = [109, 110];
30424
30691
  /**
30425
30692
  * The converter index of the mono input level, which depends on the connection.
30426
30693
  */
@@ -30543,9 +30810,9 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
30543
30810
  const mix = this.mixIndex();
30544
30811
  w[172] = w[185] = MIX_DRY[mix] & 1023;
30545
30812
  w[174] = w[187] = MIX_WET[mix] & 1023;
30546
- this.pushCoefficients([185]);
30547
- this.pushCoefficients([187]);
30548
- this.pushCoefficients([174, 172]);
30813
+ this.pushCoefficients(LIST_DRY);
30814
+ this.pushCoefficients(LIST_WET);
30815
+ this.pushCoefficients(LIST_DRY_WET);
30549
30816
  break;
30550
30817
  }
30551
30818
  case 10:
@@ -30554,7 +30821,7 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
30554
30821
  break;
30555
30822
  case MONO_INPUT$1:
30556
30823
  w[109] = w[110] = this.insertion ? 181 : 362;
30557
- this.pushCoefficients([109, 110]);
30824
+ this.pushCoefficients(LIST_MONO_INPUT);
30558
30825
  }
30559
30826
  }
30560
30827
  };
@@ -31013,6 +31280,9 @@ const VARIATION_MAP = /* @__PURE__ */ new Map([
31013
31280
  /**
31014
31281
  * The default implementation for {@link XGVariationBlock}.
31015
31282
  *
31283
+ * The default type is DELAY L,C,R in the insertion connection.
31284
+ * Unknown types use the type with LSB 0, or THRU if that is missing too.
31285
+ *
31016
31286
  * @group Synthesizer.XG Effects
31017
31287
  * @sealed
31018
31288
  */
@@ -31025,11 +31295,14 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31025
31295
  * Constructs a new default XG variation processor.
31026
31296
  * @param sampleRate The sample rate, in Hertz.
31027
31297
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
31028
- * Attempting to `.process()` more samples than this will result in an error.
31298
+ * Processing more samples than this is not supported and produces undefined output.
31029
31299
  */
31030
31300
  constructor(sampleRate, maxBufferSize) {
31031
31301
  super(VARIATION_MAP, XGThru, 1280, sampleRate, maxBufferSize, true);
31032
31302
  }
31303
+ /**
31304
+ * Resets this block to default values, including the processor type.
31305
+ */
31033
31306
  reset() {
31034
31307
  super.reset();
31035
31308
  this.insertionMode = true;
@@ -31063,6 +31336,9 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31063
31336
  inputRight[i] = outputRight[i];
31064
31337
  }
31065
31338
  }
31339
+ /**
31340
+ * Gets a snapshot of this XG effect block.
31341
+ */
31066
31342
  getSnapshot() {
31067
31343
  return {
31068
31344
  ...super.getSnapshot(),
@@ -31072,6 +31348,10 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31072
31348
  sendToChorus: this.sendToChorus
31073
31349
  };
31074
31350
  }
31351
+ /**
31352
+ * Restores this XG effect block from a snapshot.
31353
+ * @param snapshot The snapshot to restore.
31354
+ */
31075
31355
  applySnapshot(snapshot) {
31076
31356
  super.applySnapshot(snapshot);
31077
31357
  this.insertionMode = snapshot.insertionMode;
@@ -31124,6 +31404,10 @@ const INSERTION_MAP = /* @__PURE__ */ new Map([
31124
31404
  /**
31125
31405
  * The default implementation for {@link XGInsertionBlock}.
31126
31406
  *
31407
+ * The default type is DISTORTION. The EFFECT 2 types run on the same processors as the variation block types.
31408
+ * The early reflection types and PHASER 2 are variation only, so PHASER 2 falls back to PHASER 1 here.
31409
+ * Unknown types use the type with LSB 0, or THRU if that is missing too.
31410
+ *
31127
31411
  * @group Synthesizer.XG Effects
31128
31412
  * @sealed
31129
31413
  */
@@ -31133,11 +31417,14 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
31133
31417
  * Constructs a new default XG insertion processor.
31134
31418
  * @param sampleRate The sample rate, in Hertz.
31135
31419
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
31136
- * Attempting to `.process()` more samples than this will result in an error.
31420
+ * Processing more samples than this is not supported and produces undefined output.
31137
31421
  */
31138
31422
  constructor(sampleRate, maxBufferSize) {
31139
31423
  super(INSERTION_MAP, XGThru, 18688, sampleRate, maxBufferSize, true);
31140
31424
  }
31425
+ /**
31426
+ * Resets this block to default values, including the processor type.
31427
+ */
31141
31428
  reset() {
31142
31429
  super.reset();
31143
31430
  this.partNumber = 127;
@@ -31150,12 +31437,19 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
31150
31437
  inputRight[i] = outputRight[i];
31151
31438
  }
31152
31439
  }
31440
+ /**
31441
+ * Gets a snapshot of this XG effect block.
31442
+ */
31153
31443
  getSnapshot() {
31154
31444
  return {
31155
31445
  ...super.getSnapshot(),
31156
31446
  partNumber: this.partNumber
31157
31447
  };
31158
31448
  }
31449
+ /**
31450
+ * Restores this XG effect block from a snapshot.
31451
+ * @param snapshot The snapshot to restore.
31452
+ */
31159
31453
  applySnapshot(snapshot) {
31160
31454
  super.applySnapshot(snapshot);
31161
31455
  this.partNumber = snapshot.partNumber;
@@ -31181,9 +31475,28 @@ const InterpolationTypes = {
31181
31475
  */
31182
31476
  nearestNeighbor: 1,
31183
31477
  /**
31184
- * Slowest, high quality with little artifacting. (Default)
31478
+ * Cubic Hermite interpolation. Good quality with little artifacting. (Default)
31479
+ *
31480
+ * Recommended for most use-cases.
31481
+ */
31482
+ hermite: 2,
31483
+ /**
31484
+ * 32-point windowed sinc interpolation.
31485
+ *
31486
+ * Very high quality with almost no aliasing or harmonic distortion,
31487
+ * usable in real time, but at a significantly higher CPU cost.
31488
+ */
31489
+ sinc32: 3,
31490
+ /**
31491
+ * 128-point windowed sinc interpolation with antialiasing.
31492
+ *
31493
+ * The best quality, but extremely high cpu cost.
31494
+ *
31495
+ * > **Warning**
31496
+ * >
31497
+ * > This is very slow (especially for pitched up samples) and is meant for offline rendering, not real time playback.
31185
31498
  */
31186
- hermite: 2
31499
+ sinc128: 4
31187
31500
  };
31188
31501
  /**
31189
31502
  * The special EFX control sources of a {@link GSInsertionBlock} (EFX CONTROL SOURCE 1/2).
@@ -31612,7 +31925,7 @@ var DefaultGSReverb = class {
31612
31925
  * Constructs a new default GS reverb processor.
31613
31926
  * @param sampleRate The sample rate, in Hertz.
31614
31927
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
31615
- * Attempting to `.process()` more samples than this will result in an error.
31928
+ * Processing more samples than this is not supported and produces undefined output.
31616
31929
  */
31617
31930
  constructor(sampleRate, maxBufferSize) {
31618
31931
  this.sampleRate = sampleRate;
@@ -31735,12 +32048,12 @@ var DefaultGSReverb = class {
31735
32048
  this.updateLowpass();
31736
32049
  }
31737
32050
  /**
31738
- *
31739
- * @param input 0-based
31740
- * @param outputLeft startIndex-based
31741
- * @param outputRight startIndex-based
31742
- * @param startIndex
31743
- * @param sampleCount
32051
+ * Process the effect and **adds** it to the output.
32052
+ * @param input The input buffer to process. It always starts at index 0.
32053
+ * @param outputLeft The left output buffer.
32054
+ * @param outputRight The right output buffer.
32055
+ * @param startIndex The index to start mixing at into the output buffers.
32056
+ * @param sampleCount The amount of samples to mix.
31744
32057
  */
31745
32058
  process(input, outputLeft, outputRight, startIndex, sampleCount) {
31746
32059
  switch (this._character) {
@@ -31879,7 +32192,7 @@ var DefaultGSChorus = class {
31879
32192
  * Constructs a new default GS chorus processor.
31880
32193
  * @param sampleRate The sample rate, in Hertz.
31881
32194
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
31882
- * Attempting to `.process()` more samples than this will result in an error.
32195
+ * Processing more samples than this is not supported and produces undefined output.
31883
32196
  */
31884
32197
  constructor(sampleRate, maxBufferSize) {
31885
32198
  this.sampleRate = sampleRate;
@@ -32138,7 +32451,7 @@ var DefaultGSDelay = class {
32138
32451
  * Constructs a new default GS delay processor.
32139
32452
  * @param sampleRate The sample rate, in Hertz.
32140
32453
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
32141
- * Attempting to `.process()` more samples than this will result in an error.
32454
+ * Processing more samples than this is not supported and produces undefined output.
32142
32455
  */
32143
32456
  constructor(sampleRate, maxBufferSize) {
32144
32457
  this.sampleRate = sampleRate;
@@ -32330,6 +32643,148 @@ var DefaultGSDelay = class {
32330
32643
  this.gainRight = this._levelRight / 127;
32331
32644
  }
32332
32645
  };
32646
+ // src/synthesizer/audio_engine/effects/gs/implementation/equalizer.ts
32647
+ const LOW_FREQUENCIES = [225, 425];
32648
+ const HIGH_FREQUENCIES = [3e3, 6e3];
32649
+ /**
32650
+ * The default GS Equalizer implementation for {@link SpessaSynthProcessor}.
32651
+ *
32652
+ * @group Synthesizer.GS Effects
32653
+ */
32654
+ var DefaultGSEqualizer = class {
32655
+ sampleRate;
32656
+ lowB0 = 1;
32657
+ lowB1 = 0;
32658
+ lowA1 = 0;
32659
+ highB0 = 1;
32660
+ highB1 = 0;
32661
+ highA1 = 0;
32662
+ lowZL = 0;
32663
+ lowZR = 0;
32664
+ highZL = 0;
32665
+ highZR = 0;
32666
+ /**
32667
+ * Constructs a new default GS equalizer processor.
32668
+ * @param sampleRate The sample rate, in Hertz.
32669
+ */
32670
+ constructor(sampleRate) {
32671
+ this.sampleRate = sampleRate;
32672
+ this.updateLow();
32673
+ this.updateHigh();
32674
+ }
32675
+ _lowFrequency = 0;
32676
+ get lowFrequency() {
32677
+ return this._lowFrequency;
32678
+ }
32679
+ set lowFrequency(value) {
32680
+ this._lowFrequency = value;
32681
+ this.updateLow();
32682
+ }
32683
+ _lowGain = 64;
32684
+ get lowGain() {
32685
+ return this._lowGain;
32686
+ }
32687
+ set lowGain(value) {
32688
+ this._lowGain = value;
32689
+ this.updateLow();
32690
+ }
32691
+ _highFrequency = 0;
32692
+ get highFrequency() {
32693
+ return this._highFrequency;
32694
+ }
32695
+ set highFrequency(value) {
32696
+ this._highFrequency = value;
32697
+ this.updateHigh();
32698
+ }
32699
+ _highGain = 64;
32700
+ get highGain() {
32701
+ return this._highGain;
32702
+ }
32703
+ set highGain(value) {
32704
+ this._highGain = value;
32705
+ this.updateHigh();
32706
+ }
32707
+ process(inputLeft, inputRight, outputLeft, outputRight, startIndex, sampleCount) {
32708
+ const { lowB0, lowB1, lowA1, highB0, highB1, highA1 } = this;
32709
+ let { lowZL, lowZR, highZL, highZR } = this;
32710
+ for (let i = 0; i < sampleCount; i++) {
32711
+ const o = startIndex + i;
32712
+ let x = inputLeft[i];
32713
+ let y = lowB0 * x + lowZL;
32714
+ lowZL = lowB1 * x - lowA1 * y;
32715
+ x = y;
32716
+ y = highB0 * x + highZL;
32717
+ highZL = highB1 * x - highA1 * y;
32718
+ outputLeft[o] += y;
32719
+ x = inputRight[i];
32720
+ y = lowB0 * x + lowZR;
32721
+ lowZR = lowB1 * x - lowA1 * y;
32722
+ x = y;
32723
+ y = highB0 * x + highZR;
32724
+ highZR = highB1 * x - highA1 * y;
32725
+ outputRight[o] += y;
32726
+ }
32727
+ this.lowZL = lowZL;
32728
+ this.lowZR = lowZR;
32729
+ this.highZL = highZL;
32730
+ this.highZR = highZR;
32731
+ }
32732
+ getSnapshot() {
32733
+ return {
32734
+ lowFrequency: this._lowFrequency,
32735
+ lowGain: this._lowGain,
32736
+ highFrequency: this._highFrequency,
32737
+ highGain: this._highGain
32738
+ };
32739
+ }
32740
+ applySnapshot(snapshot) {
32741
+ this.lowFrequency = snapshot.lowFrequency;
32742
+ this.lowGain = snapshot.lowGain;
32743
+ this.highFrequency = snapshot.highFrequency;
32744
+ this.highGain = snapshot.highGain;
32745
+ }
32746
+ getBand(frequency, gain) {
32747
+ const fc = Math.min(frequency, this.sampleRate * .45);
32748
+ const dB = Math.max(-12, Math.min(12, gain - 64));
32749
+ return {
32750
+ k: Math.tan(Math.PI * fc / this.sampleRate),
32751
+ g: 10 ** (Math.abs(dB) / 20),
32752
+ cut: dB < 0
32753
+ };
32754
+ }
32755
+ updateLow() {
32756
+ const { k, g, cut } = this.getBand(LOW_FREQUENCIES[this._lowFrequency & 1], this._lowGain);
32757
+ let b0 = 1 + k;
32758
+ let b1 = k - 1;
32759
+ let a0 = 1 + k / g;
32760
+ let a1 = k / g - 1;
32761
+ if (cut) [b0, b1, a0, a1] = [
32762
+ a0,
32763
+ a1,
32764
+ b0,
32765
+ b1
32766
+ ];
32767
+ this.lowB0 = b0 / a0;
32768
+ this.lowB1 = b1 / a0;
32769
+ this.lowA1 = a1 / a0;
32770
+ }
32771
+ updateHigh() {
32772
+ const { k, g, cut } = this.getBand(HIGH_FREQUENCIES[this._highFrequency & 1], this._highGain);
32773
+ let b0 = g * (1 + k);
32774
+ let b1 = g * (k - 1);
32775
+ let a0 = 1 + g * k;
32776
+ let a1 = g * k - 1;
32777
+ if (cut) [b0, b1, a0, a1] = [
32778
+ a0,
32779
+ a1,
32780
+ b0,
32781
+ b1
32782
+ ];
32783
+ this.highB0 = b0 / a0;
32784
+ this.highB1 = b1 / a0;
32785
+ this.highA1 = a1 / a0;
32786
+ }
32787
+ };
32333
32788
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/gs_effect.ts
32334
32789
  /**
32335
32790
  * The number of EFX parameters (1-20 in the manuals, 0-19 here).
@@ -32838,6 +33293,16 @@ function prewarp(frequency, sampleRate) {
32838
33293
  return Math.tan(Math.PI * Math.min(frequency, sampleRate * .49) / sampleRate);
32839
33294
  }
32840
33295
  /**
33296
+ * The corner of the hardware's one-pole low pass `(1 - B) x + B y[n-1]` at 32 kHz (-3 dB, the inverse of
33297
+ * `FirstOrderFilter.setDamp`).
33298
+ * @param pole B.
33299
+ * @internal
33300
+ */
33301
+ function dampCorner(pole) {
33302
+ const k = (1 + pole * pole) / (2 * pole);
33303
+ return Math.acos(2 - k) * 32e3 / (2 * Math.PI);
33304
+ }
33305
+ /**
32841
33306
  * A first order section, `y = b0 x + b1 x[n-1] + a1 y[n-1]`, designed with the bilinear transform.
32842
33307
  * The tone filters, the shelves and the DC blockers of the Sound Canvas are all this section.
32843
33308
  * @internal
@@ -39632,7 +40097,7 @@ var GSEqualizer = class extends GSEffect {
39632
40097
  * @param x The argument.
39633
40098
  * @internal
39634
40099
  */
39635
- function besselI0(x) {
40100
+ function besselI0$1(x) {
39636
40101
  let sum = 1;
39637
40102
  let term = 1;
39638
40103
  for (let k = 1; k < 32; k++) {
@@ -39648,7 +40113,7 @@ function besselI0(x) {
39648
40113
  * @internal
39649
40114
  */
39650
40115
  function kaiser(t, beta) {
39651
- return besselI0(beta * Math.sqrt(1 - t * t)) / besselI0(beta);
40116
+ return besselI0$1(beta * Math.sqrt(1 - t * t)) / besselI0$1(beta);
39652
40117
  }
39653
40118
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/blocks/delay_line.ts
39654
40119
  /**
@@ -42963,7 +43428,7 @@ function cutoff(value) {
42963
43428
  */
42964
43429
  const STEP_MS = 1 / 32;
42965
43430
  /**
42966
- * The longest swing of `modulationDepth` (depth 127), in ms.
43431
+ * A bound on the swing of `modulationDepth` (12.4 ms at depth 127), in ms, for sizing the lines.
42967
43432
  * @internal
42968
43433
  */
42969
43434
  const SWING_MS = 12.5;
@@ -43526,7 +43991,8 @@ var Dynamics = class {
43526
43991
  };
43527
43992
  /**
43528
43993
  * The compressor: 1 ms lookahead, the gain recovers linearly, the output has a makeup gain (the "Attack" parameter,
43529
- * which sets how far below the fixed output ceiling the knee sits) and a two-sample average.
43994
+ * which sets how far below the fixed output ceiling the knee sits, the manuals call it Sustain, see `GSCompressor`)
43995
+ * and a two-sample average.
43530
43996
  * @internal
43531
43997
  */
43532
43998
  var Compressor = class extends Dynamics {
@@ -43670,6 +44136,10 @@ var GSDynamicsEffect = class extends GSEffect {
43670
44136
  * Compressor (`01 30`).
43671
44137
  * Parameters: 1 sustain (how fast the gain comes back), 2 attack (the makeup gain: the output ceiling stays, the
43672
44138
  * knee moves), 3 post gain, then the EQ, pan and level.
44139
+ *
44140
+ * The SC-VA and SC-8850 manuals list parameter 1 as Attack and 2 as Sustain, the other way round from the names
44141
+ * here. Only the names differ: parameter 1 still sets how fast the gain recovers (0.09 to 2.25 per second) and
44142
+ * parameter 2 the makeup gain, like in Sound Canvas VA. The multis' compressors and Lo-Fi 1 use the same names.
43673
44143
  * @internal
43674
44144
  */
43675
44145
  var GSCompressor = class extends GSDynamicsEffect {
@@ -46868,7 +47338,7 @@ const LINE_LENGTHS = [
46868
47338
  ];
46869
47339
  /**
46870
47340
  * How many host samples later the whole forward path runs, and how much earlier the outputs read, so that even the
46871
- * shortest pre delay is read band-limited (the interpolator needs 7 newer samples). The outputs read 141 32 kHz
47341
+ * shortest pre delay is read band-limited (the interpolator needs 7 newer samples). The outputs read 140 32 kHz
46872
47342
  * samples or more, so they have room.
46873
47343
  */
46874
47344
  const LOOKAHEAD$1 = 8;
@@ -46881,15 +47351,6 @@ const ALLPASS$1 = .5;
46881
47351
  */
46882
47352
  const PRE_GAIN$1 = (127 / 128) ** 2;
46883
47353
  /**
46884
- * The corner of the hardware's one-pole low pass `(1 - B) x + B y[n-1]` at 32 kHz (-3 dB, the inverse of
46885
- * `FirstOrderFilter.setDamp`).
46886
- * @param pole B.
46887
- */
46888
- function dampCorner(pole) {
46889
- const k = (1 + pole * pole) / (2 * pole);
46890
- return Math.acos(2 - k) * 32e3 / (2 * Math.PI);
46891
- }
46892
- /**
46893
47354
  * The Reverb: a mono input through a DC blocker and an input low pass into a pre delay, a diffuser of three
46894
47355
  * Schroeder allpasses and a delay, then two independent tanks (allpass, delay, allpass, delay, a damping low pass
46895
47356
  * and the time gain back to the start). The stereo image comes from the output taps alone, four per side spread
@@ -47597,7 +48058,7 @@ var LoFiQuantizer = class {
47597
48058
  };
47598
48059
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/effects/lo_fi_1.ts
47599
48060
  /**
47600
- * The gain after the compressor per pre filter (1 bypasses the compressor).
48061
+ * The gain after the compressor per pre-ilter (1 bypasses the compressor).
47601
48062
  */
47602
48063
  const PRE_GAINS = [
47603
48064
  1,
@@ -47652,9 +48113,9 @@ const POST_FILTERS = [
47652
48113
  }
47653
48114
  ];
47654
48115
  /**
47655
- * Lo-Fi 1 (`01 72`): `L+R -> compressor (pre filter) -> low pass -> quantizer -> high pass and dip (post filter)`,
48116
+ * Lo-Fi 1 (`01 72`): `L+R -> compressor (pre-filter) -> low pass -> quantizer -> high pass and dip (post filter)`,
47656
48117
  * mixed with the mono input by the balance, then the EQ and the pan.
47657
- * Parameters: 1 pre filter (1 dry, 2-6 the compressor with a gain of 2 to 7.94, 3 and 5 with a 4 kHz low pass),
48118
+ * Parameters: 1 pre-filter (1 dry, 2-6 the compressor with a gain of 2 to 7.94, 3 and 5 with a 4 kHz low pass),
47658
48119
  * 2 lo-fi type (15 or 11 bits at 16000, 10667 or 8000 Hz), 3 post filter (high pass at 633 or 1595 Hz and a dip at
47659
48120
  * 1 kHz of Q 2, 1 or 0.5), 16 balance (the pan law), 17 low gain, 18 high gain, 19 pan, 20 level. EFX control source
47660
48121
  * 1 modulates the balance, source 2 the pan.
@@ -47692,7 +48153,7 @@ var GSLoFi1 = class extends GSEffect {
47692
48153
  mix;
47693
48154
  compressor;
47694
48155
  /**
47695
- * The compressor's own DC blocker, for the dry path and the pre filter that bypasses it.
48156
+ * The compressor's own DC blocker, for the dry path and the pre-filter that bypasses it.
47696
48157
  */
47697
48158
  dcBlocker;
47698
48159
  lowPass;
@@ -47891,7 +48352,7 @@ function clamp32(x) {
47891
48352
  *
47892
48353
  * - White (x 1/32) or "pink": the white noise times a 500 Hz saw (on the SC-88Pro too).
47893
48354
  * - Disc: one click per turn of a record at 33 1/3, 45 or 78 rpm, 0.125 + 0.25 abs(saw) while the turn's phase is
47894
- * within 0.012 % of its wrap. RND adds a second train whose phase is offset by twice the hum's (pseudo-random
48355
+ * within 0.014 % of its wrap. RND adds a second train whose phase is offset by twice the hum's (pseudo-random
47895
48356
  * times).
47896
48357
  * - Hum: a unipolar 50 or 60 Hz square, 0 or 1/16 (it carries DC, on the hardware too).
47897
48358
  *
@@ -48239,7 +48700,7 @@ var GSLoFi2 = class extends GSEffect {
48239
48700
  this.chainR = new LoFiShaperChain(sampleRate);
48240
48701
  this.noise = new NoiseGenerator(sampleRate);
48241
48702
  this.radioNoise = new FirstOrderFilter(sampleRate);
48242
- this.radioNoise.setDamp(3500);
48703
+ this.radioNoise.setDamp(dampCorner(.5));
48243
48704
  this.shelvesL = new ShelfEQ(sampleRate);
48244
48705
  this.shelvesR = new ShelfEQ(sampleRate);
48245
48706
  this.pan = new MonoPan(sampleRate);
@@ -50683,8 +51144,9 @@ const MULTI_WAH_GAIN = 3.937744140625;
50683
51144
  const DRIVE_INPUT = 128 / 127;
50684
51145
  const DRIVE_OUTPUT = 127 / 128;
50685
51146
  /**
50686
- * The compressor of the multis (parameters 1-4: sustain, attack, level, switch): the Compressor's dynamics with
50687
- * its post gain at +6 dB and a level after it. The compressor runs when it is switched off too.
51147
+ * The compressor of the multis (parameters 1-4: sustain, attack, level, switch, the manuals swap the first two
51148
+ * names, see `GSCompressor`): the Compressor's dynamics with its post gain at +6 dB and a level after it. The
51149
+ * compressor runs when it is switched off too.
50688
51150
  * @internal
50689
51151
  */
50690
51152
  var MultiCompressor = class {
@@ -51466,10 +51928,10 @@ var GSDriveDelayMulti = class extends GSMulti {
51466
51928
  /**
51467
51929
  * GTR Multi 1 (`04 00`): `L+R -> compressor -> overdrive/distortion -> 2-band EQ -> amp simulator -> chorus/flanger
51468
51930
  * -> delay`.
51469
- * Parameters: 1-4 compressor sustain, attack, level and switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp
51470
- * switch, 9 low gain, 10 high gain, 11 drive switch, 12 chorus or flanger, 13 rate (Rate2 column), 14 depth,
51471
- * 15 flanger feedback, 16 chorus level, 17 delay time (Dly4 column), 18 feedback, 19 delay level, 20 level.
51472
- * EFX control source 1 moves the drive, source 2 the delay level.
51931
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
51932
+ * switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 low gain, 10 high gain, 11 drive switch,
51933
+ * 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
51934
+ * column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the drive, source 2 the delay level.
51473
51935
  * @internal
51474
51936
  */
51475
51937
  var GSGuitarMulti1 = class extends GSDriveDelayMulti {
@@ -51644,10 +52106,10 @@ var GSDriveEQMulti = class extends GSMulti {
51644
52106
  };
51645
52107
  /**
51646
52108
  * GTR Multi 2 (`04 01`): `L+R -> compressor -> overdrive/distortion -> amp simulator -> 3-band EQ -> chorus/flanger`.
51647
- * Parameters: 1-4 compressor sustain, attack, level and switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp
51648
- * switch, 9 drive switch, 10 low gain, 11-13 mid frequency, Q and gain, 14 high gain, 15 chorus or flanger, 16 rate
51649
- * (Rate2 column), 17 depth, 18 flanger feedback, 19 chorus level, 20 level. EFX control source 1 moves the drive,
51650
- * source 2 the chorus level.
52109
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
52110
+ * switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid-frequency,
52111
+ * Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
52112
+ * 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
51651
52113
  * @internal
51652
52114
  */
51653
52115
  var GSGuitarMulti2 = class extends GSDriveEQMulti {
@@ -51755,10 +52217,10 @@ var BassAmpSimulator = class {
51755
52217
  /**
51756
52218
  * Bass Multi (`04 05`): `L+R -> compressor -> bass overdrive/distortion -> bass amp simulator -> 3-band EQ ->
51757
52219
  * chorus/flanger`.
51758
- * Parameters: 1-4 compressor sustain, attack, level and switch, 5 drive type (OD, DS), 6 drive, 7 amp type (0-2),
51759
- * 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid frequency, Q and gain, 14 high gain, 15 chorus or flanger,
51760
- * 16 rate (Rate2 column), 17 depth, 18 flanger feedback, 19 chorus level, 20 level. EFX control source 1 moves the
51761
- * drive, source 2 the chorus level.
52220
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
52221
+ * switch, 5 drive type (OD, DS), 6 drive, 7 amp type (0-2), 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid- * frequency,
52222
+ * Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
52223
+ * 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
51762
52224
  * @internal
51763
52225
  */
51764
52226
  var GSBassMulti = class extends GSDriveEQMulti {
@@ -51812,10 +52274,10 @@ var GSBassMulti = class extends GSDriveEQMulti {
51812
52274
  const LINEAR_INPUT = 127 / 128;
51813
52275
  /**
51814
52276
  * Clean GTR Multi 1 (`04 03`): `L+R -> compressor -> 3-band EQ -> chorus/flanger -> delay`.
51815
- * Parameters: 1-4 compressor sustain, attack, level and switch, 5 low gain, 6-8 mid frequency, Q and gain, 9 high
51816
- * gain, 10 chorus or flanger, 11 rate (Rate2 column), 12 depth, 13 flanger feedback, 14 chorus level, 15 delay time
51817
- * (Dly4 column), 16 feedback, 17 HF damp, 18 delay level, 20 level. EFX control source 1 moves the chorus level,
51818
- * source 2 the delay level.
52277
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
52278
+ * switch, 5 low gain, 6-8 mid-frequency, Q and gain, 9 high gain, 10 chorus or flanger, 11 rate (Rate2 column),
52279
+ * 12 depth, 13 flanger feedback, 14 chorus level, 15 delay time (Dly4 column), 16 feedback, 17 HF damp, 18 delay
52280
+ * level, 20 level. EFX control source 1 moves the chorus level, source 2 the delay level.
51819
52281
  * @internal
51820
52282
  */
51821
52283
  var GSCleanGuitarMulti1 = class extends GSMulti {
@@ -51889,7 +52351,7 @@ var GSCleanGuitarMulti1 = class extends GSMulti {
51889
52351
  * Clean GTR Multi 2 (`04 04`): `L+R -> wah -> 3-band EQ -> chorus/flanger -> delay`. The wah is the Auto Wah's without
51890
52352
  * the envelope follower, its LFO raises the cutoff (polarity up).
51891
52353
  * Parameters: 1 wah filter type (LPF, BPF), 2 manual, 3 peak, 4 rate (Rate1 column), 5 depth, 6 wah switch (SC-VA's
51892
- * code calls it the EQ switch, but it bypasses the wah), 7 low gain, 8-10 mid frequency, Q and gain, 11 high gain,
52354
+ * code calls it the EQ switch, but it bypasses the wah), 7 low gain, 8-10 mid-frequency, Q and gain, 11 high gain,
51893
52355
  * 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
51894
52356
  * column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the wah manual, source 2 the delay
51895
52357
  * level.
@@ -52107,9 +52569,11 @@ const DEFAULT_CONTROL_DEPTH = 64;
52107
52569
  /**
52108
52570
  * The offset an EFX control source adds to its parameter, like Sound Canvas VA computes it:
52109
52571
  * `(depth - 64) * value / 63`, truncated and stored in a signed byte. Depth 0 counts as 1 (both are -100%).
52572
+ * The byte wraps without a clamp, like in SCCore.dll: pitch bend fully down (-128) at depth 0 or 1 gives +128,
52573
+ * which is stored as -128, so the offset flips sign there.
52110
52574
  * @param depth The control depth (0-127).
52111
52575
  * @param value The signed controller value.
52112
- * @returns The signed offset.
52576
+ * @returns The signed offset (-128 to 127).
52113
52577
  */
52114
52578
  function controlOffset(depth, value) {
52115
52579
  const amount = (depth === 0 ? 1 : depth) - 64;
@@ -52125,6 +52589,7 @@ var DefaultGSInsertion = class {
52125
52589
  sendLevelToReverb = DEFAULT_SEND_LEVEL_TO_REVERB;
52126
52590
  sendLevelToChorus = 0;
52127
52591
  sendLevelToDelay = 0;
52592
+ sendEQSwitch = true;
52128
52593
  controlSource1 = GSInsertionControlSources.off;
52129
52594
  controlDepth1 = DEFAULT_CONTROL_DEPTH;
52130
52595
  controlSource2 = GSInsertionControlSources.off;
@@ -52167,7 +52632,7 @@ var DefaultGSInsertion = class {
52167
52632
  * Constructs a new default GS insertion block.
52168
52633
  * @param sampleRate The sample rate, in Hertz.
52169
52634
  * @param maxBufferSize The maximum buffer size the synthesizer can render at once.
52170
- * Attempting to `.process()` more samples than this will result in an error.
52635
+ * Processing more samples than this is not supported and produces undefined output.
52171
52636
  */
52172
52637
  constructor(sampleRate, maxBufferSize) {
52173
52638
  const memory = new DelayMemory();
@@ -52194,6 +52659,7 @@ var DefaultGSInsertion = class {
52194
52659
  this.sendLevelToReverb = DEFAULT_SEND_LEVEL_TO_REVERB;
52195
52660
  this.sendLevelToChorus = 0;
52196
52661
  this.sendLevelToDelay = 0;
52662
+ this.sendEQSwitch = true;
52197
52663
  }
52198
52664
  getType() {
52199
52665
  return this.type;
@@ -52250,6 +52716,7 @@ var DefaultGSInsertion = class {
52250
52716
  sendLevelToReverb: this.sendLevelToReverb,
52251
52717
  sendLevelToChorus: this.sendLevelToChorus,
52252
52718
  sendLevelToDelay: this.sendLevelToDelay,
52719
+ sendEQSwitch: this.sendEQSwitch,
52253
52720
  controlSource1: this.controlSource1,
52254
52721
  controlDepth1: this.controlDepth1,
52255
52722
  controlSource2: this.controlSource2,
@@ -52270,6 +52737,7 @@ var DefaultGSInsertion = class {
52270
52737
  this.sendLevelToReverb = snapshot.sendLevelToReverb;
52271
52738
  this.sendLevelToChorus = snapshot.sendLevelToChorus;
52272
52739
  this.sendLevelToDelay = snapshot.sendLevelToDelay;
52740
+ this.sendEQSwitch = snapshot.sendEQSwitch ?? true;
52273
52741
  }
52274
52742
  /**
52275
52743
  * Sends the offsets of both control sources to the processor.
@@ -52532,8 +53000,9 @@ var SoundBankManager = class {
52532
53000
  const bankOffset = s.bankOffset;
52533
53001
  for (const p of bank.presets) {
52534
53002
  const selectablePreset = new SoundBankManagerPreset(p, bankOffset);
52535
- if (!addedPresets.has(selectablePreset.toMIDIString())) {
52536
- addedPresets.add(selectablePreset.toMIDIString());
53003
+ const key = selectablePreset.toMIDIString();
53004
+ if (!addedPresets.has(key)) {
53005
+ addedPresets.add(key);
52537
53006
  presetList.push(selectablePreset);
52538
53007
  }
52539
53008
  }
@@ -52624,12 +53093,13 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++) {
52624
53093
  }
52625
53094
  /**
52626
53095
  * Computes the LFO value for the given phase.
52627
- * Both waveforms are at zero and rising at the default start phase of 0.25.
53096
+ * All waveforms are at zero and rising at the default start phase of 0.25.
52628
53097
  * @param phase the LFO phase, from 0 to 1.
52629
- * @param waveform 0 for triangle, 1 for sawtooth.
53098
+ * @param waveform 0 for triangle, 1 for sawtooth, 2 for sine.
52630
53099
  * @returns the LFO value, from -1 to 1.
52631
53100
  */
52632
53101
  function getLfoValue(phase, waveform) {
53102
+ if (waveform === 2) return Math.sin(TWO_PI * (phase - .25));
52633
53103
  if (waveform === 1) {
52634
53104
  const p = phase + .25;
52635
53105
  return 2 * (p >= 1 ? p - 1 : p) - 1;
@@ -52637,10 +53107,11 @@ function getLfoValue(phase, waveform) {
52637
53107
  return 1 - 4 * Math.abs(phase - .5);
52638
53108
  }
52639
53109
  /**
52640
- * Renders a voice to the stereo output buffer
53110
+ * Renders a voice into one of the channel's voice send buffers,
53111
+ * which are later mixed into the stereo output and the effect sends.
52641
53112
  * @param voice the voice to render
52642
53113
  * @param timeNow current time in seconds
52643
- * @param sampleCount the only thing needed as it's 0-based
53114
+ * @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
52644
53115
  */
52645
53116
  function renderVoice(voice, timeNow, sampleCount) {
52646
53117
  if (timeNow < voice.startTime) {
@@ -52902,9 +53373,9 @@ var VoiceSendBuffer = class {
52902
53373
  };
52903
53374
  /**
52904
53375
  * Mixes a voice send buffer into the channel output and the effect sends, then clears it.
52905
- * @param channel
52906
- * @param buffer
52907
- * @param sampleCount
53376
+ * @param channel The channel that owns the buffer.
53377
+ * @param buffer The voice send buffer to mix.
53378
+ * @param sampleCount The number of samples to mix.
52908
53379
  */
52909
53380
  function mixVoiceSendBuffer(channel, buffer, sampleCount) {
52910
53381
  const core = channel.synthCore;
@@ -53203,7 +53674,7 @@ function handleAWE32NRPN(paramLSB, dataValue) {
53203
53674
  }
53204
53675
  // src/synthesizer/audio_engine/channel/data_entry.ts
53205
53676
  /**
53206
- * Executes a data entry change for the current channel.
53677
+ * Executes a data entry change for the current channel.
53207
53678
  */
53208
53679
  function dataEntry() {
53209
53680
  const dataValue = this._midiControllers[MIDIControllers.dataEntryMSB];
@@ -53342,7 +53813,7 @@ function dataEntry() {
53342
53813
  break;
53343
53814
  case NonRegisteredParameterTypesMSB.drumVariation:
53344
53815
  this.drumParams[parameterFine].variationSend = dataCoarse;
53345
- SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`, dataValue, "");
53816
+ SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`, dataCoarse, "");
53346
53817
  break;
53347
53818
  case NonRegisteredParameterTypesMSB.awe32:
53348
53819
  handleAWE32NRPN.call(this, parameterFine, dataValue);
@@ -53675,7 +54146,7 @@ function noteOn(midiNote, velocity, emit = true) {
53675
54146
  // src/synthesizer/audio_engine/channel/note_off.ts
53676
54147
  /**
53677
54148
  * Releases a note by its MIDI note number.
53678
- * If the note is in high performance mode and the channel is not a drum channel,
54149
+ * If the synthesizer is in high performance mode and the channel is not a drum channel,
53679
54150
  * or the drum channel has an rxNoteOff enabled,
53680
54151
  * it kills the note instead of releasing it.
53681
54152
  * @param midiNote The MIDI note number to release (0-127).
@@ -53852,7 +54323,7 @@ var DynamicModulatorManager = class {
53852
54323
  case 5:
53853
54324
  this.setModulator(source, isCC, GeneratorTypes.vibLfoToVolume, normalizedNotCentered * 200, bipolar, false, true);
53854
54325
  this.setModulator(source, isCC, GeneratorTypes.initialAttenuation, normalizedNotCentered * 200, bipolar, false, true);
53855
- SpessaLog.xgInfo(`Channel ${this.channel} ${sourceName} LFO amplitude depth control`, normalizedNotCentered * 200, "dB");
54326
+ SpessaLog.xgInfo(`Channel ${this.channel} ${sourceName} LFO amplitude depth control`, normalizedNotCentered * 200, "cB");
53856
54327
  }
53857
54328
  }
53858
54329
  setupReceiverGS(addr3, data, source, isCC, sourceName, bipolar = false) {
@@ -54099,6 +54570,7 @@ const DEFAULT_CHANNEL_MIDI_PARAMETERS = {
54099
54570
  randomPan: false,
54100
54571
  assignMode: 2,
54101
54572
  efxAssign: false,
54573
+ eqEnabled: true,
54102
54574
  cc1: 16,
54103
54575
  cc2: 17,
54104
54576
  drumMap: 0,
@@ -54259,6 +54731,7 @@ var MIDIChannel = class {
54259
54731
  /**
54260
54732
  * If true, this skips sends as they all go through insertion.
54261
54733
  * Set by {@link SpessaSynthProcessor}.
54734
+ * @internal
54262
54735
  */
54263
54736
  xgInsertionAssigned = false;
54264
54737
  /**
@@ -54333,8 +54806,8 @@ var MIDIChannel = class {
54333
54806
  */
54334
54807
  resetRP15 = resetRP15.bind(this);
54335
54808
  /**
54336
- * Executes a data entry coarse (MSB) change for the current channel.
54337
- * @param dataValue The value to set for the data entry coarse controller (0-127).
54809
+ * Executes a data entry (MSB or LSB) change for the current channel.
54810
+ * The value is read from the controller table and applied to the selected RPN or NRPN.
54338
54811
  * @internal
54339
54812
  */
54340
54813
  dataEntry = dataEntry.bind(this);
@@ -54589,7 +55062,7 @@ var MIDIChannel = class {
54589
55062
  noteOn(midiNote, velocity, emit = true) {}
54590
55063
  /**
54591
55064
  * Releases a note by its MIDI note number.
54592
- * If the note is in high performance mode and the channel is not a drum channel,
55065
+ * If the synthesizer is in high performance mode and the channel is not a drum channel,
54593
55066
  * or the drum channel has an rxNoteOff enabled,
54594
55067
  * it kills the note instead of releasing it.
54595
55068
  * @param midiNote The MIDI note number to release (0-127).
@@ -54618,14 +55091,16 @@ var MIDIChannel = class {
54618
55091
  /**
54619
55092
  * Reset this channel to its default state.
54620
55093
  * Except for the locked controllers.
55094
+ * @param sendEvent If controller change events should be emitted for the reset controllers.
54621
55095
  * @internal
54622
55096
  */
54623
55097
  reset(sendEvent = true) {}
54624
55098
  /**
54625
- * Renders a voice to the stereo output buffer
55099
+ * Renders a voice into one of the channel's voice send buffers,
55100
+ * which are later mixed into the stereo output and the effect sends.
54626
55101
  * @param voice the voice to render
54627
55102
  * @param timeNow current time in seconds
54628
- * @param sampleCount the only thing needed as it's 0-based
55103
+ * @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
54629
55104
  * @internal
54630
55105
  */
54631
55106
  renderVoice(voice, timeNow, sampleCount) {}
@@ -54970,6 +55445,7 @@ const DEFAULT_GLOBAL_SYSTEM_PARAMETERS = {
54970
55445
  xgChorusLock: false,
54971
55446
  gsDelayGain: 1,
54972
55447
  gsDelayLock: false,
55448
+ gsEqualizerLock: false,
54973
55449
  xgVariationGain: 1,
54974
55450
  xgVariationLock: false,
54975
55451
  gsInsertionLock: false,
@@ -55207,7 +55683,7 @@ var LowpassFilter = class LowpassFilter {
55207
55683
  const qCb = this.resonanceCb;
55208
55684
  let cutoffHz = absCentsToHz(cutoffCents);
55209
55685
  cutoffHz = Math.min(cutoffHz, this.maxCutoff);
55210
- const resonanceGain = cbAttenuationToGain(-(qCb - 3.01));
55686
+ const resonanceGain = cbAttenuationToGain(-(qCb - 30.1));
55211
55687
  const qGain = 1 / Math.sqrt(cbAttenuationToGain(-qCb));
55212
55688
  const w = 2 * Math.PI * cutoffHz / this.sampleRate;
55213
55689
  const cosw = Math.cos(w);
@@ -55254,7 +55730,7 @@ var VolumeEnvelope = class {
55254
55730
  /**
55255
55731
  * The sample count between updates of the volume envelope.
55256
55732
  * Since the volume envelope calculation runs once per rendering quantum,
55257
- * this effectively the buffer size.
55733
+ * this is effectively the buffer size.
55258
55734
  */
55259
55735
  updateInterval;
55260
55736
  /**
@@ -55262,11 +55738,11 @@ var VolumeEnvelope = class {
55262
55738
  */
55263
55739
  sampleTime = 0;
55264
55740
  /**
55265
- * The dB attenuation of the envelope when it entered the release stage.
55741
+ * The cB attenuation of the envelope when it entered the release stage.
55266
55742
  */
55267
55743
  releaseStartCb = CB_SILENCE;
55268
55744
  /**
55269
- * The time in samples relative to the start of the envelope.
55745
+ * The time in samples when the release stage started, relative to the start of the envelope.
55270
55746
  */
55271
55747
  releaseStartTimeSamples = 0;
55272
55748
  /**
@@ -55405,10 +55881,10 @@ var VolumeEnvelope = class {
55405
55881
  }
55406
55882
  /**
55407
55883
  * Calculates the gain value for the last sample in the block and writes it to `outputGain`.
55408
- * Essentially we use approach of 100dB is silence, 0dB is peak.
55409
- * @param sampleCount the amount of samples to write
55884
+ * Essentially we use approach of 96 dB (CB_SILENCE) is silence, 0dB is peak.
55885
+ * @param sampleCount the amount of samples in the block to advance the envelope by.
55410
55886
  * @param gainTarget the gain to apply.
55411
- * @returns if the voice has finished.
55887
+ * @returns if the voice is still active (false once the envelope has finished).
55412
55888
  */
55413
55889
  process(sampleCount, gainTarget) {
55414
55890
  const { releaseStartTimeSamples, releaseStartCb, releaseDuration, delayEnd, attackEnd, attackDuration, holdEnd, decayEnd, decayDuration, sustainCb, decay2End, decay2Duration, sustain2Cb } = this;
@@ -55566,7 +56042,7 @@ var ModulationEnvelope = class {
55566
56042
  * Calculates the current modulation envelope value for the given time and voice.
55567
56043
  * @param voice the voice we are working on.
55568
56044
  * @param currentTime in seconds.
55569
- * @returns mod env value, from 0 to 1.
56045
+ * @returns mod env value, from 0 to 1.
55570
56046
  */
55571
56047
  process(voice, currentTime) {
55572
56048
  if (this.enteredRelease) {
@@ -55839,6 +56315,217 @@ var HermiteOscillator = class extends WavetableOscillator {
55839
56315
  return true;
55840
56316
  }
55841
56317
  };
56318
+ // src/synthesizer/audio_engine/voice/wavetable_oscillator/sinc.ts
56319
+ /**
56320
+ * Kernel resolution, the amount of table points per sample of distance.
56321
+ * The table is linearly interpolated, which keeps the error around -120 dB.
56322
+ */
56323
+ const OVERSAMPLING = 256;
56324
+ /**
56325
+ * The highest playback step the anti-aliased kernel gets stretched to.
56326
+ * The tap count grows with the step, so this keeps extreme pitch ups from becoming too slow.
56327
+ * Above it, the content between the new Nyquist frequency and 1/16 of the old one folds back.
56328
+ */
56329
+ const MAX_STRETCH = 16;
56330
+ /**
56331
+ * Zeroth order modified Bessel function of the first kind, for the Kaiser window.
56332
+ * @param x
56333
+ */
56334
+ function besselI0(x) {
56335
+ const q = x * x / 4;
56336
+ let sum = 1;
56337
+ let term = 1;
56338
+ for (let k = 1; k < 100; k++) {
56339
+ term *= q / (k * k);
56340
+ sum += term;
56341
+ if (term < sum * 1e-17) break;
56342
+ }
56343
+ return sum;
56344
+ }
56345
+ /**
56346
+ * Builds a Kaiser windowed sinc kernel.
56347
+ * @param halfWidth Half of the kernel width, in samples. The kernel uses `2 * halfWidth` taps.
56348
+ * @param bandwidth The cutoff (-6 dB point) of the kernel, relative to the Nyquist frequency.
56349
+ * @param beta The Kaiser window beta, controls the stopband attenuation.
56350
+ */
56351
+ function buildKernel(halfWidth, bandwidth, beta) {
56352
+ const windowNorm = besselI0(beta);
56353
+ const kernel = (x) => {
56354
+ const w = x / halfWidth;
56355
+ if (w <= -1 || w >= 1) return 0;
56356
+ const window = besselI0(beta * Math.sqrt(1 - w * w)) / windowNorm;
56357
+ const u = Math.PI * bandwidth * x;
56358
+ return bandwidth * (u === 0 ? 1 : Math.sin(u) / u) * window;
56359
+ };
56360
+ const taps = halfWidth * 2;
56361
+ const rows = new Float64Array(taps * 257);
56362
+ for (let q = 0; q <= OVERSAMPLING; q++) {
56363
+ const row = q * taps;
56364
+ let sum = 0;
56365
+ for (let k = 0; k < taps; k++) {
56366
+ const v = kernel(k + 1 - halfWidth - q / OVERSAMPLING);
56367
+ rows[row + k] = v;
56368
+ sum += v;
56369
+ }
56370
+ for (let k = 0; k < taps; k++) rows[row + k] /= sum;
56371
+ }
56372
+ const phases = new Float32Array(taps * OVERSAMPLING);
56373
+ const phaseDeltas = new Float32Array(taps * OVERSAMPLING);
56374
+ for (let i = 0; i < phases.length; i++) {
56375
+ phases[i] = rows[i];
56376
+ phaseDeltas[i] = rows[i + taps] - rows[i];
56377
+ }
56378
+ const tableLength = halfWidth * 2 * OVERSAMPLING + 2;
56379
+ const table = new Float32Array(tableLength);
56380
+ const tableDeltas = new Float32Array(tableLength);
56381
+ for (let i = 0; i < tableLength; i++) table[i] = kernel(i / OVERSAMPLING - halfWidth);
56382
+ for (let i = 0; i < tableLength - 1; i++) tableDeltas[i] = table[i + 1] - table[i];
56383
+ return {
56384
+ halfWidth,
56385
+ phases,
56386
+ phaseDeltas,
56387
+ table,
56388
+ tableDeltas
56389
+ };
56390
+ }
56391
+ /**
56392
+ * 32 points, meant for real time playback.
56393
+ */
56394
+ const KERNEL_FAST = buildKernel(16, .92, 9);
56395
+ /**
56396
+ * 128 points, for offline rendering.
56397
+ */
56398
+ const KERNEL_BEST = buildKernel(64, .975, 12);
56399
+ var SincOscillator = class extends WavetableOscillator {
56400
+ kernel;
56401
+ /**
56402
+ * If the kernel gets stretched when the playback step is above 1.
56403
+ * This lowers the cutoff to the new Nyquist frequency so the sample content above it does not alias.
56404
+ */
56405
+ antiAlias;
56406
+ /**
56407
+ * The points around the cursor, gathered here when they need wrapping around the loop
56408
+ * or are outside the sample.
56409
+ */
56410
+ scratch;
56411
+ /**
56412
+ * Creates a new windowed sinc oscillator.
56413
+ * @param best If true, uses the larger, anti-aliased kernel. It is too slow for real time playback.
56414
+ */
56415
+ constructor(best) {
56416
+ super();
56417
+ this.kernel = best ? KERNEL_BEST : KERNEL_FAST;
56418
+ this.antiAlias = best;
56419
+ this.scratch = new Float32Array(best ? Math.ceil(this.kernel.halfWidth * MAX_STRETCH) * 2 + 2 : this.kernel.halfWidth * 2);
56420
+ }
56421
+ process(sampleCount, tuningRatio, outputBuffer) {
56422
+ const step = tuningRatio * this.playbackStep;
56423
+ const sampleData = this.sampleData;
56424
+ const scratch = this.scratch;
56425
+ const { loopStart, loopEnd, loopLength, end, isLooping } = this;
56426
+ const { halfWidth } = this.kernel;
56427
+ let cursor = this.cursor;
56428
+ let hasLooped = this.hasLooped;
56429
+ const upperLimit = isLooping ? loopEnd : end;
56430
+ const stretch = this.antiAlias && step > 1 ? Math.min(step, MAX_STRETCH) : 1;
56431
+ if (stretch === 1) {
56432
+ const { phases, phaseDeltas } = this.kernel;
56433
+ const taps = halfWidth * 2;
56434
+ for (let i = 0; i < sampleCount; i++) {
56435
+ if (isLooping) {
56436
+ if (cursor >= loopEnd) {
56437
+ hasLooped = true;
56438
+ while (cursor >= loopEnd) cursor -= loopLength;
56439
+ }
56440
+ } else if (cursor + 1 >= end) {
56441
+ outputBuffer.fill(0, i, sampleCount);
56442
+ this.cursor = cursor;
56443
+ return false;
56444
+ }
56445
+ const y0 = cursor | 0;
56446
+ let first = y0 + 1 - halfWidth;
56447
+ let source = sampleData;
56448
+ if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
56449
+ this.gather(first, taps, hasLooped);
56450
+ source = scratch;
56451
+ first = 0;
56452
+ }
56453
+ const phase = (cursor - y0) * OVERSAMPLING;
56454
+ const q = phase | 0;
56455
+ let row = q * taps;
56456
+ const fraction = phase - q;
56457
+ const rowEnd = row + taps;
56458
+ let out = 0;
56459
+ for (; row < rowEnd; row++) out += source[first++] * (phases[row] + phaseDeltas[row] * fraction);
56460
+ outputBuffer[i] = out;
56461
+ cursor += step;
56462
+ }
56463
+ } else {
56464
+ const { table, tableDeltas } = this.kernel;
56465
+ const reach = halfWidth * stretch;
56466
+ const tableStep = OVERSAMPLING / stretch;
56467
+ const center = halfWidth * OVERSAMPLING;
56468
+ const gain = 1 / stretch;
56469
+ for (let i = 0; i < sampleCount; i++) {
56470
+ if (isLooping) {
56471
+ if (cursor >= loopEnd) {
56472
+ hasLooped = true;
56473
+ while (cursor >= loopEnd) cursor -= loopLength;
56474
+ }
56475
+ } else if (cursor + 1 >= end) {
56476
+ outputBuffer.fill(0, i, sampleCount);
56477
+ this.cursor = cursor;
56478
+ return false;
56479
+ }
56480
+ let first = Math.floor(cursor - reach) + 1;
56481
+ const taps = Math.floor(cursor + reach) - first + 1;
56482
+ let position = (first - cursor) * tableStep + center;
56483
+ let source = sampleData;
56484
+ if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
56485
+ this.gather(first, taps, hasLooped);
56486
+ source = scratch;
56487
+ first = 0;
56488
+ }
56489
+ let out = 0;
56490
+ const last = first + taps;
56491
+ for (; first < last; first++) {
56492
+ const index = position | 0;
56493
+ out += source[first] * (table[index] + tableDeltas[index] * (position - index));
56494
+ position += tableStep;
56495
+ }
56496
+ outputBuffer[i] = out * gain;
56497
+ cursor += step;
56498
+ }
56499
+ }
56500
+ this.cursor = cursor;
56501
+ this.hasLooped = hasLooped;
56502
+ return true;
56503
+ }
56504
+ /**
56505
+ * Copies the points into the scratch buffer, wrapping them around the loop.
56506
+ * Points outside the sample are silent.
56507
+ * @param first The index of the first point.
56508
+ * @param count The amount of points.
56509
+ * @param hasLooped If the cursor has already looped.
56510
+ */
56511
+ gather(first, count, hasLooped) {
56512
+ const sampleData = this.sampleData;
56513
+ const scratch = this.scratch;
56514
+ const { loopStart, loopEnd, loopLength, end } = this;
56515
+ if (this.isLooping) {
56516
+ let index = first;
56517
+ if (index >= loopEnd) index = loopStart + (index - loopStart) % loopLength;
56518
+ else if (index < loopStart && hasLooped) index = loopEnd - 1 - (loopStart - 1 - index) % loopLength;
56519
+ for (let k = 0; k < count; k++) {
56520
+ scratch[k] = index < 0 ? 0 : sampleData[index];
56521
+ if (++index >= loopEnd) index = loopStart;
56522
+ }
56523
+ } else for (let k = 0; k < count; k++) {
56524
+ const index = first + k;
56525
+ scratch[k] = index >= 0 && index < end ? sampleData[index] : 0;
56526
+ }
56527
+ }
56528
+ };
55842
56529
  // src/synthesizer/audio_engine/voice/voice.ts
55843
56530
  /**
55844
56531
  * Voice.ts
@@ -55863,7 +56550,9 @@ var Voice = class {
55863
56550
  oscillators = [
55864
56551
  new LinearOscillator(),
55865
56552
  new NearestOscillator(),
55866
- new HermiteOscillator()
56553
+ new HermiteOscillator(),
56554
+ new SincOscillator(false),
56555
+ new SincOscillator(true)
55867
56556
  ];
55868
56557
  /**
55869
56558
  * The oscillator currently used by this voice.
@@ -55950,7 +56639,7 @@ var Voice = class {
55950
56639
  * This is the effective MIDI note number,
55951
56640
  * used to calculate scale tuning and envelope times,
55952
56641
  * and can be overridden by generators.
55953
- * It is also used
56642
+ * It is also used for the octave tuning and as the portamento target.
55954
56643
  */
55955
56644
  targetKey = 0;
55956
56645
  /**
@@ -56043,7 +56732,9 @@ var Voice = class {
56043
56732
  this.filter = new LowpassFilter(sampleRate);
56044
56733
  }
56045
56734
  /**
56046
- * Releases the voice as exclusiveClass.
56735
+ * Releases the voice as exclusiveClass, with a nearly instant release time.
56736
+ * @param currentTime the current time in seconds.
56737
+ * @param minExclusiveLength minimum note length in seconds.
56047
56738
  */
56048
56739
  exclusiveRelease(currentTime, minExclusiveLength = MIN_EXCLUSIVE_LENGTH) {
56049
56740
  this.overrideReleaseVolEnv = EXCLUSIVE_CUTOFF_TIME;
@@ -56051,8 +56742,9 @@ var Voice = class {
56051
56742
  this.releaseVoice(currentTime, minExclusiveLength);
56052
56743
  }
56053
56744
  /**
56054
- * Stops the voice
56055
- * @param currentTime
56745
+ * Releases the voice. The release phase starts on the first render at or after the release time,
56746
+ * which is pushed back if the note would be shorter than the minimum length.
56747
+ * @param currentTime the current time in seconds.
56056
56748
  * @param minNoteLength minimum note length in seconds
56057
56749
  */
56058
56750
  releaseVoice(currentTime, minNoteLength = MIN_NOTE_LENGTH) {
@@ -56190,7 +56882,7 @@ function lockMIDIParameterInternal(parameter, isLocked) {
56190
56882
  * @param byte1 The first byte (midi note).
56191
56883
  * @param byte2 The second byte (most significant bits).
56192
56884
  * @param byte3 The third byte (the least significant bits).
56193
- * @return An object containing the MIDI note and the cent tuning value.
56885
+ * @returns The MIDI note with the fraction added (in semitones), or -1 for no change.
56194
56886
  */
56195
56887
  function getTuning(byte1, byte2, byte3) {
56196
56888
  const midiNote = byte1;
@@ -56354,8 +57046,8 @@ function universalSystemExclusive(syx, channelOffset = 0) {
56354
57046
  const program = syx[currentMessageIndex++];
56355
57047
  const tuningName = readBinaryString(syx, 16, currentMessageIndex);
56356
57048
  currentMessageIndex += 16;
56357
- if (syx.length < 384) {
56358
- SpessaLog.warn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 384 are expected)`);
57049
+ if (syx.length < 405) {
57050
+ SpessaLog.warn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 405 are expected)`);
56359
57051
  return;
56360
57052
  }
56361
57053
  for (let midiNote = 0; midiNote < 128; midiNote++) this.tunings[program * 128 + midiNote] = getTuning(syx[currentMessageIndex++], syx[currentMessageIndex++], syx[currentMessageIndex++]);
@@ -56766,6 +57458,27 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
56766
57458
  }
56767
57459
  break;
56768
57460
  }
57461
+ if (a2 === 2) {
57462
+ if (this.systemParameters.gsEqualizerLock) return;
57463
+ const parameter = [
57464
+ "lowFrequency",
57465
+ "lowGain",
57466
+ "highFrequency",
57467
+ "highGain"
57468
+ ][a3];
57469
+ if (parameter === void 0) {
57470
+ SpessaLog.gsFail("Equalizer", [a3]);
57471
+ return;
57472
+ }
57473
+ this.gsEqualizerProcessor[parameter] = data;
57474
+ SpessaLog.gsInfo(`EQ ${parameter}`, data);
57475
+ this.callEvent("effectChange", {
57476
+ effect: "gsEqualizer",
57477
+ parameter,
57478
+ value: data
57479
+ });
57480
+ return;
57481
+ }
56769
57482
  if (a2 === 3) {
56770
57483
  if (this.systemParameters.gsInsertionLock) return;
56771
57484
  const insertion = this.gsInsertionBlock;
@@ -56842,6 +57555,17 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
56842
57555
  });
56843
57556
  return;
56844
57557
  }
57558
+ case 31: {
57559
+ const on = data !== 0;
57560
+ insertion.sendEQSwitch = on;
57561
+ SpessaLog.gsInfo("EFX Send EQ Switch", on ? "ON" : "OFF");
57562
+ this.callEvent("effectChange", {
57563
+ effect: "gsInsertion",
57564
+ parameter: "sendEQSwitch",
57565
+ value: on
57566
+ });
57567
+ return;
57568
+ }
56845
57569
  }
56846
57570
  }
56847
57571
  if (a2 >> 4 === 1) {
@@ -57028,6 +57752,12 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
57028
57752
  case 1:
57029
57753
  ch.controllerChange(MIDIControllers.bankSelectLSB, data);
57030
57754
  break;
57755
+ case 32: {
57756
+ const eq = data === 1;
57757
+ ch.setMIDIParameter("eqEnabled", eq);
57758
+ SpessaLog.gsInfo(`EQ for ${channel}`, eq ? "ON" : "OFF");
57759
+ break;
57760
+ }
57031
57761
  case 34: {
57032
57762
  const efx = data === 1;
57033
57763
  ch.setMIDIParameter("efxAssign", efx);
@@ -57799,8 +58529,8 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
57799
58529
  for (const ch of this.midiChannels) {
57800
58530
  if (ch.midiParameters.drumMap !== setupNumber) continue;
57801
58531
  ch.drumParams[drumKey].pitchFine = pitch;
57802
- SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "semitones");
57803
58532
  }
58533
+ SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "cents");
57804
58534
  break;
57805
58535
  }
57806
58536
  case 2:
@@ -57933,7 +58663,7 @@ function applyPresetZones(indexes, presetGens, presetMods, instruments, presets)
57933
58663
  }
57934
58664
  // src/soundbank/soundfont/read/presets.ts
57935
58665
  /**
57936
- * Parses soundfont presets, also includes function for getting the generators and samples from midi note and velocity
58666
+ * Parses soundfont presets
57937
58667
  */
57938
58668
  var SoundFontPreset = class extends BasicPreset {
57939
58669
  zoneStartIndex;
@@ -58030,7 +58760,7 @@ function readInstruments(instrumentChunk, keepTerminal = false) {
58030
58760
  }
58031
58761
  // src/soundbank/soundfont/read/modulators.ts
58032
58762
  /**
58033
- * Reads the modulator read
58763
+ * Reads the modulators
58034
58764
  * @param modulatorChunk pmod, imod or DMOD
58035
58765
  * @param allowSFEGenerators if destinations above endOper are allowed (SFE banks)
58036
58766
  */
@@ -58375,11 +59105,16 @@ function applySnapshot(snapshot) {
58375
59105
  this.gsReverbProcessor.applySnapshot(snapshot.gsReverbProcessor);
58376
59106
  this.gsChorusProcessor.applySnapshot(snapshot.gsChorusProcessor);
58377
59107
  this.gsDelayProcessor.applySnapshot(snapshot.gsDelayProcessor);
59108
+ if (snapshot.gsEqualizerProcessor) this.gsEqualizerProcessor.applySnapshot(snapshot.gsEqualizerProcessor);
58378
59109
  this.gsInsertionBlock.applySnapshot(snapshot.gsInsertionBlock);
58379
59110
  this.xgReverbBlock.applySnapshot(snapshot.xgReverbBlock);
58380
59111
  this.xgChorusBlock.applySnapshot(snapshot.xgChorusBlock);
58381
59112
  this.xgVariationBlock.applySnapshot(snapshot.xgVariationBlock);
58382
- for (let i = 0; i < this.xgInsertionBlocks.length; i++) this.xgInsertionBlocks[i].applySnapshot(snapshot.xgInsertionBlocks[i]);
59113
+ for (let i = 0; i < this.xgInsertionBlocks.length; i++) {
59114
+ const insertion = this.xgInsertionBlocks[i];
59115
+ if (i < snapshot.xgInsertionBlocks.length) insertion.applySnapshot(snapshot.xgInsertionBlocks[i]);
59116
+ else insertion.reset();
59117
+ }
58383
59118
  for (let drumSet = 0; drumSet < snapshot.userDrumSets.length; drumSet++) {
58384
59119
  const userDrumSet = snapshot.userDrumSets[drumSet];
58385
59120
  for (let midiNote = 0; midiNote < userDrumSet.length; midiNote++) DrumParameterUtils.copyIntoUser(userDrumSet[midiNote], this.soundBankManager.userDrumSets[drumSet].keyParams[midiNote]);
@@ -58399,6 +59134,7 @@ function getSynthesizerSnapshot() {
58399
59134
  gsReverbProcessor: this.gsReverbProcessor.getSnapshot(),
58400
59135
  gsChorusProcessor: this.gsChorusProcessor.getSnapshot(),
58401
59136
  gsDelayProcessor: this.gsDelayProcessor.getSnapshot(),
59137
+ gsEqualizerProcessor: this.gsEqualizerProcessor.getSnapshot(),
58402
59138
  gsInsertionBlock: this.gsInsertionBlock.getSnapshot(),
58403
59139
  xgReverbBlock: this.xgReverbBlock.getSnapshot(),
58404
59140
  xgChorusBlock: this.xgChorusBlock.getSnapshot(),
@@ -58411,7 +59147,8 @@ function getSynthesizerSnapshot() {
58411
59147
  /**
58412
59148
  * Keeps the decoded sample data under a size budget by releasing the least recently used samples.
58413
59149
  * A released sample decodes again the next time it plays.
58414
- * Samples that cannot decode again (replaced float data) are never released.
59150
+ * Samples that cannot decode again (replaced float data) are never released
59151
+ * and are not tracked, so they do not count towards the budget.
58415
59152
  * @internal
58416
59153
  */
58417
59154
  var DecodedSampleCache = class {
@@ -58424,6 +59161,7 @@ var DecodedSampleCache = class {
58424
59161
  */
58425
59162
  totalBytes = 0;
58426
59163
  entries = /* @__PURE__ */ new Map();
59164
+ unreleasable = /* @__PURE__ */ new WeakSet();
58427
59165
  voiceCache;
58428
59166
  /**
58429
59167
  * @param budget The budget in bytes.
@@ -58444,7 +59182,11 @@ var DecodedSampleCache = class {
58444
59182
  touch(sample, cacheKey) {
58445
59183
  let entry = this.entries.get(sample);
58446
59184
  if (entry) this.entries.delete(sample);
58447
- else {
59185
+ if (this.unreleasable.has(sample) || sample.storedForm === "float32" && !(sample instanceof DLSSample)) {
59186
+ if (entry) this.totalBytes -= entry.bytes;
59187
+ return;
59188
+ }
59189
+ if (!entry) {
58448
59190
  entry = {
58449
59191
  bytes: sample.getAudioData().byteLength,
58450
59192
  cacheKeys: /* @__PURE__ */ new Set()
@@ -58461,7 +59203,10 @@ var DecodedSampleCache = class {
58461
59203
  if (sample === keep) continue;
58462
59204
  this.entries.delete(sample);
58463
59205
  this.totalBytes -= entry.bytes;
58464
- sample.releaseDecoded();
59206
+ if (!sample.releaseDecoded()) {
59207
+ this.unreleasable.add(sample);
59208
+ continue;
59209
+ }
58465
59210
  for (const key of entry.cacheKeys) this.voiceCache.delete(key);
58466
59211
  }
58467
59212
  }
@@ -58504,6 +59249,16 @@ const GS_CHORUS_MACRO_PARAMETERS = [
58504
59249
  "sendLevelToDelay"
58505
59250
  ];
58506
59251
  /**
59252
+ * The default GS equalizer parameters (SC-8850 manual p.236).
59253
+ */
59254
+ const DEFAULT_GS_EQUALIZER = {
59255
+ lowFrequency: 0,
59256
+ lowGain: 64,
59257
+ highFrequency: 0,
59258
+ highGain: 64
59259
+ };
59260
+ const GS_EQUALIZER_PARAMETERS = Object.keys(DEFAULT_GS_EQUALIZER);
59261
+ /**
58507
59262
  * The GS delay parameters set by the delay macro.
58508
59263
  */
58509
59264
  const GS_DELAY_MACRO_PARAMETERS = [
@@ -58596,7 +59351,7 @@ var SpessaSynthProcessor = class {
58596
59351
  */
58597
59352
  sharedVoiceSendBuffer;
58598
59353
  /**
58599
- * The insertion processor's left input buffer.
59354
+ * The GS insertion processor's left input buffer.
58600
59355
  * @internal
58601
59356
  */
58602
59357
  insertionInputL;
@@ -58606,6 +59361,16 @@ var SpessaSynthProcessor = class {
58606
59361
  */
58607
59362
  insertionInputR;
58608
59363
  /**
59364
+ * The GS equalizer's left input buffer.
59365
+ * @internal
59366
+ */
59367
+ equalizerInputL;
59368
+ /**
59369
+ * The GS equalizer's right input buffer.
59370
+ * @internal
59371
+ */
59372
+ equalizerInputR;
59373
+ /**
58609
59374
  * The GS reverb processor's input buffer.
58610
59375
  * @internal
58611
59376
  */
@@ -58801,11 +59566,6 @@ var SpessaSynthProcessor = class {
58801
59566
  */
58802
59567
  cachedVoices = /* @__PURE__ */ new Map();
58803
59568
  /**
58804
- * Keeps the decoded sample data under the budget from the options.
58805
- * @internal
58806
- */
58807
- decodedSamples;
58808
- /**
58809
59569
  * Insertion is not used outside SC-88Pro+ MIDIs, this is an optimization.
58810
59570
  * @internal
58811
59571
  */
@@ -58828,6 +59588,11 @@ var SpessaSynthProcessor = class {
58828
59588
  */
58829
59589
  gsInsertionBlock;
58830
59590
  /**
59591
+ * Keeps the decoded sample data under the budget from the options.
59592
+ * @internal
59593
+ */
59594
+ decodedSamples;
59595
+ /**
58831
59596
  * The synthesizer's GS reverb processor.
58832
59597
  *
58833
59598
  * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
@@ -58849,6 +59614,13 @@ var SpessaSynthProcessor = class {
58849
59614
  */
58850
59615
  gsDelayProcessor;
58851
59616
  /**
59617
+ * The synthesizer's GS equalizer processor.
59618
+ *
59619
+ * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
59620
+ * @internal
59621
+ */
59622
+ gsEqualizerProcessor;
59623
+ /**
58852
59624
  * The synthesizer's XG reverb block.
58853
59625
  *
58854
59626
  * Used when {@link GlobalMIDIParameter.system} is `xg`.
@@ -58856,7 +59628,7 @@ var SpessaSynthProcessor = class {
58856
59628
  */
58857
59629
  xgReverbBlock;
58858
59630
  /**
58859
- * The synthesizer's XG reverb block.
59631
+ * The synthesizer's XG chorus block.
58860
59632
  *
58861
59633
  * Used when {@link GlobalMIDIParameter.system} is `xg`.
58862
59634
  * @internal
@@ -58932,6 +59704,7 @@ var SpessaSynthProcessor = class {
58932
59704
  this.gsReverbProcessor = options.gsReverbProcessor ?? new DefaultGSReverb(sampleRate, bufSize);
58933
59705
  this.gsChorusProcessor = options.gsChorusProcessor ?? new DefaultGSChorus(sampleRate, bufSize);
58934
59706
  this.gsDelayProcessor = options.gsDelayProcessor ?? new DefaultGSDelay(sampleRate, bufSize);
59707
+ this.gsEqualizerProcessor = options.gsEqualizerProcessor ?? new DefaultGSEqualizer(sampleRate);
58935
59708
  this.gsInsertionBlock = options.gsInsertionBlock ?? new DefaultGSInsertion(sampleRate, bufSize);
58936
59709
  this.xgReverbBlock = options.xgReverbBlock ?? new DefaultXGReverb(sampleRate, bufSize);
58937
59710
  this.xgChorusBlock = options.xgChorusBlock ?? new DefaultXGChorus(sampleRate, bufSize);
@@ -58941,6 +59714,8 @@ var SpessaSynthProcessor = class {
58941
59714
  this.sharedVoiceSendBuffer = new VoiceSendBuffer(bufSize);
58942
59715
  this.insertionInputL = new Float32Array(bufSize);
58943
59716
  this.insertionInputR = new Float32Array(bufSize);
59717
+ this.equalizerInputL = new Float32Array(bufSize);
59718
+ this.equalizerInputR = new Float32Array(bufSize);
58944
59719
  this.gsReverbInput = new Float32Array(bufSize);
58945
59720
  this.gsChorusInput = new Float32Array(bufSize);
58946
59721
  this.gsDelayInput = new Float32Array(bufSize);
@@ -58978,6 +59753,13 @@ var SpessaSynthProcessor = class {
58978
59753
  return this._voiceCount;
58979
59754
  }
58980
59755
  /**
59756
+ * The decoded sample data kept under {@link SynthProcessorOptions.decodedSampleBudget}, in bytes.
59757
+ * It is only counted when a budget is set.
59758
+ */
59759
+ get decodedSampleBytes() {
59760
+ return this.decodedSamples.totalBytes;
59761
+ }
59762
+ /**
58981
59763
  * Locks or unlocks a given {@link GlobalMIDIParameter}.
58982
59764
  * This prevents any changes to it until it's unlocked.
58983
59765
  * @param parameter The Global MIDI Parameter to lock.
@@ -59010,7 +59792,7 @@ var SpessaSynthProcessor = class {
59010
59792
  * > Refer to the [MIDI Implementation](../../docs/extra/midi-implementation.md)
59011
59793
  * > for the list of supported System Exclusives.
59012
59794
  *
59013
- * @param syx The system exclusive message as an array of bytes.
59795
+ * @param syx The system exclusive message as an array of bytes, without the starting `F0` byte.
59014
59796
  * @param channelOffset The channel offset for the message as they usually can only address the first 16 channels.
59015
59797
  * For example, to send a system exclusive on channel 16,
59016
59798
  * send a system exclusive for channel 0 and set an offset of 16.
@@ -59232,11 +60014,11 @@ var SpessaSynthProcessor = class {
59232
60014
  for (const message of messages) this.processMessage(message, channelOffset, options);
59233
60015
  }
59234
60016
  /**
59235
- * Destroy the synthesizer processor, clearing all channels and voices.
60017
+ * Destroy the synthesizer processor, clearing all channels and voices.
59236
60018
  *
59237
- * > **Warning**
59238
- * >
59239
- * > This is irreversible, so use with caution.
60019
+ * > **Warning**
60020
+ * >
60021
+ * > This is irreversible, so use with caution.
59240
60022
  */
59241
60023
  destroy() {
59242
60024
  this.voices.length = 0;
@@ -59264,6 +60046,9 @@ var SpessaSynthProcessor = class {
59264
60046
  * >
59265
60047
  * > This method overrides the existing System Parameters with the ones from the snapshot.
59266
60048
  *
60049
+ * If this processor has more XG insertion blocks than the snapshot,
60050
+ * the blocks without snapshot data are reset to their defaults.
60051
+ *
59267
60052
  * @param snapshot The snapshot to apply.
59268
60053
  */
59269
60054
  applySnapshot(snapshot) {
@@ -59325,6 +60110,17 @@ var SpessaSynthProcessor = class {
59325
60110
  this.setReverbMacro(4);
59326
60111
  this.setChorusMacro(2);
59327
60112
  this.setDelayMacro(0);
60113
+ if (!this.systemParameters.gsEqualizerLock) {
60114
+ const eq = this.gsEqualizerProcessor;
60115
+ for (const parameter of GS_EQUALIZER_PARAMETERS) {
60116
+ eq[parameter] = DEFAULT_GS_EQUALIZER[parameter];
60117
+ this.callEvent("effectChange", {
60118
+ effect: "gsEqualizer",
60119
+ parameter,
60120
+ value: eq[parameter]
60121
+ });
60122
+ }
60123
+ }
59328
60124
  if (!this.systemParameters.gsInsertionLock) {
59329
60125
  this.gsInsertionBlock.reset();
59330
60126
  this.callEvent("effectChange", {
@@ -59481,10 +60277,10 @@ var SpessaSynthProcessor = class {
59481
60277
  * │ │ │ Chorus │ │
59482
60278
  * │ │ │ Processor ├──────────┐ │
59483
60279
  * │ │ └─┬──────────┬──┘ │ │
59484
- * │ │ │ │ │ │
59485
- * │ │ │ │ │ │
59486
- * │ │ │ │ │ │
59487
- * │ │ │ │ │ │
60280
+ * ┌────┴────┐ │ │ │ │ │
60281
+ * │Equalizer│ │ │ │ │ │
60282
+ * │or Bypass│ │ │ │ │ │
60283
+ * └────┬────┘ │ │ │ │ │
59488
60284
  * │ │ │ 𜸊 𜸊 𜸊
59489
60285
  * │ │ │ ┌────────┴───────┐ ┌─┴─────┴────────┐
59490
60286
  * │ └───┼>┤ Delay ├─>>┤ Reverb │
@@ -59513,6 +60309,7 @@ var SpessaSynthProcessor = class {
59513
60309
  const isXG = this.midiParameters.system === "xg";
59514
60310
  const variationInsertion = this.xgVariationBlock.insertionMode;
59515
60311
  const fx = this.systemParameters.effectsEnabled;
60312
+ const eq = fx && !isXG;
59516
60313
  if (isXG && fx) {
59517
60314
  for (const channel of this.midiChannels) channel.xgInsertionAssigned = false;
59518
60315
  for (const insertion of this.xgInsertionBlocks) {
@@ -59528,6 +60325,10 @@ var SpessaSynthProcessor = class {
59528
60325
  this.insertionInputL.fill(0);
59529
60326
  this.insertionInputR.fill(0);
59530
60327
  }
60328
+ if (eq) {
60329
+ this.equalizerInputL.fill(0);
60330
+ this.equalizerInputR.fill(0);
60331
+ }
59531
60332
  for (const c of this.midiChannels) {
59532
60333
  c.clearVoiceCount();
59533
60334
  if (c.outputDirty) {
@@ -59573,10 +60374,11 @@ var SpessaSynthProcessor = class {
59573
60374
  const channel = this.midiChannels[this.xgVariationBlock.partNumber];
59574
60375
  if (channel) this.processXGInsertion(this.xgVariationBlock, channel, sampleCount);
59575
60376
  }
60377
+ const { reverbGain, chorusGain } = this.systemParameters;
59576
60378
  for (const ch of this.midiChannels) {
59577
60379
  if (!ch.xgInsertionAssigned) continue;
59578
- const revSend = (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
59579
- const choSend = (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
60380
+ const revSend = reverbGain * (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
60381
+ const choSend = chorusGain * (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
59580
60382
  if (revSend <= 0 && choSend <= 0) continue;
59581
60383
  const { outputLeft, outputRight } = ch;
59582
60384
  for (let i = 0; i < sampleCount; i++) {
@@ -59612,6 +60414,15 @@ var SpessaSynthProcessor = class {
59612
60414
  }
59613
60415
  continue;
59614
60416
  }
60417
+ if (eq && midiParameters.eqEnabled) {
60418
+ const equalizerL = this.equalizerInputL;
60419
+ const equalizerR = this.equalizerInputR;
60420
+ for (let i = 0; i < sampleCount; i++) {
60421
+ equalizerL[i] += outputLeft[i];
60422
+ equalizerR[i] += outputRight[i];
60423
+ }
60424
+ continue;
60425
+ }
59615
60426
  if (isXG && !variationInsertion) {
59616
60427
  const gain = midiParameters.dryLevel / 127;
59617
60428
  for (let i = 0; i < sampleCount; i++) {
@@ -59632,7 +60443,12 @@ var SpessaSynthProcessor = class {
59632
60443
  this.xgChorusBlock.process(this.xgChorusInputL, this.xgChorusInputR, left, right, this.xgReverbInputL, this.xgReverbInputR, startIndex, sampleCount);
59633
60444
  this.xgReverbBlock.process(this.xgReverbInputL, this.xgReverbInputR, left, right, startIndex, sampleCount);
59634
60445
  } else {
59635
- if (this.gsInsertionActive) this.gsInsertionBlock.process(insertionInputL, insertionInputR, left, right, gsReverbInput, gsChorusInput, gsDelayInput, startIndex, sampleCount);
60446
+ if (this.gsInsertionActive) {
60447
+ const insertion = this.gsInsertionBlock;
60448
+ const toEqualizer = eq && insertion.sendEQSwitch;
60449
+ insertion.process(insertionInputL, insertionInputR, toEqualizer ? this.equalizerInputL : left, toEqualizer ? this.equalizerInputR : right, gsReverbInput, gsChorusInput, gsDelayInput, toEqualizer ? 0 : startIndex, sampleCount);
60450
+ }
60451
+ if (eq) this.gsEqualizerProcessor.process(this.equalizerInputL, this.equalizerInputR, left, right, startIndex, sampleCount);
59636
60452
  this.gsChorusProcessor.process(gsChorusInput, left, right, gsReverbInput, gsDelayInput, startIndex, sampleCount);
59637
60453
  if (this.gsDelayActive) this.gsDelayProcessor.process(gsDelayInput, left, right, gsReverbInput, startIndex, sampleCount);
59638
60454
  this.gsReverbProcessor.process(gsReverbInput, left, right, startIndex, sampleCount);
@@ -59692,13 +60508,6 @@ var SpessaSynthProcessor = class {
59692
60508
  this.soundBankManager.refresh();
59693
60509
  }
59694
60510
  /**
59695
- * The decoded sample data kept under {@link SynthProcessorOptions.decodedSampleBudget}, in bytes.
59696
- * It is only counted when a budget is set.
59697
- */
59698
- get decodedSampleBytes() {
59699
- return this.decodedSamples.totalBytes;
59700
- }
59701
- /**
59702
60511
  * Clears the synthesizer's voice cache.
59703
60512
  * This can be used to hear the changes after editing a {@link BasicSoundBank}
59704
60513
  */
@@ -59730,7 +60539,7 @@ var SpessaSynthProcessor = class {
59730
60539
  */
59731
60540
  purgeCachedPatch(patch) {
59732
60541
  const patchKey = this.getCachedVoiceIndex(patch, 0, 0);
59733
- for (const key of this.cachedVoices.keys()) if (key % 2113536 === patchKey) this.cachedVoices.delete(key);
60542
+ for (const key of this.cachedVoices.keys()) if (key % 4194304 === patchKey) this.cachedVoices.delete(key);
59734
60543
  }
59735
60544
  /**
59736
60545
  * Sets a global MIDI parameter of the synthesizer.
@@ -60210,16 +61019,10 @@ var SpessaSynthProcessor = class {
60210
61019
  }, "gs");
60211
61020
  }
60212
61021
  getCachedVoiceIndex(patch, midiNote, velocity) {
60213
- let bankMSB = patch.bankMSB;
60214
- let bankLSB = patch.bankLSB;
60215
- const { isGMGSDrum, program } = patch;
60216
- if (isGMGSDrum) {
60217
- bankMSB = 128;
60218
- bankLSB = 0;
60219
- }
60220
- return bankMSB + bankLSB * 129 + program * 16512 + 2113536 * midiNote + 270532608 * velocity;
61022
+ const { isGMGSDrum, program, bankLSB } = patch;
61023
+ return (isGMGSDrum ? 128 + patch.bankMSB : patch.bankMSB) + bankLSB * 256 + program * 32768 + 4194304 * midiNote + 536870912 * velocity;
60221
61024
  }
60222
61025
  };
60223
- export { BasicInstrument, BasicInstrumentZone, BasicLinkedZone, BasicMIDI, BasicPreset, BasicPresetZone, BasicSample, BasicSoundBank, BasicZone, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_DRUM_REVERB, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, DEFAULT_MIDI_CONTROLLERS, DEFAULT_SOUND_BANK_CLONE_OPTIONS, DEFAULT_SOUND_BANK_LOAD_OPTIONS, DEFAULT_SYNTH_MODE, DefaultGSChorus, DefaultGSDelay, DefaultGSInsertion, DefaultGSReverb, DefaultXGChorus, DefaultXGEffect, DefaultXGInsertion, DefaultXGReverb, DefaultXGSystemEffect, DefaultXGVariation, DrumParameterUtils, EmptySample, GENERATORS_AMOUNT, GSInsertionControlSources, Generator, GeneratorLimits, GeneratorTypes, INSTRUMENT_ONLY_GENERATORS, IndexedByteArray, InterpolationTypes, MAX_GENERATOR, MIDIBuilder, MIDIChannel, MIDIControllers, MIDIMessage, MIDIMessageTypes, MIDIPatchTools, MIDITrack, MIDIUtils, MIDI_DRUM_CHANNEL, Modulator, ModulatorControllerSources, ModulatorCurveTypes, ModulatorSource, ModulatorTransformTypes, NonRegisteredParameterTypesLSB, NonRegisteredParameterTypesMSB, RegisteredParameterTypes, SPESSASYNTH_GAIN_FACTOR, SPESSA_BUFSIZE, SampleTypes, SoundBankCloner, SoundBankLoader, SoundBankManager, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, VOICE_CAP, audioToWav };
61026
+ export { BasicInstrument, BasicInstrumentZone, BasicLinkedZone, BasicMIDI, BasicPreset, BasicPresetZone, BasicSample, BasicSoundBank, BasicZone, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_DRUM_REVERB, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, DEFAULT_MIDI_CONTROLLERS, DEFAULT_SOUND_BANK_CLONE_OPTIONS, DEFAULT_SOUND_BANK_LOAD_OPTIONS, DEFAULT_SYNTH_MODE, DefaultGSChorus, DefaultGSDelay, DefaultGSEqualizer, DefaultGSInsertion, DefaultGSReverb, DefaultXGChorus, DefaultXGEffect, DefaultXGInsertion, DefaultXGReverb, DefaultXGSystemEffect, DefaultXGVariation, DrumParameterUtils, EmptySample, GENERATORS_AMOUNT, GSInsertionControlSources, Generator, GeneratorLimits, GeneratorTypes, INSTRUMENT_ONLY_GENERATORS, IndexedByteArray, InterpolationTypes, MAX_GENERATOR, MIDIBuilder, MIDIChannel, MIDIControllers, MIDIMessage, MIDIMessageTypes, MIDIPatchTools, MIDITrack, MIDIUtils, MIDI_DRUM_CHANNEL, Modulator, ModulatorControllerSources, ModulatorCurveTypes, ModulatorSource, ModulatorTransformTypes, NonRegisteredParameterTypesLSB, NonRegisteredParameterTypesMSB, RegisteredParameterTypes, SPESSASYNTH_GAIN_FACTOR, SPESSA_BUFSIZE, SampleTypes, SoundBankCloner, SoundBankLoader, SoundBankManager, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, VOICE_CAP, audioToWav };
60224
61027
 
60225
61028
  //# sourceMappingURL=index.js.map