spessasynth_core 4.4.7 → 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
  },
@@ -3997,6 +4033,12 @@ var MIDIUtils = class MIDIUtils {
3997
4033
  controller: MIDIControllers.reverbDepth,
3998
4034
  value
3999
4035
  }];
4036
+ case 20: return [{
4037
+ type: "Controller Change",
4038
+ channel,
4039
+ controller: MIDIControllers.variationDepth,
4040
+ value
4041
+ }];
4000
4042
  case 21: return [{
4001
4043
  type: "Controller Change",
4002
4044
  channel,
@@ -7337,6 +7379,22 @@ var BasicSample = class {
7337
7379
  return this.linkedTo.length;
7338
7380
  }
7339
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
+ /**
7340
7398
  * Get raw data for writing the file, either a compressed bit stream or signed 16-bit little endian PCM data.
7341
7399
  * @param allowVorbis if vorbis file data is allowed.
7342
7400
  * @return either s16le or vorbis data.
@@ -7412,7 +7470,7 @@ var BasicSample = class {
7412
7470
  * @returns True if the decoded data was dropped.
7413
7471
  */
7414
7472
  releaseDecoded() {
7415
- if (this.audioData && !this.dataOverridden && (this.compressedData || this.s16leData)) {
7473
+ if (this.audioData && this.canRedecode) {
7416
7474
  this.audioData = void 0;
7417
7475
  return true;
7418
7476
  }
@@ -7444,12 +7502,13 @@ var BasicSample = class {
7444
7502
  this.setAudioData(resampled, newSampleRate);
7445
7503
  }
7446
7504
  /**
7447
- * Compresses the audio data
7505
+ * Compresses the audio data.
7448
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.
7449
7508
  * @internal
7450
7509
  */
7451
7510
  async compressSample(encodeVorbis) {
7452
- if (this.isCompressed) return;
7511
+ if (this.isCompressed) return true;
7453
7512
  try {
7454
7513
  let audioData = this.getAudioData();
7455
7514
  if (this.sampleRate < 8e3 || this.sampleRate > 96e3) {
@@ -7458,9 +7517,11 @@ var BasicSample = class {
7458
7517
  }
7459
7518
  const compressed = await encodeVorbis(audioData, this.sampleRate);
7460
7519
  this.setCompressedData(compressed);
7520
+ return true;
7461
7521
  } catch (error) {
7462
7522
  SpessaLog.warn(`Failed to compress ${this.name}. Leaving as uncompressed!`, error);
7463
7523
  this.compressedData = void 0;
7524
+ return false;
7464
7525
  }
7465
7526
  }
7466
7527
  /**
@@ -7510,16 +7571,17 @@ var BasicSample = class {
7510
7571
  }
7511
7572
  }
7512
7573
  /**
7513
- * Links the sample to a given instrument
7514
- * @param instrument the instrument to link to
7574
+ * Links the sample to a given instrument.
7575
+ * @param instrument the instrument to link to.
7515
7576
  * @internal
7516
7577
  */
7517
7578
  linkTo(instrument) {
7518
7579
  this.linkedTo.push(instrument);
7519
7580
  }
7520
7581
  /**
7521
- * Unlinks the sample from a given instrument
7522
- * @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
7523
7585
  */
7524
7586
  unlinkFrom(instrument) {
7525
7587
  const index = this.linkedTo.indexOf(instrument);
@@ -7886,12 +7948,12 @@ var ModulatorSource = class ModulatorSource {
7886
7948
  */
7887
7949
  curveType;
7888
7950
  /**
7889
- * @internal
7890
- * @param index
7891
- * @param curveType
7892
- * @param isCC
7893
- * @param isBipolar
7894
- * @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.
7895
7957
  */
7896
7958
  constructor(index = ModulatorControllerSources.noController, curveType = ModulatorCurveTypes.linear, isCC = false, isBipolar = false, isNegative = false) {
7897
7959
  this.isBipolar = isBipolar;
@@ -8359,14 +8421,13 @@ var BasicLinkedZone = class extends BasicZone {
8359
8421
  }
8360
8422
  /**
8361
8423
  * Links the child to the parent, unless this zone was created without linking.
8362
- * @internal
8424
+ * Use it to put a zone back after {@link unlinkChild}, for example when undoing a zone deletion.
8363
8425
  */
8364
8426
  linkChild() {
8365
8427
  if (this.linked) this.child.linkTo(this.parent);
8366
8428
  }
8367
8429
  /**
8368
8430
  * Unlinks the child from the parent, unless this zone was created without linking.
8369
- * @internal
8370
8431
  */
8371
8432
  unlinkChild() {
8372
8433
  if (this.linked) this.child.unlinkFrom(this.parent);
@@ -8754,7 +8815,7 @@ var BasicPreset = class BasicPreset {
8754
8815
  }
8755
8816
  }
8756
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));
8757
- const zone = outputInstrument.createZone(instZone.sample);
8818
+ const zone = outputInstrument.createZone(instZone.sample, false);
8758
8819
  zone.keyRange = instZoneKeyRange;
8759
8820
  zone.velRange = instZoneVelRange;
8760
8821
  if (zone.keyRange.min === 0 && zone.keyRange.max === 127) zone.keyRange.min = -1;
@@ -8884,6 +8945,7 @@ var BasicInstrument = class {
8884
8945
  /**
8885
8946
  * Unlinks the instrument from a given preset.
8886
8947
  * @param preset The preset to unlink from.
8948
+ * @internal
8887
8949
  */
8888
8950
  unlinkFrom(preset) {
8889
8951
  const index = this.linkedTo.indexOf(preset);
@@ -9168,7 +9230,7 @@ function writeSF(bank, software, writeDefaultModulators, writeExtendedLimits, wr
9168
9230
  */
9169
9231
  const infoArrays = [];
9170
9232
  const writeSF2Info = (type, data) => {
9171
- if (!data) return;
9233
+ if (data === void 0) return;
9172
9234
  infoArrays.push(...RIFFChunk.getParts(type, [getStringBytes(data, true, true)], rf64));
9173
9235
  };
9174
9236
  const info = bank.soundBankInfo;
@@ -9179,7 +9241,7 @@ function writeSF(bank, software, writeDefaultModulators, writeExtendedLimits, wr
9179
9241
  infoArrays.push(RIFFChunk.write("ifil", ifilData, rf64));
9180
9242
  }
9181
9243
  writeSF2Info("isng", info.soundEngine);
9182
- writeSF2Info("INAM", info.name);
9244
+ writeSF2Info("INAM", info.name ?? "");
9183
9245
  writeSF2Info("irom", info.romInfo);
9184
9246
  if (info.romVersion) {
9185
9247
  const ifilData = new IndexedByteArray(4);
@@ -9566,6 +9628,9 @@ var DLSSample = class extends BasicSample {
9566
9628
  if (this.rawData && !this.audioData) return "float32";
9567
9629
  return super.storedForm;
9568
9630
  }
9631
+ get canRedecode() {
9632
+ return this.rawData !== void 0 && !this.dataOverridden || super.canRedecode;
9633
+ }
9569
9634
  getStoredData() {
9570
9635
  if (this.rawData && !this.audioData) this.getAudioData();
9571
9636
  return super.getStoredData();
@@ -9574,13 +9639,6 @@ var DLSSample = class extends BasicSample {
9574
9639
  this.rawData = void 0;
9575
9640
  super.setStoredData(stored);
9576
9641
  }
9577
- releaseDecoded() {
9578
- if (this.rawData && this.audioData && !this.dataOverridden) {
9579
- this.audioData = void 0;
9580
- return true;
9581
- }
9582
- return super.releaseDecoded();
9583
- }
9584
9642
  releaseData() {
9585
9643
  if (this.hasData) this.releasedForm = this.storedForm;
9586
9644
  this.cachedLength = this.sampleLength;
@@ -9658,7 +9716,7 @@ var DownloadableSoundsSample = class DownloadableSoundsSample extends DLSVerifie
9658
9716
  }
9659
9717
  static fromSFSample(sample) {
9660
9718
  const raw = sample.getRawData(false);
9661
- 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)));
9662
9720
  dlsSample.name = sample.name;
9663
9721
  dlsSample.waveSample = WaveSample.fromSFSample(sample);
9664
9722
  return dlsSample;
@@ -10746,8 +10804,8 @@ var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DL
10746
10804
  SpessaLog.groupCollapsed(`%cWriting %c${this.name}%c...`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info);
10747
10805
  const chunks = [this.writeHeader()];
10748
10806
  const regionChunks = this.regions.flatMap((r) => r.write());
10749
- chunks.push(...RIFFChunk.getParts("lrgn", regionChunks, false, true));
10750
- 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);
10751
10809
  const inam = RIFFChunk.write("INAM", getStringBytes(this.name, true));
10752
10810
  chunks.push(RIFFChunk.write("INFO", inam, false, true));
10753
10811
  SpessaLog.groupEnd();
@@ -11032,7 +11090,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
11032
11090
  */
11033
11091
  const DEFAULT_SOUND_BANK_CLONE_OPTIONS = {
11034
11092
  reuseByName: true,
11035
- reuseChildrenByName: true,
11093
+ reuseChildren: "name",
11036
11094
  addToBank: true
11037
11095
  };
11038
11096
  const MAX_BASE_NAME_LENGTH = 37;
@@ -11079,6 +11137,17 @@ var SoundBankCloner = class {
11079
11137
  this.bank = bank;
11080
11138
  this.options = fillWithDefaults(options, DEFAULT_SOUND_BANK_CLONE_OPTIONS);
11081
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
+ }
11082
11151
  /**
11083
11152
  * Clones a sample and its stereo partner.
11084
11153
  * The data is shared with the original, not copied.
@@ -11086,7 +11155,7 @@ var SoundBankCloner = class {
11086
11155
  * @returns The clone, or the existing sample with the same name if it is reused.
11087
11156
  */
11088
11157
  cloneSample(sample) {
11089
- return this.copySample(sample, this.options.reuseByName);
11158
+ return this.copySample(sample, this.ownReuse);
11090
11159
  }
11091
11160
  /**
11092
11161
  * Clones an instrument and the samples it uses.
@@ -11094,7 +11163,7 @@ var SoundBankCloner = class {
11094
11163
  * @returns The clone, or the existing instrument with the same name if it is reused.
11095
11164
  */
11096
11165
  cloneInstrument(instrument) {
11097
- return this.copyInstrument(instrument, this.options.reuseByName);
11166
+ return this.copyInstrument(instrument, this.ownReuse);
11098
11167
  }
11099
11168
  /**
11100
11169
  * Clones a preset and the instruments and samples it uses.
@@ -11103,10 +11172,8 @@ var SoundBankCloner = class {
11103
11172
  */
11104
11173
  clonePreset(preset) {
11105
11174
  const all = [...this.bank.presets, ...this.created.presets];
11106
- if (this.options.reuseByName) {
11107
- const existing = all.find((p) => p.name === preset.name);
11108
- if (existing) return existing;
11109
- }
11175
+ const existing = this.findReused(preset, all, this.ownReuse);
11176
+ if (existing) return existing;
11110
11177
  const newPreset = new BasicPreset(this.bank);
11111
11178
  newPreset.name = this.getFreeName(preset.name, all);
11112
11179
  newPreset.bankMSB = preset.bankMSB;
@@ -11117,7 +11184,7 @@ var SoundBankCloner = class {
11117
11184
  newPreset.genre = preset.genre;
11118
11185
  newPreset.morphology = preset.morphology;
11119
11186
  newPreset.globalZone.copyFrom(preset.globalZone);
11120
- 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);
11121
11188
  this.created.presets.push(newPreset);
11122
11189
  if (this.options.addToBank) this.bank.addPresets(newPreset);
11123
11190
  return newPreset;
@@ -11126,14 +11193,12 @@ var SoundBankCloner = class {
11126
11193
  const cloned = this.instrumentClones.get(instrument);
11127
11194
  if (cloned) return cloned;
11128
11195
  const all = [...this.bank.instruments, ...this.created.instruments];
11129
- if (reuse) {
11130
- const existing = all.find((i) => i.name === instrument.name);
11131
- if (existing) return existing;
11132
- }
11196
+ const existing = this.findReused(instrument, all, reuse);
11197
+ if (existing) return existing;
11133
11198
  const newInstrument = new BasicInstrument();
11134
11199
  newInstrument.name = this.getFreeName(instrument.name, all);
11135
11200
  newInstrument.globalZone.copyFrom(instrument.globalZone);
11136
- 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);
11137
11202
  this.instrumentClones.set(instrument, newInstrument);
11138
11203
  this.created.instruments.push(newInstrument);
11139
11204
  if (this.options.addToBank) this.bank.addInstruments(newInstrument);
@@ -11143,10 +11208,8 @@ var SoundBankCloner = class {
11143
11208
  const cloned = this.sampleClones.get(sample);
11144
11209
  if (cloned) return cloned;
11145
11210
  const all = [...this.bank.samples, ...this.created.samples];
11146
- if (reuse) {
11147
- const existing = all.find((s) => s.name === sample.name);
11148
- if (existing) return existing;
11149
- }
11211
+ const existing = this.findReused(sample, all, reuse);
11212
+ if (existing) return existing;
11150
11213
  const newSample = new BasicSample(this.getFreeName(sample.name, all), sample.sampleRate, sample.originalKey, sample.pitchCorrection, sample.sampleType, sample.loopStart, sample.loopEnd);
11151
11214
  if (sample.hasData) newSample.setStoredData(sample.getStoredData());
11152
11215
  else {
@@ -11160,10 +11223,20 @@ var SoundBankCloner = class {
11160
11223
  if (linked) {
11161
11224
  const newLinked = this.sampleClones.get(linked);
11162
11225
  if (newLinked) newSample.setLinkedSample(newLinked, sample.sampleType);
11163
- else this.copySample(linked, false);
11226
+ else this.copySample(linked, "none");
11164
11227
  }
11165
11228
  return newSample;
11166
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
+ }
11167
11240
  getFreeName(name, elements) {
11168
11241
  const taken = new Set(elements.map((e) => e.name));
11169
11242
  if (!taken.has(name)) return name;
@@ -11329,7 +11402,7 @@ var BasicSoundBank = class BasicSoundBank {
11329
11402
  const copied = new BasicSoundBank(bank.type);
11330
11403
  const cloner = new SoundBankCloner(copied, {
11331
11404
  reuseByName: false,
11332
- reuseChildrenByName: false
11405
+ reuseChildren: "none"
11333
11406
  });
11334
11407
  for (const p of bank.presets) cloner.clonePreset(p);
11335
11408
  copied.soundBankInfo = { ...bank.soundBankInfo };
@@ -11343,13 +11416,13 @@ var BasicSoundBank = class BasicSoundBank {
11343
11416
  * @param presets The presets to add.
11344
11417
  */
11345
11418
  addCompletePresets(presets) {
11346
- this.addPresets(...presets);
11419
+ this.addElements(this.presets, presets);
11347
11420
  const instrumentList = [];
11348
11421
  for (const preset of presets) for (const zone of preset.zones) if (zone.instrument && !instrumentList.includes(zone.instrument)) instrumentList.push(zone.instrument);
11349
- this.addInstruments(...instrumentList);
11422
+ this.addElements(this.instruments, instrumentList);
11350
11423
  const sampleList = [];
11351
11424
  for (const instrument of instrumentList) for (const zone of instrument.zones) if (zone.sample && !sampleList.includes(zone.sample)) sampleList.push(zone.sample);
11352
- this.addSamples(...sampleList);
11425
+ this.addElements(this.samples, sampleList);
11353
11426
  }
11354
11427
  /**
11355
11428
  * Sets the sound bank's sample format _in place_.
@@ -11363,9 +11436,11 @@ var BasicSoundBank = class BasicSoundBank {
11363
11436
  * @param options Options associated with setting the sample format.
11364
11437
  * @returns The samples whose sample rate or loop points changed.
11365
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.
11366
11440
  */
11367
11441
  async setSampleFormat(options) {
11368
11442
  const changed = new Array();
11443
+ const failed = new Array();
11369
11444
  let writtenCount = 0;
11370
11445
  const format = options.format;
11371
11446
  const progressFunc = options.progressFunction;
@@ -11379,7 +11454,7 @@ var BasicSoundBank = class BasicSoundBank {
11379
11454
  const f = options.compressionFunction;
11380
11455
  if (!f) throw new Error(`No compression function supplied but '${format}' was requested.`);
11381
11456
  const { sampleRate, loopStart, loopEnd } = s;
11382
- await s.compressSample(f);
11457
+ if (!await s.compressSample(f)) failed.push(s);
11383
11458
  if (s.sampleRate !== sampleRate || s.loopStart !== loopStart || s.loopEnd !== loopEnd) changed.push(s);
11384
11459
  }
11385
11460
  }
@@ -11387,6 +11462,7 @@ var BasicSoundBank = class BasicSoundBank {
11387
11462
  progressFunc?.(writtenCount / this.samples.length);
11388
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);
11389
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(", ")}`);
11390
11466
  switch (format) {
11391
11467
  default:
11392
11468
  case "pcm":
@@ -11450,21 +11526,21 @@ var BasicSoundBank = class BasicSoundBank {
11450
11526
  * @param presets The presets to add.
11451
11527
  */
11452
11528
  addPresets(...presets) {
11453
- this.presets.push(...presets);
11529
+ this.addElements(this.presets, presets);
11454
11530
  }
11455
11531
  /**
11456
11532
  * Adds {@link BasicInstrument}s to this sound bank.
11457
11533
  * @param instruments The instruments to add.
11458
11534
  */
11459
11535
  addInstruments(...instruments) {
11460
- this.instruments.push(...instruments);
11536
+ this.addElements(this.instruments, instruments);
11461
11537
  }
11462
11538
  /**
11463
11539
  * Adds {@link BasicSample}s to this sound bank.
11464
11540
  * @param samples The samples to add.
11465
11541
  */
11466
11542
  addSamples(...samples) {
11467
- this.samples.push(...samples);
11543
+ this.addElements(this.samples, samples);
11468
11544
  }
11469
11545
  /**
11470
11546
  * Clones a sample into this bank, together with its stereo partner.
@@ -11516,9 +11592,11 @@ var BasicSoundBank = class BasicSoundBank {
11516
11592
  }
11517
11593
  /**
11518
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.
11519
11597
  */
11520
- flush() {
11521
- this.presets.sort(MIDIPatchTools.compare.bind(MIDIPatchTools));
11598
+ flush(sort = true) {
11599
+ if (sort) this.presets.sort(MIDIPatchTools.compare.bind(MIDIPatchTools));
11522
11600
  this.parseInternal();
11523
11601
  }
11524
11602
  /**
@@ -11663,6 +11741,9 @@ var BasicSoundBank = class BasicSoundBank {
11663
11741
  this.instruments.length = 0;
11664
11742
  this.samples.length = 0;
11665
11743
  }
11744
+ addElements(list, elements) {
11745
+ for (const element of elements) list.push(element);
11746
+ }
11666
11747
  parsingError(error) {
11667
11748
  throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
11668
11749
  }
@@ -15464,7 +15545,6 @@ function setTimeToInternal(time, ticks = void 0) {
15464
15545
  const channel = analyzed.channel + portOffset;
15465
15546
  if (channel >= this.synth.midiChannels.length) break;
15466
15547
  channels[channel] ??= createChannelStatus(channel);
15467
- if (this._midiData.isMultiPort && track.channels.size === 0) break;
15468
15548
  if (controller === MIDIControllers.resetAllControllers) {
15469
15549
  resetAllControllers(channel);
15470
15550
  break;
@@ -52867,15 +52947,19 @@ var SoundBankManager = class {
52867
52947
  */
52868
52948
  userDrumSets;
52869
52949
  presetListChangeCallback;
52950
+ bankRemovedCallback;
52870
52951
  selectablePresetList = [];
52871
52952
  /**
52872
52953
  * @internal
52873
52954
  * @param presetListChangeCallback Supplied by the parent synthesizer class,
52874
52955
  * this is called whenever the preset list changes.
52875
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.
52876
52959
  */
52877
- constructor(presetListChangeCallback) {
52960
+ constructor(presetListChangeCallback, bankRemovedCallback) {
52878
52961
  this.presetListChangeCallback = presetListChangeCallback;
52962
+ this.bankRemovedCallback = bankRemovedCallback;
52879
52963
  const resolvePatch = (patch) => this.getPreset(patch, this.systemGetter());
52880
52964
  this.userDrumSets = [new UserDrumSet(64, "User Drum Set 1", resolvePatch), new UserDrumSet(65, "User Drum Set 2", resolvePatch)];
52881
52965
  }
@@ -52924,13 +53008,16 @@ var SoundBankManager = class {
52924
53008
  /**
52925
53009
  * This method removes a sound bank with a given ID from the sound bank list.
52926
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.
52927
53013
  * @throws Error An error if there's no sound bank with the given ID.
52928
53014
  */
52929
- deleteSoundBank(id) {
53015
+ deleteSoundBank(id, reset = true) {
52930
53016
  const index = this.soundBankList.findIndex((s) => s.id === id);
52931
53017
  if (index === -1) throw new Error(`No sound bank with id "${id}"`);
52932
- this.soundBankList.splice(index, 1);
52933
- this.generatePresetList();
53018
+ const [removed] = this.soundBankList.splice(index, 1);
53019
+ this.bankRemoved(removed.soundBank);
53020
+ this.generatePresetList(reset);
52934
53021
  }
52935
53022
  /**
52936
53023
  * Adds a new sound bank with a given ID, or replaces an existing one.
@@ -52938,8 +53025,10 @@ var SoundBankManager = class {
52938
53025
  * @param id The unique ID to assign to the sound bank.
52939
53026
  * If a sound bank with this ID already exists, it will be replaced.
52940
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.
52941
53030
  */
52942
- addSoundBank(bank, id, bankOffset = 0) {
53031
+ addSoundBank(bank, id, bankOffset = 0, reset = true) {
52943
53032
  const foundBank = this.soundBankList.find((s) => s.id === id);
52944
53033
  if (foundBank === void 0) this.soundBankList.push({
52945
53034
  id,
@@ -52947,10 +53036,12 @@ var SoundBankManager = class {
52947
53036
  bankOffset
52948
53037
  });
52949
53038
  else {
53039
+ const old = foundBank.soundBank;
52950
53040
  foundBank.soundBank = bank;
52951
53041
  foundBank.bankOffset = bankOffset;
53042
+ this.bankRemoved(old);
52952
53043
  }
52953
- this.generatePresetList();
53044
+ this.generatePresetList(reset);
52954
53045
  }
52955
53046
  /**
52956
53047
  * Rebuilds the preset list from the current sound banks.
@@ -52980,11 +53071,17 @@ var SoundBankManager = class {
52980
53071
  * @internal
52981
53072
  */
52982
53073
  destroy() {
52983
- 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
+ }
52984
53078
  this.soundBankList = [];
52985
53079
  this.selectablePresetList = [];
52986
53080
  this._presetList = [];
52987
53081
  }
53082
+ bankRemoved(bank) {
53083
+ if (!this.soundBankList.some((s) => s.soundBank === bank)) this.bankRemovedCallback?.(bank);
53084
+ }
52988
53085
  generatePresetList(resetSynth = true) {
52989
53086
  const presetList = new Array();
52990
53087
  const addedPresets = /* @__PURE__ */ new Set();
@@ -53133,7 +53230,7 @@ function renderVoice(voice, timeNow, sampleCount) {
53133
53230
  let targetKey = voice.targetKey;
53134
53231
  let cents = voice.pitchOffset + modulated[GeneratorTypes.fineTune] + this.octaveTuning[targetKey] + this.currentTuning;
53135
53232
  let semitones = modulated[GeneratorTypes.coarseTune];
53136
- const tuning = core.tunings[this.preset.program * 128 + voice.midiNote];
53233
+ const tuning = core.tunings[(this.preset?.program ?? this.patch.program) * 128 + voice.midiNote];
53137
53234
  if (tuning !== -1) {
53138
53235
  targetKey = Math.trunc(tuning);
53139
53236
  semitones += tuning - targetKey;
@@ -58986,7 +59083,7 @@ var SoundFont2 = class extends BasicSoundBank {
58986
59083
  }
58987
59084
  }
58988
59085
  for (const s of samples) s.name = s.name.trim();
58989
- this.samples.push(...samples);
59086
+ this.addElements(this.samples, samples);
58990
59087
  const instrumentGenerators = readGenerators(igenChunk, isSFE);
58991
59088
  const instrumentModulators = readModulators(imodChunk, isSFE);
58992
59089
  const extendInstruments = xdtaChunk !== void 0 && !!xChunks.inst;
@@ -59003,7 +59100,7 @@ var SoundFont2 = class extends BasicSoundBank {
59003
59100
  instruments.pop();
59004
59101
  }
59005
59102
  for (const i of instruments) i.name = i.name.trim();
59006
- this.instruments.push(...instruments);
59103
+ this.addElements(this.instruments, instruments);
59007
59104
  const ibagIndexes = readZoneIndexes(ibagChunk);
59008
59105
  if (xdtaChunk && xChunks.ibag) {
59009
59106
  const extraIndexes = readZoneIndexes(xChunks.ibag);
@@ -59030,7 +59127,7 @@ var SoundFont2 = class extends BasicSoundBank {
59030
59127
  presets.pop();
59031
59128
  }
59032
59129
  for (const p of presets) p.name = p.name.trim();
59033
- this.addPresets(...presets);
59130
+ this.addElements(this.presets, presets);
59034
59131
  const pbagIndexes = readZoneIndexes(pbagChunk);
59035
59132
  if (xdtaChunk && xChunks.pbag) {
59036
59133
  const extraIndexes = readZoneIndexes(xChunks.pbag);
@@ -59161,7 +59258,6 @@ var DecodedSampleCache = class {
59161
59258
  */
59162
59259
  totalBytes = 0;
59163
59260
  entries = /* @__PURE__ */ new Map();
59164
- unreleasable = /* @__PURE__ */ new WeakSet();
59165
59261
  voiceCache;
59166
59262
  /**
59167
59263
  * @param budget The budget in bytes.
@@ -59182,7 +59278,7 @@ var DecodedSampleCache = class {
59182
59278
  touch(sample, cacheKey) {
59183
59279
  let entry = this.entries.get(sample);
59184
59280
  if (entry) this.entries.delete(sample);
59185
- if (this.unreleasable.has(sample) || sample.storedForm === "float32" && !(sample instanceof DLSSample)) {
59281
+ if (!sample.canRedecode) {
59186
59282
  if (entry) this.totalBytes -= entry.bytes;
59187
59283
  return;
59188
59284
  }
@@ -59197,16 +59293,29 @@ var DecodedSampleCache = class {
59197
59293
  this.entries.set(sample, entry);
59198
59294
  if (this.totalBytes > this.budget) this.evict(sample);
59199
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
+ }
59200
59311
  evict(keep) {
59201
59312
  for (const [sample, entry] of this.entries) {
59202
59313
  if (this.totalBytes <= this.budget) return;
59203
59314
  if (sample === keep) continue;
59204
59315
  this.entries.delete(sample);
59205
59316
  this.totalBytes -= entry.bytes;
59206
- if (!sample.releaseDecoded()) {
59207
- this.unreleasable.add(sample);
59208
- continue;
59209
- }
59317
+ if (!sample.canRedecode) continue;
59318
+ sample.releaseDecoded();
59210
59319
  for (const key of entry.cacheKeys) this.voiceCache.delete(key);
59211
59320
  }
59212
59321
  }
@@ -59494,10 +59603,6 @@ var SpessaSynthProcessor = class {
59494
59603
  */
59495
59604
  gsDelayActive = false;
59496
59605
  /**
59497
- * The sound bank manager, which manages all sound banks and presets.
59498
- */
59499
- soundBankManager = new SoundBankManager(this.updatePresetList.bind(this));
59500
- /**
59501
59606
  * The sample rate, in Hertz.
59502
59607
  */
59503
59608
  sampleRate;
@@ -59593,6 +59698,12 @@ var SpessaSynthProcessor = class {
59593
59698
  */
59594
59699
  decodedSamples;
59595
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
+ /**
59596
59707
  * The synthesizer's GS reverb processor.
59597
59708
  *
59598
59709
  * Used when {@link GlobalMIDIParameter.system} is `gm` `gm2` or `gs`.
@@ -61023,6 +61134,6 @@ var SpessaSynthProcessor = class {
61023
61134
  return (isGMGSDrum ? 128 + patch.bankMSB : patch.bankMSB) + bankLSB * 256 + program * 32768 + 4194304 * midiNote + 536870912 * velocity;
61024
61135
  }
61025
61136
  };
61026
- export { BasicInstrument, BasicInstrumentZone, BasicLinkedZone, BasicMIDI, BasicPreset, BasicPresetZone, BasicSample, BasicSoundBank, BasicZone, DEFAULT_CHANNEL_MIDI_PARAMETERS, DEFAULT_CHANNEL_SYSTEM_PARAMETERS, DEFAULT_DRUM_REVERB, DEFAULT_GLOBAL_MIDI_PARAMETERS, DEFAULT_GLOBAL_SYSTEM_PARAMETERS, DEFAULT_MIDI_CONTROLLERS, DEFAULT_SOUND_BANK_CLONE_OPTIONS, DEFAULT_SOUND_BANK_LOAD_OPTIONS, DEFAULT_SYNTH_MODE, DefaultGSChorus, DefaultGSDelay, DefaultGSEqualizer, DefaultGSInsertion, DefaultGSReverb, DefaultXGChorus, DefaultXGEffect, DefaultXGInsertion, DefaultXGReverb, DefaultXGSystemEffect, DefaultXGVariation, DrumParameterUtils, EmptySample, GENERATORS_AMOUNT, GSInsertionControlSources, Generator, GeneratorLimits, GeneratorTypes, INSTRUMENT_ONLY_GENERATORS, IndexedByteArray, InterpolationTypes, MAX_GENERATOR, MIDIBuilder, MIDIChannel, MIDIControllers, MIDIMessage, MIDIMessageTypes, MIDIPatchTools, MIDITrack, MIDIUtils, MIDI_DRUM_CHANNEL, Modulator, ModulatorControllerSources, ModulatorCurveTypes, ModulatorSource, ModulatorTransformTypes, NonRegisteredParameterTypesLSB, NonRegisteredParameterTypesMSB, RegisteredParameterTypes, SPESSASYNTH_GAIN_FACTOR, SPESSA_BUFSIZE, SampleTypes, SoundBankCloner, SoundBankLoader, SoundBankManager, SpessaLog, SpessaSynthCoreUtils, SpessaSynthProcessor, SpessaSynthSequencer, VOICE_CAP, audioToWav };
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 };
61027
61138
 
61028
61139
  //# sourceMappingURL=index.js.map