spessasynth_core 4.3.9 → 4.3.11

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
@@ -39,6 +39,29 @@ function writeBigEndian(number, bytesAmount) {
39
39
  //#endregion
40
40
  //#region src/utils/byte_functions/little_endian.ts
41
41
  /**
42
+ * Reads the number as little endian from an IndexedByteArray. Uses BigInt to go above 32 bytes.
43
+ * @param dataArray the array to read from.
44
+ * @param bytesAmount the number of bytes to read.
45
+ * @returns the number.
46
+ */
47
+ function readLE64Indexed(dataArray, bytesAmount) {
48
+ const res = readLE64(dataArray, bytesAmount, dataArray.currentIndex);
49
+ dataArray.currentIndex += bytesAmount;
50
+ return res;
51
+ }
52
+ /**
53
+ * Reads the number as little endian. Uses BigInt to go above 32 bytes.
54
+ * @param dataArray the array to read from.
55
+ * @param bytesAmount the number of bytes to read.
56
+ * @param offset the offset to start reading at.
57
+ * @returns the number.
58
+ */
59
+ function readLE64(dataArray, bytesAmount, offset = 0) {
60
+ let out = 0n;
61
+ for (let i = 0; i < bytesAmount; i++) out |= BigInt(dataArray[offset + i]) << BigInt(i * 8);
62
+ return Number(out);
63
+ }
64
+ /**
42
65
  * Reads the number as little endian from an IndexedByteArray.
43
66
  * @param dataArray the array to read from.
44
67
  * @param bytesAmount the number of bytes to read.
@@ -73,6 +96,7 @@ function writeLittleEndianIndexed(dataArray, number, byteTarget) {
73
96
  }
74
97
  /**
75
98
  * Writes a WORD (SHORT)
99
+ * 16 bits.
76
100
  */
77
101
  function writeWord(dataArray, word) {
78
102
  dataArray[dataArray.currentIndex++] = word & 255;
@@ -80,11 +104,20 @@ function writeWord(dataArray, word) {
80
104
  }
81
105
  /**
82
106
  * Writes a DWORD (INT)
107
+ * 32 bits.
83
108
  */
84
109
  function writeDword(dataArray, dword) {
85
110
  writeLittleEndianIndexed(dataArray, dword, 4);
86
111
  }
87
112
  /**
113
+ * Writes a QWORD (LONG)
114
+ * 64 bits.
115
+ */
116
+ function writeQword(dataArray, qword) {
117
+ const qb = BigInt(qword);
118
+ for (let i = 0n; i < 8n; i++) dataArray[dataArray.currentIndex++] = Number(qb >> i * 8n & 255n);
119
+ }
120
+ /**
88
121
  * Reads two bytes as a signed short.
89
122
  */
90
123
  function signedInt16(byte1, byte2) {
@@ -525,44 +558,49 @@ var RIFFChunk = class RIFFChunk {
525
558
  */
526
559
  data;
527
560
  /**
561
+ * The size of the chunk's header in bytes.
562
+ * This varies for 32-bit and 64-bit RIFF chunks.
563
+ */
564
+ headerSize;
565
+ /**
528
566
  * Creates a new RIFF chunk.
529
567
  */
530
- constructor(header, size, data) {
568
+ constructor(header, size, data, headerSize = 8) {
531
569
  this.header = header;
532
570
  this.size = size;
533
571
  this.data = data;
572
+ this.headerSize = headerSize;
534
573
  }
535
574
  /**
536
575
  * Reads a RIFF chunk from an array.
537
576
  * @param dataArray the array to read from.
577
+ * @param rf64 if the chunk uses a 64-bit size.
538
578
  * @param readData if the data should be read as well.
539
- * @param forceShift if the index should be shifted to the end of the chunk even if the data has not been read.
540
579
  */
541
- static read(dataArray, readData = true, forceShift = false) {
580
+ static read(dataArray, rf64 = false, readData = true) {
542
581
  const header = readBinaryStringIndexed(dataArray, 4);
543
- let size = readLittleEndianIndexed(dataArray, 4);
582
+ let size = rf64 ? readLE64Indexed(dataArray, 8) : readLittleEndianIndexed(dataArray, 4);
544
583
  if (header === "") size = 0;
545
584
  const chunkData = readData ? dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + size) : new IndexedByteArray(0);
546
- if (readData || forceShift) {
585
+ if (readData) {
547
586
  dataArray.currentIndex += size;
548
587
  if (size % 2 !== 0) dataArray.currentIndex++;
549
588
  }
550
- return new RIFFChunk(header, size, chunkData);
589
+ return new RIFFChunk(header, size, chunkData, rf64 ? 12 : 8);
551
590
  }
552
591
  /**
553
592
  * Writes a RIFF chunk correctly.
554
593
  * @param header the fourCC code of the header.
555
594
  * @param data the binary chunk data.
556
- * @param addZeroByte if a zero byte should be at the end of the chunk's data.
557
595
  * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
596
+ * @param rf64 if the chunk uses a 64-bit size.
558
597
  * @returns the binary data.
559
598
  */
560
- static write(header, data, addZeroByte = false, isList = false) {
599
+ static write(header, data, rf64 = false, isList = false) {
561
600
  if (header.length !== 4) throw new Error(`Invalid header length: ${header}`);
562
- let dataStartOffset = 8;
601
+ let dataStartOffset = rf64 ? 12 : 8;
563
602
  let headerWritten = header;
564
- let dataLength = data.length;
565
- if (addZeroByte) dataLength++;
603
+ const dataLength = data.length;
566
604
  let writtenSize = dataLength;
567
605
  if (isList) {
568
606
  dataStartOffset += 4;
@@ -573,7 +611,8 @@ var RIFFChunk = class RIFFChunk {
573
611
  if (finalSize % 2 !== 0) finalSize++;
574
612
  const outArray = new IndexedByteArray(finalSize);
575
613
  writeBinaryStringIndexed(outArray, headerWritten);
576
- writeDword(outArray, writtenSize);
614
+ if (rf64) writeQword(outArray, writtenSize);
615
+ else writeDword(outArray, writtenSize);
577
616
  if (isList) writeBinaryStringIndexed(outArray, header);
578
617
  outArray.set(data, dataStartOffset);
579
618
  return outArray;
@@ -586,18 +625,25 @@ var RIFFChunk = class RIFFChunk {
586
625
  * @param header the fourCC code of the header.
587
626
  * @param chunks binary chunk data parts, will be combined in order.
588
627
  * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
628
+ * @param rf64 if the chunk uses a 64-bit size.
589
629
  * @returns the chunk as binary blobs.
590
630
  */
591
- static getParts(header, chunks, isList = false) {
631
+ static getParts(header, chunks, rf64 = false, isList = false) {
592
632
  let headerWritten = header;
593
633
  let totalSize = chunks.reduce((len, c) => c.length + len, 0);
594
634
  if (isList) {
595
635
  totalSize += 4;
596
636
  headerWritten = "LIST";
597
637
  }
598
- const dwordSize = new IndexedByteArray(4);
599
- writeDword(dwordSize, totalSize);
600
- const parts = [getStringBytes(headerWritten), dwordSize];
638
+ let sizeBytes;
639
+ if (rf64) {
640
+ sizeBytes = new IndexedByteArray(8);
641
+ writeQword(sizeBytes, totalSize);
642
+ } else {
643
+ sizeBytes = new IndexedByteArray(4);
644
+ writeDword(sizeBytes, totalSize);
645
+ }
646
+ const parts = [getStringBytes(headerWritten), sizeBytes];
601
647
  if (isList) parts.push(getStringBytes(header));
602
648
  parts.push(...chunks);
603
649
  if (totalSize % 2 !== 0) parts.push(new Uint8Array(1));
@@ -609,10 +655,11 @@ var RIFFChunk = class RIFFChunk {
609
655
  * @param header the fourCC code of the header.
610
656
  * @param chunks binary chunk data parts, will be combined in order.
611
657
  * @param isList if a "LIST" should be set as the chunk type and the actual type should be written at the start of the data.
658
+ * @param rf64 if the chunk uses a 64-bit size.
612
659
  * @returns the binary data.
613
660
  */
614
- static writeParts(header, chunks, isList = false) {
615
- let dataOffset = 8;
661
+ static writeParts(header, chunks, rf64 = false, isList = false) {
662
+ let dataOffset = rf64 ? 12 : 8;
616
663
  let headerWritten = header;
617
664
  const dataLength = chunks.reduce((len, c) => c.length + len, 0);
618
665
  let writtenSize = dataLength;
@@ -625,7 +672,8 @@ var RIFFChunk = class RIFFChunk {
625
672
  if (finalSize % 2 !== 0) finalSize++;
626
673
  const outArray = new IndexedByteArray(finalSize);
627
674
  writeBinaryStringIndexed(outArray, headerWritten);
628
- writeDword(outArray, writtenSize);
675
+ if (rf64) writeQword(outArray, writtenSize);
676
+ else writeDword(outArray, writtenSize);
629
677
  if (isList) writeBinaryStringIndexed(outArray, header);
630
678
  for (const c of chunks) {
631
679
  outArray.set(c, dataOffset);
@@ -684,12 +732,12 @@ function audioToWav(audioData, sampleRate, options = DEFAULT_WAV_WRITE_OPTIONS)
684
732
  const infoOn = Object.keys(metadata).length > 0;
685
733
  if (infoOn) {
686
734
  const encoder = new TextEncoder();
687
- const infoChunks = [RIFFChunk.write("ICMT", encoder.encode("Created with SpessaSynth"), true)];
688
- if (metadata.artist) infoChunks.push(RIFFChunk.write("IART", encoder.encode(metadata.artist), true));
689
- if (metadata.album) infoChunks.push(RIFFChunk.write("IPRD", encoder.encode(metadata.album), true));
690
- if (metadata.genre) infoChunks.push(RIFFChunk.write("IGNR", encoder.encode(metadata.genre), true));
691
- if (metadata.title) infoChunks.push(RIFFChunk.write("INAM", encoder.encode(metadata.title), true));
692
- infoChunk = RIFFChunk.writeParts("INFO", infoChunks, true);
735
+ const infoChunks = [RIFFChunk.writeParts("ICMT", [encoder.encode("Created with SpessaSynth"), [0]])];
736
+ if (metadata.artist) infoChunks.push(RIFFChunk.writeParts("IART", [encoder.encode(metadata.artist), [0]]));
737
+ if (metadata.album) infoChunks.push(RIFFChunk.writeParts("IPRD", [encoder.encode(metadata.album), [0]]));
738
+ if (metadata.genre) infoChunks.push(RIFFChunk.writeParts("IGNR", [encoder.encode(metadata.genre), [0]]));
739
+ if (metadata.title) infoChunks.push(RIFFChunk.writeParts("INAM", [encoder.encode(metadata.title), [0]]));
740
+ infoChunk = RIFFChunk.writeParts("INFO", infoChunks, false, true);
693
741
  }
694
742
  let cueChunk = new IndexedByteArray(0);
695
743
  const cueOn = loop?.end !== void 0 && loop?.start !== void 0;
@@ -2588,7 +2636,7 @@ function writeRMIDIInternal(mid, soundBankBinary, options) {
2588
2636
  return RIFFChunk.writeParts("RIFF", [
2589
2637
  getStringBytes("RMID"),
2590
2638
  RIFFChunk.write("data", newMid),
2591
- RIFFChunk.writeParts("INFO", infoContent, true),
2639
+ RIFFChunk.writeParts("INFO", infoContent, false, true),
2592
2640
  new IndexedByteArray(soundBankBinary)
2593
2641
  ]).buffer;
2594
2642
  }
@@ -3651,7 +3699,7 @@ function parseRMIDIInternal(outputMIDI, binaryData, fileName) {
3651
3699
  let foundDBNK = false;
3652
3700
  while (binaryData.currentIndex < binaryData.length) {
3653
3701
  const startIndex = binaryData.currentIndex;
3654
- const currentChunk = RIFFChunk.read(binaryData, true);
3702
+ const currentChunk = RIFFChunk.read(binaryData);
3655
3703
  if (currentChunk.header === "RIFF") {
3656
3704
  const type = readBinaryStringIndexed(currentChunk.data, 4).toLowerCase();
3657
3705
  if (type === "sfbk" || type === "sfpk" || type === "dls ") {
@@ -3664,7 +3712,7 @@ function parseRMIDIInternal(outputMIDI, binaryData, fileName) {
3664
3712
  if (readBinaryStringIndexed(currentChunk.data, 4) === "INFO") {
3665
3713
  SpessaLog.info("%cFound RMIDI INFO chunk!", ConsoleColors.recognized);
3666
3714
  while (currentChunk.data.currentIndex < currentChunk.size) {
3667
- const infoChunk = RIFFChunk.read(currentChunk.data, true);
3715
+ const infoChunk = RIFFChunk.read(currentChunk.data);
3668
3716
  const headerTyped = infoChunk.header;
3669
3717
  const infoData = infoChunk.data;
3670
3718
  switch (headerTyped) {
@@ -8605,7 +8653,7 @@ var BasicSample = class {
8605
8653
  return decoded;
8606
8654
  } catch (error) {
8607
8655
  SpessaLog.warn(`Error decoding sample ${this.name}: ${error}`);
8608
- return new Float32Array(this.loopEnd + 1);
8656
+ return new Float32Array(this.loopEnd);
8609
8657
  }
8610
8658
  }
8611
8659
  };
@@ -8954,14 +9002,19 @@ var BasicPreset = class BasicPreset {
8954
9002
  * Writes the SF2 header
8955
9003
  * @param phdrData
8956
9004
  * @param index
9005
+ * @param writeLSB
9006
+ * @internal
8957
9007
  */
8958
- write(phdrData, index) {
9008
+ write(phdrData, index, writeLSB) {
8959
9009
  SpessaLog.info(`%cWriting ${this.name}...`, ConsoleColors.info);
8960
9010
  writeBinaryStringIndexed(phdrData.pdta, this.name.slice(0, 20), 20);
8961
9011
  writeBinaryStringIndexed(phdrData.xdta, this.name.slice(20), 20);
8962
9012
  writeWord(phdrData.pdta, this.program);
8963
9013
  let wBank = this.bankMSB;
8964
- if (this.isGMGSDrum) wBank = 128;
9014
+ if (writeLSB) {
9015
+ wBank = this.bankMSB & 127 | (this.bankLSB & 127) << 8;
9016
+ if (this.isGMGSDrum) wBank |= 128;
9017
+ } else if (this.isGMGSDrum) wBank = 128;
8965
9018
  else if (this.bankMSB === 0) wBank = this.bankLSB;
8966
9019
  writeWord(phdrData.pdta, wBank);
8967
9020
  phdrData.xdta.currentIndex += 4;
@@ -9150,6 +9203,11 @@ var BasicInstrument = class {
9150
9203
  }
9151
9204
  }
9152
9205
  }
9206
+ /**
9207
+ * @internal
9208
+ * @param instData
9209
+ * @param index
9210
+ */
9153
9211
  write(instData, index) {
9154
9212
  SpessaLog.info(`%cWriting ${this.name}...`, ConsoleColors.info);
9155
9213
  writeBinaryStringIndexed(instData.pdta, this.name.slice(0, 20), 20);
@@ -9160,7 +9218,7 @@ var BasicInstrument = class {
9160
9218
  };
9161
9219
  //#endregion
9162
9220
  //#region src/soundbank/soundfont/write/sdta.ts
9163
- function getSDTA(bank, smplStartOffsets, smplEndOffsets, progressFunction) {
9221
+ function getSDTA(bank, smplStartOffsets, smplEndOffsets, rf64, progressFunction) {
9164
9222
  let writtenCount = 0;
9165
9223
  const sampleData = [];
9166
9224
  const sampleSize = [];
@@ -9173,8 +9231,8 @@ function getSDTA(bank, smplStartOffsets, smplEndOffsets, progressFunction) {
9173
9231
  sampleSize.push(r.length);
9174
9232
  if (!s.isCompressed) sampleData.push(new Uint8Array(92));
9175
9233
  }
9176
- const smpl = RIFFChunk.getParts("smpl", sampleData);
9177
- const sdta = RIFFChunk.getParts("sdta", smpl, true);
9234
+ const smpl = RIFFChunk.getParts("smpl", sampleData, rf64);
9235
+ const sdta = RIFFChunk.getParts("sdta", smpl, rf64, true);
9178
9236
  let offset = 0;
9179
9237
  for (const [i, sample] of bank.samples.entries()) {
9180
9238
  const size = sampleSize[i];
@@ -9314,7 +9372,7 @@ function readSample(index, sampleHeaderData, smplArrayData) {
9314
9372
  }
9315
9373
  //#endregion
9316
9374
  //#region src/soundbank/soundfont/write/shdr.ts
9317
- function getSHDR(bank, smplStartOffsets, smplEndOffsets) {
9375
+ function getSHDR(bank, smplStartOffsets, smplEndOffsets, rf64) {
9318
9376
  const sampleLength = 46;
9319
9377
  const shdrSize = sampleLength * (bank.samples.length + 1);
9320
9378
  const shdrData = new IndexedByteArray(shdrSize);
@@ -9353,13 +9411,13 @@ function getSHDR(bank, smplStartOffsets, smplEndOffsets) {
9353
9411
  writeBinaryStringIndexed(shdrData, "EOS", sampleLength);
9354
9412
  writeBinaryStringIndexed(xshdrData, "EOS", sampleLength);
9355
9413
  return {
9356
- pdta: RIFFChunk.write("shdr", shdrData),
9357
- xdta: RIFFChunk.write("shdr", xshdrData)
9414
+ pdta: RIFFChunk.write("shdr", shdrData, rf64),
9415
+ xdta: RIFFChunk.write("shdr", xshdrData, rf64)
9358
9416
  };
9359
9417
  }
9360
9418
  //#endregion
9361
9419
  //#region src/soundbank/soundfont/write/write_sf2_elements.ts
9362
- function writeSF2Elements(bank, isPreset = false) {
9420
+ function writeSF2Elements(bank, rf64, isPreset, writeBankLSB = false) {
9363
9421
  const elements = isPreset ? bank.presets : bank.instruments;
9364
9422
  const genHeader = isPreset ? "pgen" : "igen";
9365
9423
  const modHeader = isPreset ? "pmod" : "imod";
@@ -9416,7 +9474,7 @@ function writeSF2Elements(bank, isPreset = false) {
9416
9474
  pdta: new IndexedByteArray(hdrSize),
9417
9475
  xdta: new IndexedByteArray(hdrSize)
9418
9476
  };
9419
- for (const [i, el] of elements.entries()) el.write(hdrData, zoneIndexes[i]);
9477
+ for (const [i, el] of elements.entries()) el.write(hdrData, zoneIndexes[i], writeBankLSB);
9420
9478
  if (isPreset) {
9421
9479
  writeBinaryStringIndexed(hdrData.pdta, "EOP", 20);
9422
9480
  hdrData.pdta.currentIndex += 4;
@@ -9435,20 +9493,20 @@ function writeSF2Elements(bank, isPreset = false) {
9435
9493
  return {
9436
9494
  writeXdta: Math.max(currentGenIndex, currentModIndex, zoneIndex) > 65535,
9437
9495
  gen: {
9438
- pdta: RIFFChunk.write(genHeader, genData),
9439
- xdta: RIFFChunk.write(modHeader, new IndexedByteArray(4))
9496
+ pdta: RIFFChunk.write(genHeader, genData, rf64),
9497
+ xdta: RIFFChunk.write(modHeader, new IndexedByteArray(4), rf64)
9440
9498
  },
9441
9499
  mod: {
9442
- pdta: RIFFChunk.write(modHeader, modData),
9443
- xdta: RIFFChunk.write(modHeader, new IndexedByteArray(10))
9500
+ pdta: RIFFChunk.write(modHeader, modData, rf64),
9501
+ xdta: RIFFChunk.write(modHeader, new IndexedByteArray(10), rf64)
9444
9502
  },
9445
9503
  bag: {
9446
- pdta: RIFFChunk.write(bagHeader, bagData.pdta),
9447
- xdta: RIFFChunk.write(bagHeader, bagData.xdta)
9504
+ pdta: RIFFChunk.write(bagHeader, bagData.pdta, rf64),
9505
+ xdta: RIFFChunk.write(bagHeader, bagData.xdta, rf64)
9448
9506
  },
9449
9507
  hdr: {
9450
- pdta: RIFFChunk.write(hdrHeader, hdrData.pdta),
9451
- xdta: RIFFChunk.write(hdrHeader, hdrData.xdta)
9508
+ pdta: RIFFChunk.write(hdrHeader, hdrData.pdta, rf64),
9509
+ xdta: RIFFChunk.write(hdrHeader, hdrData.xdta, rf64)
9452
9510
  }
9453
9511
  };
9454
9512
  }
@@ -9459,6 +9517,10 @@ const DEFAULT_SF2_WRITE_OPTIONS = {
9459
9517
  writeExtendedLimits: true,
9460
9518
  software: "SpessaSynth"
9461
9519
  };
9520
+ const DEFAULT_SFE_WRITE_OPTIONS = {
9521
+ rf64: true,
9522
+ software: "SpessaSynth"
9523
+ };
9462
9524
  /**
9463
9525
  * Writes the sound bank as an SF2 file.
9464
9526
  * @param bank
@@ -9467,6 +9529,28 @@ const DEFAULT_SF2_WRITE_OPTIONS = {
9467
9529
  */
9468
9530
  function writeSF2Internal(bank, writeOptions) {
9469
9531
  const options = fillWithDefaults(writeOptions, DEFAULT_SF2_WRITE_OPTIONS);
9532
+ return writeSF(bank, options.software, options.writeDefaultModulators, options.writeExtendedLimits, false, false);
9533
+ }
9534
+ /**
9535
+ * Writes the sound bank as an SFE 4 file.
9536
+ * @param bank
9537
+ * @param writeOptions the options for writing.
9538
+ * @returns the binary file data.
9539
+ */
9540
+ function writeSFEInternal(bank, writeOptions) {
9541
+ return writeSF(bank, fillWithDefaults(writeOptions, DEFAULT_SFE_WRITE_OPTIONS).software, true, true, true, true);
9542
+ }
9543
+ /**
9544
+ * General writing function for both SFE and SF2.
9545
+ * @param bank the bank
9546
+ * @param software software param
9547
+ * @param writeDefaultModulators SFE + SF2 compatible
9548
+ * @param writeExtendedLimits SFE + SF2 compatible
9549
+ * @param writeBankLSB SFE Only
9550
+ * @param rf64 SFE Only
9551
+ * @internal
9552
+ */
9553
+ function writeSF(bank, software, writeDefaultModulators, writeExtendedLimits, writeBankLSB, rf64) {
9470
9554
  SpessaLog.groupCollapsed("%cSaving soundbank...", ConsoleColors.info);
9471
9555
  SpessaLog.group("%cWriting INFO...", ConsoleColors.info);
9472
9556
  /**
@@ -9475,14 +9559,14 @@ function writeSF2Internal(bank, writeOptions) {
9475
9559
  const infoArrays = [];
9476
9560
  const writeSF2Info = (type, data) => {
9477
9561
  if (!data) return;
9478
- infoArrays.push(...RIFFChunk.getParts(type, [getStringBytes(data, true, true)]));
9562
+ infoArrays.push(...RIFFChunk.getParts(type, [getStringBytes(data, true, true)], rf64));
9479
9563
  };
9480
9564
  const info = bank.soundBankInfo;
9481
9565
  {
9482
9566
  const ifilData = new IndexedByteArray(4);
9483
9567
  writeWord(ifilData, info.version.major);
9484
9568
  writeWord(ifilData, info.version.minor);
9485
- infoArrays.push(RIFFChunk.write("ifil", ifilData));
9569
+ infoArrays.push(RIFFChunk.write("ifil", ifilData, rf64));
9486
9570
  }
9487
9571
  writeSF2Info("isng", info.soundEngine);
9488
9572
  writeSF2Info("INAM", info.name);
@@ -9491,36 +9575,35 @@ function writeSF2Internal(bank, writeOptions) {
9491
9575
  const ifilData = new IndexedByteArray(4);
9492
9576
  writeWord(ifilData, info.romVersion.major);
9493
9577
  writeWord(ifilData, info.romVersion.minor);
9494
- infoArrays.push(RIFFChunk.write("iver", ifilData));
9578
+ infoArrays.push(RIFFChunk.write("iver", ifilData, rf64));
9495
9579
  }
9496
9580
  writeSF2Info("ICRD", toISODateString(info.creationDate));
9497
9581
  writeSF2Info("IENG", info.engineer);
9498
9582
  writeSF2Info("IPRD", info.product);
9499
9583
  writeSF2Info("ICOP", info.copyright);
9500
9584
  writeSF2Info("ICMT", info?.subject ? (info?.comment ? info.comment + "\n" : "") + info.subject : info?.comment);
9501
- const software = options.software;
9502
9585
  writeSF2Info("ISFT", software);
9503
- if (bank.defaultModulators.some((mod) => !SPESSASYNTH_DEFAULT_MODULATORS.some((m) => Modulator.isIdentical(m, mod, true))) && options?.writeDefaultModulators) {
9586
+ if (bank.defaultModulators.some((mod) => !SPESSASYNTH_DEFAULT_MODULATORS.some((m) => Modulator.isIdentical(m, mod, true))) && writeDefaultModulators) {
9504
9587
  const mods = bank.defaultModulators;
9505
9588
  SpessaLog.info(`%cWriting %c${mods.length}%c default modulators...`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info);
9506
9589
  const dmodData = new IndexedByteArray(10 + mods.length * 10);
9507
9590
  for (const mod of mods) mod.write(dmodData);
9508
9591
  writeLittleEndianIndexed(dmodData, 0, 10);
9509
- infoArrays.push(...RIFFChunk.getParts("DMOD", [dmodData]));
9592
+ infoArrays.push(...RIFFChunk.getParts("DMOD", [dmodData], rf64));
9510
9593
  }
9511
9594
  SpessaLog.groupEnd();
9512
9595
  SpessaLog.info("%cWriting SDTA...", ConsoleColors.info);
9513
9596
  const smplStartOffsets = [];
9514
9597
  const smplEndOffsets = [];
9515
- const sdtaChunk = getSDTA(bank, smplStartOffsets, smplEndOffsets);
9598
+ const sdtaChunk = getSDTA(bank, smplStartOffsets, smplEndOffsets, rf64);
9516
9599
  SpessaLog.info("%cWriting PDTA...", ConsoleColors.info);
9517
9600
  SpessaLog.info("%cWriting SHDR...", ConsoleColors.info);
9518
- const shdrChunk = getSHDR(bank, smplStartOffsets, smplEndOffsets);
9601
+ const shdrChunk = getSHDR(bank, smplStartOffsets, smplEndOffsets, rf64);
9519
9602
  SpessaLog.group("%cWriting instruments...", ConsoleColors.info);
9520
- const instData = writeSF2Elements(bank, false);
9603
+ const instData = writeSF2Elements(bank, rf64, false);
9521
9604
  SpessaLog.groupEnd();
9522
9605
  SpessaLog.group("%cWriting presets...", ConsoleColors.info);
9523
- const presData = writeSF2Elements(bank, true);
9606
+ const presData = writeSF2Elements(bank, rf64, true, writeBankLSB);
9524
9607
  SpessaLog.groupEnd();
9525
9608
  const chunks = [
9526
9609
  presData.hdr,
@@ -9533,19 +9616,19 @@ function writeSF2Internal(bank, writeOptions) {
9533
9616
  instData.gen,
9534
9617
  shdrChunk
9535
9618
  ];
9536
- const pdtaChunk = RIFFChunk.getParts("pdta", chunks.map((c) => c.pdta), true);
9537
- if (options.writeExtendedLimits && (instData.writeXdta || presData.writeXdta || bank.presets.some((p) => p.name.length > 20) || bank.instruments.some((i) => i.name.length > 20) || bank.samples.some((s) => s.name.length > 20))) {
9619
+ const pdtaChunk = RIFFChunk.getParts("pdta", chunks.map((c) => c.pdta), rf64, true);
9620
+ if (writeExtendedLimits && (instData.writeXdta || presData.writeXdta || bank.presets.some((p) => p.name.length > 20) || bank.instruments.some((i) => i.name.length > 20) || bank.samples.some((s) => s.name.length > 20))) {
9538
9621
  SpessaLog.info(`%cWriting the xdta chunk as writeExtendedLimits is enabled and at least one condition was met.`, ConsoleColors.info, ConsoleColors.value);
9539
- infoArrays.push(...RIFFChunk.getParts("xdta", chunks.map((c) => c.xdta), true));
9622
+ infoArrays.push(...RIFFChunk.getParts("xdta", chunks.map((c) => c.xdta), rf64, true));
9540
9623
  }
9541
- const infoChunk = RIFFChunk.getParts("INFO", infoArrays, true);
9624
+ const infoChunk = RIFFChunk.getParts("INFO", infoArrays, rf64, true);
9542
9625
  SpessaLog.info("%cWriting the output file...", ConsoleColors.info);
9543
- const main = RIFFChunk.writeParts("RIFF", [
9544
- getStringBytes("sfbk"),
9626
+ const main = RIFFChunk.writeParts(rf64 ? "RIFS" : "RIFF", [
9627
+ getStringBytes(writeBankLSB ? "sfen" : "sfbk"),
9545
9628
  ...infoChunk,
9546
9629
  ...sdtaChunk,
9547
9630
  ...pdtaChunk
9548
- ]);
9631
+ ], rf64);
9549
9632
  SpessaLog.info(`%cSaved successfully! Final file size: %c${main.length}`, ConsoleColors.info, ConsoleColors.recognized);
9550
9633
  SpessaLog.groupEnd();
9551
9634
  return main.buffer;
@@ -9967,7 +10050,7 @@ var DownloadableSoundsSample = class DownloadableSoundsSample extends DLSVerifie
9967
10050
  wsmp,
9968
10051
  ...data,
9969
10052
  info
9970
- ], true);
10053
+ ], false, true);
9971
10054
  }
9972
10055
  writeFmt() {
9973
10056
  const fmtData = new IndexedByteArray(18);
@@ -10895,7 +10978,7 @@ var DownloadableSoundsRegion = class DownloadableSoundsRegion extends DLSVerifie
10895
10978
  this.waveLink.write(),
10896
10979
  ...this.articulation.write()
10897
10980
  ];
10898
- return RIFFChunk.getParts("rgn2", chunks, true);
10981
+ return RIFFChunk.getParts("rgn2", chunks, false, true);
10899
10982
  }
10900
10983
  toSFZone(instrument, samples) {
10901
10984
  const sample = samples[this.waveLink.tableIndex];
@@ -11016,12 +11099,12 @@ var DownloadableSoundsInstrument = class DownloadableSoundsInstrument extends DL
11016
11099
  SpessaLog.groupCollapsed(`%cWriting %c${this.name}%c...`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info);
11017
11100
  const chunks = [this.writeHeader()];
11018
11101
  const regionChunks = this.regions.flatMap((r) => r.write());
11019
- chunks.push(...RIFFChunk.getParts("lrgn", regionChunks, true));
11102
+ chunks.push(...RIFFChunk.getParts("lrgn", regionChunks, false, true));
11020
11103
  if (this.articulation.length > 0) chunks.push(...this.articulation.write());
11021
11104
  const inam = RIFFChunk.write("INAM", getStringBytes(this.name, true));
11022
11105
  chunks.push(RIFFChunk.write("INFO", inam, false, true));
11023
11106
  SpessaLog.groupEnd();
11024
- return RIFFChunk.writeParts("ins ", chunks, true);
11107
+ return RIFFChunk.writeParts("ins ", chunks, false, true);
11025
11108
  }
11026
11109
  /**
11027
11110
  * Performs the full DLS to SF2 instrument conversion.
@@ -11076,7 +11159,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
11076
11159
  if (!buffer) throw new Error("No data provided!");
11077
11160
  const dataArray = new IndexedByteArray(buffer);
11078
11161
  SpessaLog.group("%cParsing DLS file...", ConsoleColors.info);
11079
- const firstChunk = RIFFChunk.read(dataArray, false);
11162
+ const firstChunk = RIFFChunk.read(dataArray, false, false);
11080
11163
  this.verifyHeader(firstChunk, "RIFF");
11081
11164
  this.verifyText(readBinaryStringIndexed(dataArray, 4).toLowerCase(), "dls ");
11082
11165
  /**
@@ -11230,7 +11313,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
11230
11313
  writeDword(colhNum, this.instruments.length);
11231
11314
  const colh = RIFFChunk.write("colh", colhNum);
11232
11315
  SpessaLog.groupCollapsed("%cWriting instruments...", ConsoleColors.info);
11233
- const lins = RIFFChunk.getParts("lins", this.instruments.map((i) => i.write()), true);
11316
+ const lins = RIFFChunk.getParts("lins", this.instruments.map((i) => i.write()), false, true);
11234
11317
  SpessaLog.info("%cSuccess!", ConsoleColors.recognized);
11235
11318
  SpessaLog.groupEnd();
11236
11319
  SpessaLog.groupCollapsed("%cWriting WAVE samples...", ConsoleColors.info);
@@ -11247,7 +11330,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
11247
11330
  samples.push(...out);
11248
11331
  written++;
11249
11332
  }
11250
- const wvpl = RIFFChunk.getParts("wvpl", samples, true);
11333
+ const wvpl = RIFFChunk.getParts("wvpl", samples, false, true);
11251
11334
  SpessaLog.info("%cSucceeded!", ConsoleColors.recognized);
11252
11335
  const ptblData = new IndexedByteArray(8 + 4 * ptblOffsets.length);
11253
11336
  writeDword(ptblData, 8);
@@ -11276,7 +11359,7 @@ var DownloadableSounds = class DownloadableSounds extends DLSVerifier {
11276
11359
  ...lins,
11277
11360
  ptbl,
11278
11361
  ...wvpl,
11279
- ...RIFFChunk.getParts("INFO", infos, true)
11362
+ ...RIFFChunk.getParts("INFO", infos, false, true)
11280
11363
  ]);
11281
11364
  SpessaLog.info("%cSaved successfully!", ConsoleColors.recognized);
11282
11365
  SpessaLog.groupEnd();
@@ -11311,7 +11394,7 @@ var BasicSoundBank = class BasicSoundBank {
11311
11394
  static isSF3DecoderReady = stb.isInitialized;
11312
11395
  /**
11313
11396
  * The type of the sound bank that was loaded.
11314
- * Either `sf2` for SoundFont2/SoundFont3 or `dls` for DownLoadable Sounds.
11397
+ * Either `sf2` for SoundFont2/SoundFont3 or `dls` for DownLoadable Sounds or `sfe` for SF-Enhanced.
11315
11398
  *
11316
11399
  * Please note that SF3 or SFOGG files are parsed as `sf2` files, but with compressed samples.
11317
11400
  * The type is still `sf2`.
@@ -11390,7 +11473,7 @@ var BasicSoundBank = class BasicSoundBank {
11390
11473
  sample.name = "Saw";
11391
11474
  sample.originalKey = 65;
11392
11475
  sample.pitchCorrection = 20;
11393
- sample.loopEnd = 127;
11476
+ sample.loopEnd = 128;
11394
11477
  sample.setAudioData(sampleData, 44100);
11395
11478
  font.addSamples(sample);
11396
11479
  const inst = new BasicInstrument();
@@ -11403,7 +11486,7 @@ var BasicSoundBank = class BasicSoundBank {
11403
11486
  preset.name = "Saw Wave";
11404
11487
  preset.createZone(inst);
11405
11488
  font.addPresets(preset);
11406
- font.soundBankInfo.name = "Dummy";
11489
+ font.soundBankInfo.name = "SpessaSynth Sample Sound Bank";
11407
11490
  font.flush();
11408
11491
  return font.writeSF2();
11409
11492
  }
@@ -11486,6 +11569,16 @@ var BasicSoundBank = class BasicSoundBank {
11486
11569
  writeSF2(writeOptions = DEFAULT_SF2_WRITE_OPTIONS) {
11487
11570
  return writeSF2Internal(this, writeOptions);
11488
11571
  }
11572
+ /**
11573
+ * Writes the sound bank as an [SFE 4](https://sfe-team-was-taken.github.io/SFE/) file.
11574
+ * This enables features such as bank LSB and RIFF64.
11575
+ * Note that spessasynth is currently the only software that can read these files.
11576
+ * @param writeOptions the options for writing.
11577
+ * @returns the binary file data.
11578
+ */
11579
+ writeSFE(writeOptions = DEFAULT_SFE_WRITE_OPTIONS) {
11580
+ return writeSFEInternal(this, writeOptions);
11581
+ }
11489
11582
  addPresets(...presets) {
11490
11583
  this.presets.push(...presets);
11491
11584
  }
@@ -11941,33 +12034,29 @@ var SoundFont2 = class extends BasicSoundBank {
11941
12034
  /**
11942
12035
  * Initializes a new SoundFont2 Parser and parses the given data array
11943
12036
  */
11944
- constructor(arrayBuffer, warnDeprecated = true) {
11945
- super("sf2");
11946
- if (warnDeprecated) throw new Error("Using the constructor directly is deprecated. Use SoundBankLoader.fromArrayBuffer() instead.");
12037
+ constructor(arrayBuffer, sfe) {
12038
+ super(sfe ? "sfe" : "sf2");
11947
12039
  const mainFileArray = new IndexedByteArray(arrayBuffer);
11948
12040
  SpessaLog.group("%cParsing a SoundFont2 file...", ConsoleColors.info);
11949
- if (!mainFileArray) {
11950
- SpessaLog.groupEnd();
11951
- this.parsingError("No data provided!");
11952
- }
11953
- const firstChunk = RIFFChunk.read(mainFileArray, false);
11954
- this.verifyHeader(firstChunk, "riff");
12041
+ const fourCC = readBinaryString(mainFileArray, 4).toLowerCase();
12042
+ this.verifyTexts(fourCC, ["riff", "rifs"]);
12043
+ const rf64 = fourCC === "rifs";
12044
+ if (rf64) SpessaLog.info("%cRIFF64 Detected!", ConsoleColors.recognized);
12045
+ RIFFChunk.read(mainFileArray, rf64, false);
11955
12046
  const type = readBinaryStringIndexed(mainFileArray, 4).toLowerCase();
11956
- if (type !== "sfbk" && type !== "sfpk") {
11957
- SpessaLog.groupEnd();
11958
- throw new SyntaxError(`Invalid soundFont! Expected "sfbk" or "sfpk" got "${type}"`);
11959
- }
12047
+ this.verifyTexts(type, [
12048
+ "sfbk",
12049
+ "sfpk",
12050
+ "sfen"
12051
+ ]);
11960
12052
  const isSF2Pack = type === "sfpk";
11961
- const infoChunk = RIFFChunk.read(mainFileArray);
12053
+ const infoChunk = RIFFChunk.read(mainFileArray, rf64);
11962
12054
  this.verifyHeader(infoChunk, "list");
11963
12055
  const infoString = readBinaryStringIndexed(infoChunk.data, 4);
11964
- if (infoString !== "INFO") {
11965
- SpessaLog.groupEnd();
11966
- throw new SyntaxError(`Invalid soundFont! Expected "INFO" got "${infoString}"`);
11967
- }
12056
+ this.verifyText(infoString, "info");
11968
12057
  let xdtaChunk;
11969
12058
  while (infoChunk.data.length > infoChunk.data.currentIndex) {
11970
- const chunk = RIFFChunk.read(infoChunk.data);
12059
+ const chunk = RIFFChunk.read(infoChunk.data, rf64);
11971
12060
  const text = readBinaryString(chunk.data, chunk.data.length);
11972
12061
  const headerTyped = chunk.header;
11973
12062
  switch (headerTyped) {
@@ -12025,27 +12114,27 @@ var SoundFont2 = class extends BasicSoundBank {
12025
12114
  this.printInfo();
12026
12115
  const xChunks = {};
12027
12116
  if (xdtaChunk !== void 0) {
12028
- xChunks.phdr = RIFFChunk.read(xdtaChunk.data);
12029
- xChunks.pbag = RIFFChunk.read(xdtaChunk.data);
12030
- xChunks.pmod = RIFFChunk.read(xdtaChunk.data);
12031
- xChunks.pgen = RIFFChunk.read(xdtaChunk.data);
12032
- xChunks.inst = RIFFChunk.read(xdtaChunk.data);
12033
- xChunks.ibag = RIFFChunk.read(xdtaChunk.data);
12034
- xChunks.imod = RIFFChunk.read(xdtaChunk.data);
12035
- xChunks.igen = RIFFChunk.read(xdtaChunk.data);
12036
- xChunks.shdr = RIFFChunk.read(xdtaChunk.data);
12037
- }
12038
- const sdtaChunk = RIFFChunk.read(mainFileArray, false);
12117
+ xChunks.phdr = RIFFChunk.read(xdtaChunk.data, rf64);
12118
+ xChunks.pbag = RIFFChunk.read(xdtaChunk.data, rf64);
12119
+ xChunks.pmod = RIFFChunk.read(xdtaChunk.data, rf64);
12120
+ xChunks.pgen = RIFFChunk.read(xdtaChunk.data, rf64);
12121
+ xChunks.inst = RIFFChunk.read(xdtaChunk.data, rf64);
12122
+ xChunks.ibag = RIFFChunk.read(xdtaChunk.data, rf64);
12123
+ xChunks.imod = RIFFChunk.read(xdtaChunk.data, rf64);
12124
+ xChunks.igen = RIFFChunk.read(xdtaChunk.data, rf64);
12125
+ xChunks.shdr = RIFFChunk.read(xdtaChunk.data, rf64);
12126
+ }
12127
+ const sdtaChunk = RIFFChunk.read(mainFileArray, rf64, false);
12039
12128
  this.verifyHeader(sdtaChunk, "list");
12040
12129
  this.verifyText(readBinaryStringIndexed(mainFileArray, 4), "sdta");
12041
12130
  SpessaLog.info("%cVerifying smpl chunk...", ConsoleColors.warn);
12042
- const sampleDataChunk = RIFFChunk.read(mainFileArray, false);
12131
+ const sampleDataChunk = RIFFChunk.read(mainFileArray, rf64, false);
12043
12132
  this.verifyHeader(sampleDataChunk, "smpl");
12044
12133
  let sampleData;
12045
12134
  if (isSF2Pack) {
12046
12135
  SpessaLog.info("%cSF2Pack detected, attempting to decode the smpl chunk...", ConsoleColors.info);
12047
12136
  try {
12048
- sampleData = stb.decode(mainFileArray.buffer.slice(mainFileArray.currentIndex, mainFileArray.currentIndex + sdtaChunk.size - 12)).data[0];
12137
+ sampleData = stb.decode(mainFileArray.buffer.slice(mainFileArray.currentIndex, mainFileArray.currentIndex + sdtaChunk.size - 4 - sdtaChunk.headerSize)).data[0];
12049
12138
  } catch (error) {
12050
12139
  SpessaLog.groupEnd();
12051
12140
  throw new Error(`SF2Pack Ogg Vorbis decode error: ${error}`, { cause: error });
@@ -12055,29 +12144,29 @@ var SoundFont2 = class extends BasicSoundBank {
12055
12144
  sampleData = mainFileArray;
12056
12145
  this.sampleDataStartIndex = mainFileArray.currentIndex;
12057
12146
  }
12058
- SpessaLog.info(`%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`, ConsoleColors.info, ConsoleColors.value);
12059
- mainFileArray.currentIndex += sdtaChunk.size - 12;
12147
+ SpessaLog.info(`%cSkipping sample chunk, length: %c${sdtaChunk.size - 4 - sdtaChunk.headerSize}`, ConsoleColors.info, ConsoleColors.value);
12148
+ mainFileArray.currentIndex += sdtaChunk.size - 4 - sdtaChunk.headerSize;
12060
12149
  SpessaLog.info("%cLoading preset data chunk...", ConsoleColors.warn);
12061
- const presetChunk = RIFFChunk.read(mainFileArray);
12150
+ const presetChunk = RIFFChunk.read(mainFileArray, rf64);
12062
12151
  this.verifyHeader(presetChunk, "list");
12063
12152
  readBinaryStringIndexed(presetChunk.data, 4);
12064
- const phdrChunk = RIFFChunk.read(presetChunk.data);
12153
+ const phdrChunk = RIFFChunk.read(presetChunk.data, rf64);
12065
12154
  this.verifyHeader(phdrChunk, "phdr");
12066
- const pbagChunk = RIFFChunk.read(presetChunk.data);
12155
+ const pbagChunk = RIFFChunk.read(presetChunk.data, rf64);
12067
12156
  this.verifyHeader(pbagChunk, "pbag");
12068
- const pmodChunk = RIFFChunk.read(presetChunk.data);
12157
+ const pmodChunk = RIFFChunk.read(presetChunk.data, rf64);
12069
12158
  this.verifyHeader(pmodChunk, "pmod");
12070
- const pgenChunk = RIFFChunk.read(presetChunk.data);
12159
+ const pgenChunk = RIFFChunk.read(presetChunk.data, rf64);
12071
12160
  this.verifyHeader(pgenChunk, "pgen");
12072
- const instChunk = RIFFChunk.read(presetChunk.data);
12161
+ const instChunk = RIFFChunk.read(presetChunk.data, rf64);
12073
12162
  this.verifyHeader(instChunk, "inst");
12074
- const ibagChunk = RIFFChunk.read(presetChunk.data);
12163
+ const ibagChunk = RIFFChunk.read(presetChunk.data, rf64);
12075
12164
  this.verifyHeader(ibagChunk, "ibag");
12076
- const imodChunk = RIFFChunk.read(presetChunk.data);
12165
+ const imodChunk = RIFFChunk.read(presetChunk.data, rf64);
12077
12166
  this.verifyHeader(imodChunk, "imod");
12078
- const igenChunk = RIFFChunk.read(presetChunk.data);
12167
+ const igenChunk = RIFFChunk.read(presetChunk.data, rf64);
12079
12168
  this.verifyHeader(igenChunk, "igen");
12080
- const shdrChunk = RIFFChunk.read(presetChunk.data);
12169
+ const shdrChunk = RIFFChunk.read(presetChunk.data, rf64);
12081
12170
  this.verifyHeader(shdrChunk, "shdr");
12082
12171
  SpessaLog.info("%cParsing samples...", ConsoleColors.info);
12083
12172
  /**
@@ -12159,6 +12248,12 @@ var SoundFont2 = class extends BasicSoundBank {
12159
12248
  this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
12160
12249
  }
12161
12250
  }
12251
+ verifyTexts(text, expected) {
12252
+ if (!expected.includes(text.toLowerCase())) {
12253
+ SpessaLog.groupEnd();
12254
+ this.parsingError(`Invalid FourCC: Expected ${expected.map((s) => `"${s}"`).join(", ")} but got "${text.toLowerCase()}"`);
12255
+ }
12256
+ }
12162
12257
  };
12163
12258
  //#endregion
12164
12259
  //#region src/soundbank/sound_bank_loader.ts
@@ -12169,8 +12264,11 @@ var SoundBankLoader = class {
12169
12264
  * @returns The loaded sound bank, a BasicSoundBank instance.
12170
12265
  */
12171
12266
  static fromArrayBuffer(buffer) {
12172
- if (readBinaryStringIndexed(new IndexedByteArray(buffer.slice(8, 12)), 4).toLowerCase() === "dls ") return this.loadDLS(buffer);
12173
- return new SoundFont2(buffer, false);
12267
+ const riffText = readBinaryStringIndexed(new IndexedByteArray(buffer.slice(0, 4)), 4);
12268
+ if (riffText !== "RIFF" && riffText !== "RIFS") throw new Error(`Expected 'RIFF' or 'RIFS' header, got '${riffText}'`);
12269
+ const id = readBinaryStringIndexed(new IndexedByteArray(riffText === "RIFS" ? buffer.slice(12, 16) : buffer.slice(8, 12)), 4).toLowerCase();
12270
+ if (id === "dls ") return this.loadDLS(buffer);
12271
+ return new SoundFont2(buffer, id === "sfen");
12174
12272
  }
12175
12273
  static loadDLS(buffer) {
12176
12274
  return DownloadableSounds.read(buffer).toSF();