spessasynth_core 4.4.5 → 4.4.6

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++) {
@@ -2170,6 +2259,7 @@ function resetChannelInternal(sendCCEvents = true) {
2170
2259
  this.resetMIDIParameter("dryLevel");
2171
2260
  this.octaveTuning.fill(0);
2172
2261
  this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
2262
+ this.portamentoForce = false;
2173
2263
  this.resetDrumParams();
2174
2264
  this.resetGeneratorOverrides();
2175
2265
  this.resetGeneratorOffsets();
@@ -2293,7 +2383,7 @@ var DrumParameterUtils = class {
2293
2383
  dest.rxNoteOn = source.rxNoteOn;
2294
2384
  }
2295
2385
  /**
2296
- * Checks if this user drum parameter is the default.
2386
+ * Checks if this drum parameter is the default.
2297
2387
  * @param param the param to check.
2298
2388
  * @param midiNote the MIDI note of this parameter.
2299
2389
  */
@@ -2389,6 +2479,7 @@ function applySnapshotInternal(midi, snapshot) {
2389
2479
  // src/midi/midi_tools/get_note_times.ts
2390
2480
  /**
2391
2481
  * Calculates all note times in seconds.
2482
+ * MIDI ports are ignored, every port is merged into the same 16 channels.
2392
2483
  * @param midi the midi to use
2393
2484
  * @param minDrumLength the shortest a drum note (channel 10) can be, in seconds.
2394
2485
  * @returns an array of 16 channels, each channel containing its notes,
@@ -2462,26 +2553,32 @@ var MIDIPatchTools = class MIDIPatchTools {
2462
2553
  /**
2463
2554
  * Converts a given {@link MIDIPatch} to a string.
2464
2555
  * The format is:
2465
- * - `DRUM:program` for `isGMGSDrum` set to `true`.
2556
+ * - `DRUM:bankMSB:bankLSB:program` for `isGMGSDrum` set to `true`.
2466
2557
  * - `bankLSB:bankMSB:program` for `isGMGSDrum` set to `false`.
2558
+ * @param patch The patch to convert.
2467
2559
  */
2468
2560
  static toMIDIString(patch) {
2469
- if (patch.isGMGSDrum) return `DRUM:${patch.program}`;
2561
+ if (patch.isGMGSDrum) return `DRUM:${patch.bankMSB}:${patch.bankLSB}:${patch.program}`;
2470
2562
  return `${patch.bankLSB}:${patch.bankMSB}:${patch.program}`;
2471
2563
  }
2472
2564
  /**
2473
2565
  * Gets {@link MIDIPatch} from a given string.
2566
+ * @param string The string in the {@link MIDIPatchTools.toMIDIString} format.
2474
2567
  */
2475
2568
  static fromMIDIString(string) {
2476
2569
  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
- } : {
2570
+ if (string.startsWith("DRUM")) {
2571
+ if (parts.length !== 4 && parts.length !== 2) throw new Error(`Invalid MIDI string: ${string}`);
2572
+ const long = parts.length === 4;
2573
+ return {
2574
+ bankMSB: long ? Number.parseInt(parts[1]) : 0,
2575
+ bankLSB: long ? Number.parseInt(parts[2]) : 0,
2576
+ program: Number.parseInt(parts[parts.length - 1]),
2577
+ isGMGSDrum: true
2578
+ };
2579
+ }
2580
+ if (parts.length !== 3) throw new Error(`Invalid MIDI string: ${string}`);
2581
+ return {
2485
2582
  bankLSB: Number.parseInt(parts[0]),
2486
2583
  bankMSB: Number.parseInt(parts[1]),
2487
2584
  program: Number.parseInt(parts[2]),
@@ -2493,12 +2590,14 @@ var MIDIPatchTools = class MIDIPatchTools {
2493
2590
  * The format is:
2494
2591
  * - `<MIDIPatch string> D <name>` for `isDrum` set to `true`.
2495
2592
  * - `<MIDIPatch string> M <name>` for `isDrum` set to `false`.
2593
+ * @param patch The patch to convert.
2496
2594
  */
2497
2595
  static toFullMIDIString(patch) {
2498
2596
  return `${this.toMIDIString(patch)} ${patch.isDrum ? "D" : "M"} ${patch.name}`;
2499
2597
  }
2500
2598
  /**
2501
2599
  * Gets {@link MIDIPatchFull} from a given string.
2600
+ * @param string The string in the {@link MIDIPatchTools.toFullMIDIString} format.
2502
2601
  */
2503
2602
  static fromFullMIDIString(string) {
2504
2603
  const firstSpace = string.indexOf(" ");
@@ -2515,14 +2614,18 @@ var MIDIPatchTools = class MIDIPatchTools {
2515
2614
  }
2516
2615
  /**
2517
2616
  * Checks if two MIDI patches represent the same one.
2617
+ * @param patch1 The first patch.
2618
+ * @param patch2 The second patch.
2518
2619
  */
2519
2620
  static matches(patch1, patch2) {
2520
- if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program;
2621
+ if (patch1.isGMGSDrum || patch2.isGMGSDrum) return patch1.isGMGSDrum === patch2.isGMGSDrum && patch1.program === patch2.program && patch1.bankMSB === patch2.bankMSB && patch1.bankLSB === patch2.bankLSB;
2521
2622
  return patch1.program === patch2.program && patch1.bankLSB === patch2.bankLSB && patch1.bankMSB === patch2.bankMSB;
2522
2623
  }
2523
2624
  /**
2524
2625
  * A comparison function for `.sort()` or `.toSorted()`,
2525
- * ordering the patches in ascending order.
2626
+ * ordering the patches in ascending order: by program, then bank MSB, then bank LSB, with GM/GS drums last.
2627
+ * @param a The first patch.
2628
+ * @param b The second patch.
2526
2629
  */
2527
2630
  static compare(a, b) {
2528
2631
  if (a.isGMGSDrum && !b.isGMGSDrum) return 1;
@@ -2533,6 +2636,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2533
2636
  }
2534
2637
  /**
2535
2638
  * Checks if the given {@link MIDIPatchFull} is an XG/GM2 drum patch.
2639
+ * @param p The patch to check.
2536
2640
  */
2537
2641
  static isXGDrum(p) {
2538
2642
  return p.isDrum && !p.isGMGSDrum;
@@ -2544,7 +2648,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2544
2648
  * @param patch The {@link MIDIPatch} to select.
2545
2649
  * @param system The MIDI system to select for.
2546
2650
  * @returns The selected patch.
2547
- * @throws Error An error if the array is empty,
2651
+ * @throws Error An error if the array is empty.
2548
2652
  */
2549
2653
  static selectPatch(patches, patch, system) {
2550
2654
  if (patches.length === 0) throw new Error("No presets!");
@@ -2563,7 +2667,7 @@ var MIDIPatchTools = class MIDIPatchTools {
2563
2667
  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
2668
  };
2565
2669
  if (isGMGSDrum) {
2566
- let p = patches.find((p) => p.isGMGSDrum && p.program === program);
2670
+ let p = patches.find((p) => p.isGMGSDrum && p.program === program && p.bankLSB === bankLSB) ?? patches.find((p) => p.isGMGSDrum && p.program === program);
2567
2671
  if (p) {
2568
2672
  returnReplacement(p);
2569
2673
  return p;
@@ -2811,7 +2915,7 @@ var MIDIUtils = class MIDIUtils {
2811
2915
  * @param channel The MIDI channel number.
2812
2916
  * @param rpn The 14-bit RPN number.
2813
2917
  * @param value The 14-bit value for that number.
2814
- * @returns The analysis result(s) of the message.
2918
+ * @returns The analysis result of the message.
2815
2919
  */
2816
2920
  static analyzeRPN(channel, rpn, value) {
2817
2921
  switch (rpn) {
@@ -2850,7 +2954,7 @@ var MIDIUtils = class MIDIUtils {
2850
2954
  * @param value The 14-bit value for that number.
2851
2955
  * @param drumPitchStep The drum pitch coarse resolution in semitones.
2852
2956
  * 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.
2957
+ * @returns The analysis result of the message.
2854
2958
  */
2855
2959
  static analyzeNRPN(channel, nrpn, value, drumPitchStep = .5) {
2856
2960
  const msb = nrpn >> 7;
@@ -4877,7 +4981,7 @@ var MIDIEditor = class {
4877
4981
  * GM -> GS -> note data
4878
4982
  *
4879
4983
  * 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
4984
+ * Meaningful GM ones are replaced with the fallback reset in-place
4881
4985
  * (unless the `system` parameter is explicitly set).
4882
4986
  */
4883
4987
  meaningfulResets = new Array();
@@ -5946,7 +6050,7 @@ var MIDITrack = class MIDITrack {
5946
6050
  */
5947
6051
  port = 0;
5948
6052
  /**
5949
- * A set that contains the MIDI channel numbers used by this track.
6053
+ * A set that contains the MIDI channel numbers (0-15) that have note on events in this track.
5950
6054
  */
5951
6055
  channels = /* @__PURE__ */ new Set();
5952
6056
  /**
@@ -5975,7 +6079,7 @@ var MIDITrack = class MIDITrack {
5975
6079
  }
5976
6080
  /**
5977
6081
  * Adds events to the track.
5978
- * @param index The index at which to add these event.
6082
+ * @param index The index at which to add these events.
5979
6083
  * @param events The events to add.
5980
6084
  */
5981
6085
  addEvents(index, ...events) {
@@ -7043,7 +7147,7 @@ var BasicSample = class {
7043
7147
  *
7044
7148
  * > **Warning**
7045
7149
  * >
7046
- * > Do not change this value directly. use {@link BasicSample.setSampleType},
7150
+ * > Do not change this value directly. Use {@link BasicSample.setSampleType},
7047
7151
  * > {@link BasicSample.setLinkedSample} or {@link BasicSample.unlinkSample} instead.
7048
7152
  */
7049
7153
  sampleType;
@@ -7105,7 +7209,7 @@ var BasicSample = class {
7105
7209
  * @param sampleRate The sample's rate in Hz.
7106
7210
  * @param originalKey The sample's pitch as a MIDI note number.
7107
7211
  * @param pitchCorrection The sample's pitch correction in cents.
7108
- * @param sampleType The sample's type, an enum that defines the sample type/compression.
7212
+ * @param sampleType The sample's type, one of the {@link SampleTypes}.
7109
7213
  * @param loopStart The sample's loop start relative to the sample start in sample points.
7110
7214
  * @param loopEnd The sample's loop end relative to the sample start in sample points. Exclusive.
7111
7215
  */
@@ -7705,7 +7809,7 @@ var ModulatorSource = class ModulatorSource {
7705
7809
  /**
7706
7810
  * If this field is set true, the direction of the controller should be from the maximum value to the minimum value. So, for
7707
7811
  * 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.
7812
+ * controller output, and the Key Number value of 127 corresponds to the minimum possible controller output.
7709
7813
  */
7710
7814
  isNegative;
7711
7815
  /**
@@ -8519,7 +8623,7 @@ var BasicPreset = class BasicPreset {
8519
8623
  * >
8520
8624
  * > Consider reading about [the DLS conversion problem](../../../docs/extra/dls-conversion-problem.md).
8521
8625
  *
8522
- * @returns The instrument containing the flattened zones. If flattening was successful, it should exactly the same as this preset.
8626
+ * @returns The instrument containing the flattened zones. If flattening was successful, it should sound exactly the same as this preset.
8523
8627
  */
8524
8628
  toFlattenedInstrument() {
8525
8629
  const addUnique = (main, adder) => {
@@ -8710,7 +8814,7 @@ var BasicInstrument = class {
8710
8814
  return zone;
8711
8815
  }
8712
8816
  /**
8713
- * Links the instrument ta a given preset.
8817
+ * Links the instrument to a given preset.
8714
8818
  * @param preset The preset to link to.
8715
8819
  * @internal
8716
8820
  */
@@ -10170,8 +10274,8 @@ var DownloadableSoundsArticulation = class extends DLSVerifier {
10170
10274
  for (const modulator of zone.modulators) ConnectionBlock.fromSFModulator(modulator, this);
10171
10275
  }
10172
10276
  /**
10173
- * Chunk list for the region/instrument (containing lar2 or lart)
10174
- * @param chunks
10277
+ * Reads the articulation from a chunk list.
10278
+ * @param chunks Chunk list for the region/instrument (containing lar2 or lart)
10175
10279
  */
10176
10280
  read(chunks) {
10177
10281
  const lart = RIFFChunk.findListType(chunks, "lart");
@@ -10493,7 +10597,6 @@ var DownloadableSoundsRegion = class DownloadableSoundsRegion extends DLSVerifie
10493
10597
  // src/soundbank/downloadable_sounds/instrument.ts
10494
10598
  /**
10495
10599
  * Represents a proper DLS instrument, with regions and articulation.
10496
- * DLS
10497
10600
  */
10498
10601
  var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DLSVerifier {
10499
10602
  articulation = new DownloadableSoundsArticulation();
@@ -10765,7 +10868,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
10765
10868
  for (let i = 0; i < bank.samples.length; i++) {
10766
10869
  const s = bank.samples[i];
10767
10870
  dls.samples.push(DownloadableSoundsSample.fromSFSample(s));
10768
- progressFunc?.(i / bank.samples.length);
10871
+ progressFunc?.((i + 1) / bank.samples.length);
10769
10872
  }
10770
10873
  for (const p of bank.presets) dls.instruments.push(DownloadableSoundsInstrument.fromSFPreset(p, bank.samples));
10771
10874
  SpessaLog.info("%cConversion complete!", ConsoleColors.recognized);
@@ -10799,12 +10902,12 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
10799
10902
  let written = 0;
10800
10903
  for (const s of this.samples) {
10801
10904
  const out = s.write();
10905
+ written++;
10802
10906
  options.progressFunction?.(written / this.samples.length);
10803
10907
  SpessaLog.info(`%cWrote sample %c${written}. ${s.name}%c of %c${this.samples.length}.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.recognized);
10804
10908
  ptblOffsets.push(currentIndex);
10805
10909
  currentIndex += out.reduce((sum, cur) => sum + cur.length, 0);
10806
10910
  samples.push(...out);
10807
- written++;
10808
10911
  }
10809
10912
  const wvpl = RIFFChunk.getParts("wvpl", samples, false, true);
10810
10913
  SpessaLog.info("%cSucceeded!", ConsoleColors.recognized);
@@ -11016,9 +11119,8 @@ var SoundBankCloner = class {
11016
11119
  * This module handles parsing and writing SoundFont2 (`.sf2`, `.sf3` and `.sfogg`) files.
11017
11120
  * It represents a single sound bank.
11018
11121
  *
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).
11122
+ * 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
11123
  *
11021
-
11022
11124
  * **Specifications:**
11023
11125
  *
11024
11126
  * - [SoundFont2 Specification](http://www.synthfont.com/sfspec24.pdf)
@@ -11062,7 +11164,7 @@ var BasicSoundBank = class BasicSoundBank {
11062
11164
  }
11063
11165
  };
11064
11166
  /**
11065
- * An array of all presets in the bank, ordered by bank and preset number.
11167
+ * An array of all presets in the bank, ordered by program and bank number, with GM/GS drum presets last.
11066
11168
  */
11067
11169
  presets = [];
11068
11170
  /**
@@ -11129,8 +11231,9 @@ var BasicSoundBank = class BasicSoundBank {
11129
11231
  return b;
11130
11232
  }
11131
11233
  /**
11132
- * Creates a simple sound bank with a single saw wave preset.
11234
+ * Creates a simple SF2 file with a single saw wave preset.
11133
11235
  * Useful for testing synthesizer's functionality without providing a file.
11236
+ * @returns The binary representation of the SF2 file.
11134
11237
  */
11135
11238
  static getSampleSoundBankFile() {
11136
11239
  const font = new BasicSoundBank();
@@ -11158,12 +11261,17 @@ var BasicSoundBank = class BasicSoundBank {
11158
11261
  return font.writeSF2();
11159
11262
  }
11160
11263
  /**
11161
- * Copies a given sound bank, deeply cloning all presets instruments and samples.
11264
+ * Copies a given sound bank, cloning all presets, instruments and samples.
11265
+ * The sample data is shared with the original, not copied.
11266
+ * Elements sharing a name are all copied, the repeats get a free name like `Piano-1`.
11162
11267
  * @param bank The sound bank to copy.
11163
11268
  */
11164
11269
  static copyFrom(bank) {
11165
11270
  const copied = new BasicSoundBank(bank.type);
11166
- const cloner = new SoundBankCloner(copied);
11271
+ const cloner = new SoundBankCloner(copied, {
11272
+ reuseByName: false,
11273
+ reuseChildrenByName: false
11274
+ });
11167
11275
  for (const p of bank.presets) cloner.clonePreset(p);
11168
11276
  copied.soundBankInfo = { ...bank.soundBankInfo };
11169
11277
  copied.defaultModulators = bank.defaultModulators.map((m) => Modulator.copyFrom(m));
@@ -11189,8 +11297,8 @@ var BasicSoundBank = class BasicSoundBank {
11189
11297
  *
11190
11298
  * > **Warning**
11191
11299
  * >
11192
- * > Note that decompressing (sample format `pcm`) usually results
11193
- * > in permanent sample quality loss!
11300
+ * > Note that compressing (sample format `compressed`) usually results
11301
+ * > in permanent sample quality loss! Decompressing does not restore it.
11194
11302
  * > This method is memory and CPU intensive with large sound banks.
11195
11303
  *
11196
11304
  * @param options Options associated with setting the sample format.
@@ -11234,7 +11342,7 @@ var BasicSoundBank = class BasicSoundBank {
11234
11342
  }
11235
11343
  /**
11236
11344
  * 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.
11345
+ * Sample data is always written in the S16LE PCM encoding.
11238
11346
  *
11239
11347
  * > **Note**
11240
11348
  * >
@@ -11357,16 +11465,15 @@ var BasicSoundBank = class BasicSoundBank {
11357
11465
  /**
11358
11466
  * Trims the sound bank _in-place_ to only contain samples in a given MIDI file.
11359
11467
  *
11360
-
11361
11468
  * Absent presets will be removed from the sound bank,
11362
11469
  * and samples that don't get activated in the remaining presets will be removed as well.
11363
11470
  *
11364
11471
  * > **Note**
11365
11472
  * >
11366
- * > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}
11473
+ * > This exact type is returned from {@link BasicMIDI.getUsedProgramsAndKeys}.
11367
11474
  * > Consider reading the page for more explanation about the parameter.
11368
11475
  *
11369
- * @param presetData - A `Map`: `BasicPreset` -> `Set<"key-velocity">`.
11476
+ * @param presetData A `Map`: `BasicPreset` -> `Map<MIDI note, Set<velocity>>`.
11370
11477
  * Absent presets will be removed from the sound bank,
11371
11478
  * and samples that don't get activated in the remaining presets will be removed as well.
11372
11479
  */
@@ -13766,11 +13873,13 @@ const DEFAULT_RMIDI_WRITE_OPTIONS = {
13766
13873
  */
13767
13874
  function writeRMIDIInternal(mid, soundBankBinary, options) {
13768
13875
  const metadata = { ...options.metadata };
13769
- if (options.correctBankOffset && !options.soundBank) throw new Error("Sound bank must be provided if correcting bank offset.");
13770
13876
  SpessaLog.group("%cWriting the RMIDI File...", ConsoleColors.info);
13771
13877
  SpessaLog.info("metadata", metadata);
13772
13878
  SpessaLog.info("Initial bank offset", mid.bankOffset);
13773
- if (options.correctBankOffset && options.soundBank) correctBankOffsetInternal(mid, options.bankOffset, options.soundBank);
13879
+ if (options.correctBankOffset) {
13880
+ if (options.soundBank) correctBankOffsetInternal(mid, options.bankOffset, options.soundBank);
13881
+ else SpessaLog.warn("No sound bank provided, skipping the bank offset correction.");
13882
+ }
13774
13883
  const newMid = new IndexedByteArray(mid.writeMIDI());
13775
13884
  metadata.name ??= mid.getName();
13776
13885
  metadata.creationDate ??= /* @__PURE__ */ new Date();
@@ -13958,7 +14067,8 @@ var BasicMIDI = class BasicMIDI {
13958
14067
  * The loop points (in ticks) of the sequence, including both start and end points.
13959
14068
  *
13960
14069
  * 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.
14070
+ * Current looping detection is: CC 2/4 (Touhou), CC 111 (RPG Maker), CC 116/117 and 118/119 (EMIDI/XMI)
14071
+ * and the "start", "loopStart" and "loopEnd" markers.
13962
14072
  */
13963
14073
  loop = {
13964
14074
  start: 0,
@@ -13976,6 +14086,7 @@ var BasicMIDI = class BasicMIDI {
13976
14086
  format = 0;
13977
14087
  /**
13978
14088
  * The RMID (Resource-Interchangeable MIDI) info data, if the file is RMID formatted.
14089
+ * XMF metadata and converted tracker modules are stored here as well.
13979
14090
  * Otherwise, this object is empty.
13980
14091
  * Info type: Chunk data as a binary array.
13981
14092
  *
@@ -14047,7 +14158,7 @@ var BasicMIDI = class BasicMIDI {
14047
14158
  * This function reads the MIDI file format, extracts the header and track chunks,
14048
14159
  * and populates the BasicMIDI instance with the parsed data.
14049
14160
  * It supports Standard MIDI Files (SMF), RIFF MIDI (RMIDI), and Extensible Music Format (XMF).
14050
- * It also handles embedded soundbanks in RMIDI files.
14161
+ * It also handles embedded soundbanks in RMIDI and XMF files.
14051
14162
  * Tracker modules (ProTracker MOD, Fast Tracker 2 XM, Scream Tracker 3 S3M) are converted
14052
14163
  * into MIDI with an embedded sound bank.
14053
14164
  */
@@ -14086,7 +14197,7 @@ var BasicMIDI = class BasicMIDI {
14086
14197
  return m;
14087
14198
  }
14088
14199
  /**
14089
- * Copies another instance this `BasicMIDI` instance, including track data.
14200
+ * Copies another instance into this `BasicMIDI` instance, including track data.
14090
14201
  * @param mid The MIDI to copy.
14091
14202
  */
14092
14203
  copyFrom(mid) {
@@ -14159,7 +14270,7 @@ var BasicMIDI = class BasicMIDI {
14159
14270
  }
14160
14271
  /**
14161
14272
  * 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.
14273
+ * This caches all the needed voices for playing back this sequence, resulting in a smooth playback.
14163
14274
  * The sequencer calls this function by default when loading the songs. (it can be disabled: {@link SpessaSynthSequencer.preload}).
14164
14275
  * @param synth The synthesizer to preload.
14165
14276
  */
@@ -14189,6 +14300,12 @@ var BasicMIDI = class BasicMIDI {
14189
14300
  }
14190
14301
  /**
14191
14302
  * Calculates all note times in seconds.
14303
+ *
14304
+ * > **Note**
14305
+ * >
14306
+ * > MIDI ports are ignored, so the channels of every port are merged into the 16 channels.
14307
+ * > For example, channel 1 of port 2 is returned as channel 1.
14308
+ *
14192
14309
  * @param minDrumLength In seconds, represents the minimum allowed time for a drum note,
14193
14310
  * since they sometimes have a length of 0.
14194
14311
  * @returns An array of 16 channels, each channel containing its notes,
@@ -14241,7 +14358,7 @@ var BasicMIDI = class BasicMIDI {
14241
14358
  return writeMIDIInternal(this);
14242
14359
  }
14243
14360
  /**
14244
- * Writes out an RMIDI file (MIDI + SF2).
14361
+ * Writes out an RMIDI file (MIDI + SF2 or DLS).
14245
14362
  * [See more info about this format](https://github.com/spessasus/sf2-rmidi-specification#readme).
14246
14363
  *
14247
14364
  * Note that this method modifies the MIDI file in-place.
@@ -14250,11 +14367,16 @@ var BasicMIDI = class BasicMIDI {
14250
14367
  * The method is called on a `BasicMIDI` instance;
14251
14368
  * that instance is the MIDI file to embed.
14252
14369
  *
14370
+ * > **Warning**
14371
+ * >
14372
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
14373
+ * > Bank offset correction (on by default) needs `soundBank`. Without it, the correction is skipped with a warning.
14374
+ *
14253
14375
  * > **Tip**
14254
14376
  * >
14255
14377
  * > 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!)
14378
+ * > Consider also using compression (like shown in example) to save even more space.
14379
+ * > (using both of these methods, I managed to shrink a 1GB sound bank into a 5MB RMIDI!)
14258
14380
  *
14259
14381
  * @param soundBankBinary The binary sound bank (SF2 or DLS) to embed into the file.
14260
14382
  * @param configuration Extra options for writing the file.
@@ -14292,7 +14414,7 @@ var BasicMIDI = class BasicMIDI {
14292
14414
  *
14293
14415
  * // trim the soundfont
14294
14416
  * soundBank.trim(midi.getUsedProgramsAndKeys(soundBank));
14295
- * // write out with compression to save space (0.5 is medium quality)
14417
+ * // write out with compression to save space
14296
14418
  * await soundBank.setSampleFormat({
14297
14419
  * format: "compressed",
14298
14420
  * compressionFunction: SampleEncodingFunction // Remember to get your compression function
@@ -14330,6 +14452,8 @@ var BasicMIDI = class BasicMIDI {
14330
14452
  * and inserts/deletes messages appropriately.
14331
14453
  *
14332
14454
  * This modifies the MIDI sequence _in-place_.
14455
+ *
14456
+ * @param opts The modifications to apply.
14333
14457
  */
14334
14458
  modify(opts) {
14335
14459
  new MIDIEditor(this, opts).apply();
@@ -14339,8 +14463,10 @@ var BasicMIDI = class BasicMIDI {
14339
14463
  * This means changing the programs and controllers if they are locked.
14340
14464
  *
14341
14465
  * 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
14466
+ * this will remove all program changes for channel 1 and add one before the channel's first note on
14467
+ * (and again after every system reset) to change the program to
14343
14468
  * `Drawbar organ` (using bank MSB/LSB and program change).
14469
+ * Muted channels are removed from the sequence.
14344
14470
  *
14345
14471
  * > **Warning**
14346
14472
  * >
@@ -14413,11 +14539,31 @@ var BasicMIDI = class BasicMIDI {
14413
14539
  * > **Note**
14414
14540
  * >
14415
14541
  * > This sets the Info encoding to `utf-8`.
14542
+ * > Text chunks that are already present get re-encoded from their old encoding (IENC) to UTF-8,
14543
+ * > so they stay readable. Without IENC, they are assumed to be UTF-8 already, like {@link BasicMIDI.getRMIDInfo} does.
14544
+ *
14545
+ * > **Warning**
14546
+ * >
14547
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder and TextEncoder.
14416
14548
  *
14417
14549
  * @param infoType The type to set.
14418
14550
  * @param infoData The value to set it to.
14419
14551
  */
14420
14552
  setRMIDInfo(infoType, infoData) {
14553
+ const oldEncoding = this.infoEncoding;
14554
+ if (oldEncoding !== void 0 && !/^utf-?8$/i.test(oldEncoding.trim())) try {
14555
+ const decoder = new TextDecoder(oldEncoding.trim());
14556
+ const encoder = new TextEncoder();
14557
+ for (const key of Object.keys(this.rmidiInfo)) {
14558
+ if (key === "picture" || key === "creationDate" || key === "infoEncoding") continue;
14559
+ const data = this.rmidiInfo[key];
14560
+ if (!data) continue;
14561
+ const text = decoder.decode(data[data.length - 1] === 0 ? data.subarray(0, -1) : data);
14562
+ this.rmidiInfo[key] = new Uint8Array([...encoder.encode(text), 0]);
14563
+ }
14564
+ } catch (error) {
14565
+ SpessaLog.warn(`Failed to re-encode the RMIDI info from ${oldEncoding}: ${error}`);
14566
+ }
14421
14567
  this.rmidiInfo.infoEncoding = getStringBytes("utf-8", true);
14422
14568
  if (infoType === "picture") this.rmidiInfo.picture = new Uint8Array(infoData);
14423
14569
  else if (infoType === "creationDate") this.rmidiInfo.creationDate = getStringBytes(toISODateString(infoData), true);
@@ -14428,6 +14574,11 @@ var BasicMIDI = class BasicMIDI {
14428
14574
  }
14429
14575
  /**
14430
14576
  * Gets a given chunk from the RMIDI information.
14577
+ *
14578
+ * > **Warning**
14579
+ * >
14580
+ * > Do not call in audioWorkletGlobalScope as it uses TextDecoder.
14581
+ *
14431
14582
  * @param infoType The metadata type.
14432
14583
  * @returns `string`, `Date`, `ArrayBuffer` or undefined if the info is not set.
14433
14584
  */
@@ -14455,7 +14606,7 @@ var BasicMIDI = class BasicMIDI {
14455
14606
  * > if you are not editing the MIDI file in your loop.
14456
14607
  * > It is usually a faster solution and allows custom loops.
14457
14608
  *
14458
- * If the track data is being edited, remember to call {@link BasicMIDI.flush} it after editing!
14609
+ * If the track data is being edited, remember to call {@link BasicMIDI.flush} after editing!
14459
14610
  *
14460
14611
  * @param callback The callback function to process each event.
14461
14612
  */
@@ -14562,11 +14713,15 @@ var BasicMIDI = class BasicMIDI {
14562
14713
  case MIDIMessageTypes.controllerChange:
14563
14714
  switch (e.data[0]) {
14564
14715
  case MIDIControllers.breathController:
14565
- case MIDIControllers.undefinedCC111LSB:
14566
14716
  if (e.data[1] === 0) loopStart = e.ticks;
14567
14717
  break;
14568
- case MIDIControllers.undefinedCC116LSB:
14569
- case MIDIControllers.undefinedCC118LSB:
14718
+ case MIDIControllers.undefinedCC111:
14719
+ if (e.data[1] !== 0) break;
14720
+ loopStart = e.ticks;
14721
+ loopType = "soft";
14722
+ break;
14723
+ case MIDIControllers.undefinedCC116:
14724
+ case MIDIControllers.undefinedCC118:
14570
14725
  loopStart = e.ticks;
14571
14726
  break;
14572
14727
  case MIDIControllers.footController:
@@ -14579,8 +14734,8 @@ var BasicMIDI = class BasicMIDI {
14579
14734
  loopType = "hard";
14580
14735
  }
14581
14736
  break;
14582
- case MIDIControllers.undefinedCC117LSB:
14583
- case MIDIControllers.undefinedCC119LSB:
14737
+ case MIDIControllers.undefinedCC117:
14738
+ case MIDIControllers.undefinedCC119:
14584
14739
  loopEnd = loopEnd === null ? e.ticks : 0;
14585
14740
  loopType = "hard";
14586
14741
  break;
@@ -14776,6 +14931,7 @@ const DEFAULT_MIDI_BUILDER_OPTIONS = {
14776
14931
  * > **Warning**
14777
14932
  * >
14778
14933
  * > Remember to call {@link BasicMIDI.flush} after you're done building the file!
14934
+ * > Do not use in audioWorkletGlobalScope as it uses TextEncoder.
14779
14935
  *
14780
14936
  * @group MIDI.Files
14781
14937
  */
@@ -15071,7 +15227,7 @@ function processEventInternal(event, trackIndex) {
15071
15227
  }
15072
15228
  // src/sequencer/process_tick.ts
15073
15229
  /**
15074
- * Processes a single MIDI tick.
15230
+ * Processes a single sequencer tick, playing all MIDI events up to the current time.
15075
15231
  * Call this every rendering quantum to process the sequencer events in real-time.
15076
15232
  */
15077
15233
  function processTick() {
@@ -17958,7 +18114,8 @@ function wordToFloat(word, mode) {
17958
18114
  return mode === 1 ? signed / 128 : signed / 512;
17959
18115
  }
17960
18116
  /**
17961
- * Rounds a scaled coefficient half away from zero, like the DLL's biased truncation.
18117
+ * Adds the DLL's half step bias (away from zero) to a scaled coefficient.
18118
+ * Truncating the result rounds half away from zero, like the DLL's biased truncation.
17962
18119
  * @param value The exact scaled value.
17963
18120
  */
17964
18121
  function biasedRound(value) {
@@ -18487,6 +18644,8 @@ var XGDSPProgram = class {
18487
18644
  var DefaultXGEffect = class {
18488
18645
  /**
18489
18646
  * The currently used processor.
18647
+ * Hidden from the docs as the processor types are internal.
18648
+ * @hidden
18490
18649
  */
18491
18650
  processor;
18492
18651
  outputLeft;
@@ -18506,8 +18665,9 @@ var DefaultXGEffect = class {
18506
18665
  * @param defaultType The default effect type.
18507
18666
  * @param sampleRate The sample rate, in Hertz.
18508
18667
  * @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.
18668
+ * Processing more samples than this is not supported and produces undefined output.
18510
18669
  * @param variation True for the variation and insertion blocks.
18670
+ * @hidden
18511
18671
  */
18512
18672
  constructor(effectMap, fallbackProcessor, defaultType, sampleRate, maxBufferSize, variation) {
18513
18673
  let memoryLength = getMemorySize(sampleRate, fallbackProcessor.memorySize);
@@ -18569,11 +18729,17 @@ for (let pan = MIN_PAN$1; pan <= MAX_PAN$1; pan++) {
18569
18729
  var DefaultXGSystemEffect = class extends DefaultXGEffect {
18570
18730
  return = 64;
18571
18731
  pan = 64;
18732
+ /**
18733
+ * Resets this block to default values, including the processor type.
18734
+ */
18572
18735
  reset() {
18573
18736
  super.reset();
18574
18737
  this.return = 64;
18575
18738
  this.pan = 64;
18576
18739
  }
18740
+ /**
18741
+ * Gets a snapshot of this XG effect block.
18742
+ */
18577
18743
  getSnapshot() {
18578
18744
  return {
18579
18745
  ...super.getSnapshot(),
@@ -18581,6 +18747,10 @@ var DefaultXGSystemEffect = class extends DefaultXGEffect {
18581
18747
  pan: this.pan
18582
18748
  };
18583
18749
  }
18750
+ /**
18751
+ * Restores this XG effect block from a snapshot.
18752
+ * @param snapshot The snapshot to restore.
18753
+ */
18584
18754
  applySnapshot(snapshot) {
18585
18755
  super.applySnapshot(snapshot);
18586
18756
  this.return = snapshot.return;
@@ -18721,9 +18891,9 @@ const TIME_OFFSETS = new Int8Array([
18721
18891
  107
18722
18892
  ]);
18723
18893
  /**
18724
- * Damping line feedback coefficient words.
18894
+ * Damping line input gain coefficient words.
18725
18895
  */
18726
- const DAMP_FEEDBACK = new Uint16Array([
18896
+ const DAMP_INPUT = new Uint16Array([
18727
18897
  105,
18728
18898
  105,
18729
18899
  105,
@@ -18982,9 +19152,9 @@ const DAMP_FEEDBACK = new Uint16Array([
18982
19152
  2
18983
19153
  ]);
18984
19154
  /**
18985
- * Damping line filter coefficient words.
19155
+ * Damping line feedback coefficient words, used for both taps of the two-tap feedback filter.
18986
19156
  */
18987
- const DAMP_FILTER = new Uint16Array([
19157
+ const DAMP_FEEDBACK = new Uint16Array([
18988
19158
  1,
18989
19159
  1,
18990
19160
  1,
@@ -19756,7 +19926,7 @@ const LIST_LPF = [
19756
19926
  50
19757
19927
  ];
19758
19928
  /**
19759
- * Diffusion allpass addresses, 4 per line: base, density tap, write, read.
19929
+ * Damped delay line addresses, 4 per line: write, feedback read, left output tap, right output tap.
19760
19930
  */
19761
19931
  const LIST_DIFFUSION = [
19762
19932
  31,
@@ -19777,7 +19947,7 @@ const LIST_DIFFUSION = [
19777
19947
  7
19778
19948
  ];
19779
19949
  /**
19780
- * Damping line coefficients, 3 per line: feedback, filter, filter.
19950
+ * Damping line coefficients, 3 per line: input, read and previous read gains.
19781
19951
  */
19782
19952
  const LIST_TIME = [
19783
19953
  91,
@@ -20059,13 +20229,14 @@ var XGReverb = class extends XGDSPProgram {
20059
20229
  * @param offset The first list index.
20060
20230
  */
20061
20231
  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;
20232
+ this.coefficientWords[list[offset]] = DAMP_INPUT[index];
20233
+ const feedback = index > 202 ? 255 : DAMP_FEEDBACK[index];
20234
+ this.coefficientWords[list[offset + 1]] = feedback;
20235
+ this.coefficientWords[list[offset + 2]] = feedback;
20066
20236
  }
20067
20237
  /**
20068
- * Places the diffusion allpasses from the diffusion parameter.
20238
+ * Places the delay line read and output taps from the diffusion parameter.
20239
+ * The diffusion spreads the left and right output taps of each line apart.
20069
20240
  */
20070
20241
  diffusion() {
20071
20242
  const scale = DIFFUSION_SCALE[this.params[1]];
@@ -20221,7 +20392,7 @@ var XGRoomReverb = class extends XGReverb {
20221
20392
  this.writeDamping(extra + base & 255, LIST_TIME, offset);
20222
20393
  }
20223
20394
  /**
20224
- * Places a diffusion allpass from a room dimension.
20395
+ * Places a delay line's length and output taps from a room dimension.
20225
20396
  * @param step The room diffusion step index.
20226
20397
  * @param size The dimension size.
20227
20398
  * @param offset The first diffusion list index.
@@ -20242,7 +20413,7 @@ var XGRoomReverb = class extends XGReverb {
20242
20413
  }
20243
20414
  }
20244
20415
  /**
20245
- * Updates one dimension's damping line and allpass, then pushes them.
20416
+ * Updates one dimension's delay line (damping, length and taps), then pushes it.
20246
20417
  * @param dimension 0 width, 1 height, 2 depth.
20247
20418
  * @param step The room diffusion step index.
20248
20419
  * @param size The dimension size.
@@ -22066,6 +22237,8 @@ const REVERB_MAP = /* @__PURE__ */ new Map([
22066
22237
  /**
22067
22238
  * The default implementation for {@link XGReverbBlock}.
22068
22239
  *
22240
+ * The default type is HALL 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
22241
+ *
22069
22242
  * @group Synthesizer.XG Effects
22070
22243
  * @sealed
22071
22244
  */
@@ -22074,7 +22247,7 @@ var DefaultXGReverb = class extends DefaultXGSystemEffect {
22074
22247
  * Constructs a new default XG reverb processor.
22075
22248
  * @param sampleRate The sample rate, in Hertz.
22076
22249
  * @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.
22250
+ * Processing more samples than this is not supported and produces undefined output.
22078
22251
  */
22079
22252
  constructor(sampleRate, maxBufferSize) {
22080
22253
  super(REVERB_MAP, XGNoEffect, 256, sampleRate, maxBufferSize, false);
@@ -22166,7 +22339,7 @@ const LIST_RIGHT_TAPS = [
22166
22339
  25,
22167
22340
  26
22168
22341
  ];
22169
- const LIST_DRY_WET = [
22342
+ const LIST_DRY_WET$4 = [
22170
22343
  95,
22171
22344
  96,
22172
22345
  97,
@@ -22407,7 +22580,7 @@ var XGChorusBase = class extends XGDSPProgram {
22407
22580
  const w = this.coefficientWords;
22408
22581
  w[95] = w[104] = dry & 1023;
22409
22582
  w[96] = w[105] = w[97] = w[106] = wet & 1023;
22410
- this.pushCoefficients(LIST_DRY_WET);
22583
+ this.pushCoefficients(LIST_DRY_WET$4);
22411
22584
  break;
22412
22585
  }
22413
22586
  }
@@ -23679,6 +23852,8 @@ const CHORUS_MAP = /* @__PURE__ */ new Map([
23679
23852
  /**
23680
23853
  * The default implementation for {@link XGChorusBlock}.
23681
23854
  *
23855
+ * The default type is CHORUS 1. Unknown types use the type with LSB 0, or NO EFFECT (silence) if that is missing too.
23856
+ *
23682
23857
  * @group Synthesizer.XG Effects
23683
23858
  * @sealed
23684
23859
  */
@@ -23688,11 +23863,14 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
23688
23863
  * Constructs a new default XG chorus processor.
23689
23864
  * @param sampleRate The sample rate, in Hertz.
23690
23865
  * @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.
23866
+ * Processing more samples than this is not supported and produces undefined output.
23692
23867
  */
23693
23868
  constructor(sampleRate, maxBufferSize) {
23694
23869
  super(CHORUS_MAP, XGNoEffect, 16640, sampleRate, maxBufferSize, false);
23695
23870
  }
23871
+ /**
23872
+ * Resets this block to default values, including the processor type.
23873
+ */
23696
23874
  reset() {
23697
23875
  super.reset();
23698
23876
  this.sendToReverb = 0;
@@ -23707,12 +23885,19 @@ var DefaultXGChorus = class extends DefaultXGSystemEffect {
23707
23885
  reverbRight[i] += this.outputRight[i] * gain;
23708
23886
  }
23709
23887
  }
23888
+ /**
23889
+ * Gets a snapshot of this XG effect block.
23890
+ */
23710
23891
  getSnapshot() {
23711
23892
  return {
23712
23893
  ...super.getSnapshot(),
23713
23894
  sendToReverb: this.sendToReverb
23714
23895
  };
23715
23896
  }
23897
+ /**
23898
+ * Restores this XG effect block from a snapshot.
23899
+ * @param snapshot The snapshot to restore.
23900
+ */
23716
23901
  applySnapshot(snapshot) {
23717
23902
  super.applySnapshot(snapshot);
23718
23903
  this.sendToReverb = snapshot.sendToReverb;
@@ -24141,6 +24326,16 @@ const PARAMETER_RANGES$15 = {
24141
24326
  13: [4, 124],
24142
24327
  14: [0, 1]
24143
24328
  };
24329
+ const LIST_INPUT_COEFFICIENT = [
24330
+ 170,
24331
+ 180,
24332
+ 185
24333
+ ];
24334
+ const LIST_INPUT_ADDRESS = [
24335
+ 59,
24336
+ 45,
24337
+ 46
24338
+ ];
24144
24339
  /**
24145
24340
  * TREMOLO (`46 00`): amplitude and pitch modulation.
24146
24341
  */
@@ -24328,16 +24523,8 @@ var XGTremolo = class extends XGTremoloBase {
24328
24523
  a[45] = 1025;
24329
24524
  a[46] = 1026;
24330
24525
  }
24331
- this.pushCoefficients([
24332
- 170,
24333
- 180,
24334
- 185
24335
- ]);
24336
- this.pushAddresses([
24337
- 59,
24338
- 45,
24339
- 46
24340
- ]);
24526
+ this.pushCoefficients(LIST_INPUT_COEFFICIENT);
24527
+ this.pushAddresses(LIST_INPUT_ADDRESS);
24341
24528
  this.convert(5);
24342
24529
  this.convert(7);
24343
24530
  break;
@@ -24353,6 +24540,7 @@ const PARAMETER_RANGES$14 = {
24353
24540
  7: [28, 58],
24354
24541
  8: [52, 76]
24355
24542
  };
24543
+ const LIST_OUTPUT$3 = [180, 185];
24356
24544
  /**
24357
24545
  * LFO phases (left, right) per pan direction: L<->R, L->R, L<-R, L turn, R turn.
24358
24546
  */
@@ -24555,7 +24743,7 @@ var XGAutoPan = class extends XGTremoloBase {
24555
24743
  break;
24556
24744
  case 9:
24557
24745
  w[185] = w[180] = this.outputWord(this.insertion ? 362 : 724);
24558
- this.pushCoefficients([180, 185]);
24746
+ this.pushCoefficients(LIST_OUTPUT$3);
24559
24747
  }
24560
24748
  }
24561
24749
  };
@@ -24779,6 +24967,11 @@ const PARAMETER_RANGES$13 = {
24779
24967
  8: [52, 76],
24780
24968
  10: [6, 10]
24781
24969
  };
24970
+ const LIST_DRY_LEFT = [162];
24971
+ const LIST_MIX_RIGHT$1 = [166, 167];
24972
+ const LIST_WET_SECOND = [164];
24973
+ const LIST_WET_FOURTH = [165];
24974
+ const LIST_INPUT_MIX = [132, 133];
24782
24975
  /**
24783
24976
  * The converter index of the input mix level, which depends on the connection.
24784
24977
  */
@@ -25001,14 +25194,14 @@ var XGPhaser1 = class extends XGPhaserBase {
25001
25194
  case 9:
25002
25195
  w[166] = w[162] = PAN_LEFT[mix];
25003
25196
  w[167] = PAN_RIGHT[mix];
25004
- this.pushCoefficients([162]);
25005
- this.pushCoefficients([166, 167]);
25197
+ this.pushCoefficients(LIST_DRY_LEFT);
25198
+ this.pushCoefficients(LIST_MIX_RIGHT$1);
25006
25199
  if (p[10] < 9) {
25007
25200
  w[164] = PAN_RIGHT[mix];
25008
- this.pushCoefficients([164]);
25201
+ this.pushCoefficients(LIST_WET_SECOND);
25009
25202
  } else {
25010
25203
  w[165] = PAN_RIGHT[mix];
25011
- this.pushCoefficients([165]);
25204
+ this.pushCoefficients(LIST_WET_FOURTH);
25012
25205
  }
25013
25206
  break;
25014
25207
  case 10: {
@@ -25032,7 +25225,7 @@ var XGPhaser1 = class extends XGPhaserBase {
25032
25225
  }
25033
25226
  case INPUT_MIX:
25034
25227
  w[132] = w[133] = this.insertion ? 256 : 512;
25035
- this.pushCoefficients([132, 133]);
25228
+ this.pushCoefficients(LIST_INPUT_MIX);
25036
25229
  }
25037
25230
  }
25038
25231
  };
@@ -25046,6 +25239,8 @@ const PARAMETER_RANGES$12 = {
25046
25239
  10: [3, 5],
25047
25240
  12: [4, 124]
25048
25241
  };
25242
+ const LIST_MIX_LEFT = [162, 163];
25243
+ const LIST_MIX_RIGHT = [166, 167];
25049
25244
  /**
25050
25245
  * PHASER 2 (`48 08`): a stereo phaser with 3 to 5 stages and an LFO phase difference between the sides.
25051
25246
  */
@@ -25218,8 +25413,8 @@ var XGPhaser2 = class extends XGPhaserBase {
25218
25413
  const mix = this.mixIndex();
25219
25414
  w[166] = w[162] = PAN_LEFT[mix];
25220
25415
  w[167] = w[163] = PAN_RIGHT[mix];
25221
- this.pushCoefficients([162, 163]);
25222
- this.pushCoefficients([166, 167]);
25416
+ this.pushCoefficients(LIST_MIX_LEFT);
25417
+ this.pushCoefficients(LIST_MIX_RIGHT);
25223
25418
  break;
25224
25419
  }
25225
25420
  case 10: {
@@ -25616,6 +25811,8 @@ const LIST_HIGH_LEFT = [
25616
25811
  131,
25617
25812
  133
25618
25813
  ];
25814
+ const LIST_OUTPUT$2 = [185, 186];
25815
+ const LIST_DRY_WET$3 = [171, 173];
25619
25816
  /**
25620
25817
  * AUTO WAH (`4E 00`): a resonant filter per side, swept by an exponential LFO.
25621
25818
  */
@@ -25869,8 +26066,8 @@ var XGAutoWah = class extends XGFilterVariationProgram {
25869
26066
  const mix = this.mixIndex();
25870
26067
  w[171] = w[185] = PAN_LEFT[mix];
25871
26068
  w[173] = w[186] = PAN_RIGHT[mix];
25872
- this.pushCoefficients([185, 186]);
25873
- this.pushCoefficients([171, 173]);
26069
+ this.pushCoefficients(LIST_OUTPUT$2);
26070
+ this.pushCoefficients(LIST_DRY_WET$3);
25874
26071
  break;
25875
26072
  }
25876
26073
  }
@@ -25897,16 +26094,17 @@ const UNWRITTEN_CATEGORY_2 = [
25897
26094
  128,
25898
26095
  174
25899
26096
  ];
26097
+ const LIST_MONO_INPUT$1 = [109, 110];
25900
26098
  /**
25901
26099
  * 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
26100
+ * They process a mono sum of the input through two first-order input sections, keeping scaled copies
25903
26101
  * of the dry input that the routines mix back in.
25904
26102
  * Any of 0x76, 0x7a, 0x7d and 0x80 sets 0x76 to their product.
25905
26103
  */
25906
26104
  var XGMonoVariationProgram = class extends XGVariationProgram {
25907
26105
  unwrittenCoefficients = UNWRITTEN_CATEGORY_2;
25908
26106
  /**
25909
- * The mono input after the low shelf.
26107
+ * The mono input after the input sections.
25910
26108
  */
25911
26109
  mono;
25912
26110
  /**
@@ -26027,7 +26225,7 @@ var XGMonoVariationProgram = class extends XGVariationProgram {
26027
26225
  monoInputLevel() {
26028
26226
  const w = this.coefficientWords;
26029
26227
  w[109] = w[110] = this.insertion ? 256 : 512;
26030
- this.pushCoefficients([109, 110]);
26228
+ this.pushCoefficients(LIST_MONO_INPUT$1);
26031
26229
  }
26032
26230
  };
26033
26231
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/equalizer/three_band_eq.ts
@@ -26387,6 +26585,12 @@ const LIST_MID = [
26387
26585
  166,
26388
26586
  165
26389
26587
  ];
26588
+ const LIST_DRY_WET$2 = [
26589
+ 173,
26590
+ 176,
26591
+ 177,
26592
+ 179
26593
+ ];
26390
26594
  /**
26391
26595
  * The clipping range (the original works on a 16-bit scale, which is 4 times the full scale).
26392
26596
  */
@@ -26480,12 +26684,7 @@ var XGDistortionBase = class extends XGMonoVariationProgram {
26480
26684
  if (this.insertion) wet >>= 1;
26481
26685
  w[173] = w[177] = PAN_LEFT[mix];
26482
26686
  w[176] = w[179] = VARIATION_GAIN * wet >> 6;
26483
- this.pushCoefficients([
26484
- 173,
26485
- 176,
26486
- 177,
26487
- 179
26488
- ]);
26687
+ this.pushCoefficients(LIST_DRY_WET$2);
26489
26688
  }
26490
26689
  };
26491
26690
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/distortion/distortion.ts
@@ -28425,6 +28624,12 @@ const LIST_HIGH_DAMP = [
28425
28624
  119,
28426
28625
  121
28427
28626
  ];
28627
+ const LIST_DRY_WET$1 = [
28628
+ 173,
28629
+ 175,
28630
+ 177,
28631
+ 178
28632
+ ];
28428
28633
  /**
28429
28634
  * The converter index of the mono input level, which depends on the connection.
28430
28635
  */
@@ -28544,12 +28749,7 @@ var XGEarlyReflectionsBase = class extends XGMonoVariationProgram {
28544
28749
  const mix = this.mixIndex();
28545
28750
  w[173] = w[177] = MIX_DRY[mix] & 1023;
28546
28751
  w[175] = w[178] = MIX_WET[mix] & 1023;
28547
- this.pushCoefficients([
28548
- 173,
28549
- 175,
28550
- 177,
28551
- 178
28552
- ]);
28752
+ this.pushCoefficients(LIST_DRY_WET$1);
28553
28753
  break;
28554
28754
  }
28555
28755
  case 10:
@@ -29308,7 +29508,7 @@ const DELAY_LEVEL = new Float32Array([
29308
29508
  ]);
29309
29509
  // src/synthesizer/audio_engine/effects/xg/implementation/dsp/delay/xg_delay.ts
29310
29510
  /**
29311
- * The right line input position used by the two-line delays (in the upper half of the memory).
29511
+ * The right line input position used by the two-line delays (past the longest left line).
29312
29512
  */
29313
29513
  const RIGHT_LINE_START = 16384;
29314
29514
  const LIST_DAMP_A = [159, 158];
@@ -29373,7 +29573,7 @@ var XGDelayBase = class extends XGStereoVariationProgram {
29373
29573
  this.pushAddresses([tap]);
29374
29574
  }
29375
29575
  /**
29376
- * Designs a feedback damping filter (a one-pole low-pass) from the high damp parameter.
29576
+ * Designs a feedback damping filter (a two-tap low-pass) from the high damp parameter.
29377
29577
  * @param damp The high damp value (1-10).
29378
29578
  * @param list The coefficient indices for the input and feedback coefficients.
29379
29579
  */
@@ -29402,6 +29602,14 @@ const PARAMETER_RANGES$4 = {
29402
29602
  14: [28, 58],
29403
29603
  15: [52, 76]
29404
29604
  };
29605
+ const LIST_MIX$3 = [
29606
+ 178,
29607
+ 181,
29608
+ 184,
29609
+ 185,
29610
+ 187,
29611
+ 182
29612
+ ];
29405
29613
  /**
29406
29614
  * DELAY L,C,R (`05 00`): left, right and center taps with a separate feedback tap.
29407
29615
  */
@@ -29575,14 +29783,7 @@ var XGDelayLCR = class extends XGDelayBase {
29575
29783
  if (this.insertion) wet >>= 1;
29576
29784
  w[185] = w[181] = wet & 1023;
29577
29785
  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
- ]);
29786
+ this.pushCoefficients(LIST_MIX$3);
29586
29787
  break;
29587
29788
  }
29588
29789
  case 6:
@@ -29611,6 +29812,12 @@ const PARAMETER_RANGES$3 = {
29611
29812
  14: [28, 58],
29612
29813
  15: [52, 76]
29613
29814
  };
29815
+ const LIST_MIX$2 = [
29816
+ 178,
29817
+ 181,
29818
+ 184,
29819
+ 187
29820
+ ];
29614
29821
  /**
29615
29822
  * DELAY L,R (`06 00`): left and right taps with two feedback taps.
29616
29823
  */
@@ -29786,12 +29993,7 @@ var XGDelayLR = class extends XGDelayBase {
29786
29993
  let wet = MIX_WET[mix];
29787
29994
  if (this.insertion) wet >>= 1;
29788
29995
  w[187] = w[181] = wet & 1023;
29789
- this.pushCoefficients([
29790
- 178,
29791
- 181,
29792
- 184,
29793
- 187
29794
- ]);
29996
+ this.pushCoefficients(LIST_MIX$2);
29795
29997
  break;
29796
29998
  }
29797
29999
  case 12:
@@ -29818,6 +30020,14 @@ const PARAMETER_RANGES$2 = {
29818
30020
  14: [28, 58],
29819
30021
  15: [52, 76]
29820
30022
  };
30023
+ const LIST_MIX$1 = [
30024
+ 178,
30025
+ 180,
30026
+ 181,
30027
+ 184,
30028
+ 185,
30029
+ 187
30030
+ ];
29821
30031
  /**
29822
30032
  * ECHO (`07 00`): two independent delay lines with feedback and a second tap each.
29823
30033
  */
@@ -29999,14 +30209,7 @@ var XGEcho = class extends XGDelayBase {
29999
30209
  const wet = MIX_WET[mix];
30000
30210
  w[185] = w[180] = wet & 1023;
30001
30211
  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
- ]);
30212
+ this.pushCoefficients(LIST_MIX$1);
30010
30213
  break;
30011
30214
  }
30012
30215
  case 12:
@@ -30031,6 +30234,12 @@ const PARAMETER_RANGES$1 = {
30031
30234
  14: [28, 58],
30032
30235
  15: [52, 76]
30033
30236
  };
30237
+ const LIST_MIX = [
30238
+ 178,
30239
+ 180,
30240
+ 184,
30241
+ 185
30242
+ ];
30034
30243
  /**
30035
30244
  * CROSS DELAY (`08 00`): two delay lines feeding back into each other.
30036
30245
  */
@@ -30221,12 +30430,7 @@ var XGCrossDelay = class extends XGDelayBase {
30221
30430
  let wet = MIX_WET[mix];
30222
30431
  if (p[3] < 2) wet >>= 1;
30223
30432
  w[185] = w[180] = wet & 1023;
30224
- this.pushCoefficients([
30225
- 178,
30226
- 180,
30227
- 184,
30228
- 185
30229
- ]);
30433
+ this.pushCoefficients(LIST_MIX);
30230
30434
  break;
30231
30435
  }
30232
30436
  case 12:
@@ -30421,6 +30625,10 @@ const LIST_DENSITY_ADDRESS = [
30421
30625
  54,
30422
30626
  51
30423
30627
  ];
30628
+ const LIST_DRY = [185];
30629
+ const LIST_WET = [187];
30630
+ const LIST_DRY_WET = [174, 172];
30631
+ const LIST_MONO_INPUT = [109, 110];
30424
30632
  /**
30425
30633
  * The converter index of the mono input level, which depends on the connection.
30426
30634
  */
@@ -30543,9 +30751,9 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
30543
30751
  const mix = this.mixIndex();
30544
30752
  w[172] = w[185] = MIX_DRY[mix] & 1023;
30545
30753
  w[174] = w[187] = MIX_WET[mix] & 1023;
30546
- this.pushCoefficients([185]);
30547
- this.pushCoefficients([187]);
30548
- this.pushCoefficients([174, 172]);
30754
+ this.pushCoefficients(LIST_DRY);
30755
+ this.pushCoefficients(LIST_WET);
30756
+ this.pushCoefficients(LIST_DRY_WET);
30549
30757
  break;
30550
30758
  }
30551
30759
  case 10:
@@ -30554,7 +30762,7 @@ var XGKaraokeBase = class extends XGFilterVariationProgram {
30554
30762
  break;
30555
30763
  case MONO_INPUT$1:
30556
30764
  w[109] = w[110] = this.insertion ? 181 : 362;
30557
- this.pushCoefficients([109, 110]);
30765
+ this.pushCoefficients(LIST_MONO_INPUT);
30558
30766
  }
30559
30767
  }
30560
30768
  };
@@ -31013,6 +31221,9 @@ const VARIATION_MAP = /* @__PURE__ */ new Map([
31013
31221
  /**
31014
31222
  * The default implementation for {@link XGVariationBlock}.
31015
31223
  *
31224
+ * The default type is DELAY L,C,R in the insertion connection.
31225
+ * Unknown types use the type with LSB 0, or THRU if that is missing too.
31226
+ *
31016
31227
  * @group Synthesizer.XG Effects
31017
31228
  * @sealed
31018
31229
  */
@@ -31025,11 +31236,14 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31025
31236
  * Constructs a new default XG variation processor.
31026
31237
  * @param sampleRate The sample rate, in Hertz.
31027
31238
  * @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.
31239
+ * Processing more samples than this is not supported and produces undefined output.
31029
31240
  */
31030
31241
  constructor(sampleRate, maxBufferSize) {
31031
31242
  super(VARIATION_MAP, XGThru, 1280, sampleRate, maxBufferSize, true);
31032
31243
  }
31244
+ /**
31245
+ * Resets this block to default values, including the processor type.
31246
+ */
31033
31247
  reset() {
31034
31248
  super.reset();
31035
31249
  this.insertionMode = true;
@@ -31063,6 +31277,9 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31063
31277
  inputRight[i] = outputRight[i];
31064
31278
  }
31065
31279
  }
31280
+ /**
31281
+ * Gets a snapshot of this XG effect block.
31282
+ */
31066
31283
  getSnapshot() {
31067
31284
  return {
31068
31285
  ...super.getSnapshot(),
@@ -31072,6 +31289,10 @@ var DefaultXGVariation = class extends DefaultXGSystemEffect {
31072
31289
  sendToChorus: this.sendToChorus
31073
31290
  };
31074
31291
  }
31292
+ /**
31293
+ * Restores this XG effect block from a snapshot.
31294
+ * @param snapshot The snapshot to restore.
31295
+ */
31075
31296
  applySnapshot(snapshot) {
31076
31297
  super.applySnapshot(snapshot);
31077
31298
  this.insertionMode = snapshot.insertionMode;
@@ -31124,6 +31345,10 @@ const INSERTION_MAP = /* @__PURE__ */ new Map([
31124
31345
  /**
31125
31346
  * The default implementation for {@link XGInsertionBlock}.
31126
31347
  *
31348
+ * The default type is DISTORTION. The EFFECT 2 types run on the same processors as the variation block types.
31349
+ * The early reflection types and PHASER 2 are variation only, so PHASER 2 falls back to PHASER 1 here.
31350
+ * Unknown types use the type with LSB 0, or THRU if that is missing too.
31351
+ *
31127
31352
  * @group Synthesizer.XG Effects
31128
31353
  * @sealed
31129
31354
  */
@@ -31133,11 +31358,14 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
31133
31358
  * Constructs a new default XG insertion processor.
31134
31359
  * @param sampleRate The sample rate, in Hertz.
31135
31360
  * @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.
31361
+ * Processing more samples than this is not supported and produces undefined output.
31137
31362
  */
31138
31363
  constructor(sampleRate, maxBufferSize) {
31139
31364
  super(INSERTION_MAP, XGThru, 18688, sampleRate, maxBufferSize, true);
31140
31365
  }
31366
+ /**
31367
+ * Resets this block to default values, including the processor type.
31368
+ */
31141
31369
  reset() {
31142
31370
  super.reset();
31143
31371
  this.partNumber = 127;
@@ -31150,12 +31378,19 @@ var DefaultXGInsertion = class extends DefaultXGEffect {
31150
31378
  inputRight[i] = outputRight[i];
31151
31379
  }
31152
31380
  }
31381
+ /**
31382
+ * Gets a snapshot of this XG effect block.
31383
+ */
31153
31384
  getSnapshot() {
31154
31385
  return {
31155
31386
  ...super.getSnapshot(),
31156
31387
  partNumber: this.partNumber
31157
31388
  };
31158
31389
  }
31390
+ /**
31391
+ * Restores this XG effect block from a snapshot.
31392
+ * @param snapshot The snapshot to restore.
31393
+ */
31159
31394
  applySnapshot(snapshot) {
31160
31395
  super.applySnapshot(snapshot);
31161
31396
  this.partNumber = snapshot.partNumber;
@@ -31181,9 +31416,28 @@ const InterpolationTypes = {
31181
31416
  */
31182
31417
  nearestNeighbor: 1,
31183
31418
  /**
31184
- * Slowest, high quality with little artifacting. (Default)
31419
+ * Cubic Hermite interpolation. Good quality with little artifacting. (Default)
31420
+ *
31421
+ * Recommended for most use-cases.
31185
31422
  */
31186
- hermite: 2
31423
+ hermite: 2,
31424
+ /**
31425
+ * 32-point windowed sinc interpolation.
31426
+ *
31427
+ * Very high quality with almost no aliasing or harmonic distortion,
31428
+ * usable in real time, but at a significantly higher CPU cost.
31429
+ */
31430
+ sinc32: 3,
31431
+ /**
31432
+ * 128-point windowed sinc interpolation with antialiasing.
31433
+ *
31434
+ * The best quality, but extremely high cpu cost.
31435
+ *
31436
+ * > **Warning**
31437
+ * >
31438
+ * > This is very slow (especially for pitched up samples) and is meant for offline rendering, not real time playback.
31439
+ */
31440
+ sinc128: 4
31187
31441
  };
31188
31442
  /**
31189
31443
  * The special EFX control sources of a {@link GSInsertionBlock} (EFX CONTROL SOURCE 1/2).
@@ -31612,7 +31866,7 @@ var DefaultGSReverb = class {
31612
31866
  * Constructs a new default GS reverb processor.
31613
31867
  * @param sampleRate The sample rate, in Hertz.
31614
31868
  * @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.
31869
+ * Processing more samples than this is not supported and produces undefined output.
31616
31870
  */
31617
31871
  constructor(sampleRate, maxBufferSize) {
31618
31872
  this.sampleRate = sampleRate;
@@ -31735,12 +31989,12 @@ var DefaultGSReverb = class {
31735
31989
  this.updateLowpass();
31736
31990
  }
31737
31991
  /**
31738
- *
31739
- * @param input 0-based
31740
- * @param outputLeft startIndex-based
31741
- * @param outputRight startIndex-based
31742
- * @param startIndex
31743
- * @param sampleCount
31992
+ * Process the effect and **adds** it to the output.
31993
+ * @param input The input buffer to process. It always starts at index 0.
31994
+ * @param outputLeft The left output buffer.
31995
+ * @param outputRight The right output buffer.
31996
+ * @param startIndex The index to start mixing at into the output buffers.
31997
+ * @param sampleCount The amount of samples to mix.
31744
31998
  */
31745
31999
  process(input, outputLeft, outputRight, startIndex, sampleCount) {
31746
32000
  switch (this._character) {
@@ -31879,7 +32133,7 @@ var DefaultGSChorus = class {
31879
32133
  * Constructs a new default GS chorus processor.
31880
32134
  * @param sampleRate The sample rate, in Hertz.
31881
32135
  * @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.
32136
+ * Processing more samples than this is not supported and produces undefined output.
31883
32137
  */
31884
32138
  constructor(sampleRate, maxBufferSize) {
31885
32139
  this.sampleRate = sampleRate;
@@ -32138,7 +32392,7 @@ var DefaultGSDelay = class {
32138
32392
  * Constructs a new default GS delay processor.
32139
32393
  * @param sampleRate The sample rate, in Hertz.
32140
32394
  * @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.
32395
+ * Processing more samples than this is not supported and produces undefined output.
32142
32396
  */
32143
32397
  constructor(sampleRate, maxBufferSize) {
32144
32398
  this.sampleRate = sampleRate;
@@ -32838,6 +33092,16 @@ function prewarp(frequency, sampleRate) {
32838
33092
  return Math.tan(Math.PI * Math.min(frequency, sampleRate * .49) / sampleRate);
32839
33093
  }
32840
33094
  /**
33095
+ * 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
33096
+ * `FirstOrderFilter.setDamp`).
33097
+ * @param pole B.
33098
+ * @internal
33099
+ */
33100
+ function dampCorner(pole) {
33101
+ const k = (1 + pole * pole) / (2 * pole);
33102
+ return Math.acos(2 - k) * 32e3 / (2 * Math.PI);
33103
+ }
33104
+ /**
32841
33105
  * A first order section, `y = b0 x + b1 x[n-1] + a1 y[n-1]`, designed with the bilinear transform.
32842
33106
  * The tone filters, the shelves and the DC blockers of the Sound Canvas are all this section.
32843
33107
  * @internal
@@ -39632,7 +39896,7 @@ var GSEqualizer = class extends GSEffect {
39632
39896
  * @param x The argument.
39633
39897
  * @internal
39634
39898
  */
39635
- function besselI0(x) {
39899
+ function besselI0$1(x) {
39636
39900
  let sum = 1;
39637
39901
  let term = 1;
39638
39902
  for (let k = 1; k < 32; k++) {
@@ -39648,7 +39912,7 @@ function besselI0(x) {
39648
39912
  * @internal
39649
39913
  */
39650
39914
  function kaiser(t, beta) {
39651
- return besselI0(beta * Math.sqrt(1 - t * t)) / besselI0(beta);
39915
+ return besselI0$1(beta * Math.sqrt(1 - t * t)) / besselI0$1(beta);
39652
39916
  }
39653
39917
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/blocks/delay_line.ts
39654
39918
  /**
@@ -42963,7 +43227,7 @@ function cutoff(value) {
42963
43227
  */
42964
43228
  const STEP_MS = 1 / 32;
42965
43229
  /**
42966
- * The longest swing of `modulationDepth` (depth 127), in ms.
43230
+ * A bound on the swing of `modulationDepth` (12.4 ms at depth 127), in ms, for sizing the lines.
42967
43231
  * @internal
42968
43232
  */
42969
43233
  const SWING_MS = 12.5;
@@ -43526,7 +43790,8 @@ var Dynamics = class {
43526
43790
  };
43527
43791
  /**
43528
43792
  * 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.
43793
+ * which sets how far below the fixed output ceiling the knee sits, the manuals call it Sustain, see `GSCompressor`)
43794
+ * and a two-sample average.
43530
43795
  * @internal
43531
43796
  */
43532
43797
  var Compressor = class extends Dynamics {
@@ -43670,6 +43935,10 @@ var GSDynamicsEffect = class extends GSEffect {
43670
43935
  * Compressor (`01 30`).
43671
43936
  * Parameters: 1 sustain (how fast the gain comes back), 2 attack (the makeup gain: the output ceiling stays, the
43672
43937
  * knee moves), 3 post gain, then the EQ, pan and level.
43938
+ *
43939
+ * The SC-VA and SC-8850 manuals list parameter 1 as Attack and 2 as Sustain, the other way round from the names
43940
+ * here. Only the names differ: parameter 1 still sets how fast the gain recovers (0.09 to 2.25 per second) and
43941
+ * parameter 2 the makeup gain, like in Sound Canvas VA. The multis' compressors and Lo-Fi 1 use the same names.
43673
43942
  * @internal
43674
43943
  */
43675
43944
  var GSCompressor = class extends GSDynamicsEffect {
@@ -46868,7 +47137,7 @@ const LINE_LENGTHS = [
46868
47137
  ];
46869
47138
  /**
46870
47139
  * 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
47140
+ * shortest pre delay is read band-limited (the interpolator needs 7 newer samples). The outputs read 140 32 kHz
46872
47141
  * samples or more, so they have room.
46873
47142
  */
46874
47143
  const LOOKAHEAD$1 = 8;
@@ -46881,15 +47150,6 @@ const ALLPASS$1 = .5;
46881
47150
  */
46882
47151
  const PRE_GAIN$1 = (127 / 128) ** 2;
46883
47152
  /**
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
47153
  * The Reverb: a mono input through a DC blocker and an input low pass into a pre delay, a diffuser of three
46894
47154
  * Schroeder allpasses and a delay, then two independent tanks (allpass, delay, allpass, delay, a damping low pass
46895
47155
  * and the time gain back to the start). The stereo image comes from the output taps alone, four per side spread
@@ -47597,7 +47857,7 @@ var LoFiQuantizer = class {
47597
47857
  };
47598
47858
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/effects/lo_fi_1.ts
47599
47859
  /**
47600
- * The gain after the compressor per pre filter (1 bypasses the compressor).
47860
+ * The gain after the compressor per pre-ilter (1 bypasses the compressor).
47601
47861
  */
47602
47862
  const PRE_GAINS = [
47603
47863
  1,
@@ -47652,9 +47912,9 @@ const POST_FILTERS = [
47652
47912
  }
47653
47913
  ];
47654
47914
  /**
47655
- * Lo-Fi 1 (`01 72`): `L+R -> compressor (pre filter) -> low pass -> quantizer -> high pass and dip (post filter)`,
47915
+ * Lo-Fi 1 (`01 72`): `L+R -> compressor (pre-filter) -> low pass -> quantizer -> high pass and dip (post filter)`,
47656
47916
  * 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),
47917
+ * 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
47918
  * 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
47919
  * 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
47920
  * 1 modulates the balance, source 2 the pan.
@@ -47692,7 +47952,7 @@ var GSLoFi1 = class extends GSEffect {
47692
47952
  mix;
47693
47953
  compressor;
47694
47954
  /**
47695
- * The compressor's own DC blocker, for the dry path and the pre filter that bypasses it.
47955
+ * The compressor's own DC blocker, for the dry path and the pre-filter that bypasses it.
47696
47956
  */
47697
47957
  dcBlocker;
47698
47958
  lowPass;
@@ -47891,7 +48151,7 @@ function clamp32(x) {
47891
48151
  *
47892
48152
  * - White (x 1/32) or "pink": the white noise times a 500 Hz saw (on the SC-88Pro too).
47893
48153
  * - 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
48154
+ * within 0.014 % of its wrap. RND adds a second train whose phase is offset by twice the hum's (pseudo-random
47895
48155
  * times).
47896
48156
  * - Hum: a unipolar 50 or 60 Hz square, 0 or 1/16 (it carries DC, on the hardware too).
47897
48157
  *
@@ -48239,7 +48499,7 @@ var GSLoFi2 = class extends GSEffect {
48239
48499
  this.chainR = new LoFiShaperChain(sampleRate);
48240
48500
  this.noise = new NoiseGenerator(sampleRate);
48241
48501
  this.radioNoise = new FirstOrderFilter(sampleRate);
48242
- this.radioNoise.setDamp(3500);
48502
+ this.radioNoise.setDamp(dampCorner(.5));
48243
48503
  this.shelvesL = new ShelfEQ(sampleRate);
48244
48504
  this.shelvesR = new ShelfEQ(sampleRate);
48245
48505
  this.pan = new MonoPan(sampleRate);
@@ -50683,8 +50943,9 @@ const MULTI_WAH_GAIN = 3.937744140625;
50683
50943
  const DRIVE_INPUT = 128 / 127;
50684
50944
  const DRIVE_OUTPUT = 127 / 128;
50685
50945
  /**
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.
50946
+ * The compressor of the multis (parameters 1-4: sustain, attack, level, switch, the manuals swap the first two
50947
+ * names, see `GSCompressor`): the Compressor's dynamics with its post gain at +6 dB and a level after it. The
50948
+ * compressor runs when it is switched off too.
50688
50949
  * @internal
50689
50950
  */
50690
50951
  var MultiCompressor = class {
@@ -51466,10 +51727,10 @@ var GSDriveDelayMulti = class extends GSMulti {
51466
51727
  /**
51467
51728
  * GTR Multi 1 (`04 00`): `L+R -> compressor -> overdrive/distortion -> 2-band EQ -> amp simulator -> chorus/flanger
51468
51729
  * -> 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.
51730
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
51731
+ * switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 low gain, 10 high gain, 11 drive switch,
51732
+ * 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
51733
+ * column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the drive, source 2 the delay level.
51473
51734
  * @internal
51474
51735
  */
51475
51736
  var GSGuitarMulti1 = class extends GSDriveDelayMulti {
@@ -51644,10 +51905,10 @@ var GSDriveEQMulti = class extends GSMulti {
51644
51905
  };
51645
51906
  /**
51646
51907
  * 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.
51908
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
51909
+ * switch, 5 drive type (OD, DS), 6 drive, 7 amp type, 8 amp switch, 9 drive switch, 10 low gain, 11-13 mid-frequency,
51910
+ * Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
51911
+ * 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
51651
51912
  * @internal
51652
51913
  */
51653
51914
  var GSGuitarMulti2 = class extends GSDriveEQMulti {
@@ -51755,10 +52016,10 @@ var BassAmpSimulator = class {
51755
52016
  /**
51756
52017
  * Bass Multi (`04 05`): `L+R -> compressor -> bass overdrive/distortion -> bass amp simulator -> 3-band EQ ->
51757
52018
  * 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.
52019
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
52020
+ * 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,
52021
+ * Q and gain, 14 high gain, 15 chorus or flanger, 16 rate (Rate2 column), 17 depth, 18 flanger feedback,
52022
+ * 19 chorus level, 20 level. EFX control source 1 moves the drive, source 2 the chorus level.
51762
52023
  * @internal
51763
52024
  */
51764
52025
  var GSBassMulti = class extends GSDriveEQMulti {
@@ -51812,10 +52073,10 @@ var GSBassMulti = class extends GSDriveEQMulti {
51812
52073
  const LINEAR_INPUT = 127 / 128;
51813
52074
  /**
51814
52075
  * 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.
52076
+ * Parameters: 1-4 compressor sustain, attack (the manuals swap these two names, see `GSCompressor`), level and
52077
+ * switch, 5 low gain, 6-8 mid-frequency, Q and gain, 9 high gain, 10 chorus or flanger, 11 rate (Rate2 column),
52078
+ * 12 depth, 13 flanger feedback, 14 chorus level, 15 delay time (Dly4 column), 16 feedback, 17 HF damp, 18 delay
52079
+ * level, 20 level. EFX control source 1 moves the chorus level, source 2 the delay level.
51819
52080
  * @internal
51820
52081
  */
51821
52082
  var GSCleanGuitarMulti1 = class extends GSMulti {
@@ -51889,7 +52150,7 @@ var GSCleanGuitarMulti1 = class extends GSMulti {
51889
52150
  * Clean GTR Multi 2 (`04 04`): `L+R -> wah -> 3-band EQ -> chorus/flanger -> delay`. The wah is the Auto Wah's without
51890
52151
  * the envelope follower, its LFO raises the cutoff (polarity up).
51891
52152
  * 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,
52153
+ * 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
52154
  * 12 chorus or flanger, 13 rate (Rate2 column), 14 depth, 15 flanger feedback, 16 chorus level, 17 delay time (Dly4
51894
52155
  * column), 18 feedback, 19 delay level, 20 level. EFX control source 1 moves the wah manual, source 2 the delay
51895
52156
  * level.
@@ -52107,9 +52368,11 @@ const DEFAULT_CONTROL_DEPTH = 64;
52107
52368
  /**
52108
52369
  * The offset an EFX control source adds to its parameter, like Sound Canvas VA computes it:
52109
52370
  * `(depth - 64) * value / 63`, truncated and stored in a signed byte. Depth 0 counts as 1 (both are -100%).
52371
+ * The byte wraps without a clamp, like in SCCore.dll: pitch bend fully down (-128) at depth 0 or 1 gives +128,
52372
+ * which is stored as -128, so the offset flips sign there.
52110
52373
  * @param depth The control depth (0-127).
52111
52374
  * @param value The signed controller value.
52112
- * @returns The signed offset.
52375
+ * @returns The signed offset (-128 to 127).
52113
52376
  */
52114
52377
  function controlOffset(depth, value) {
52115
52378
  const amount = (depth === 0 ? 1 : depth) - 64;
@@ -52167,7 +52430,7 @@ var DefaultGSInsertion = class {
52167
52430
  * Constructs a new default GS insertion block.
52168
52431
  * @param sampleRate The sample rate, in Hertz.
52169
52432
  * @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.
52433
+ * Processing more samples than this is not supported and produces undefined output.
52171
52434
  */
52172
52435
  constructor(sampleRate, maxBufferSize) {
52173
52436
  const memory = new DelayMemory();
@@ -52532,8 +52795,9 @@ var SoundBankManager = class {
52532
52795
  const bankOffset = s.bankOffset;
52533
52796
  for (const p of bank.presets) {
52534
52797
  const selectablePreset = new SoundBankManagerPreset(p, bankOffset);
52535
- if (!addedPresets.has(selectablePreset.toMIDIString())) {
52536
- addedPresets.add(selectablePreset.toMIDIString());
52798
+ const key = selectablePreset.toMIDIString();
52799
+ if (!addedPresets.has(key)) {
52800
+ addedPresets.add(key);
52537
52801
  presetList.push(selectablePreset);
52538
52802
  }
52539
52803
  }
@@ -52624,12 +52888,13 @@ for (let pan = MIN_PAN; pan <= MAX_PAN; pan++) {
52624
52888
  }
52625
52889
  /**
52626
52890
  * Computes the LFO value for the given phase.
52627
- * Both waveforms are at zero and rising at the default start phase of 0.25.
52891
+ * All waveforms are at zero and rising at the default start phase of 0.25.
52628
52892
  * @param phase the LFO phase, from 0 to 1.
52629
- * @param waveform 0 for triangle, 1 for sawtooth.
52893
+ * @param waveform 0 for triangle, 1 for sawtooth, 2 for sine.
52630
52894
  * @returns the LFO value, from -1 to 1.
52631
52895
  */
52632
52896
  function getLfoValue(phase, waveform) {
52897
+ if (waveform === 2) return Math.sin(TWO_PI * (phase - .25));
52633
52898
  if (waveform === 1) {
52634
52899
  const p = phase + .25;
52635
52900
  return 2 * (p >= 1 ? p - 1 : p) - 1;
@@ -52637,10 +52902,11 @@ function getLfoValue(phase, waveform) {
52637
52902
  return 1 - 4 * Math.abs(phase - .5);
52638
52903
  }
52639
52904
  /**
52640
- * Renders a voice to the stereo output buffer
52905
+ * Renders a voice into one of the channel's voice send buffers,
52906
+ * which are later mixed into the stereo output and the effect sends.
52641
52907
  * @param voice the voice to render
52642
52908
  * @param timeNow current time in seconds
52643
- * @param sampleCount the only thing needed as it's 0-based
52909
+ * @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
52644
52910
  */
52645
52911
  function renderVoice(voice, timeNow, sampleCount) {
52646
52912
  if (timeNow < voice.startTime) {
@@ -52902,9 +53168,9 @@ var VoiceSendBuffer = class {
52902
53168
  };
52903
53169
  /**
52904
53170
  * 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
53171
+ * @param channel The channel that owns the buffer.
53172
+ * @param buffer The voice send buffer to mix.
53173
+ * @param sampleCount The number of samples to mix.
52908
53174
  */
52909
53175
  function mixVoiceSendBuffer(channel, buffer, sampleCount) {
52910
53176
  const core = channel.synthCore;
@@ -53203,7 +53469,7 @@ function handleAWE32NRPN(paramLSB, dataValue) {
53203
53469
  }
53204
53470
  // src/synthesizer/audio_engine/channel/data_entry.ts
53205
53471
  /**
53206
- * Executes a data entry change for the current channel.
53472
+ * Executes a data entry change for the current channel.
53207
53473
  */
53208
53474
  function dataEntry() {
53209
53475
  const dataValue = this._midiControllers[MIDIControllers.dataEntryMSB];
@@ -53342,7 +53608,7 @@ function dataEntry() {
53342
53608
  break;
53343
53609
  case NonRegisteredParameterTypesMSB.drumVariation:
53344
53610
  this.drumParams[parameterFine].variationSend = dataCoarse;
53345
- SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`, dataValue, "");
53611
+ SpessaLog.coolInfo(`Drum ${parameterFine} Variation Send for ${this.channel}`, dataCoarse, "");
53346
53612
  break;
53347
53613
  case NonRegisteredParameterTypesMSB.awe32:
53348
53614
  handleAWE32NRPN.call(this, parameterFine, dataValue);
@@ -53675,7 +53941,7 @@ function noteOn(midiNote, velocity, emit = true) {
53675
53941
  // src/synthesizer/audio_engine/channel/note_off.ts
53676
53942
  /**
53677
53943
  * 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,
53944
+ * If the synthesizer is in high performance mode and the channel is not a drum channel,
53679
53945
  * or the drum channel has an rxNoteOff enabled,
53680
53946
  * it kills the note instead of releasing it.
53681
53947
  * @param midiNote The MIDI note number to release (0-127).
@@ -53852,7 +54118,7 @@ var DynamicModulatorManager = class {
53852
54118
  case 5:
53853
54119
  this.setModulator(source, isCC, GeneratorTypes.vibLfoToVolume, normalizedNotCentered * 200, bipolar, false, true);
53854
54120
  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");
54121
+ SpessaLog.xgInfo(`Channel ${this.channel} ${sourceName} LFO amplitude depth control`, normalizedNotCentered * 200, "cB");
53856
54122
  }
53857
54123
  }
53858
54124
  setupReceiverGS(addr3, data, source, isCC, sourceName, bipolar = false) {
@@ -54259,6 +54525,7 @@ var MIDIChannel = class {
54259
54525
  /**
54260
54526
  * If true, this skips sends as they all go through insertion.
54261
54527
  * Set by {@link SpessaSynthProcessor}.
54528
+ * @internal
54262
54529
  */
54263
54530
  xgInsertionAssigned = false;
54264
54531
  /**
@@ -54333,8 +54600,8 @@ var MIDIChannel = class {
54333
54600
  */
54334
54601
  resetRP15 = resetRP15.bind(this);
54335
54602
  /**
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).
54603
+ * Executes a data entry (MSB or LSB) change for the current channel.
54604
+ * The value is read from the controller table and applied to the selected RPN or NRPN.
54338
54605
  * @internal
54339
54606
  */
54340
54607
  dataEntry = dataEntry.bind(this);
@@ -54589,7 +54856,7 @@ var MIDIChannel = class {
54589
54856
  noteOn(midiNote, velocity, emit = true) {}
54590
54857
  /**
54591
54858
  * 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,
54859
+ * If the synthesizer is in high performance mode and the channel is not a drum channel,
54593
54860
  * or the drum channel has an rxNoteOff enabled,
54594
54861
  * it kills the note instead of releasing it.
54595
54862
  * @param midiNote The MIDI note number to release (0-127).
@@ -54618,14 +54885,16 @@ var MIDIChannel = class {
54618
54885
  /**
54619
54886
  * Reset this channel to its default state.
54620
54887
  * Except for the locked controllers.
54888
+ * @param sendEvent If controller change events should be emitted for the reset controllers.
54621
54889
  * @internal
54622
54890
  */
54623
54891
  reset(sendEvent = true) {}
54624
54892
  /**
54625
- * Renders a voice to the stereo output buffer
54893
+ * Renders a voice into one of the channel's voice send buffers,
54894
+ * which are later mixed into the stereo output and the effect sends.
54626
54895
  * @param voice the voice to render
54627
54896
  * @param timeNow current time in seconds
54628
- * @param sampleCount the only thing needed as it's 0-based
54897
+ * @param sampleCount the number of samples to render (the buffers are 0-based, so no offset is needed)
54629
54898
  * @internal
54630
54899
  */
54631
54900
  renderVoice(voice, timeNow, sampleCount) {}
@@ -55207,7 +55476,7 @@ var LowpassFilter = class LowpassFilter {
55207
55476
  const qCb = this.resonanceCb;
55208
55477
  let cutoffHz = absCentsToHz(cutoffCents);
55209
55478
  cutoffHz = Math.min(cutoffHz, this.maxCutoff);
55210
- const resonanceGain = cbAttenuationToGain(-(qCb - 3.01));
55479
+ const resonanceGain = cbAttenuationToGain(-(qCb - 30.1));
55211
55480
  const qGain = 1 / Math.sqrt(cbAttenuationToGain(-qCb));
55212
55481
  const w = 2 * Math.PI * cutoffHz / this.sampleRate;
55213
55482
  const cosw = Math.cos(w);
@@ -55254,7 +55523,7 @@ var VolumeEnvelope = class {
55254
55523
  /**
55255
55524
  * The sample count between updates of the volume envelope.
55256
55525
  * Since the volume envelope calculation runs once per rendering quantum,
55257
- * this effectively the buffer size.
55526
+ * this is effectively the buffer size.
55258
55527
  */
55259
55528
  updateInterval;
55260
55529
  /**
@@ -55262,11 +55531,11 @@ var VolumeEnvelope = class {
55262
55531
  */
55263
55532
  sampleTime = 0;
55264
55533
  /**
55265
- * The dB attenuation of the envelope when it entered the release stage.
55534
+ * The cB attenuation of the envelope when it entered the release stage.
55266
55535
  */
55267
55536
  releaseStartCb = CB_SILENCE;
55268
55537
  /**
55269
- * The time in samples relative to the start of the envelope.
55538
+ * The time in samples when the release stage started, relative to the start of the envelope.
55270
55539
  */
55271
55540
  releaseStartTimeSamples = 0;
55272
55541
  /**
@@ -55405,10 +55674,10 @@ var VolumeEnvelope = class {
55405
55674
  }
55406
55675
  /**
55407
55676
  * 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
55677
+ * Essentially we use approach of 96 dB (CB_SILENCE) is silence, 0dB is peak.
55678
+ * @param sampleCount the amount of samples in the block to advance the envelope by.
55410
55679
  * @param gainTarget the gain to apply.
55411
- * @returns if the voice has finished.
55680
+ * @returns if the voice is still active (false once the envelope has finished).
55412
55681
  */
55413
55682
  process(sampleCount, gainTarget) {
55414
55683
  const { releaseStartTimeSamples, releaseStartCb, releaseDuration, delayEnd, attackEnd, attackDuration, holdEnd, decayEnd, decayDuration, sustainCb, decay2End, decay2Duration, sustain2Cb } = this;
@@ -55566,7 +55835,7 @@ var ModulationEnvelope = class {
55566
55835
  * Calculates the current modulation envelope value for the given time and voice.
55567
55836
  * @param voice the voice we are working on.
55568
55837
  * @param currentTime in seconds.
55569
- * @returns mod env value, from 0 to 1.
55838
+ * @returns mod env value, from 0 to 1.
55570
55839
  */
55571
55840
  process(voice, currentTime) {
55572
55841
  if (this.enteredRelease) {
@@ -55839,6 +56108,217 @@ var HermiteOscillator = class extends WavetableOscillator {
55839
56108
  return true;
55840
56109
  }
55841
56110
  };
56111
+ // src/synthesizer/audio_engine/voice/wavetable_oscillator/sinc.ts
56112
+ /**
56113
+ * Kernel resolution, the amount of table points per sample of distance.
56114
+ * The table is linearly interpolated, which keeps the error around -120 dB.
56115
+ */
56116
+ const OVERSAMPLING = 256;
56117
+ /**
56118
+ * The highest playback step the anti-aliased kernel gets stretched to.
56119
+ * The tap count grows with the step, so this keeps extreme pitch ups from becoming too slow.
56120
+ * Above it, the content between the new Nyquist frequency and 1/16 of the old one folds back.
56121
+ */
56122
+ const MAX_STRETCH = 16;
56123
+ /**
56124
+ * Zeroth order modified Bessel function of the first kind, for the Kaiser window.
56125
+ * @param x
56126
+ */
56127
+ function besselI0(x) {
56128
+ const q = x * x / 4;
56129
+ let sum = 1;
56130
+ let term = 1;
56131
+ for (let k = 1; k < 100; k++) {
56132
+ term *= q / (k * k);
56133
+ sum += term;
56134
+ if (term < sum * 1e-17) break;
56135
+ }
56136
+ return sum;
56137
+ }
56138
+ /**
56139
+ * Builds a Kaiser windowed sinc kernel.
56140
+ * @param halfWidth Half of the kernel width, in samples. The kernel uses `2 * halfWidth` taps.
56141
+ * @param bandwidth The cutoff (-6 dB point) of the kernel, relative to the Nyquist frequency.
56142
+ * @param beta The Kaiser window beta, controls the stopband attenuation.
56143
+ */
56144
+ function buildKernel(halfWidth, bandwidth, beta) {
56145
+ const windowNorm = besselI0(beta);
56146
+ const kernel = (x) => {
56147
+ const w = x / halfWidth;
56148
+ if (w <= -1 || w >= 1) return 0;
56149
+ const window = besselI0(beta * Math.sqrt(1 - w * w)) / windowNorm;
56150
+ const u = Math.PI * bandwidth * x;
56151
+ return bandwidth * (u === 0 ? 1 : Math.sin(u) / u) * window;
56152
+ };
56153
+ const taps = halfWidth * 2;
56154
+ const rows = new Float64Array(taps * 257);
56155
+ for (let q = 0; q <= OVERSAMPLING; q++) {
56156
+ const row = q * taps;
56157
+ let sum = 0;
56158
+ for (let k = 0; k < taps; k++) {
56159
+ const v = kernel(k + 1 - halfWidth - q / OVERSAMPLING);
56160
+ rows[row + k] = v;
56161
+ sum += v;
56162
+ }
56163
+ for (let k = 0; k < taps; k++) rows[row + k] /= sum;
56164
+ }
56165
+ const phases = new Float32Array(taps * OVERSAMPLING);
56166
+ const phaseDeltas = new Float32Array(taps * OVERSAMPLING);
56167
+ for (let i = 0; i < phases.length; i++) {
56168
+ phases[i] = rows[i];
56169
+ phaseDeltas[i] = rows[i + taps] - rows[i];
56170
+ }
56171
+ const tableLength = halfWidth * 2 * OVERSAMPLING + 2;
56172
+ const table = new Float32Array(tableLength);
56173
+ const tableDeltas = new Float32Array(tableLength);
56174
+ for (let i = 0; i < tableLength; i++) table[i] = kernel(i / OVERSAMPLING - halfWidth);
56175
+ for (let i = 0; i < tableLength - 1; i++) tableDeltas[i] = table[i + 1] - table[i];
56176
+ return {
56177
+ halfWidth,
56178
+ phases,
56179
+ phaseDeltas,
56180
+ table,
56181
+ tableDeltas
56182
+ };
56183
+ }
56184
+ /**
56185
+ * 32 points, meant for real time playback.
56186
+ */
56187
+ const KERNEL_FAST = buildKernel(16, .92, 9);
56188
+ /**
56189
+ * 128 points, for offline rendering.
56190
+ */
56191
+ const KERNEL_BEST = buildKernel(64, .975, 12);
56192
+ var SincOscillator = class extends WavetableOscillator {
56193
+ kernel;
56194
+ /**
56195
+ * If the kernel gets stretched when the playback step is above 1.
56196
+ * This lowers the cutoff to the new Nyquist frequency so the sample content above it does not alias.
56197
+ */
56198
+ antiAlias;
56199
+ /**
56200
+ * The points around the cursor, gathered here when they need wrapping around the loop
56201
+ * or are outside the sample.
56202
+ */
56203
+ scratch;
56204
+ /**
56205
+ * Creates a new windowed sinc oscillator.
56206
+ * @param best If true, uses the larger, anti-aliased kernel. It is too slow for real time playback.
56207
+ */
56208
+ constructor(best) {
56209
+ super();
56210
+ this.kernel = best ? KERNEL_BEST : KERNEL_FAST;
56211
+ this.antiAlias = best;
56212
+ this.scratch = new Float32Array(best ? Math.ceil(this.kernel.halfWidth * MAX_STRETCH) * 2 + 2 : this.kernel.halfWidth * 2);
56213
+ }
56214
+ process(sampleCount, tuningRatio, outputBuffer) {
56215
+ const step = tuningRatio * this.playbackStep;
56216
+ const sampleData = this.sampleData;
56217
+ const scratch = this.scratch;
56218
+ const { loopStart, loopEnd, loopLength, end, isLooping } = this;
56219
+ const { halfWidth } = this.kernel;
56220
+ let cursor = this.cursor;
56221
+ let hasLooped = this.hasLooped;
56222
+ const upperLimit = isLooping ? loopEnd : end;
56223
+ const stretch = this.antiAlias && step > 1 ? Math.min(step, MAX_STRETCH) : 1;
56224
+ if (stretch === 1) {
56225
+ const { phases, phaseDeltas } = this.kernel;
56226
+ const taps = halfWidth * 2;
56227
+ for (let i = 0; i < sampleCount; i++) {
56228
+ if (isLooping) {
56229
+ if (cursor >= loopEnd) {
56230
+ hasLooped = true;
56231
+ while (cursor >= loopEnd) cursor -= loopLength;
56232
+ }
56233
+ } else if (cursor + 1 >= end) {
56234
+ outputBuffer.fill(0, i, sampleCount);
56235
+ this.cursor = cursor;
56236
+ return false;
56237
+ }
56238
+ const y0 = cursor | 0;
56239
+ let first = y0 + 1 - halfWidth;
56240
+ let source = sampleData;
56241
+ if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
56242
+ this.gather(first, taps, hasLooped);
56243
+ source = scratch;
56244
+ first = 0;
56245
+ }
56246
+ const phase = (cursor - y0) * OVERSAMPLING;
56247
+ const q = phase | 0;
56248
+ let row = q * taps;
56249
+ const fraction = phase - q;
56250
+ const rowEnd = row + taps;
56251
+ let out = 0;
56252
+ for (; row < rowEnd; row++) out += source[first++] * (phases[row] + phaseDeltas[row] * fraction);
56253
+ outputBuffer[i] = out;
56254
+ cursor += step;
56255
+ }
56256
+ } else {
56257
+ const { table, tableDeltas } = this.kernel;
56258
+ const reach = halfWidth * stretch;
56259
+ const tableStep = OVERSAMPLING / stretch;
56260
+ const center = halfWidth * OVERSAMPLING;
56261
+ const gain = 1 / stretch;
56262
+ for (let i = 0; i < sampleCount; i++) {
56263
+ if (isLooping) {
56264
+ if (cursor >= loopEnd) {
56265
+ hasLooped = true;
56266
+ while (cursor >= loopEnd) cursor -= loopLength;
56267
+ }
56268
+ } else if (cursor + 1 >= end) {
56269
+ outputBuffer.fill(0, i, sampleCount);
56270
+ this.cursor = cursor;
56271
+ return false;
56272
+ }
56273
+ let first = Math.floor(cursor - reach) + 1;
56274
+ const taps = Math.floor(cursor + reach) - first + 1;
56275
+ let position = (first - cursor) * tableStep + center;
56276
+ let source = sampleData;
56277
+ if (first < (isLooping && hasLooped ? loopStart : 0) || first + taps > upperLimit) {
56278
+ this.gather(first, taps, hasLooped);
56279
+ source = scratch;
56280
+ first = 0;
56281
+ }
56282
+ let out = 0;
56283
+ const last = first + taps;
56284
+ for (; first < last; first++) {
56285
+ const index = position | 0;
56286
+ out += source[first] * (table[index] + tableDeltas[index] * (position - index));
56287
+ position += tableStep;
56288
+ }
56289
+ outputBuffer[i] = out * gain;
56290
+ cursor += step;
56291
+ }
56292
+ }
56293
+ this.cursor = cursor;
56294
+ this.hasLooped = hasLooped;
56295
+ return true;
56296
+ }
56297
+ /**
56298
+ * Copies the points into the scratch buffer, wrapping them around the loop.
56299
+ * Points outside the sample are silent.
56300
+ * @param first The index of the first point.
56301
+ * @param count The amount of points.
56302
+ * @param hasLooped If the cursor has already looped.
56303
+ */
56304
+ gather(first, count, hasLooped) {
56305
+ const sampleData = this.sampleData;
56306
+ const scratch = this.scratch;
56307
+ const { loopStart, loopEnd, loopLength, end } = this;
56308
+ if (this.isLooping) {
56309
+ let index = first;
56310
+ if (index >= loopEnd) index = loopStart + (index - loopStart) % loopLength;
56311
+ else if (index < loopStart && hasLooped) index = loopEnd - 1 - (loopStart - 1 - index) % loopLength;
56312
+ for (let k = 0; k < count; k++) {
56313
+ scratch[k] = index < 0 ? 0 : sampleData[index];
56314
+ if (++index >= loopEnd) index = loopStart;
56315
+ }
56316
+ } else for (let k = 0; k < count; k++) {
56317
+ const index = first + k;
56318
+ scratch[k] = index >= 0 && index < end ? sampleData[index] : 0;
56319
+ }
56320
+ }
56321
+ };
55842
56322
  // src/synthesizer/audio_engine/voice/voice.ts
55843
56323
  /**
55844
56324
  * Voice.ts
@@ -55863,7 +56343,9 @@ var Voice = class {
55863
56343
  oscillators = [
55864
56344
  new LinearOscillator(),
55865
56345
  new NearestOscillator(),
55866
- new HermiteOscillator()
56346
+ new HermiteOscillator(),
56347
+ new SincOscillator(false),
56348
+ new SincOscillator(true)
55867
56349
  ];
55868
56350
  /**
55869
56351
  * The oscillator currently used by this voice.
@@ -55950,7 +56432,7 @@ var Voice = class {
55950
56432
  * This is the effective MIDI note number,
55951
56433
  * used to calculate scale tuning and envelope times,
55952
56434
  * and can be overridden by generators.
55953
- * It is also used
56435
+ * It is also used for the octave tuning and as the portamento target.
55954
56436
  */
55955
56437
  targetKey = 0;
55956
56438
  /**
@@ -56043,7 +56525,9 @@ var Voice = class {
56043
56525
  this.filter = new LowpassFilter(sampleRate);
56044
56526
  }
56045
56527
  /**
56046
- * Releases the voice as exclusiveClass.
56528
+ * Releases the voice as exclusiveClass, with a nearly instant release time.
56529
+ * @param currentTime the current time in seconds.
56530
+ * @param minExclusiveLength minimum note length in seconds.
56047
56531
  */
56048
56532
  exclusiveRelease(currentTime, minExclusiveLength = MIN_EXCLUSIVE_LENGTH) {
56049
56533
  this.overrideReleaseVolEnv = EXCLUSIVE_CUTOFF_TIME;
@@ -56051,8 +56535,9 @@ var Voice = class {
56051
56535
  this.releaseVoice(currentTime, minExclusiveLength);
56052
56536
  }
56053
56537
  /**
56054
- * Stops the voice
56055
- * @param currentTime
56538
+ * Releases the voice. The release phase starts on the first render at or after the release time,
56539
+ * which is pushed back if the note would be shorter than the minimum length.
56540
+ * @param currentTime the current time in seconds.
56056
56541
  * @param minNoteLength minimum note length in seconds
56057
56542
  */
56058
56543
  releaseVoice(currentTime, minNoteLength = MIN_NOTE_LENGTH) {
@@ -56190,7 +56675,7 @@ function lockMIDIParameterInternal(parameter, isLocked) {
56190
56675
  * @param byte1 The first byte (midi note).
56191
56676
  * @param byte2 The second byte (most significant bits).
56192
56677
  * @param byte3 The third byte (the least significant bits).
56193
- * @return An object containing the MIDI note and the cent tuning value.
56678
+ * @returns The MIDI note with the fraction added (in semitones), or -1 for no change.
56194
56679
  */
56195
56680
  function getTuning(byte1, byte2, byte3) {
56196
56681
  const midiNote = byte1;
@@ -56354,8 +56839,8 @@ function universalSystemExclusive(syx, channelOffset = 0) {
56354
56839
  const program = syx[currentMessageIndex++];
56355
56840
  const tuningName = readBinaryString(syx, 16, currentMessageIndex);
56356
56841
  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)`);
56842
+ if (syx.length < 405) {
56843
+ SpessaLog.warn(`The Bulk Tuning Dump is too short! (${syx.length} bytes, at least 405 are expected)`);
56359
56844
  return;
56360
56845
  }
56361
56846
  for (let midiNote = 0; midiNote < 128; midiNote++) this.tunings[program * 128 + midiNote] = getTuning(syx[currentMessageIndex++], syx[currentMessageIndex++], syx[currentMessageIndex++]);
@@ -57799,8 +58284,8 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
57799
58284
  for (const ch of this.midiChannels) {
57800
58285
  if (ch.midiParameters.drumMap !== setupNumber) continue;
57801
58286
  ch.drumParams[drumKey].pitchFine = pitch;
57802
- SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "semitones");
57803
58287
  }
58288
+ SpessaLog.xgInfo(`Drum Pitch Fine for key ${drumKey}`, pitch, "cents");
57804
58289
  break;
57805
58290
  }
57806
58291
  case 2:
@@ -57933,7 +58418,7 @@ function applyPresetZones(indexes, presetGens, presetMods, instruments, presets)
57933
58418
  }
57934
58419
  // src/soundbank/soundfont/read/presets.ts
57935
58420
  /**
57936
- * Parses soundfont presets, also includes function for getting the generators and samples from midi note and velocity
58421
+ * Parses soundfont presets
57937
58422
  */
57938
58423
  var SoundFontPreset = class extends BasicPreset {
57939
58424
  zoneStartIndex;
@@ -58030,7 +58515,7 @@ function readInstruments(instrumentChunk, keepTerminal = false) {
58030
58515
  }
58031
58516
  // src/soundbank/soundfont/read/modulators.ts
58032
58517
  /**
58033
- * Reads the modulator read
58518
+ * Reads the modulators
58034
58519
  * @param modulatorChunk pmod, imod or DMOD
58035
58520
  * @param allowSFEGenerators if destinations above endOper are allowed (SFE banks)
58036
58521
  */
@@ -58379,7 +58864,11 @@ function applySnapshot(snapshot) {
58379
58864
  this.xgReverbBlock.applySnapshot(snapshot.xgReverbBlock);
58380
58865
  this.xgChorusBlock.applySnapshot(snapshot.xgChorusBlock);
58381
58866
  this.xgVariationBlock.applySnapshot(snapshot.xgVariationBlock);
58382
- for (let i = 0; i < this.xgInsertionBlocks.length; i++) this.xgInsertionBlocks[i].applySnapshot(snapshot.xgInsertionBlocks[i]);
58867
+ for (let i = 0; i < this.xgInsertionBlocks.length; i++) {
58868
+ const insertion = this.xgInsertionBlocks[i];
58869
+ if (i < snapshot.xgInsertionBlocks.length) insertion.applySnapshot(snapshot.xgInsertionBlocks[i]);
58870
+ else insertion.reset();
58871
+ }
58383
58872
  for (let drumSet = 0; drumSet < snapshot.userDrumSets.length; drumSet++) {
58384
58873
  const userDrumSet = snapshot.userDrumSets[drumSet];
58385
58874
  for (let midiNote = 0; midiNote < userDrumSet.length; midiNote++) DrumParameterUtils.copyIntoUser(userDrumSet[midiNote], this.soundBankManager.userDrumSets[drumSet].keyParams[midiNote]);
@@ -58411,7 +58900,8 @@ function getSynthesizerSnapshot() {
58411
58900
  /**
58412
58901
  * Keeps the decoded sample data under a size budget by releasing the least recently used samples.
58413
58902
  * A released sample decodes again the next time it plays.
58414
- * Samples that cannot decode again (replaced float data) are never released.
58903
+ * Samples that cannot decode again (replaced float data) are never released
58904
+ * and are not tracked, so they do not count towards the budget.
58415
58905
  * @internal
58416
58906
  */
58417
58907
  var DecodedSampleCache = class {
@@ -58424,6 +58914,7 @@ var DecodedSampleCache = class {
58424
58914
  */
58425
58915
  totalBytes = 0;
58426
58916
  entries = /* @__PURE__ */ new Map();
58917
+ unreleasable = /* @__PURE__ */ new WeakSet();
58427
58918
  voiceCache;
58428
58919
  /**
58429
58920
  * @param budget The budget in bytes.
@@ -58444,7 +58935,11 @@ var DecodedSampleCache = class {
58444
58935
  touch(sample, cacheKey) {
58445
58936
  let entry = this.entries.get(sample);
58446
58937
  if (entry) this.entries.delete(sample);
58447
- else {
58938
+ if (this.unreleasable.has(sample) || sample.storedForm === "float32" && !(sample instanceof DLSSample)) {
58939
+ if (entry) this.totalBytes -= entry.bytes;
58940
+ return;
58941
+ }
58942
+ if (!entry) {
58448
58943
  entry = {
58449
58944
  bytes: sample.getAudioData().byteLength,
58450
58945
  cacheKeys: /* @__PURE__ */ new Set()
@@ -58461,7 +58956,10 @@ var DecodedSampleCache = class {
58461
58956
  if (sample === keep) continue;
58462
58957
  this.entries.delete(sample);
58463
58958
  this.totalBytes -= entry.bytes;
58464
- sample.releaseDecoded();
58959
+ if (!sample.releaseDecoded()) {
58960
+ this.unreleasable.add(sample);
58961
+ continue;
58962
+ }
58465
58963
  for (const key of entry.cacheKeys) this.voiceCache.delete(key);
58466
58964
  }
58467
58965
  }
@@ -58596,7 +59094,7 @@ var SpessaSynthProcessor = class {
58596
59094
  */
58597
59095
  sharedVoiceSendBuffer;
58598
59096
  /**
58599
- * The insertion processor's left input buffer.
59097
+ * The GS insertion processor's left input buffer.
58600
59098
  * @internal
58601
59099
  */
58602
59100
  insertionInputL;
@@ -58801,11 +59299,6 @@ var SpessaSynthProcessor = class {
58801
59299
  */
58802
59300
  cachedVoices = /* @__PURE__ */ new Map();
58803
59301
  /**
58804
- * Keeps the decoded sample data under the budget from the options.
58805
- * @internal
58806
- */
58807
- decodedSamples;
58808
- /**
58809
59302
  * Insertion is not used outside SC-88Pro+ MIDIs, this is an optimization.
58810
59303
  * @internal
58811
59304
  */
@@ -58828,6 +59321,11 @@ var SpessaSynthProcessor = class {
58828
59321
  */
58829
59322
  gsInsertionBlock;
58830
59323
  /**
59324
+ * Keeps the decoded sample data under the budget from the options.
59325
+ * @internal
59326
+ */
59327
+ decodedSamples;
59328
+ /**
58831
59329
  * The synthesizer's GS reverb processor.
58832
59330
  *
58833
59331
  * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
@@ -58856,7 +59354,7 @@ var SpessaSynthProcessor = class {
58856
59354
  */
58857
59355
  xgReverbBlock;
58858
59356
  /**
58859
- * The synthesizer's XG reverb block.
59357
+ * The synthesizer's XG chorus block.
58860
59358
  *
58861
59359
  * Used when {@link GlobalMIDIParameter.system} is `xg`.
58862
59360
  * @internal
@@ -58978,6 +59476,13 @@ var SpessaSynthProcessor = class {
58978
59476
  return this._voiceCount;
58979
59477
  }
58980
59478
  /**
59479
+ * The decoded sample data kept under {@link SynthProcessorOptions.decodedSampleBudget}, in bytes.
59480
+ * It is only counted when a budget is set.
59481
+ */
59482
+ get decodedSampleBytes() {
59483
+ return this.decodedSamples.totalBytes;
59484
+ }
59485
+ /**
58981
59486
  * Locks or unlocks a given {@link GlobalMIDIParameter}.
58982
59487
  * This prevents any changes to it until it's unlocked.
58983
59488
  * @param parameter The Global MIDI Parameter to lock.
@@ -59010,7 +59515,7 @@ var SpessaSynthProcessor = class {
59010
59515
  * > Refer to the [MIDI Implementation](../../docs/extra/midi-implementation.md)
59011
59516
  * > for the list of supported System Exclusives.
59012
59517
  *
59013
- * @param syx The system exclusive message as an array of bytes.
59518
+ * @param syx The system exclusive message as an array of bytes, without the starting `F0` byte.
59014
59519
  * @param channelOffset The channel offset for the message as they usually can only address the first 16 channels.
59015
59520
  * For example, to send a system exclusive on channel 16,
59016
59521
  * send a system exclusive for channel 0 and set an offset of 16.
@@ -59232,11 +59737,11 @@ var SpessaSynthProcessor = class {
59232
59737
  for (const message of messages) this.processMessage(message, channelOffset, options);
59233
59738
  }
59234
59739
  /**
59235
- * Destroy the synthesizer processor, clearing all channels and voices.
59740
+ * Destroy the synthesizer processor, clearing all channels and voices.
59236
59741
  *
59237
- * > **Warning**
59238
- * >
59239
- * > This is irreversible, so use with caution.
59742
+ * > **Warning**
59743
+ * >
59744
+ * > This is irreversible, so use with caution.
59240
59745
  */
59241
59746
  destroy() {
59242
59747
  this.voices.length = 0;
@@ -59264,6 +59769,9 @@ var SpessaSynthProcessor = class {
59264
59769
  * >
59265
59770
  * > This method overrides the existing System Parameters with the ones from the snapshot.
59266
59771
  *
59772
+ * If this processor has more XG insertion blocks than the snapshot,
59773
+ * the blocks without snapshot data are reset to their defaults.
59774
+ *
59267
59775
  * @param snapshot The snapshot to apply.
59268
59776
  */
59269
59777
  applySnapshot(snapshot) {
@@ -59573,10 +60081,11 @@ var SpessaSynthProcessor = class {
59573
60081
  const channel = this.midiChannels[this.xgVariationBlock.partNumber];
59574
60082
  if (channel) this.processXGInsertion(this.xgVariationBlock, channel, sampleCount);
59575
60083
  }
60084
+ const { reverbGain, chorusGain } = this.systemParameters;
59576
60085
  for (const ch of this.midiChannels) {
59577
60086
  if (!ch.xgInsertionAssigned) continue;
59578
- const revSend = (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
59579
- const choSend = (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
60087
+ const revSend = reverbGain * (ch.midiControllers[MIDIControllers.reverbDepth] >> 7) / 127;
60088
+ const choSend = chorusGain * (ch.midiControllers[MIDIControllers.chorusDepth] >> 7) / 127;
59580
60089
  if (revSend <= 0 && choSend <= 0) continue;
59581
60090
  const { outputLeft, outputRight } = ch;
59582
60091
  for (let i = 0; i < sampleCount; i++) {
@@ -59692,13 +60201,6 @@ var SpessaSynthProcessor = class {
59692
60201
  this.soundBankManager.refresh();
59693
60202
  }
59694
60203
  /**
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
60204
  * Clears the synthesizer's voice cache.
59703
60205
  * This can be used to hear the changes after editing a {@link BasicSoundBank}
59704
60206
  */
@@ -59730,7 +60232,7 @@ var SpessaSynthProcessor = class {
59730
60232
  */
59731
60233
  purgeCachedPatch(patch) {
59732
60234
  const patchKey = this.getCachedVoiceIndex(patch, 0, 0);
59733
- for (const key of this.cachedVoices.keys()) if (key % 2113536 === patchKey) this.cachedVoices.delete(key);
60235
+ for (const key of this.cachedVoices.keys()) if (key % 4194304 === patchKey) this.cachedVoices.delete(key);
59734
60236
  }
59735
60237
  /**
59736
60238
  * Sets a global MIDI parameter of the synthesizer.
@@ -60210,14 +60712,8 @@ var SpessaSynthProcessor = class {
60210
60712
  }, "gs");
60211
60713
  }
60212
60714
  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;
60715
+ const { isGMGSDrum, program, bankLSB } = patch;
60716
+ return (isGMGSDrum ? 128 + patch.bankMSB : patch.bankMSB) + bankLSB * 256 + program * 32768 + 4194304 * midiNote + 536870912 * velocity;
60221
60717
  }
60222
60718
  };
60223
60719
  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 };