spessasynth_core 4.4.6 → 4.4.8

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
@@ -411,7 +411,7 @@ var RIFFChunk = class RIFFChunk {
411
411
  }
412
412
  const parts = [getStringBytes(headerWritten), sizeBytes];
413
413
  if (isList) parts.push(getStringBytes(header));
414
- parts.push(...chunks);
414
+ for (const chunk of chunks) parts.push(chunk);
415
415
  if (totalSize % 2 !== 0) parts.push(/* @__PURE__ */ new Uint8Array(1));
416
416
  return parts;
417
417
  }
@@ -511,8 +511,8 @@ function audioToWav(audioData, sampleRate, options = DEFAULT_WAV_WRITE_OPTIONS)
511
511
  let cueChunk = new IndexedByteArray(0);
512
512
  const cueOn = loop?.end !== void 0 && loop?.start !== void 0;
513
513
  if (cueOn) {
514
- const loopStartSamples = Math.floor(loop.start * sampleRate);
515
- const loopEndSamples = Math.floor(loop.end * sampleRate);
514
+ const loopStartSamples = Math.round(loop.start * sampleRate);
515
+ const loopEndSamples = Math.round(loop.end * sampleRate);
516
516
  const cueStart = new IndexedByteArray(24);
517
517
  writeLittleEndianIndexed(cueStart, 0, 4);
518
518
  writeLittleEndianIndexed(cueStart, 0, 4);
@@ -629,6 +629,105 @@ function audioToWav(audioData, sampleRate, options = DEFAULT_WAV_WRITE_OPTIONS)
629
629
  if (cueOn) wavData.set(cueChunk, offset);
630
630
  return wavData.buffer;
631
631
  }
632
+ // src/utils/read_wav.ts
633
+ /**
634
+ * Reads a WAVE file into PCM audio data, the counterpart of {@link audioToWav}.
635
+ *
636
+ * It reads 8, 16, 24 and 32-bit integer PCM and 32 and 64-bit float files (including `WAVE_FORMAT_EXTENSIBLE`).
637
+ * The loop is read from the first loop of the `smpl` chunk. Without one, the first two points of the `cue ` chunk
638
+ * are taken as the loop start and end, which is how {@link audioToWav} writes it.
639
+ * Other files may use cue points as plain markers, so such a loop is only reliable for files written by core.
640
+ *
641
+ * > **Note**
642
+ * >
643
+ * > The returned loop end is exclusive, like the SoundFont loop end.
644
+ *
645
+ * @param buffer The binary WAVE file.
646
+ * @returns The audio data (one array per channel), the sample rate and the loop, if the file has one.
647
+ * @throws Error if the file is not a valid or supported WAVE file.
648
+ *
649
+ * @group Utilities
650
+ */
651
+ function wavToAudio(buffer) {
652
+ const view = new DataView(buffer);
653
+ const bytes = new Uint8Array(buffer);
654
+ const fourCC = (at) => String.fromCodePoint(bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]);
655
+ if (buffer.byteLength < 12 || fourCC(0) !== "RIFF" || fourCC(8) !== "WAVE") throw new Error("Not a WAVE file.");
656
+ let format = 0;
657
+ let channels = 0;
658
+ let sampleRate = 0;
659
+ let bitsPerSample = 0;
660
+ let dataStart = -1;
661
+ let dataSize = 0;
662
+ let smplLoop;
663
+ const cuePoints = new Array();
664
+ let pos = 12;
665
+ while (pos + 8 <= buffer.byteLength) {
666
+ const id = fourCC(pos);
667
+ const size = view.getUint32(pos + 4, true);
668
+ const body = pos + 8;
669
+ const available = Math.min(size, buffer.byteLength - body);
670
+ switch (id) {
671
+ case "fmt ":
672
+ format = view.getUint16(body, true);
673
+ channels = view.getUint16(body + 2, true);
674
+ sampleRate = view.getUint32(body + 4, true);
675
+ bitsPerSample = view.getUint16(body + 14, true);
676
+ if (format === 65534 && size >= 26) format = view.getUint16(body + 24, true);
677
+ break;
678
+ case "data":
679
+ dataStart = body;
680
+ dataSize = available;
681
+ break;
682
+ case "smpl":
683
+ if ((size >= 36 ? view.getUint32(body + 28, true) : 0) > 0 && size >= 60) smplLoop = {
684
+ start: view.getUint32(body + 36 + 8, true),
685
+ end: view.getUint32(body + 36 + 12, true) + 1
686
+ };
687
+ break;
688
+ case "cue ": {
689
+ const count = view.getUint32(body, true);
690
+ for (let i = 0; i < count && 4 + (i + 1) * 24 <= size; i++) cuePoints.push(view.getUint32(body + 4 + i * 24 + 20, true));
691
+ break;
692
+ }
693
+ }
694
+ pos = body + size + size % 2;
695
+ }
696
+ if (channels === 0 || dataStart === -1) throw new Error("The WAVE file has no fmt or data chunk!");
697
+ const bytesPerSample = bitsPerSample >> 3;
698
+ const isFloat = format === 3;
699
+ if (format !== 1 && format !== 3 || (isFloat ? ![4, 8].includes(bytesPerSample) : bytesPerSample < 1 || bytesPerSample > 4)) throw new Error(`Unsupported WAVE format ${format} with ${bitsPerSample} bits per sample.`);
700
+ const frames = Math.floor(dataSize / (bytesPerSample * channels));
701
+ const audioData = Array.from({ length: channels }, () => new Float32Array(frames));
702
+ for (let i = 0; i < frames; i++) for (let c = 0; c < channels; c++) {
703
+ const at = dataStart + (i * channels + c) * bytesPerSample;
704
+ let value;
705
+ if (isFloat) value = bytesPerSample === 4 ? view.getFloat32(at, true) : view.getFloat64(at, true);
706
+ else switch (bytesPerSample) {
707
+ case 1:
708
+ value = (view.getUint8(at) - 128) / 128;
709
+ break;
710
+ case 2:
711
+ value = view.getInt16(at, true) / 32768;
712
+ break;
713
+ case 3:
714
+ value = ((bytes[at + 2] << 24 | bytes[at + 1] << 16 | bytes[at] << 8) >> 8) / 8388608;
715
+ break;
716
+ default: value = view.getInt32(at, true) / 2147483648;
717
+ }
718
+ audioData[c][i] = value;
719
+ }
720
+ let loop = smplLoop;
721
+ if (!loop && cuePoints.length >= 2) loop = {
722
+ start: cuePoints[0],
723
+ end: cuePoints[1]
724
+ };
725
+ return {
726
+ audioData,
727
+ sampleRate,
728
+ loop
729
+ };
730
+ }
632
731
  // src/utils/loggin.ts
633
732
  /**
634
733
  * SpessaSynth can print out additional info to the console or print nothing at all.
@@ -1603,56 +1702,43 @@ const GeneratorLimits = Object.freeze({
1603
1702
  min: -12e3,
1604
1703
  max: 12e3,
1605
1704
  def: 0,
1606
- nrpn: 2,
1607
- presetMin: -24e3,
1608
- presetMax: 24e3
1705
+ nrpn: 2
1609
1706
  },
1610
1707
  [GeneratorTypes.vibLfoToPitch]: {
1611
1708
  min: -12e3,
1612
1709
  max: 12e3,
1613
1710
  def: 0,
1614
- nrpn: 2,
1615
- presetMin: -24e3,
1616
- presetMax: 24e3
1711
+ nrpn: 2
1617
1712
  },
1618
1713
  [GeneratorTypes.modEnvToPitch]: {
1619
1714
  min: -12e3,
1620
1715
  max: 12e3,
1621
1716
  def: 0,
1622
- nrpn: 2,
1623
- presetMin: -24e3,
1624
- presetMax: 24e3
1717
+ nrpn: 2
1625
1718
  },
1626
1719
  [GeneratorTypes.initialFilterFc]: {
1627
1720
  min: 1500,
1628
1721
  max: 13500,
1629
1722
  def: 13500,
1630
- nrpn: 2,
1631
- presetMin: -21e3,
1632
- presetMax: 21e3
1723
+ nrpn: 2
1633
1724
  },
1634
1725
  [GeneratorTypes.initialFilterQ]: {
1635
1726
  min: 0,
1636
1727
  max: 960,
1637
1728
  def: 0,
1638
- nrpn: 1,
1639
- presetMin: -960
1729
+ nrpn: 1
1640
1730
  },
1641
1731
  [GeneratorTypes.modLfoToFilterFc]: {
1642
1732
  min: -12e3,
1643
1733
  max: 12e3,
1644
1734
  def: 0,
1645
- nrpn: 2,
1646
- presetMin: -24e3,
1647
- presetMax: 24e3
1735
+ nrpn: 2
1648
1736
  },
1649
1737
  [GeneratorTypes.modEnvToFilterFc]: {
1650
1738
  min: -12e3,
1651
1739
  max: 12e3,
1652
1740
  def: 0,
1653
- nrpn: 2,
1654
- presetMin: -24e3,
1655
- presetMax: 24e3
1741
+ nrpn: 2
1656
1742
  },
1657
1743
  [GeneratorTypes.endAddrsCoarseOffset]: {
1658
1744
  min: -32768,
@@ -1664,189 +1750,145 @@ const GeneratorLimits = Object.freeze({
1664
1750
  min: -960,
1665
1751
  max: 960,
1666
1752
  def: 0,
1667
- nrpn: 1,
1668
- presetMin: -1920,
1669
- presetMax: 1920
1753
+ nrpn: 1
1670
1754
  },
1671
1755
  [GeneratorTypes.chorusEffectsSend]: {
1672
1756
  min: 0,
1673
1757
  max: 1e3,
1674
1758
  def: 0,
1675
- nrpn: 1,
1676
- presetMin: -1e3
1759
+ nrpn: 1
1677
1760
  },
1678
1761
  [GeneratorTypes.reverbEffectsSend]: {
1679
1762
  min: 0,
1680
1763
  max: 1e3,
1681
1764
  def: 0,
1682
- nrpn: 1,
1683
- presetMin: -1e3
1765
+ nrpn: 1
1684
1766
  },
1685
1767
  [GeneratorTypes.pan]: {
1686
1768
  min: -500,
1687
1769
  max: 500,
1688
1770
  def: 0,
1689
- nrpn: 1,
1690
- presetMin: -1e3,
1691
- presetMax: 1e3
1771
+ nrpn: 1
1692
1772
  },
1693
1773
  [GeneratorTypes.delayModLFO]: {
1694
1774
  min: -12e3,
1695
1775
  max: 5e3,
1696
1776
  def: -12e3,
1697
- nrpn: 2,
1698
- presetMin: -21e3,
1699
- presetMax: 21e3
1777
+ nrpn: 2
1700
1778
  },
1701
1779
  [GeneratorTypes.freqModLFO]: {
1702
1780
  min: -16e3,
1703
1781
  max: 4500,
1704
1782
  def: 0,
1705
- nrpn: 4,
1706
- presetMin: -21e3,
1707
- presetMax: 21e3
1783
+ nrpn: 4
1708
1784
  },
1709
1785
  [GeneratorTypes.delayVibLFO]: {
1710
1786
  min: -12e3,
1711
1787
  max: 5e3,
1712
1788
  def: -12e3,
1713
- nrpn: 2,
1714
- presetMin: -21e3,
1715
- presetMax: 21e3
1789
+ nrpn: 2
1716
1790
  },
1717
1791
  [GeneratorTypes.freqVibLFO]: {
1718
1792
  min: -16e3,
1719
1793
  max: 4500,
1720
1794
  def: 0,
1721
- nrpn: 4,
1722
- presetMin: -21e3,
1723
- presetMax: 21e3
1795
+ nrpn: 4
1724
1796
  },
1725
1797
  [GeneratorTypes.delayModEnv]: {
1726
1798
  min: -32768,
1727
1799
  max: 5e3,
1728
1800
  def: -32768,
1729
- nrpn: 2,
1730
- presetMin: -21e3,
1731
- presetMax: 21e3
1801
+ nrpn: 2
1732
1802
  },
1733
1803
  [GeneratorTypes.attackModEnv]: {
1734
1804
  min: -32768,
1735
1805
  max: 8e3,
1736
1806
  def: -32768,
1737
- nrpn: 2,
1738
- presetMin: -21e3,
1739
- presetMax: 21e3
1807
+ nrpn: 2
1740
1808
  },
1741
1809
  [GeneratorTypes.holdModEnv]: {
1742
1810
  min: -12e3,
1743
1811
  max: 5e3,
1744
1812
  def: -12e3,
1745
- nrpn: 2,
1746
- presetMin: -21e3,
1747
- presetMax: 21e3
1813
+ nrpn: 2
1748
1814
  },
1749
1815
  [GeneratorTypes.decayModEnv]: {
1750
1816
  min: -12e3,
1751
1817
  max: 8e3,
1752
1818
  def: -12e3,
1753
- nrpn: 2,
1754
- presetMin: -21e3,
1755
- presetMax: 21e3
1819
+ nrpn: 2
1756
1820
  },
1757
1821
  [GeneratorTypes.sustainModEnv]: {
1758
1822
  min: 0,
1759
1823
  max: 1e3,
1760
1824
  def: 0,
1761
- nrpn: 1,
1762
- presetMin: -1e3
1825
+ nrpn: 1
1763
1826
  },
1764
1827
  [GeneratorTypes.releaseModEnv]: {
1765
1828
  min: -12e3,
1766
1829
  max: 8e3,
1767
1830
  def: -12e3,
1768
- nrpn: 2,
1769
- presetMin: -21e3,
1770
- presetMax: 21e3
1831
+ nrpn: 2
1771
1832
  },
1772
1833
  [GeneratorTypes.keyNumToModEnvHold]: {
1773
1834
  min: -1200,
1774
1835
  max: 1200,
1775
1836
  def: 0,
1776
- nrpn: 1,
1777
- presetMin: -2400,
1778
- presetMax: 2400
1837
+ nrpn: 1
1779
1838
  },
1780
1839
  [GeneratorTypes.keyNumToModEnvDecay]: {
1781
1840
  min: -1200,
1782
1841
  max: 1200,
1783
1842
  def: 0,
1784
- nrpn: 1,
1785
- presetMin: -2400,
1786
- presetMax: 2400
1843
+ nrpn: 1
1787
1844
  },
1788
1845
  [GeneratorTypes.delayVolEnv]: {
1789
1846
  min: -12e3,
1790
1847
  max: 5e3,
1791
1848
  def: -12e3,
1792
- nrpn: 2,
1793
- presetMin: -21e3,
1794
- presetMax: 21e3
1849
+ nrpn: 2
1795
1850
  },
1796
1851
  [GeneratorTypes.attackVolEnv]: {
1797
1852
  min: -12e3,
1798
1853
  max: 8e3,
1799
1854
  def: -12e3,
1800
- nrpn: 2,
1801
- presetMin: -21e3,
1802
- presetMax: 21e3
1855
+ nrpn: 2
1803
1856
  },
1804
1857
  [GeneratorTypes.holdVolEnv]: {
1805
1858
  min: -12e3,
1806
1859
  max: 5e3,
1807
1860
  def: -12e3,
1808
- nrpn: 2,
1809
- presetMin: -21e3,
1810
- presetMax: 21e3
1861
+ nrpn: 2
1811
1862
  },
1812
1863
  [GeneratorTypes.decayVolEnv]: {
1813
1864
  min: -12e3,
1814
1865
  max: 8e3,
1815
1866
  def: -12e3,
1816
- nrpn: 2,
1817
- presetMin: -21e3,
1818
- presetMax: 21e3
1867
+ nrpn: 2
1819
1868
  },
1820
1869
  [GeneratorTypes.sustainVolEnv]: {
1821
1870
  min: 0,
1822
1871
  max: 1440,
1823
1872
  def: 0,
1824
- nrpn: 1,
1825
- presetMin: -1440
1873
+ nrpn: 1
1826
1874
  },
1827
1875
  [GeneratorTypes.releaseVolEnv]: {
1828
1876
  min: -12e3,
1829
1877
  max: 8e3,
1830
1878
  def: -12e3,
1831
- nrpn: 2,
1832
- presetMin: -21e3,
1833
- presetMax: 21e3
1879
+ nrpn: 2
1834
1880
  },
1835
1881
  [GeneratorTypes.keyNumToVolEnvHold]: {
1836
1882
  min: -1200,
1837
1883
  max: 1200,
1838
1884
  def: 0,
1839
- nrpn: 1,
1840
- presetMin: -2400,
1841
- presetMax: 2400
1885
+ nrpn: 1
1842
1886
  },
1843
1887
  [GeneratorTypes.keyNumToVolEnvDecay]: {
1844
1888
  min: -1200,
1845
1889
  max: 1200,
1846
1890
  def: 0,
1847
- nrpn: 1,
1848
- presetMin: -2400,
1849
- presetMax: 2400
1891
+ nrpn: 1
1850
1892
  },
1851
1893
  [GeneratorTypes.startloopAddrsCoarseOffset]: {
1852
1894
  min: -32768,
@@ -1870,8 +1912,7 @@ const GeneratorLimits = Object.freeze({
1870
1912
  min: 0,
1871
1913
  max: 1440,
1872
1914
  def: 0,
1873
- nrpn: 1,
1874
- presetMin: -1440
1915
+ nrpn: 1
1875
1916
  },
1876
1917
  [GeneratorTypes.endloopAddrsCoarseOffset]: {
1877
1918
  min: -32768,
@@ -1883,28 +1924,23 @@ const GeneratorLimits = Object.freeze({
1883
1924
  min: -120,
1884
1925
  max: 120,
1885
1926
  def: 0,
1886
- nrpn: 1,
1887
- presetMin: -240,
1888
- presetMax: 240
1927
+ nrpn: 1
1889
1928
  },
1890
1929
  [GeneratorTypes.fineTune]: {
1891
1930
  min: -12700,
1892
1931
  max: 12700,
1893
1932
  def: 0,
1894
- nrpn: 1,
1895
- presetMin: -198,
1896
- presetMax: 198
1933
+ nrpn: 1
1897
1934
  },
1898
1935
  [GeneratorTypes.scaleTuning]: {
1899
1936
  min: 0,
1900
1937
  max: 1200,
1901
1938
  def: 100,
1902
- nrpn: 1,
1903
- presetMin: -1200
1939
+ nrpn: 1
1904
1940
  },
1905
1941
  [GeneratorTypes.exclusiveClass]: {
1906
1942
  min: 0,
1907
- max: 99999,
1943
+ max: 32767,
1908
1944
  def: 0,
1909
1945
  nrpn: 0
1910
1946
  },
@@ -2246,6 +2282,7 @@ function resetChannelInternal(sendCCEvents = true) {
2246
2282
  this.resetMIDIParameter("modulationDepth");
2247
2283
  this.resetMIDIParameter("rxChannel");
2248
2284
  this.resetMIDIParameter("efxAssign");
2285
+ this.resetMIDIParameter("eqEnabled");
2249
2286
  this.resetMIDIParameter("polyMode");
2250
2287
  this.resetMIDIParameter("keyShift");
2251
2288
  this.resetMIDIParameter("fineTune");
@@ -2467,6 +2504,7 @@ function applySnapshotInternal(midi, snapshot) {
2467
2504
  gsReverbParams: snapshot.systemParameters.gsReverbLock ? snapshot.gsReverbProcessor : void 0,
2468
2505
  gsChorusParams: snapshot.systemParameters.gsChorusLock ? snapshot.gsChorusProcessor : void 0,
2469
2506
  gsDelayParams: snapshot.systemParameters.gsDelayLock ? snapshot.gsDelayProcessor : void 0,
2507
+ gsEqualizerParams: snapshot.systemParameters.gsEqualizerLock ? snapshot.gsEqualizerProcessor : void 0,
2470
2508
  gsInsertionParams: snapshot.systemParameters.gsInsertionLock ? snapshot.gsInsertionBlock : void 0,
2471
2509
  xgReverbParams: snapshot.systemParameters.xgReverbLock ? snapshot.xgReverbBlock : void 0,
2472
2510
  xgChorusParams: snapshot.systemParameters.xgChorusLock ? snapshot.xgChorusBlock : void 0,
@@ -2779,7 +2817,8 @@ const GSInsertionAddressMap = {
2779
2817
  controlSource1: 27,
2780
2818
  controlDepth1: 28,
2781
2819
  controlSource2: 29,
2782
- controlDepth2: 30
2820
+ controlDepth2: 30,
2821
+ sendEQSwitch: 31
2783
2822
  };
2784
2823
  const GSChorusAddressMap = {
2785
2824
  macro: 56,
@@ -2805,6 +2844,12 @@ const GSDelayAddressMap = {
2805
2844
  feedback: 89,
2806
2845
  sendLevelToReverb: 90
2807
2846
  };
2847
+ const GSEqualizerAddressMap = {
2848
+ lowFrequency: 0,
2849
+ lowGain: 1,
2850
+ highFrequency: 2,
2851
+ highGain: 3
2852
+ };
2808
2853
  /**
2809
2854
  * The SysEx addresses of the channel MIDI parameters that GS and XG both set per part.
2810
2855
  * `gs` is the third address byte of GS PATCH PART (`40 1x xx`) and `xg` is the third address byte of
@@ -2875,6 +2920,10 @@ const NRPNDrumParamMap = {
2875
2920
  const NRPNDrumParamByMSB = new Map(Object.entries(NRPNDrumParamMap).map(([parameter, msb]) => [msb, parameter]));
2876
2921
  // src/midi/midi_tools/midi_utils.ts
2877
2922
  /**
2923
+ * The GS equalizer parameters by their address (`40 02 xx`).
2924
+ */
2925
+ const GS_EQUALIZER_PARAMETER_BY_ADDRESS = new Map(Object.entries(GSEqualizerAddressMap).map(([name, address]) => [address, name]));
2926
+ /**
2878
2927
  * The named GS insertion parameters by their address (`40 03 xx`).
2879
2928
  */
2880
2929
  const GS_INSERTION_PARAMETER_BY_ADDRESS = new Map(Object.entries(GSInsertionAddressMap).map(([name, address]) => [address, name]));
@@ -3017,20 +3066,33 @@ var MIDIUtils = class MIDIUtils {
3017
3066
  return this.gsMessage(ticks, 64, 1, a3, [value]);
3018
3067
  }
3019
3068
  /**
3069
+ * Returns a MIDI event needed to set the given GS Equalizer Parameter.
3070
+ * @param ticks The MIDI tick time for the output event.
3071
+ * @param parameter The parameter to set.
3072
+ * @param value The value to set it to.
3073
+ * @returns The {@link MIDIMessage} needed to set this GS Equalizer Parameter.
3074
+ */
3075
+ static setGSEqualizerParameter(ticks, parameter, value) {
3076
+ const a3 = GSEqualizerAddressMap[parameter];
3077
+ if (a3 === void 0) throw new Error(`Invalid equalizer parameter: ${parameter}`);
3078
+ return this.gsMessage(ticks, 64, 2, a3, [value]);
3079
+ }
3080
+ /**
3020
3081
  * Returns a MIDI event needed to set the given GS Insertion Parameter.
3021
3082
  * @param ticks The MIDI tick time for the output event.
3022
3083
  * @param parameter The parameter to set: `"type"`, a send level or EFX control name (see {@link GSInsertionParameter}),
3023
3084
  * or a 0-based effect-specific parameter number (0-19).
3024
- * @param value The value to set it to.
3085
+ * @param value The value to set it to. A boolean for `"sendEQSwitch"`.
3025
3086
  * @returns The {@link MIDIMessage} needed to set this GS Insertion Parameter.
3026
3087
  */
3027
3088
  static setGSInsertionParameter(ticks, parameter, value) {
3089
+ const data = typeof value === "boolean" ? value ? 1 : 0 : value;
3028
3090
  switch (parameter) {
3029
- case "type": return this.gsMessage(ticks, 64, 3, 0, [value >> 8 & 127, value & 127]);
3091
+ case "type": return this.gsMessage(ticks, 64, 3, 0, [data >> 8 & 127, data & 127]);
3030
3092
  default:
3031
- if (typeof parameter === "string") return this.gsMessage(ticks, 64, 3, GSInsertionAddressMap[parameter], [value]);
3093
+ if (typeof parameter === "string") return this.gsMessage(ticks, 64, 3, GSInsertionAddressMap[parameter], [data]);
3032
3094
  if (!Number.isInteger(parameter) || parameter < 0 || parameter > 19) throw new Error(`Invalid insertion parameter: ${parameter}`);
3033
- return this.gsMessage(ticks, 64, 3, parameter + 3, [value]);
3095
+ return this.gsMessage(ticks, 64, 3, parameter + 3, [data]);
3034
3096
  }
3035
3097
  }
3036
3098
  /**
@@ -3238,6 +3300,7 @@ var MIDIUtils = class MIDIUtils {
3238
3300
  case "drumMap":
3239
3301
  case "velocitySenseDepth":
3240
3302
  case "velocitySenseOffset": return [MIDIUtils.partParamMessage(ticks, channel, system, parameter, value)];
3303
+ case "eqEnabled": return [MIDIUtils.gsMessage(ticks, 64, 64 | MIDIUtils.channelToGSPart(channel), 32, [value ? 1 : 0])];
3241
3304
  case "efxAssign": return [MIDIUtils.gsMessage(ticks, 64, 64 | MIDIUtils.channelToGSPart(channel), 34, [value ? 1 : 0])];
3242
3305
  case "dryLevel": return [MIDIUtils.xgMessage(ticks, 8, channel, 17, [value])];
3243
3306
  }
@@ -3970,6 +4033,12 @@ var MIDIUtils = class MIDIUtils {
3970
4033
  controller: MIDIControllers.reverbDepth,
3971
4034
  value
3972
4035
  }];
4036
+ case 20: return [{
4037
+ type: "Controller Change",
4038
+ channel,
4039
+ controller: MIDIControllers.variationDepth,
4040
+ value
4041
+ }];
3973
4042
  case 21: return [{
3974
4043
  type: "Controller Change",
3975
4044
  channel,
@@ -4438,6 +4507,15 @@ var MIDIUtils = class MIDIUtils {
4438
4507
  value
4439
4508
  }];
4440
4509
  }
4510
+ if (a2 === 2) {
4511
+ const parameter = GS_EQUALIZER_PARAMETER_BY_ADDRESS.get(a3);
4512
+ if (parameter === void 0) return [OTHER];
4513
+ return [{
4514
+ type: "GS Equalizer Param",
4515
+ parameter,
4516
+ value
4517
+ }];
4518
+ }
4441
4519
  if (a2 === 3) {
4442
4520
  switch (a3) {
4443
4521
  case 0: return [{
@@ -4447,6 +4525,11 @@ var MIDIUtils = class MIDIUtils {
4447
4525
  }];
4448
4526
  }
4449
4527
  const named = GS_INSERTION_PARAMETER_BY_ADDRESS.get(a3);
4528
+ if (named === "sendEQSwitch") return [{
4529
+ type: "GS Insertion Param",
4530
+ parameter: named,
4531
+ value: value !== 0
4532
+ }];
4450
4533
  if (named !== void 0) return [{
4451
4534
  type: "GS Insertion Param",
4452
4535
  parameter: named,
@@ -4650,6 +4733,12 @@ var MIDIUtils = class MIDIUtils {
4650
4733
  controller: MIDIControllers.bankSelectLSB,
4651
4734
  value
4652
4735
  }];
4736
+ case 32: return [{
4737
+ type: "Channel MIDI Param",
4738
+ channel,
4739
+ parameter: "eqEnabled",
4740
+ value: value === 1
4741
+ }];
4653
4742
  case 34: return [{
4654
4743
  type: "Channel MIDI Param",
4655
4744
  channel,
@@ -5023,6 +5112,7 @@ var MIDIEditor = class {
5023
5112
  gsReverbParams;
5024
5113
  gsChorusParams;
5025
5114
  gsDelayParams;
5115
+ gsEqualizerParams;
5026
5116
  gsInsertionParams;
5027
5117
  xgReverbParams;
5028
5118
  xgChorusParams;
@@ -5050,10 +5140,11 @@ var MIDIEditor = class {
5050
5140
  constructor(midi, opts) {
5051
5141
  this.midi = midi;
5052
5142
  SpessaLog.groupCollapsed("%cApplying changes to the MIDI file...", ConsoleColors.info);
5053
- const { channels, gsReverbParams, gsChorusParams, gsDelayParams, gsInsertionParams, xgReverbParams, xgChorusParams, xgVariationParams, xgInsertionParams, userDrumParams, midiParams } = opts;
5143
+ const { channels, gsReverbParams, gsChorusParams, gsDelayParams, gsEqualizerParams, gsInsertionParams, xgReverbParams, xgChorusParams, xgVariationParams, xgInsertionParams, userDrumParams, midiParams } = opts;
5054
5144
  this.gsReverbParams = gsReverbParams;
5055
5145
  this.gsChorusParams = gsChorusParams;
5056
5146
  this.gsDelayParams = gsDelayParams;
5147
+ this.gsEqualizerParams = gsEqualizerParams;
5057
5148
  this.gsInsertionParams = gsInsertionParams;
5058
5149
  this.xgReverbParams = xgReverbParams;
5059
5150
  this.xgChorusParams = xgChorusParams;
@@ -5126,12 +5217,12 @@ var MIDIEditor = class {
5126
5217
  }
5127
5218
  if (this.lastReset !== void 0 && replacedEvents.has(this.lastReset)) this.system = this.fallbackReset ?? "gs";
5128
5219
  }
5129
- if (!this.addedReset && this.midiParams?.system !== "clear" && (this.midiParams?.system !== void 0 || this.fallbackReset !== void 0 || this.gsReverbParams && this.gsReverbParams !== "clear" || this.gsChorusParams && this.gsChorusParams !== "clear" || this.gsDelayParams && this.gsDelayParams !== "clear" || this.gsInsertionParams && this.gsInsertionParams !== "clear" || this.xgReverbParams && this.xgReverbParams !== "clear" || this.xgChorusParams && this.xgChorusParams !== "clear" || this.xgVariationParams && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear") || this.userDrumSetParams?.size || [...this.channelChanges.values()].some((c) => c.patch && c.patch !== "clear"))) {
5220
+ if (!this.addedReset && this.midiParams?.system !== "clear" && (this.midiParams?.system !== void 0 || this.fallbackReset !== void 0 || this.gsReverbParams && this.gsReverbParams !== "clear" || this.gsChorusParams && this.gsChorusParams !== "clear" || this.gsDelayParams && this.gsDelayParams !== "clear" || this.gsEqualizerParams && this.gsEqualizerParams !== "clear" || this.gsInsertionParams && this.gsInsertionParams !== "clear" || this.xgReverbParams && this.xgReverbParams !== "clear" || this.xgChorusParams && this.xgChorusParams !== "clear" || this.xgVariationParams && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear") || this.userDrumSetParams?.size || [...this.channelChanges.values()].some((c) => c.patch && c.patch !== "clear"))) {
5130
5221
  let index = 0;
5131
5222
  const firstTrack = this.midi.tracks[0];
5132
5223
  while (index < firstTrack.events.length && firstTrack.events[index].statusByte < MIDIMessageTypes.noteOff) index++;
5133
5224
  const hasXG = this.xgReverbParams !== void 0 && this.xgReverbParams !== "clear" || this.xgChorusParams !== void 0 && this.xgChorusParams !== "clear" || this.xgVariationParams !== void 0 && this.xgVariationParams !== "clear" || [...this.xgInsertionParams?.values() ?? []].some((p) => p !== "clear");
5134
- const hasGS = this.gsReverbParams !== void 0 && this.gsReverbParams !== "clear" || this.gsChorusParams !== void 0 && this.gsChorusParams !== "clear" || this.gsDelayParams !== void 0 && this.gsDelayParams !== "clear" || this.gsInsertionParams !== void 0 && this.gsInsertionParams !== "clear";
5225
+ const hasGS = this.gsReverbParams !== void 0 && this.gsReverbParams !== "clear" || this.gsChorusParams !== void 0 && this.gsChorusParams !== "clear" || this.gsDelayParams !== void 0 && this.gsDelayParams !== "clear" || this.gsEqualizerParams !== void 0 && this.gsEqualizerParams !== "clear" || this.gsInsertionParams !== void 0 && this.gsInsertionParams !== "clear";
5135
5226
  const targetSystem = this.midiParams?.system ?? this.fallbackReset ?? (hasXG && !hasGS ? "xg" : "gs");
5136
5227
  const event = MIDIUtils.reset(0, targetSystem);
5137
5228
  firstTrack.addEvents(index, event);
@@ -5371,6 +5462,12 @@ var MIDIEditor = class {
5371
5462
  return;
5372
5463
  }
5373
5464
  break;
5465
+ case "GS Equalizer Param":
5466
+ if (this.gsEqualizerParams) {
5467
+ this.deleteCurrentEvent();
5468
+ return;
5469
+ }
5470
+ break;
5374
5471
  case "GS Insertion Param":
5375
5472
  if (this.gsInsertionParams) {
5376
5473
  this.deleteCurrentEvent();
@@ -5785,6 +5882,10 @@ var MIDIEditor = class {
5785
5882
  const p = this.gsDelayParams;
5786
5883
  output.push(MIDIUtils.setGSDelayParameter(targetTicks, "level", p.level), MIDIUtils.setGSDelayParameter(targetTicks, "preLowpass", p.preLowpass), MIDIUtils.setGSDelayParameter(targetTicks, "timeCenter", p.timeCenter), MIDIUtils.setGSDelayParameter(targetTicks, "timeRatioLeft", p.timeRatioLeft), MIDIUtils.setGSDelayParameter(targetTicks, "timeRatioRight", p.timeRatioRight), MIDIUtils.setGSDelayParameter(targetTicks, "levelCenter", p.levelCenter), MIDIUtils.setGSDelayParameter(targetTicks, "levelLeft", p.levelLeft), MIDIUtils.setGSDelayParameter(targetTicks, "levelRight", p.levelRight), MIDIUtils.setGSDelayParameter(targetTicks, "feedback", p.feedback), MIDIUtils.setGSDelayParameter(targetTicks, "sendLevelToReverb", p.sendLevelToReverb));
5787
5884
  }
5885
+ if (this.gsEqualizerParams && this.gsEqualizerParams !== "clear") {
5886
+ const p = this.gsEqualizerParams;
5887
+ for (const name of Object.keys(GSEqualizerAddressMap)) output.push(MIDIUtils.setGSEqualizerParameter(targetTicks, name, p[name]));
5888
+ }
5788
5889
  if (this.gsInsertionParams && this.gsInsertionParams !== "clear") {
5789
5890
  const p = this.gsInsertionParams;
5790
5891
  output.push(MIDIUtils.setGSInsertionParameter(targetTicks, "type", p.type));
@@ -7278,6 +7379,22 @@ var BasicSample = class {
7278
7379
  return this.linkedTo.length;
7279
7380
  }
7280
7381
  /**
7382
+ * If the decoded float data is currently held in memory.
7383
+ * Check it before {@link getAudioData} to know if the call has to decode,
7384
+ * then use {@link releaseDecoded} to drop the copy again.
7385
+ */
7386
+ get isDecoded() {
7387
+ return this.audioData !== void 0;
7388
+ }
7389
+ /**
7390
+ * If the decoded float data can be made again from the stored data.
7391
+ * False for replaced data (set with {@link BasicSample.setAudioData}), which has nothing to decode from.
7392
+ * @internal
7393
+ */
7394
+ get canRedecode() {
7395
+ return !this.dataOverridden && (this.compressedData !== void 0 || this.s16leData !== void 0);
7396
+ }
7397
+ /**
7281
7398
  * Get raw data for writing the file, either a compressed bit stream or signed 16-bit little endian PCM data.
7282
7399
  * @param allowVorbis if vorbis file data is allowed.
7283
7400
  * @return either s16le or vorbis data.
@@ -7353,7 +7470,7 @@ var BasicSample = class {
7353
7470
  * @returns True if the decoded data was dropped.
7354
7471
  */
7355
7472
  releaseDecoded() {
7356
- if (this.audioData && !this.dataOverridden && (this.compressedData || this.s16leData)) {
7473
+ if (this.audioData && this.canRedecode) {
7357
7474
  this.audioData = void 0;
7358
7475
  return true;
7359
7476
  }
@@ -7385,12 +7502,13 @@ var BasicSample = class {
7385
7502
  this.setAudioData(resampled, newSampleRate);
7386
7503
  }
7387
7504
  /**
7388
- * Compresses the audio data
7505
+ * Compresses the audio data.
7389
7506
  * @param encodeVorbis the compression function to use when compressing.
7507
+ * @returns True if the sample is compressed afterward, false if the encoder failed and it stays uncompressed.
7390
7508
  * @internal
7391
7509
  */
7392
7510
  async compressSample(encodeVorbis) {
7393
- if (this.isCompressed) return;
7511
+ if (this.isCompressed) return true;
7394
7512
  try {
7395
7513
  let audioData = this.getAudioData();
7396
7514
  if (this.sampleRate < 8e3 || this.sampleRate > 96e3) {
@@ -7399,9 +7517,11 @@ var BasicSample = class {
7399
7517
  }
7400
7518
  const compressed = await encodeVorbis(audioData, this.sampleRate);
7401
7519
  this.setCompressedData(compressed);
7520
+ return true;
7402
7521
  } catch (error) {
7403
7522
  SpessaLog.warn(`Failed to compress ${this.name}. Leaving as uncompressed!`, error);
7404
7523
  this.compressedData = void 0;
7524
+ return false;
7405
7525
  }
7406
7526
  }
7407
7527
  /**
@@ -7451,16 +7571,17 @@ var BasicSample = class {
7451
7571
  }
7452
7572
  }
7453
7573
  /**
7454
- * Links the sample to a given instrument
7455
- * @param instrument the instrument to link to
7574
+ * Links the sample to a given instrument.
7575
+ * @param instrument the instrument to link to.
7456
7576
  * @internal
7457
7577
  */
7458
7578
  linkTo(instrument) {
7459
7579
  this.linkedTo.push(instrument);
7460
7580
  }
7461
7581
  /**
7462
- * Unlinks the sample from a given instrument
7463
- * @param instrument the instrument to unlink from
7582
+ * Unlinks the sample from a given instrument.
7583
+ * @param instrument the instrument to unlink from.
7584
+ * @internal
7464
7585
  */
7465
7586
  unlinkFrom(instrument) {
7466
7587
  const index = this.linkedTo.indexOf(instrument);
@@ -7827,12 +7948,12 @@ var ModulatorSource = class ModulatorSource {
7827
7948
  */
7828
7949
  curveType;
7829
7950
  /**
7830
- * @internal
7831
- * @param index
7832
- * @param curveType
7833
- * @param isCC
7834
- * @param isBipolar
7835
- * @param isNegative
7951
+ * Creates a new modulator source.
7952
+ * @param index The controller or MIDI CC number, see `isCC`.
7953
+ * @param curveType How the minimum value approaches the maximum value.
7954
+ * @param isCC If the index is a MIDI CC instead of a general controller.
7955
+ * @param isBipolar If the source is bipolar (-1 to 1) instead of unipolar (0 to 1).
7956
+ * @param isNegative If the direction of the source is flipped.
7836
7957
  */
7837
7958
  constructor(index = ModulatorControllerSources.noController, curveType = ModulatorCurveTypes.linear, isCC = false, isBipolar = false, isNegative = false) {
7838
7959
  this.isBipolar = isBipolar;
@@ -8300,14 +8421,13 @@ var BasicLinkedZone = class extends BasicZone {
8300
8421
  }
8301
8422
  /**
8302
8423
  * Links the child to the parent, unless this zone was created without linking.
8303
- * @internal
8424
+ * Use it to put a zone back after {@link unlinkChild}, for example when undoing a zone deletion.
8304
8425
  */
8305
8426
  linkChild() {
8306
8427
  if (this.linked) this.child.linkTo(this.parent);
8307
8428
  }
8308
8429
  /**
8309
8430
  * Unlinks the child from the parent, unless this zone was created without linking.
8310
- * @internal
8311
8431
  */
8312
8432
  unlinkChild() {
8313
8433
  if (this.linked) this.child.unlinkFrom(this.parent);
@@ -8695,7 +8815,7 @@ var BasicPreset = class BasicPreset {
8695
8815
  }
8696
8816
  }
8697
8817
  finalGenList = finalGenList.filter((g) => g.type !== GeneratorTypes.sampleID && g.type !== GeneratorTypes.keyRange && g.type !== GeneratorTypes.velRange && g.type !== GeneratorTypes.endOper && g.type !== GeneratorTypes.instrument && (!(g.type in GeneratorLimits) || g.value !== GeneratorLimits[g.type].def));
8698
- const zone = outputInstrument.createZone(instZone.sample);
8818
+ const zone = outputInstrument.createZone(instZone.sample, false);
8699
8819
  zone.keyRange = instZoneKeyRange;
8700
8820
  zone.velRange = instZoneVelRange;
8701
8821
  if (zone.keyRange.min === 0 && zone.keyRange.max === 127) zone.keyRange.min = -1;
@@ -8825,6 +8945,7 @@ var BasicInstrument = class {
8825
8945
  /**
8826
8946
  * Unlinks the instrument from a given preset.
8827
8947
  * @param preset The preset to unlink from.
8948
+ * @internal
8828
8949
  */
8829
8950
  unlinkFrom(preset) {
8830
8951
  const index = this.linkedTo.indexOf(preset);
@@ -9109,7 +9230,7 @@ function writeSF(bank, software, writeDefaultModulators, writeExtendedLimits, wr
9109
9230
  */
9110
9231
  const infoArrays = [];
9111
9232
  const writeSF2Info = (type, data) => {
9112
- if (!data) return;
9233
+ if (data === void 0) return;
9113
9234
  infoArrays.push(...RIFFChunk.getParts(type, [getStringBytes(data, true, true)], rf64));
9114
9235
  };
9115
9236
  const info = bank.soundBankInfo;
@@ -9120,7 +9241,7 @@ function writeSF(bank, software, writeDefaultModulators, writeExtendedLimits, wr
9120
9241
  infoArrays.push(RIFFChunk.write("ifil", ifilData, rf64));
9121
9242
  }
9122
9243
  writeSF2Info("isng", info.soundEngine);
9123
- writeSF2Info("INAM", info.name);
9244
+ writeSF2Info("INAM", info.name ?? "");
9124
9245
  writeSF2Info("irom", info.romInfo);
9125
9246
  if (info.romVersion) {
9126
9247
  const ifilData = new IndexedByteArray(4);
@@ -9507,6 +9628,9 @@ var DLSSample = class extends BasicSample {
9507
9628
  if (this.rawData && !this.audioData) return "float32";
9508
9629
  return super.storedForm;
9509
9630
  }
9631
+ get canRedecode() {
9632
+ return this.rawData !== void 0 && !this.dataOverridden || super.canRedecode;
9633
+ }
9510
9634
  getStoredData() {
9511
9635
  if (this.rawData && !this.audioData) this.getAudioData();
9512
9636
  return super.getStoredData();
@@ -9515,13 +9639,6 @@ var DLSSample = class extends BasicSample {
9515
9639
  this.rawData = void 0;
9516
9640
  super.setStoredData(stored);
9517
9641
  }
9518
- releaseDecoded() {
9519
- if (this.rawData && this.audioData && !this.dataOverridden) {
9520
- this.audioData = void 0;
9521
- return true;
9522
- }
9523
- return super.releaseDecoded();
9524
- }
9525
9642
  releaseData() {
9526
9643
  if (this.hasData) this.releasedForm = this.storedForm;
9527
9644
  this.cachedLength = this.sampleLength;
@@ -9599,7 +9716,7 @@ var DownloadableSoundsSample = class DownloadableSoundsSample extends DLSVerifie
9599
9716
  }
9600
9717
  static fromSFSample(sample) {
9601
9718
  const raw = sample.getRawData(false);
9602
- const dlsSample = new DownloadableSoundsSample(1, 2, sample.sampleRate, new RIFFChunk("data", raw.length, new IndexedByteArray(raw.buffer)));
9719
+ const dlsSample = new DownloadableSoundsSample(1, 2, sample.sampleRate, new RIFFChunk("data", raw.length, new IndexedByteArray(raw.buffer, raw.byteOffset, raw.byteLength)));
9603
9720
  dlsSample.name = sample.name;
9604
9721
  dlsSample.waveSample = WaveSample.fromSFSample(sample);
9605
9722
  return dlsSample;
@@ -10687,8 +10804,8 @@ var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DL
10687
10804
  SpessaLog.groupCollapsed(`%cWriting %c${this.name}%c...`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info);
10688
10805
  const chunks = [this.writeHeader()];
10689
10806
  const regionChunks = this.regions.flatMap((r) => r.write());
10690
- chunks.push(...RIFFChunk.getParts("lrgn", regionChunks, false, true));
10691
- if (this.articulation.length > 0) chunks.push(...this.articulation.write());
10807
+ for (const part of RIFFChunk.getParts("lrgn", regionChunks, false, true)) chunks.push(part);
10808
+ if (this.articulation.length > 0) for (const part of this.articulation.write()) chunks.push(part);
10692
10809
  const inam = RIFFChunk.write("INAM", getStringBytes(this.name, true));
10693
10810
  chunks.push(RIFFChunk.write("INFO", inam, false, true));
10694
10811
  SpessaLog.groupEnd();
@@ -10973,7 +11090,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
10973
11090
  */
10974
11091
  const DEFAULT_SOUND_BANK_CLONE_OPTIONS = {
10975
11092
  reuseByName: true,
10976
- reuseChildrenByName: true,
11093
+ reuseChildren: "name",
10977
11094
  addToBank: true
10978
11095
  };
10979
11096
  const MAX_BASE_NAME_LENGTH = 37;
@@ -11020,6 +11137,17 @@ var SoundBankCloner = class {
11020
11137
  this.bank = bank;
11021
11138
  this.options = fillWithDefaults(options, DEFAULT_SOUND_BANK_CLONE_OPTIONS);
11022
11139
  }
11140
+ get ownReuse() {
11141
+ return this.options.reuseByName ? "name" : "none";
11142
+ }
11143
+ /**
11144
+ * Gets the clone this session made of an element.
11145
+ * @param source The sample or instrument that was cloned.
11146
+ * @returns The clone, or undefined if the session has not cloned the element.
11147
+ */
11148
+ getClone(source) {
11149
+ return source instanceof BasicSample ? this.sampleClones.get(source) : this.instrumentClones.get(source);
11150
+ }
11023
11151
  /**
11024
11152
  * Clones a sample and its stereo partner.
11025
11153
  * The data is shared with the original, not copied.
@@ -11027,7 +11155,7 @@ var SoundBankCloner = class {
11027
11155
  * @returns The clone, or the existing sample with the same name if it is reused.
11028
11156
  */
11029
11157
  cloneSample(sample) {
11030
- return this.copySample(sample, this.options.reuseByName);
11158
+ return this.copySample(sample, this.ownReuse);
11031
11159
  }
11032
11160
  /**
11033
11161
  * Clones an instrument and the samples it uses.
@@ -11035,7 +11163,7 @@ var SoundBankCloner = class {
11035
11163
  * @returns The clone, or the existing instrument with the same name if it is reused.
11036
11164
  */
11037
11165
  cloneInstrument(instrument) {
11038
- return this.copyInstrument(instrument, this.options.reuseByName);
11166
+ return this.copyInstrument(instrument, this.ownReuse);
11039
11167
  }
11040
11168
  /**
11041
11169
  * Clones a preset and the instruments and samples it uses.
@@ -11044,10 +11172,8 @@ var SoundBankCloner = class {
11044
11172
  */
11045
11173
  clonePreset(preset) {
11046
11174
  const all = [...this.bank.presets, ...this.created.presets];
11047
- if (this.options.reuseByName) {
11048
- const existing = all.find((p) => p.name === preset.name);
11049
- if (existing) return existing;
11050
- }
11175
+ const existing = this.findReused(preset, all, this.ownReuse);
11176
+ if (existing) return existing;
11051
11177
  const newPreset = new BasicPreset(this.bank);
11052
11178
  newPreset.name = this.getFreeName(preset.name, all);
11053
11179
  newPreset.bankMSB = preset.bankMSB;
@@ -11058,7 +11184,7 @@ var SoundBankCloner = class {
11058
11184
  newPreset.genre = preset.genre;
11059
11185
  newPreset.morphology = preset.morphology;
11060
11186
  newPreset.globalZone.copyFrom(preset.globalZone);
11061
- for (const zone of preset.zones) newPreset.createZone(this.copyInstrument(zone.instrument, this.options.reuseChildrenByName)).copyFrom(zone);
11187
+ for (const zone of preset.zones) newPreset.createZone(this.copyInstrument(zone.instrument, this.options.reuseChildren)).copyFrom(zone);
11062
11188
  this.created.presets.push(newPreset);
11063
11189
  if (this.options.addToBank) this.bank.addPresets(newPreset);
11064
11190
  return newPreset;
@@ -11067,14 +11193,12 @@ var SoundBankCloner = class {
11067
11193
  const cloned = this.instrumentClones.get(instrument);
11068
11194
  if (cloned) return cloned;
11069
11195
  const all = [...this.bank.instruments, ...this.created.instruments];
11070
- if (reuse) {
11071
- const existing = all.find((i) => i.name === instrument.name);
11072
- if (existing) return existing;
11073
- }
11196
+ const existing = this.findReused(instrument, all, reuse);
11197
+ if (existing) return existing;
11074
11198
  const newInstrument = new BasicInstrument();
11075
11199
  newInstrument.name = this.getFreeName(instrument.name, all);
11076
11200
  newInstrument.globalZone.copyFrom(instrument.globalZone);
11077
- for (const zone of instrument.zones) newInstrument.createZone(this.copySample(zone.sample, this.options.reuseChildrenByName)).copyFrom(zone);
11201
+ for (const zone of instrument.zones) newInstrument.createZone(this.copySample(zone.sample, this.options.reuseChildren)).copyFrom(zone);
11078
11202
  this.instrumentClones.set(instrument, newInstrument);
11079
11203
  this.created.instruments.push(newInstrument);
11080
11204
  if (this.options.addToBank) this.bank.addInstruments(newInstrument);
@@ -11084,10 +11208,8 @@ var SoundBankCloner = class {
11084
11208
  const cloned = this.sampleClones.get(sample);
11085
11209
  if (cloned) return cloned;
11086
11210
  const all = [...this.bank.samples, ...this.created.samples];
11087
- if (reuse) {
11088
- const existing = all.find((s) => s.name === sample.name);
11089
- if (existing) return existing;
11090
- }
11211
+ const existing = this.findReused(sample, all, reuse);
11212
+ if (existing) return existing;
11091
11213
  const newSample = new BasicSample(this.getFreeName(sample.name, all), sample.sampleRate, sample.originalKey, sample.pitchCorrection, sample.sampleType, sample.loopStart, sample.loopEnd);
11092
11214
  if (sample.hasData) newSample.setStoredData(sample.getStoredData());
11093
11215
  else {
@@ -11101,10 +11223,20 @@ var SoundBankCloner = class {
11101
11223
  if (linked) {
11102
11224
  const newLinked = this.sampleClones.get(linked);
11103
11225
  if (newLinked) newSample.setLinkedSample(newLinked, sample.sampleType);
11104
- else this.copySample(linked, false);
11226
+ else this.copySample(linked, "none");
11105
11227
  }
11106
11228
  return newSample;
11107
11229
  }
11230
+ /**
11231
+ * Finds the element to use instead of a copy, if the options allow one.
11232
+ */
11233
+ findReused(element, elements, reuse) {
11234
+ switch (reuse) {
11235
+ case "none": return;
11236
+ case "identity": return elements.includes(element) ? element : void 0;
11237
+ case "name": return elements.find((e) => e.name === element.name);
11238
+ }
11239
+ }
11108
11240
  getFreeName(name, elements) {
11109
11241
  const taken = new Set(elements.map((e) => e.name));
11110
11242
  if (!taken.has(name)) return name;
@@ -11270,7 +11402,7 @@ var BasicSoundBank = class BasicSoundBank {
11270
11402
  const copied = new BasicSoundBank(bank.type);
11271
11403
  const cloner = new SoundBankCloner(copied, {
11272
11404
  reuseByName: false,
11273
- reuseChildrenByName: false
11405
+ reuseChildren: "none"
11274
11406
  });
11275
11407
  for (const p of bank.presets) cloner.clonePreset(p);
11276
11408
  copied.soundBankInfo = { ...bank.soundBankInfo };
@@ -11284,13 +11416,13 @@ var BasicSoundBank = class BasicSoundBank {
11284
11416
  * @param presets The presets to add.
11285
11417
  */
11286
11418
  addCompletePresets(presets) {
11287
- this.addPresets(...presets);
11419
+ this.addElements(this.presets, presets);
11288
11420
  const instrumentList = [];
11289
11421
  for (const preset of presets) for (const zone of preset.zones) if (zone.instrument && !instrumentList.includes(zone.instrument)) instrumentList.push(zone.instrument);
11290
- this.addInstruments(...instrumentList);
11422
+ this.addElements(this.instruments, instrumentList);
11291
11423
  const sampleList = [];
11292
11424
  for (const instrument of instrumentList) for (const zone of instrument.zones) if (zone.sample && !sampleList.includes(zone.sample)) sampleList.push(zone.sample);
11293
- this.addSamples(...sampleList);
11425
+ this.addElements(this.samples, sampleList);
11294
11426
  }
11295
11427
  /**
11296
11428
  * Sets the sound bank's sample format _in place_.
@@ -11304,9 +11436,11 @@ var BasicSoundBank = class BasicSoundBank {
11304
11436
  * @param options Options associated with setting the sample format.
11305
11437
  * @returns The samples whose sample rate or loop points changed.
11306
11438
  * Compressing resamples samples outside 8000 to 96000 Hz.
11439
+ * @throws Error if `rejectOnFailure` is set and one of the samples has failed to compress.
11307
11440
  */
11308
11441
  async setSampleFormat(options) {
11309
11442
  const changed = new Array();
11443
+ const failed = new Array();
11310
11444
  let writtenCount = 0;
11311
11445
  const format = options.format;
11312
11446
  const progressFunc = options.progressFunction;
@@ -11320,7 +11454,7 @@ var BasicSoundBank = class BasicSoundBank {
11320
11454
  const f = options.compressionFunction;
11321
11455
  if (!f) throw new Error(`No compression function supplied but '${format}' was requested.`);
11322
11456
  const { sampleRate, loopStart, loopEnd } = s;
11323
- await s.compressSample(f);
11457
+ if (!await s.compressSample(f)) failed.push(s);
11324
11458
  if (s.sampleRate !== sampleRate || s.loopStart !== loopStart || s.loopEnd !== loopEnd) changed.push(s);
11325
11459
  }
11326
11460
  }
@@ -11328,6 +11462,7 @@ var BasicSoundBank = class BasicSoundBank {
11328
11462
  progressFunc?.(writtenCount / this.samples.length);
11329
11463
  SpessaLog.info(`%cEncoded sample %c${writtenCount}. ${s.name}%c of %c${this.samples.length}%c. Compressed: %c${s.isCompressed}%c.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, s.isCompressed ? ConsoleColors.recognized : ConsoleColors.unrecognized, ConsoleColors.info);
11330
11464
  }
11465
+ if (failed.length > 0 && options.format === "compressed" && options.rejectOnFailure) throw new Error(`Failed to compress ${failed.length} samples: ${failed.map((s) => s.name).join(", ")}`);
11331
11466
  switch (format) {
11332
11467
  default:
11333
11468
  case "pcm":
@@ -11391,21 +11526,21 @@ var BasicSoundBank = class BasicSoundBank {
11391
11526
  * @param presets The presets to add.
11392
11527
  */
11393
11528
  addPresets(...presets) {
11394
- this.presets.push(...presets);
11529
+ this.addElements(this.presets, presets);
11395
11530
  }
11396
11531
  /**
11397
11532
  * Adds {@link BasicInstrument}s to this sound bank.
11398
11533
  * @param instruments The instruments to add.
11399
11534
  */
11400
11535
  addInstruments(...instruments) {
11401
- this.instruments.push(...instruments);
11536
+ this.addElements(this.instruments, instruments);
11402
11537
  }
11403
11538
  /**
11404
11539
  * Adds {@link BasicSample}s to this sound bank.
11405
11540
  * @param samples The samples to add.
11406
11541
  */
11407
11542
  addSamples(...samples) {
11408
- this.samples.push(...samples);
11543
+ this.addElements(this.samples, samples);
11409
11544
  }
11410
11545
  /**
11411
11546
  * Clones a sample into this bank, together with its stereo partner.
@@ -11457,9 +11592,11 @@ var BasicSoundBank = class BasicSoundBank {
11457
11592
  }
11458
11593
  /**
11459
11594
  * Updates internal values. Call after updating the preset list.
11595
+ * @param sort If the presets should be sorted. Set it to `false` to keep a custom order,
11596
+ * only the internal values (like {@link isXGBank}) are then recomputed.
11460
11597
  */
11461
- flush() {
11462
- this.presets.sort(MIDIPatchTools.compare.bind(MIDIPatchTools));
11598
+ flush(sort = true) {
11599
+ if (sort) this.presets.sort(MIDIPatchTools.compare.bind(MIDIPatchTools));
11463
11600
  this.parseInternal();
11464
11601
  }
11465
11602
  /**
@@ -11604,6 +11741,9 @@ var BasicSoundBank = class BasicSoundBank {
11604
11741
  this.instruments.length = 0;
11605
11742
  this.samples.length = 0;
11606
11743
  }
11744
+ addElements(list, elements) {
11745
+ for (const element of elements) list.push(element);
11746
+ }
11607
11747
  parsingError(error) {
11608
11748
  throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
11609
11749
  }
@@ -15405,7 +15545,6 @@ function setTimeToInternal(time, ticks = void 0) {
15405
15545
  const channel = analyzed.channel + portOffset;
15406
15546
  if (channel >= this.synth.midiChannels.length) break;
15407
15547
  channels[channel] ??= createChannelStatus(channel);
15408
- if (this._midiData.isMultiPort && track.channels.size === 0) break;
15409
15548
  if (controller === MIDIControllers.resetAllControllers) {
15410
15549
  resetAllControllers(channel);
15411
15550
  break;
@@ -32584,6 +32723,148 @@ var DefaultGSDelay = class {
32584
32723
  this.gainRight = this._levelRight / 127;
32585
32724
  }
32586
32725
  };
32726
+ // src/synthesizer/audio_engine/effects/gs/implementation/equalizer.ts
32727
+ const LOW_FREQUENCIES = [225, 425];
32728
+ const HIGH_FREQUENCIES = [3e3, 6e3];
32729
+ /**
32730
+ * The default GS Equalizer implementation for {@link SpessaSynthProcessor}.
32731
+ *
32732
+ * @group Synthesizer.GS Effects
32733
+ */
32734
+ var DefaultGSEqualizer = class {
32735
+ sampleRate;
32736
+ lowB0 = 1;
32737
+ lowB1 = 0;
32738
+ lowA1 = 0;
32739
+ highB0 = 1;
32740
+ highB1 = 0;
32741
+ highA1 = 0;
32742
+ lowZL = 0;
32743
+ lowZR = 0;
32744
+ highZL = 0;
32745
+ highZR = 0;
32746
+ /**
32747
+ * Constructs a new default GS equalizer processor.
32748
+ * @param sampleRate The sample rate, in Hertz.
32749
+ */
32750
+ constructor(sampleRate) {
32751
+ this.sampleRate = sampleRate;
32752
+ this.updateLow();
32753
+ this.updateHigh();
32754
+ }
32755
+ _lowFrequency = 0;
32756
+ get lowFrequency() {
32757
+ return this._lowFrequency;
32758
+ }
32759
+ set lowFrequency(value) {
32760
+ this._lowFrequency = value;
32761
+ this.updateLow();
32762
+ }
32763
+ _lowGain = 64;
32764
+ get lowGain() {
32765
+ return this._lowGain;
32766
+ }
32767
+ set lowGain(value) {
32768
+ this._lowGain = value;
32769
+ this.updateLow();
32770
+ }
32771
+ _highFrequency = 0;
32772
+ get highFrequency() {
32773
+ return this._highFrequency;
32774
+ }
32775
+ set highFrequency(value) {
32776
+ this._highFrequency = value;
32777
+ this.updateHigh();
32778
+ }
32779
+ _highGain = 64;
32780
+ get highGain() {
32781
+ return this._highGain;
32782
+ }
32783
+ set highGain(value) {
32784
+ this._highGain = value;
32785
+ this.updateHigh();
32786
+ }
32787
+ process(inputLeft, inputRight, outputLeft, outputRight, startIndex, sampleCount) {
32788
+ const { lowB0, lowB1, lowA1, highB0, highB1, highA1 } = this;
32789
+ let { lowZL, lowZR, highZL, highZR } = this;
32790
+ for (let i = 0; i < sampleCount; i++) {
32791
+ const o = startIndex + i;
32792
+ let x = inputLeft[i];
32793
+ let y = lowB0 * x + lowZL;
32794
+ lowZL = lowB1 * x - lowA1 * y;
32795
+ x = y;
32796
+ y = highB0 * x + highZL;
32797
+ highZL = highB1 * x - highA1 * y;
32798
+ outputLeft[o] += y;
32799
+ x = inputRight[i];
32800
+ y = lowB0 * x + lowZR;
32801
+ lowZR = lowB1 * x - lowA1 * y;
32802
+ x = y;
32803
+ y = highB0 * x + highZR;
32804
+ highZR = highB1 * x - highA1 * y;
32805
+ outputRight[o] += y;
32806
+ }
32807
+ this.lowZL = lowZL;
32808
+ this.lowZR = lowZR;
32809
+ this.highZL = highZL;
32810
+ this.highZR = highZR;
32811
+ }
32812
+ getSnapshot() {
32813
+ return {
32814
+ lowFrequency: this._lowFrequency,
32815
+ lowGain: this._lowGain,
32816
+ highFrequency: this._highFrequency,
32817
+ highGain: this._highGain
32818
+ };
32819
+ }
32820
+ applySnapshot(snapshot) {
32821
+ this.lowFrequency = snapshot.lowFrequency;
32822
+ this.lowGain = snapshot.lowGain;
32823
+ this.highFrequency = snapshot.highFrequency;
32824
+ this.highGain = snapshot.highGain;
32825
+ }
32826
+ getBand(frequency, gain) {
32827
+ const fc = Math.min(frequency, this.sampleRate * .45);
32828
+ const dB = Math.max(-12, Math.min(12, gain - 64));
32829
+ return {
32830
+ k: Math.tan(Math.PI * fc / this.sampleRate),
32831
+ g: 10 ** (Math.abs(dB) / 20),
32832
+ cut: dB < 0
32833
+ };
32834
+ }
32835
+ updateLow() {
32836
+ const { k, g, cut } = this.getBand(LOW_FREQUENCIES[this._lowFrequency & 1], this._lowGain);
32837
+ let b0 = 1 + k;
32838
+ let b1 = k - 1;
32839
+ let a0 = 1 + k / g;
32840
+ let a1 = k / g - 1;
32841
+ if (cut) [b0, b1, a0, a1] = [
32842
+ a0,
32843
+ a1,
32844
+ b0,
32845
+ b1
32846
+ ];
32847
+ this.lowB0 = b0 / a0;
32848
+ this.lowB1 = b1 / a0;
32849
+ this.lowA1 = a1 / a0;
32850
+ }
32851
+ updateHigh() {
32852
+ const { k, g, cut } = this.getBand(HIGH_FREQUENCIES[this._highFrequency & 1], this._highGain);
32853
+ let b0 = g * (1 + k);
32854
+ let b1 = g * (k - 1);
32855
+ let a0 = 1 + g * k;
32856
+ let a1 = g * k - 1;
32857
+ if (cut) [b0, b1, a0, a1] = [
32858
+ a0,
32859
+ a1,
32860
+ b0,
32861
+ b1
32862
+ ];
32863
+ this.highB0 = b0 / a0;
32864
+ this.highB1 = b1 / a0;
32865
+ this.highA1 = a1 / a0;
32866
+ }
32867
+ };
32587
32868
  // src/synthesizer/audio_engine/effects/gs/implementation/dsp/gs_effect.ts
32588
32869
  /**
32589
32870
  * The number of EFX parameters (1-20 in the manuals, 0-19 here).
@@ -52388,6 +52669,7 @@ var DefaultGSInsertion = class {
52388
52669
  sendLevelToReverb = DEFAULT_SEND_LEVEL_TO_REVERB;
52389
52670
  sendLevelToChorus = 0;
52390
52671
  sendLevelToDelay = 0;
52672
+ sendEQSwitch = true;
52391
52673
  controlSource1 = GSInsertionControlSources.off;
52392
52674
  controlDepth1 = DEFAULT_CONTROL_DEPTH;
52393
52675
  controlSource2 = GSInsertionControlSources.off;
@@ -52457,6 +52739,7 @@ var DefaultGSInsertion = class {
52457
52739
  this.sendLevelToReverb = DEFAULT_SEND_LEVEL_TO_REVERB;
52458
52740
  this.sendLevelToChorus = 0;
52459
52741
  this.sendLevelToDelay = 0;
52742
+ this.sendEQSwitch = true;
52460
52743
  }
52461
52744
  getType() {
52462
52745
  return this.type;
@@ -52513,6 +52796,7 @@ var DefaultGSInsertion = class {
52513
52796
  sendLevelToReverb: this.sendLevelToReverb,
52514
52797
  sendLevelToChorus: this.sendLevelToChorus,
52515
52798
  sendLevelToDelay: this.sendLevelToDelay,
52799
+ sendEQSwitch: this.sendEQSwitch,
52516
52800
  controlSource1: this.controlSource1,
52517
52801
  controlDepth1: this.controlDepth1,
52518
52802
  controlSource2: this.controlSource2,
@@ -52533,6 +52817,7 @@ var DefaultGSInsertion = class {
52533
52817
  this.sendLevelToReverb = snapshot.sendLevelToReverb;
52534
52818
  this.sendLevelToChorus = snapshot.sendLevelToChorus;
52535
52819
  this.sendLevelToDelay = snapshot.sendLevelToDelay;
52820
+ this.sendEQSwitch = snapshot.sendEQSwitch ?? true;
52536
52821
  }
52537
52822
  /**
52538
52823
  * Sends the offsets of both control sources to the processor.
@@ -52662,15 +52947,19 @@ var SoundBankManager = class {
52662
52947
  */
52663
52948
  userDrumSets;
52664
52949
  presetListChangeCallback;
52950
+ bankRemovedCallback;
52665
52951
  selectablePresetList = [];
52666
52952
  /**
52667
52953
  * @internal
52668
52954
  * @param presetListChangeCallback Supplied by the parent synthesizer class,
52669
52955
  * this is called whenever the preset list changes.
52670
52956
  * The argument tells if the synthesizer should reset after updating its channels.
52957
+ * @param bankRemovedCallback Called when a sound bank leaves the manager (deleted, replaced or destroyed),
52958
+ * so the synthesizer can drop what it holds of it.
52671
52959
  */
52672
- constructor(presetListChangeCallback) {
52960
+ constructor(presetListChangeCallback, bankRemovedCallback) {
52673
52961
  this.presetListChangeCallback = presetListChangeCallback;
52962
+ this.bankRemovedCallback = bankRemovedCallback;
52674
52963
  const resolvePatch = (patch) => this.getPreset(patch, this.systemGetter());
52675
52964
  this.userDrumSets = [new UserDrumSet(64, "User Drum Set 1", resolvePatch), new UserDrumSet(65, "User Drum Set 2", resolvePatch)];
52676
52965
  }
@@ -52719,13 +53008,16 @@ var SoundBankManager = class {
52719
53008
  /**
52720
53009
  * This method removes a sound bank with a given ID from the sound bank list.
52721
53010
  * @param id The ID of the sound bank to delete.
53011
+ * @param reset If the synthesizer should reset (put every channel's controllers back to defaults) after the change.
53012
+ * Set it to `false` to keep a song playing, the channels then only update to the new presets.
52722
53013
  * @throws Error An error if there's no sound bank with the given ID.
52723
53014
  */
52724
- deleteSoundBank(id) {
53015
+ deleteSoundBank(id, reset = true) {
52725
53016
  const index = this.soundBankList.findIndex((s) => s.id === id);
52726
53017
  if (index === -1) throw new Error(`No sound bank with id "${id}"`);
52727
- this.soundBankList.splice(index, 1);
52728
- this.generatePresetList();
53018
+ const [removed] = this.soundBankList.splice(index, 1);
53019
+ this.bankRemoved(removed.soundBank);
53020
+ this.generatePresetList(reset);
52729
53021
  }
52730
53022
  /**
52731
53023
  * Adds a new sound bank with a given ID, or replaces an existing one.
@@ -52733,8 +53025,10 @@ var SoundBankManager = class {
52733
53025
  * @param id The unique ID to assign to the sound bank.
52734
53026
  * If a sound bank with this ID already exists, it will be replaced.
52735
53027
  * @param bankOffset The bank offset of the sound bank. Offsets the bank MSB value of all presets in this sound bank.
53028
+ * @param reset If the synthesizer should reset (put every channel's controllers back to defaults) after the change.
53029
+ * Set it to `false` to keep a song playing, the channels then only update to the new presets.
52736
53030
  */
52737
- addSoundBank(bank, id, bankOffset = 0) {
53031
+ addSoundBank(bank, id, bankOffset = 0, reset = true) {
52738
53032
  const foundBank = this.soundBankList.find((s) => s.id === id);
52739
53033
  if (foundBank === void 0) this.soundBankList.push({
52740
53034
  id,
@@ -52742,10 +53036,12 @@ var SoundBankManager = class {
52742
53036
  bankOffset
52743
53037
  });
52744
53038
  else {
53039
+ const old = foundBank.soundBank;
52745
53040
  foundBank.soundBank = bank;
52746
53041
  foundBank.bankOffset = bankOffset;
53042
+ this.bankRemoved(old);
52747
53043
  }
52748
- this.generatePresetList();
53044
+ this.generatePresetList(reset);
52749
53045
  }
52750
53046
  /**
52751
53047
  * Rebuilds the preset list from the current sound banks.
@@ -52775,11 +53071,17 @@ var SoundBankManager = class {
52775
53071
  * @internal
52776
53072
  */
52777
53073
  destroy() {
52778
- for (const s of this.soundBankList) s.soundBank.destroySoundBank();
53074
+ for (const s of this.soundBankList) {
53075
+ this.bankRemovedCallback?.(s.soundBank);
53076
+ s.soundBank.destroySoundBank();
53077
+ }
52779
53078
  this.soundBankList = [];
52780
53079
  this.selectablePresetList = [];
52781
53080
  this._presetList = [];
52782
53081
  }
53082
+ bankRemoved(bank) {
53083
+ if (!this.soundBankList.some((s) => s.soundBank === bank)) this.bankRemovedCallback?.(bank);
53084
+ }
52783
53085
  generatePresetList(resetSynth = true) {
52784
53086
  const presetList = new Array();
52785
53087
  const addedPresets = /* @__PURE__ */ new Set();
@@ -52928,7 +53230,7 @@ function renderVoice(voice, timeNow, sampleCount) {
52928
53230
  let targetKey = voice.targetKey;
52929
53231
  let cents = voice.pitchOffset + modulated[GeneratorTypes.fineTune] + this.octaveTuning[targetKey] + this.currentTuning;
52930
53232
  let semitones = modulated[GeneratorTypes.coarseTune];
52931
- const tuning = core.tunings[this.preset.program * 128 + voice.midiNote];
53233
+ const tuning = core.tunings[(this.preset?.program ?? this.patch.program) * 128 + voice.midiNote];
52932
53234
  if (tuning !== -1) {
52933
53235
  targetKey = Math.trunc(tuning);
52934
53236
  semitones += tuning - targetKey;
@@ -54365,6 +54667,7 @@ const DEFAULT_CHANNEL_MIDI_PARAMETERS = {
54365
54667
  randomPan: false,
54366
54668
  assignMode: 2,
54367
54669
  efxAssign: false,
54670
+ eqEnabled: true,
54368
54671
  cc1: 16,
54369
54672
  cc2: 17,
54370
54673
  drumMap: 0,
@@ -55239,6 +55542,7 @@ const DEFAULT_GLOBAL_SYSTEM_PARAMETERS = {
55239
55542
  xgChorusLock: false,
55240
55543
  gsDelayGain: 1,
55241
55544
  gsDelayLock: false,
55545
+ gsEqualizerLock: false,
55242
55546
  xgVariationGain: 1,
55243
55547
  xgVariationLock: false,
55244
55548
  gsInsertionLock: false,
@@ -57251,6 +57555,27 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
57251
57555
  }
57252
57556
  break;
57253
57557
  }
57558
+ if (a2 === 2) {
57559
+ if (this.systemParameters.gsEqualizerLock) return;
57560
+ const parameter = [
57561
+ "lowFrequency",
57562
+ "lowGain",
57563
+ "highFrequency",
57564
+ "highGain"
57565
+ ][a3];
57566
+ if (parameter === void 0) {
57567
+ SpessaLog.gsFail("Equalizer", [a3]);
57568
+ return;
57569
+ }
57570
+ this.gsEqualizerProcessor[parameter] = data;
57571
+ SpessaLog.gsInfo(`EQ ${parameter}`, data);
57572
+ this.callEvent("effectChange", {
57573
+ effect: "gsEqualizer",
57574
+ parameter,
57575
+ value: data
57576
+ });
57577
+ return;
57578
+ }
57254
57579
  if (a2 === 3) {
57255
57580
  if (this.systemParameters.gsInsertionLock) return;
57256
57581
  const insertion = this.gsInsertionBlock;
@@ -57327,6 +57652,17 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
57327
57652
  });
57328
57653
  return;
57329
57654
  }
57655
+ case 31: {
57656
+ const on = data !== 0;
57657
+ insertion.sendEQSwitch = on;
57658
+ SpessaLog.gsInfo("EFX Send EQ Switch", on ? "ON" : "OFF");
57659
+ this.callEvent("effectChange", {
57660
+ effect: "gsInsertion",
57661
+ parameter: "sendEQSwitch",
57662
+ value: on
57663
+ });
57664
+ return;
57665
+ }
57330
57666
  }
57331
57667
  }
57332
57668
  if (a2 >> 4 === 1) {
@@ -57513,6 +57849,12 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
57513
57849
  case 1:
57514
57850
  ch.controllerChange(MIDIControllers.bankSelectLSB, data);
57515
57851
  break;
57852
+ case 32: {
57853
+ const eq = data === 1;
57854
+ ch.setMIDIParameter("eqEnabled", eq);
57855
+ SpessaLog.gsInfo(`EQ for ${channel}`, eq ? "ON" : "OFF");
57856
+ break;
57857
+ }
57516
57858
  case 34: {
57517
57859
  const efx = data === 1;
57518
57860
  ch.setMIDIParameter("efxAssign", efx);
@@ -58741,7 +59083,7 @@ var SoundFont2 = class extends BasicSoundBank {
58741
59083
  }
58742
59084
  }
58743
59085
  for (const s of samples) s.name = s.name.trim();
58744
- this.samples.push(...samples);
59086
+ this.addElements(this.samples, samples);
58745
59087
  const instrumentGenerators = readGenerators(igenChunk, isSFE);
58746
59088
  const instrumentModulators = readModulators(imodChunk, isSFE);
58747
59089
  const extendInstruments = xdtaChunk !== void 0 && !!xChunks.inst;
@@ -58758,7 +59100,7 @@ var SoundFont2 = class extends BasicSoundBank {
58758
59100
  instruments.pop();
58759
59101
  }
58760
59102
  for (const i of instruments) i.name = i.name.trim();
58761
- this.instruments.push(...instruments);
59103
+ this.addElements(this.instruments, instruments);
58762
59104
  const ibagIndexes = readZoneIndexes(ibagChunk);
58763
59105
  if (xdtaChunk && xChunks.ibag) {
58764
59106
  const extraIndexes = readZoneIndexes(xChunks.ibag);
@@ -58785,7 +59127,7 @@ var SoundFont2 = class extends BasicSoundBank {
58785
59127
  presets.pop();
58786
59128
  }
58787
59129
  for (const p of presets) p.name = p.name.trim();
58788
- this.addPresets(...presets);
59130
+ this.addElements(this.presets, presets);
58789
59131
  const pbagIndexes = readZoneIndexes(pbagChunk);
58790
59132
  if (xdtaChunk && xChunks.pbag) {
58791
59133
  const extraIndexes = readZoneIndexes(xChunks.pbag);
@@ -58860,6 +59202,7 @@ function applySnapshot(snapshot) {
58860
59202
  this.gsReverbProcessor.applySnapshot(snapshot.gsReverbProcessor);
58861
59203
  this.gsChorusProcessor.applySnapshot(snapshot.gsChorusProcessor);
58862
59204
  this.gsDelayProcessor.applySnapshot(snapshot.gsDelayProcessor);
59205
+ if (snapshot.gsEqualizerProcessor) this.gsEqualizerProcessor.applySnapshot(snapshot.gsEqualizerProcessor);
58863
59206
  this.gsInsertionBlock.applySnapshot(snapshot.gsInsertionBlock);
58864
59207
  this.xgReverbBlock.applySnapshot(snapshot.xgReverbBlock);
58865
59208
  this.xgChorusBlock.applySnapshot(snapshot.xgChorusBlock);
@@ -58888,6 +59231,7 @@ function getSynthesizerSnapshot() {
58888
59231
  gsReverbProcessor: this.gsReverbProcessor.getSnapshot(),
58889
59232
  gsChorusProcessor: this.gsChorusProcessor.getSnapshot(),
58890
59233
  gsDelayProcessor: this.gsDelayProcessor.getSnapshot(),
59234
+ gsEqualizerProcessor: this.gsEqualizerProcessor.getSnapshot(),
58891
59235
  gsInsertionBlock: this.gsInsertionBlock.getSnapshot(),
58892
59236
  xgReverbBlock: this.xgReverbBlock.getSnapshot(),
58893
59237
  xgChorusBlock: this.xgChorusBlock.getSnapshot(),
@@ -58914,7 +59258,6 @@ var DecodedSampleCache = class {
58914
59258
  */
58915
59259
  totalBytes = 0;
58916
59260
  entries = /* @__PURE__ */ new Map();
58917
- unreleasable = /* @__PURE__ */ new WeakSet();
58918
59261
  voiceCache;
58919
59262
  /**
58920
59263
  * @param budget The budget in bytes.
@@ -58935,7 +59278,7 @@ var DecodedSampleCache = class {
58935
59278
  touch(sample, cacheKey) {
58936
59279
  let entry = this.entries.get(sample);
58937
59280
  if (entry) this.entries.delete(sample);
58938
- if (this.unreleasable.has(sample) || sample.storedForm === "float32" && !(sample instanceof DLSSample)) {
59281
+ if (!sample.canRedecode) {
58939
59282
  if (entry) this.totalBytes -= entry.bytes;
58940
59283
  return;
58941
59284
  }
@@ -58950,16 +59293,29 @@ var DecodedSampleCache = class {
58950
59293
  this.entries.set(sample, entry);
58951
59294
  if (this.totalBytes > this.budget) this.evict(sample);
58952
59295
  }
59296
+ /**
59297
+ * Stops tracking samples, for example when their sound bank is removed.
59298
+ * The samples are not released, the cache just stops holding them.
59299
+ * Voices cached from them are dropped too.
59300
+ * @param samples The samples to forget.
59301
+ */
59302
+ forget(samples) {
59303
+ for (const sample of samples) {
59304
+ const entry = this.entries.get(sample);
59305
+ if (!entry) continue;
59306
+ this.entries.delete(sample);
59307
+ this.totalBytes -= entry.bytes;
59308
+ for (const key of entry.cacheKeys) this.voiceCache.delete(key);
59309
+ }
59310
+ }
58953
59311
  evict(keep) {
58954
59312
  for (const [sample, entry] of this.entries) {
58955
59313
  if (this.totalBytes <= this.budget) return;
58956
59314
  if (sample === keep) continue;
58957
59315
  this.entries.delete(sample);
58958
59316
  this.totalBytes -= entry.bytes;
58959
- if (!sample.releaseDecoded()) {
58960
- this.unreleasable.add(sample);
58961
- continue;
58962
- }
59317
+ if (!sample.canRedecode) continue;
59318
+ sample.releaseDecoded();
58963
59319
  for (const key of entry.cacheKeys) this.voiceCache.delete(key);
58964
59320
  }
58965
59321
  }
@@ -59002,6 +59358,16 @@ const GS_CHORUS_MACRO_PARAMETERS = [
59002
59358
  "sendLevelToDelay"
59003
59359
  ];
59004
59360
  /**
59361
+ * The default GS equalizer parameters (SC-8850 manual p.236).
59362
+ */
59363
+ const DEFAULT_GS_EQUALIZER = {
59364
+ lowFrequency: 0,
59365
+ lowGain: 64,
59366
+ highFrequency: 0,
59367
+ highGain: 64
59368
+ };
59369
+ const GS_EQUALIZER_PARAMETERS = Object.keys(DEFAULT_GS_EQUALIZER);
59370
+ /**
59005
59371
  * The GS delay parameters set by the delay macro.
59006
59372
  */
59007
59373
  const GS_DELAY_MACRO_PARAMETERS = [
@@ -59104,6 +59470,16 @@ var SpessaSynthProcessor = class {
59104
59470
  */
59105
59471
  insertionInputR;
59106
59472
  /**
59473
+ * The GS equalizer's left input buffer.
59474
+ * @internal
59475
+ */
59476
+ equalizerInputL;
59477
+ /**
59478
+ * The GS equalizer's right input buffer.
59479
+ * @internal
59480
+ */
59481
+ equalizerInputR;
59482
+ /**
59107
59483
  * The GS reverb processor's input buffer.
59108
59484
  * @internal
59109
59485
  */
@@ -59227,10 +59603,6 @@ var SpessaSynthProcessor = class {
59227
59603
  */
59228
59604
  gsDelayActive = false;
59229
59605
  /**
59230
- * The sound bank manager, which manages all sound banks and presets.
59231
- */
59232
- soundBankManager = new SoundBankManager(this.updatePresetList.bind(this));
59233
- /**
59234
59606
  * The sample rate, in Hertz.
59235
59607
  */
59236
59608
  sampleRate;
@@ -59326,6 +59698,12 @@ var SpessaSynthProcessor = class {
59326
59698
  */
59327
59699
  decodedSamples;
59328
59700
  /**
59701
+ * The sound bank manager, which manages all sound banks and presets.
59702
+ */
59703
+ soundBankManager = new SoundBankManager(this.updatePresetList.bind(this), (bank) => {
59704
+ this.decodedSamples.forget(bank.samples);
59705
+ });
59706
+ /**
59329
59707
  * The synthesizer's GS reverb processor.
59330
59708
  *
59331
59709
  * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
@@ -59347,6 +59725,13 @@ var SpessaSynthProcessor = class {
59347
59725
  */
59348
59726
  gsDelayProcessor;
59349
59727
  /**
59728
+ * The synthesizer's GS equalizer processor.
59729
+ *
59730
+ * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
59731
+ * @internal
59732
+ */
59733
+ gsEqualizerProcessor;
59734
+ /**
59350
59735
  * The synthesizer's XG reverb block.
59351
59736
  *
59352
59737
  * Used when {@link GlobalMIDIParameter.system} is `xg`.
@@ -59430,6 +59815,7 @@ var SpessaSynthProcessor = class {
59430
59815
  this.gsReverbProcessor = options.gsReverbProcessor ?? new DefaultGSReverb(sampleRate, bufSize);
59431
59816
  this.gsChorusProcessor = options.gsChorusProcessor ?? new DefaultGSChorus(sampleRate, bufSize);
59432
59817
  this.gsDelayProcessor = options.gsDelayProcessor ?? new DefaultGSDelay(sampleRate, bufSize);
59818
+ this.gsEqualizerProcessor = options.gsEqualizerProcessor ?? new DefaultGSEqualizer(sampleRate);
59433
59819
  this.gsInsertionBlock = options.gsInsertionBlock ?? new DefaultGSInsertion(sampleRate, bufSize);
59434
59820
  this.xgReverbBlock = options.xgReverbBlock ?? new DefaultXGReverb(sampleRate, bufSize);
59435
59821
  this.xgChorusBlock = options.xgChorusBlock ?? new DefaultXGChorus(sampleRate, bufSize);
@@ -59439,6 +59825,8 @@ var SpessaSynthProcessor = class {
59439
59825
  this.sharedVoiceSendBuffer = new VoiceSendBuffer(bufSize);
59440
59826
  this.insertionInputL = new Float32Array(bufSize);
59441
59827
  this.insertionInputR = new Float32Array(bufSize);
59828
+ this.equalizerInputL = new Float32Array(bufSize);
59829
+ this.equalizerInputR = new Float32Array(bufSize);
59442
59830
  this.gsReverbInput = new Float32Array(bufSize);
59443
59831
  this.gsChorusInput = new Float32Array(bufSize);
59444
59832
  this.gsDelayInput = new Float32Array(bufSize);
@@ -59833,6 +60221,17 @@ var SpessaSynthProcessor = class {
59833
60221
  this.setReverbMacro(4);
59834
60222
  this.setChorusMacro(2);
59835
60223
  this.setDelayMacro(0);
60224
+ if (!this.systemParameters.gsEqualizerLock) {
60225
+ const eq = this.gsEqualizerProcessor;
60226
+ for (const parameter of GS_EQUALIZER_PARAMETERS) {
60227
+ eq[parameter] = DEFAULT_GS_EQUALIZER[parameter];
60228
+ this.callEvent("effectChange", {
60229
+ effect: "gsEqualizer",
60230
+ parameter,
60231
+ value: eq[parameter]
60232
+ });
60233
+ }
60234
+ }
59836
60235
  if (!this.systemParameters.gsInsertionLock) {
59837
60236
  this.gsInsertionBlock.reset();
59838
60237
  this.callEvent("effectChange", {
@@ -59989,10 +60388,10 @@ var SpessaSynthProcessor = class {
59989
60388
  * │ │ │ Chorus │ │
59990
60389
  * │ │ │ Processor ├──────────┐ │
59991
60390
  * │ │ └─┬──────────┬──┘ │ │
59992
- * │ │ │ │ │ │
59993
- * │ │ │ │ │ │
59994
- * │ │ │ │ │ │
59995
- * │ │ │ │ │ │
60391
+ * ┌────┴────┐ │ │ │ │ │
60392
+ * │Equalizer│ │ │ │ │ │
60393
+ * │or Bypass│ │ │ │ │ │
60394
+ * └────┬────┘ │ │ │ │ │
59996
60395
  * │ │ │ 𜸊 𜸊 𜸊
59997
60396
  * │ │ │ ┌────────┴───────┐ ┌─┴─────┴────────┐
59998
60397
  * │ └───┼>┤ Delay ├─>>┤ Reverb │
@@ -60021,6 +60420,7 @@ var SpessaSynthProcessor = class {
60021
60420
  const isXG = this.midiParameters.system === "xg";
60022
60421
  const variationInsertion = this.xgVariationBlock.insertionMode;
60023
60422
  const fx = this.systemParameters.effectsEnabled;
60423
+ const eq = fx && !isXG;
60024
60424
  if (isXG && fx) {
60025
60425
  for (const channel of this.midiChannels) channel.xgInsertionAssigned = false;
60026
60426
  for (const insertion of this.xgInsertionBlocks) {
@@ -60036,6 +60436,10 @@ var SpessaSynthProcessor = class {
60036
60436
  this.insertionInputL.fill(0);
60037
60437
  this.insertionInputR.fill(0);
60038
60438
  }
60439
+ if (eq) {
60440
+ this.equalizerInputL.fill(0);
60441
+ this.equalizerInputR.fill(0);
60442
+ }
60039
60443
  for (const c of this.midiChannels) {
60040
60444
  c.clearVoiceCount();
60041
60445
  if (c.outputDirty) {
@@ -60121,6 +60525,15 @@ var SpessaSynthProcessor = class {
60121
60525
  }
60122
60526
  continue;
60123
60527
  }
60528
+ if (eq && midiParameters.eqEnabled) {
60529
+ const equalizerL = this.equalizerInputL;
60530
+ const equalizerR = this.equalizerInputR;
60531
+ for (let i = 0; i < sampleCount; i++) {
60532
+ equalizerL[i] += outputLeft[i];
60533
+ equalizerR[i] += outputRight[i];
60534
+ }
60535
+ continue;
60536
+ }
60124
60537
  if (isXG && !variationInsertion) {
60125
60538
  const gain = midiParameters.dryLevel / 127;
60126
60539
  for (let i = 0; i < sampleCount; i++) {
@@ -60141,7 +60554,12 @@ var SpessaSynthProcessor = class {
60141
60554
  this.xgChorusBlock.process(this.xgChorusInputL, this.xgChorusInputR, left, right, this.xgReverbInputL, this.xgReverbInputR, startIndex, sampleCount);
60142
60555
  this.xgReverbBlock.process(this.xgReverbInputL, this.xgReverbInputR, left, right, startIndex, sampleCount);
60143
60556
  } else {
60144
- if (this.gsInsertionActive) this.gsInsertionBlock.process(insertionInputL, insertionInputR, left, right, gsReverbInput, gsChorusInput, gsDelayInput, startIndex, sampleCount);
60557
+ if (this.gsInsertionActive) {
60558
+ const insertion = this.gsInsertionBlock;
60559
+ const toEqualizer = eq && insertion.sendEQSwitch;
60560
+ insertion.process(insertionInputL, insertionInputR, toEqualizer ? this.equalizerInputL : left, toEqualizer ? this.equalizerInputR : right, gsReverbInput, gsChorusInput, gsDelayInput, toEqualizer ? 0 : startIndex, sampleCount);
60561
+ }
60562
+ if (eq) this.gsEqualizerProcessor.process(this.equalizerInputL, this.equalizerInputR, left, right, startIndex, sampleCount);
60145
60563
  this.gsChorusProcessor.process(gsChorusInput, left, right, gsReverbInput, gsDelayInput, startIndex, sampleCount);
60146
60564
  if (this.gsDelayActive) this.gsDelayProcessor.process(gsDelayInput, left, right, gsReverbInput, startIndex, sampleCount);
60147
60565
  this.gsReverbProcessor.process(gsReverbInput, left, right, startIndex, sampleCount);
@@ -60716,6 +61134,6 @@ var SpessaSynthProcessor = class {
60716
61134
  return (isGMGSDrum ? 128 + patch.bankMSB : patch.bankMSB) + bankLSB * 256 + program * 32768 + 4194304 * midiNote + 536870912 * velocity;
60717
61135
  }
60718
61136
  };
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 };
61137
+ export { BasicInstrument, BasicInstrumentZone, BasicLinkedZone, BasicMIDI, BasicPreset, BasicPresetZone, BasicSample, BasicSoundBank, BasicZone, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_DRUM_REVERB, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, DEFAULT_MIDI_CONTROLLERS, DEFAULT_SOUND_BANK_CLONE_OPTIONS, DEFAULT_SOUND_BANK_LOAD_OPTIONS, DEFAULT_SYNTH_MODE, DefaultGSChorus, DefaultGSDelay, DefaultGSEqualizer, DefaultGSInsertion, DefaultGSReverb, DefaultXGChorus, DefaultXGEffect, DefaultXGInsertion, DefaultXGReverb, DefaultXGSystemEffect, DefaultXGVariation, DrumParameterUtils, EmptySample, GENERATORS_AMOUNT, GSInsertionControlSources, Generator, GeneratorLimits, GeneratorTypes, INSTRUMENT_ONLY_GENERATORS, IndexedByteArray, InterpolationTypes, MAX_GENERATOR, MIDIBuilder, MIDIChannel, MIDIControllers, MIDIMessage, MIDIMessageTypes, MIDIPatchTools, MIDITrack, MIDIUtils, MIDI_DRUM_CHANNEL, Modulator, ModulatorControllerSources, ModulatorCurveTypes, ModulatorSource, ModulatorTransformTypes, NonRegisteredParameterTypesLSB, NonRegisteredParameterTypesMSB, RegisteredParameterTypes, SPESSASYNTH_GAIN_FACTOR, SPESSA_BUFSIZE, SampleTypes, SoundBankCloner, SoundBankLoader, SoundBankManager, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, VOICE_CAP, audioToWav, wavToAudio };
60720
61138
 
60721
61139
  //# sourceMappingURL=index.js.map