spessasynth_core 3.26.28 → 3.26.30

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.
Files changed (26) hide show
  1. package/package.json +1 -1
  2. package/src/soundfont/basic_soundfont/basic_instrument.js +16 -1
  3. package/src/soundfont/basic_soundfont/basic_instrument_zone.js +1 -1
  4. package/src/soundfont/basic_soundfont/basic_sample.js +39 -17
  5. package/src/soundfont/basic_soundfont/basic_soundbank.js +3 -3
  6. package/src/soundfont/basic_soundfont/riff_chunk.js +4 -1
  7. package/src/soundfont/basic_soundfont/write_dls/wave.js +1 -1
  8. package/src/soundfont/basic_soundfont/write_sf2/ibag.js +28 -10
  9. package/src/soundfont/basic_soundfont/write_sf2/igen.js +26 -11
  10. package/src/soundfont/basic_soundfont/write_sf2/imod.js +28 -14
  11. package/src/soundfont/basic_soundfont/write_sf2/inst.js +28 -10
  12. package/src/soundfont/basic_soundfont/write_sf2/pbag.js +26 -11
  13. package/src/soundfont/basic_soundfont/write_sf2/pgen.js +25 -11
  14. package/src/soundfont/basic_soundfont/write_sf2/phdr.js +45 -20
  15. package/src/soundfont/basic_soundfont/write_sf2/pmod.js +28 -14
  16. package/src/soundfont/basic_soundfont/write_sf2/shdr.js +28 -5
  17. package/src/soundfont/basic_soundfont/write_sf2/write.js +53 -14
  18. package/src/soundfont/dls/dls_sample.js +179 -18
  19. package/src/soundfont/dls/read_samples.js +7 -123
  20. package/src/soundfont/read_sf2/instrument_zones.js +4 -17
  21. package/src/soundfont/read_sf2/instruments.js +1 -1
  22. package/src/soundfont/read_sf2/preset_zones.js +6 -19
  23. package/src/soundfont/read_sf2/presets.js +0 -1
  24. package/src/soundfont/read_sf2/samples.js +121 -103
  25. package/src/soundfont/read_sf2/soundfont.js +198 -56
  26. package/src/soundfont/read_sf2/zones.js +28 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.28",
3
+ "version": "3.26.30",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -53,7 +53,7 @@ export class BasicInstrument
53
53
  linkTo(preset)
54
54
  {
55
55
  this.linkedPresets.push(preset);
56
- this.instrumentZones.forEach(z => z.useCount = this.linkedPresets.length);
56
+ this.instrumentZones.forEach(z => z.useCount++);
57
57
  }
58
58
 
59
59
  /**
@@ -67,6 +67,20 @@ export class BasicInstrument
67
67
  throw new Error(`Cannot unlink ${preset.presetName} from ${this.instrumentName}: not linked.`);
68
68
  }
69
69
  this.linkedPresets.splice(index, 1);
70
+ this.instrumentZones.forEach(z => z.useCount--);
71
+ }
72
+
73
+ deleteUnusedZones()
74
+ {
75
+ this.instrumentZones = this.instrumentZones.filter(z =>
76
+ {
77
+ const stays = z.useCount > 0;
78
+ if (!stays)
79
+ {
80
+ z.deleteZone();
81
+ }
82
+ return stays;
83
+ });
70
84
  }
71
85
 
72
86
  deleteInstrument()
@@ -86,6 +100,7 @@ export class BasicInstrument
86
100
  deleteZone(index)
87
101
  {
88
102
  const zone = this.instrumentZones[index];
103
+ zone.useCount -= 1;
89
104
  if (zone.useCount < 1)
90
105
  {
91
106
  zone.deleteZone();
@@ -26,7 +26,7 @@ export class BasicInstrumentZone extends BasicZone
26
26
  {
27
27
  super();
28
28
  this.parentInstrument = instrument;
29
- this.useCount = instrument.linkedPresets.length;
29
+ this.useCount = instrument.useCount;
30
30
  }
31
31
 
32
32
  /**
@@ -4,6 +4,7 @@
4
4
  * loads sample data, handles async loading of sf3 compressed samples
5
5
  */
6
6
  import { SpessaSynthWarn } from "../../utils/loggin.js";
7
+ import { IndexedByteArray } from "../../utils/indexed_array.js";
7
8
 
8
9
  // should be reasonable for most cases
9
10
  const RESAMPLE_RATE = 48000;
@@ -22,7 +23,6 @@ export const sampleTypes = {
22
23
  romLinkedSample: 32776
23
24
  };
24
25
 
25
-
26
26
  /**
27
27
  * @typedef {function} EncodeVorbisFunction
28
28
  * @param channelAudioData {Float32Array[]}
@@ -90,8 +90,8 @@ export class BasicSample
90
90
  isCompressed;
91
91
 
92
92
  /**
93
- * The compressed sample data if it was compressed by spessasynth
94
- * @type {Uint8Array}
93
+ * The compressed sample data if the sample has been compressed
94
+ * @type {Uint8Array|undefined}
95
95
  */
96
96
  compressedData = undefined;
97
97
  /**
@@ -107,7 +107,13 @@ export class BasicSample
107
107
  sampleData = undefined;
108
108
 
109
109
  /**
110
- * The basic representation of a soundfont sample
110
+ * Indicates if the data was overriden, so it cannot be copied back unchanged
111
+ * @type {boolean}
112
+ */
113
+ dataOverriden = false;
114
+
115
+ /**
116
+ * The basic representation of a sample
111
117
  * @param sampleName {string} The sample's name
112
118
  * @param sampleRate {number} The sample's rate in Hz
113
119
  * @param samplePitch {number} The sample's pitch as a MIDI note number
@@ -156,18 +162,14 @@ export class BasicSample
156
162
  }
157
163
 
158
164
  /**
159
- * @returns {Uint8Array|IndexedByteArray}
165
+ * Get raw data for writing the file
166
+ * @param allowVorbis {boolean}
167
+ * @return {Uint8Array} either s16 or vorbis data
168
+ * @virtual
160
169
  */
161
- getRawData()
170
+ getRawData(allowVorbis = true)
162
171
  {
163
- const uint8 = new Uint8Array(this.sampleData.length * 2);
164
- for (let i = 0; i < this.sampleData.length; i++)
165
- {
166
- const sample = Math.floor(this.sampleData[i] * 32768);
167
- uint8[i * 2] = sample & 0xFF; // lower byte
168
- uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
169
- }
170
- return uint8;
172
+ return this.encodeS16LE();
171
173
  }
172
174
 
173
175
  resampleData(newSampleRate)
@@ -210,14 +212,16 @@ export class BasicSample
210
212
  }
211
213
  this.compressedData = encodeVorbis([audioData], 1, this.sampleRate, quality);
212
214
  // flag as compressed
213
- this.setSampleType(this.sampleType | 0x10);
215
+ this.isCompressed = true;
216
+ // allow the data to be copied from the compressedData chunk during the write operation
217
+ this.dataOverriden = false;
214
218
  }
215
219
  catch (e)
216
220
  {
217
221
  SpessaSynthWarn(`Failed to compress ${this.sampleName}. Leaving as uncompressed!`);
218
- this.compressedData = undefined;
222
+ delete this.compressedData;
219
223
  // flag as uncompressed
220
- this.setSampleType(this.sampleType & 0xEF);
224
+ this.isCompressed = false;
221
225
  }
222
226
 
223
227
  }
@@ -329,8 +333,25 @@ export class BasicSample
329
333
  return this.sampleData;
330
334
  }
331
335
 
336
+ /**
337
+ * Encodes s16le sample
338
+ * @return {IndexedByteArray}
339
+ */
340
+ encodeS16LE()
341
+ {
342
+ const data = this.getAudioData();
343
+ const data16 = new Int16Array(data.length);
344
+
345
+ for (let i = 0; i < data.length; i++)
346
+ {
347
+ data16[i] = data[i] * 32768;
348
+ }
349
+ return new IndexedByteArray(data16.buffer);
350
+ }
351
+
332
352
  // noinspection JSUnusedGlobalSymbols
333
353
  /**
354
+ * REPLACES the audio data
334
355
  * @param audioData {Float32Array}
335
356
  * @virtual
336
357
  */
@@ -339,5 +360,6 @@ export class BasicSample
339
360
  this.isCompressed = false;
340
361
  delete this.compressedData;
341
362
  this.sampleData = audioData;
363
+ this.dataOverriden = true;
342
364
  }
343
365
  }
@@ -358,7 +358,6 @@ class BasicSoundBank
358
358
  consoleColors.info
359
359
  );
360
360
  soundfont.deletePreset(p);
361
- soundfont.removeUnusedElements();
362
361
  presetIndex--;
363
362
  }
364
363
  else
@@ -444,12 +443,13 @@ class BasicSoundBank
444
443
  {
445
444
  this.instruments = this.instruments.filter(i =>
446
445
  {
446
+ i.deleteUnusedZones();
447
447
  const deletable = i.useCount < 1;
448
448
  if (deletable)
449
449
  {
450
450
  i.deleteInstrument();
451
451
  }
452
- return deletable;
452
+ return !deletable;
453
453
  });
454
454
  this.samples = this.samples.filter(s =>
455
455
  {
@@ -458,7 +458,7 @@ class BasicSoundBank
458
458
  {
459
459
  s.deleteSample();
460
460
  }
461
- return deletable;
461
+ return !deletable;
462
462
  });
463
463
  }
464
464
 
@@ -36,10 +36,13 @@ export function readRIFFChunk(dataArray, readData = true, forceShift = false)
36
36
  let header = readBytesAsString(dataArray, 4);
37
37
 
38
38
  let size = readLittleEndian(dataArray, 4);
39
+ /**
40
+ * @type {IndexedByteArray}
41
+ */
39
42
  let chunkData = undefined;
40
43
  if (readData)
41
44
  {
42
- chunkData = new IndexedByteArray(dataArray.buffer.slice(dataArray.currentIndex, dataArray.currentIndex + size));
45
+ chunkData = dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + size);
43
46
  }
44
47
  if (readData || forceShift)
45
48
  {
@@ -60,7 +60,7 @@ export function writeDLSSample(sample)
60
60
  {
61
61
  data = writeRIFFOddSize(
62
62
  "data",
63
- sample.getRawData()
63
+ sample.getRawData(false) // no vorbis allowed
64
64
  );
65
65
  }
66
66
 
@@ -6,18 +6,20 @@ const BAG_SIZE = 4;
6
6
 
7
7
  /**
8
8
  * @this {BasicSoundBank}
9
- * @returns {IndexedByteArray}
9
+ * @returns {ReturnedExtendedSf2Chunks}
10
10
  */
11
11
  export function getIBAG()
12
12
  {
13
13
  // write all ibag with their start indexes as they were changed in getIGEN() and getIMOD()
14
- const ibagsize = this.instruments.reduce(
14
+ const ibagSize = this.instruments.reduce(
15
15
  (sum, i) =>
16
16
  // +1 because global zone
17
17
  (i.instrumentZones.length + 1) * BAG_SIZE + sum,
18
18
  BAG_SIZE
19
19
  );
20
- const ibagdata = new IndexedByteArray(ibagsize);
20
+ const ibagData = new IndexedByteArray(ibagSize);
21
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
22
+ const xibagData = new IndexedByteArray(ibagSize);
21
23
  let generatorIndex = 0;
22
24
  let modulatorIndex = 0;
23
25
  /**
@@ -25,8 +27,12 @@ export function getIBAG()
25
27
  */
26
28
  const writeZone = z =>
27
29
  {
28
- writeWord(ibagdata, generatorIndex);
29
- writeWord(ibagdata, modulatorIndex);
30
+ // bottom WORD: regular ibag
31
+ writeWord(ibagData, generatorIndex & 0xFFFF);
32
+ writeWord(ibagData, modulatorIndex & 0xFFFF);
33
+ // top WORD: extended ibag
34
+ writeWord(xibagData, generatorIndex >> 16);
35
+ writeWord(xibagData, modulatorIndex >> 16);
30
36
  generatorIndex += z.generators.length;
31
37
  modulatorIndex += z.modulators.length;
32
38
  };
@@ -40,11 +46,23 @@ export function getIBAG()
40
46
  }
41
47
  }
42
48
  // write the terminal IBAG
43
- writeWord(ibagdata, generatorIndex);
44
- writeWord(ibagdata, modulatorIndex);
45
- return writeRIFFChunk(new RiffChunk(
49
+ writeWord(ibagData, generatorIndex & 0xFFFF);
50
+ writeWord(ibagData, modulatorIndex & 0xFFFF);
51
+ writeWord(xibagData, generatorIndex >> 16);
52
+ writeWord(xibagData, modulatorIndex >> 16);
53
+ const ibag = writeRIFFChunk(new RiffChunk(
46
54
  "ibag",
47
- ibagdata.length,
48
- ibagdata
55
+ ibagData.length,
56
+ ibagData
49
57
  ));
58
+ const xibag = writeRIFFChunk(new RiffChunk(
59
+ "ibag",
60
+ xibagData.length,
61
+ xibagData
62
+ ));
63
+ return {
64
+ pdta: ibag,
65
+ xdta: xibag,
66
+ highestIndex: Math.max(generatorIndex, modulatorIndex)
67
+ };
50
68
  }
@@ -6,16 +6,16 @@ import { generatorTypes } from "../generator_types.js";
6
6
 
7
7
  /**
8
8
  * @this {BasicSoundBank}
9
- * @returns {IndexedByteArray}
9
+ * @returns {ReturnedExtendedSf2Chunks}
10
10
  */
11
11
  export function getIGEN()
12
12
  {
13
13
  // go through all instruments -> zones and write generators sequentially (add 4 for terminal)
14
- let igensize = GEN_BYTE_SIZE;
14
+ let igenSize = GEN_BYTE_SIZE;
15
15
  for (const inst of this.instruments)
16
16
  {
17
- igensize += inst.globalZone.generators.length * GEN_BYTE_SIZE;
18
- igensize += inst.instrumentZones.reduce((sum, z) =>
17
+ igenSize += inst.globalZone.generators.length * GEN_BYTE_SIZE;
18
+ igenSize += inst.instrumentZones.reduce((sum, z) =>
19
19
  {
20
20
  // clear sample and range generators before determining the size
21
21
  z.generators = (z.generators.filter(g =>
@@ -50,7 +50,7 @@ export function getIGEN()
50
50
  return z.generators.length * GEN_BYTE_SIZE + sum;
51
51
  }, 0);
52
52
  }
53
- const igendata = new IndexedByteArray(igensize);
53
+ const igenData = new IndexedByteArray(igenSize);
54
54
 
55
55
  /**
56
56
  * @param z {BasicZone}
@@ -60,8 +60,8 @@ export function getIGEN()
60
60
  for (const gen of z.generators)
61
61
  {
62
62
  // name is deceptive, it works on negatives
63
- writeWord(igendata, gen.generatorType);
64
- writeWord(igendata, gen.generatorValue);
63
+ writeWord(igenData, gen.generatorType);
64
+ writeWord(igenData, gen.generatorValue);
65
65
  }
66
66
  };
67
67
 
@@ -75,10 +75,25 @@ export function getIGEN()
75
75
  }
76
76
  }
77
77
  // terminal generator, is zero
78
- writeDword(igendata, 0);
79
- return writeRIFFChunk(new RiffChunk(
78
+ writeDword(igenData, 0);
79
+
80
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
81
+ const xigenData = new IndexedByteArray(GEN_BYTE_SIZE);
82
+ writeDword(xigenData, 0);
83
+
84
+ const igen = writeRIFFChunk(new RiffChunk(
80
85
  "igen",
81
- igendata.length,
82
- igendata
86
+ igenData.length,
87
+ igenData
83
88
  ));
89
+ const xigen = writeRIFFChunk(new RiffChunk(
90
+ "igen",
91
+ xigenData.length,
92
+ xigenData
93
+ ));
94
+ return {
95
+ pdta: igen,
96
+ xdta: xigen,
97
+ highestIndex: 0 // not applicable
98
+ };
84
99
  }
@@ -5,20 +5,20 @@ import { MOD_BYTE_SIZE } from "../modulator.js";
5
5
 
6
6
  /**
7
7
  * @this {BasicSoundBank}
8
- * @returns {IndexedByteArray}
8
+ * @returns {ReturnedExtendedSf2Chunks}
9
9
  */
10
10
  export function getIMOD()
11
11
  {
12
12
  // very similar to igen,
13
13
  // go through all instruments -> zones and write modulators sequentially
14
- let imodsize = MOD_BYTE_SIZE; // terminal
14
+ let imodSize = MOD_BYTE_SIZE; // terminal
15
15
  for (const inst of this.instruments)
16
16
  {
17
- imodsize += inst.globalZone.modulators.length * MOD_BYTE_SIZE;
17
+ imodSize += inst.globalZone.modulators.length * MOD_BYTE_SIZE;
18
18
  // start with one mod for global
19
- imodsize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * MOD_BYTE_SIZE + sum, 0);
19
+ imodSize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * MOD_BYTE_SIZE + sum, 0);
20
20
  }
21
- const imoddata = new IndexedByteArray(imodsize);
21
+ const imodData = new IndexedByteArray(imodSize);
22
22
 
23
23
  /**
24
24
  * @param z {BasicZone}
@@ -27,11 +27,11 @@ export function getIMOD()
27
27
  {
28
28
  for (const mod of z.modulators)
29
29
  {
30
- writeWord(imoddata, mod.getSourceEnum());
31
- writeWord(imoddata, mod.modulatorDestination);
32
- writeWord(imoddata, mod.transformAmount);
33
- writeWord(imoddata, mod.getSecSrcEnum());
34
- writeWord(imoddata, mod.transformType);
30
+ writeWord(imodData, mod.getSourceEnum());
31
+ writeWord(imodData, mod.modulatorDestination);
32
+ writeWord(imodData, mod.transformAmount);
33
+ writeWord(imodData, mod.getSecSrcEnum());
34
+ writeWord(imodData, mod.transformType);
35
35
  }
36
36
  };
37
37
 
@@ -46,11 +46,25 @@ export function getIMOD()
46
46
  }
47
47
 
48
48
  // terminal modulator, is zero
49
- writeLittleEndian(imoddata, 0, MOD_BYTE_SIZE);
49
+ writeLittleEndian(imodData, 0, MOD_BYTE_SIZE);
50
50
 
51
- return writeRIFFChunk(new RiffChunk(
51
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
52
+ const ximodData = new IndexedByteArray(MOD_BYTE_SIZE);
53
+ writeLittleEndian(ximodData, 0, MOD_BYTE_SIZE);
54
+
55
+ const imod = writeRIFFChunk(new RiffChunk(
56
+ "imod",
57
+ imodData.length,
58
+ imodData
59
+ ));
60
+ const ximod = writeRIFFChunk(new RiffChunk(
52
61
  "imod",
53
- imoddata.length,
54
- imoddata
62
+ ximodData.length,
63
+ ximodData
55
64
  ));
65
+ return {
66
+ pdta: imod,
67
+ xdta: ximod,
68
+ highestIndex: 0 // not applicable
69
+ };
56
70
  }
@@ -7,27 +7,45 @@ const INST_SIZE = 22;
7
7
 
8
8
  /**
9
9
  * @this {BasicSoundBank}
10
- * @returns {IndexedByteArray}
10
+ * @returns {ReturnedExtendedSf2Chunks}
11
11
  */
12
12
  export function getINST()
13
13
  {
14
- const instsize = this.instruments.length * INST_SIZE + INST_SIZE;
15
- const instdata = new IndexedByteArray(instsize);
14
+ const instSize = this.instruments.length * INST_SIZE + INST_SIZE;
15
+ const instData = new IndexedByteArray(instSize);
16
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
17
+ const xinstData = new IndexedByteArray(instSize);
16
18
  // the instrument start index is adjusted in ibag, write it here
17
19
  let instrumentStart = 0;
18
20
  for (const inst of this.instruments)
19
21
  {
20
- writeStringAsBytes(instdata, inst.instrumentName, 20);
21
- writeWord(instdata, instrumentStart);
22
+ writeStringAsBytes(instData, inst.instrumentName.substring(0, 20), 20);
23
+ writeStringAsBytes(xinstData, inst.instrumentName.substring(20), 20);
24
+ writeWord(instData, instrumentStart & 0xFFFF);
25
+ writeWord(xinstData, instrumentStart >> 16);
22
26
  instrumentStart += inst.instrumentZones.length + 1; // global
23
27
  }
24
28
  // write EOI
25
- writeStringAsBytes(instdata, "EOI", 20);
26
- writeWord(instdata, instrumentStart);
29
+ writeStringAsBytes(instData, "EOI", 20);
30
+ writeStringAsBytes(xinstData, "EOI", 20);
31
+ writeWord(instData, instrumentStart & 0xFFFF);
32
+ writeWord(xinstData, instrumentStart >> 16);
27
33
 
28
- return writeRIFFChunk(new RiffChunk(
34
+ const inst = writeRIFFChunk(new RiffChunk(
29
35
  "inst",
30
- instdata.length,
31
- instdata
36
+ instData.length,
37
+ instData
32
38
  ));
39
+
40
+ const xinst = writeRIFFChunk(new RiffChunk(
41
+ "inst",
42
+ xinstData.length,
43
+ xinstData
44
+ ));
45
+
46
+ return {
47
+ pdta: inst,
48
+ xdta: xinst,
49
+ highestIndex: instrumentStart
50
+ };
33
51
  }
@@ -6,15 +6,17 @@ const BAG_SIZE = 4;
6
6
 
7
7
  /**
8
8
  * @this {BasicSoundBank}
9
- * @returns {IndexedByteArray}
9
+ * @returns {ReturnedExtendedSf2Chunks}
10
10
  */
11
11
  export function getPBAG()
12
12
  {
13
13
  // write all pbag with their start indexes as they were changed in getPGEN() and getPMOD()
14
- const pbagsize = this.presets.reduce((sum, i) =>
14
+ const pbagSize = this.presets.reduce((sum, i) =>
15
15
  // +1 because global zone
16
16
  (i.presetZones.length + 1) * BAG_SIZE + sum, BAG_SIZE);
17
- const pbagdata = new IndexedByteArray(pbagsize);
17
+ const pbagData = new IndexedByteArray(pbagSize);
18
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
19
+ const xpbagData = new IndexedByteArray(pbagSize);
18
20
  let generatorIndex = 0;
19
21
  let modulatorIndex = 0;
20
22
 
@@ -23,8 +25,10 @@ export function getPBAG()
23
25
  */
24
26
  const writeZone = z =>
25
27
  {
26
- writeWord(pbagdata, generatorIndex);
27
- writeWord(pbagdata, modulatorIndex);
28
+ writeWord(pbagData, generatorIndex & 0xFFFF);
29
+ writeWord(pbagData, modulatorIndex & 0xFFFF);
30
+ writeWord(xpbagData, generatorIndex >> 16);
31
+ writeWord(xpbagData, modulatorIndex >> 16);
28
32
  generatorIndex += z.generators.length;
29
33
  modulatorIndex += z.modulators.length;
30
34
  };
@@ -39,12 +43,23 @@ export function getPBAG()
39
43
  }
40
44
  }
41
45
  // write the terminal PBAG
42
- writeWord(pbagdata, generatorIndex);
43
- writeWord(pbagdata, modulatorIndex);
44
-
45
- return writeRIFFChunk(new RiffChunk(
46
+ writeWord(pbagData, generatorIndex);
47
+ writeWord(pbagData, modulatorIndex);
48
+ writeWord(xpbagData, generatorIndex);
49
+ writeWord(xpbagData, modulatorIndex);
50
+ const pbag = writeRIFFChunk(new RiffChunk(
51
+ "pbag",
52
+ pbagData.length,
53
+ pbagData
54
+ ));
55
+ const xbag = writeRIFFChunk(new RiffChunk(
46
56
  "pbag",
47
- pbagdata.length,
48
- pbagdata
57
+ xpbagData.length,
58
+ xpbagData
49
59
  ));
60
+ return {
61
+ pdta: pbag,
62
+ xdta: xbag,
63
+ highestIndex: Math.max(generatorIndex, modulatorIndex)
64
+ };
50
65
  }
@@ -7,17 +7,17 @@ import { generatorTypes } from "../generator_types.js";
7
7
 
8
8
  /**
9
9
  * @this {BasicSoundBank}
10
- * @returns {IndexedByteArray}
10
+ * @returns {ReturnedExtendedSf2Chunks}
11
11
  */
12
12
  export function getPGEN()
13
13
  {
14
14
  // almost identical to igen, except the correct instrument instead of sample gen
15
15
  // goes through all preset zones and writes generators sequentially (add 4 for terminal)
16
- let pgensize = GEN_BYTE_SIZE;
16
+ let pgenSize = GEN_BYTE_SIZE;
17
17
  for (const preset of this.presets)
18
18
  {
19
- pgensize += preset.globalZone.generators.length * GEN_BYTE_SIZE;
20
- pgensize += preset.presetZones.reduce((size, z) =>
19
+ pgenSize += preset.globalZone.generators.length * GEN_BYTE_SIZE;
20
+ pgenSize += preset.presetZones.reduce((size, z) =>
21
21
  {
22
22
  // clear instrument and range generators before determining the size
23
23
  z.generators = z.generators.filter(g =>
@@ -51,7 +51,7 @@ export function getPGEN()
51
51
  return z.generators.length * GEN_BYTE_SIZE + size;
52
52
  }, 0);
53
53
  }
54
- const pgendata = new IndexedByteArray(pgensize);
54
+ const pgenData = new IndexedByteArray(pgenSize);
55
55
 
56
56
  /**
57
57
  * @param z {BasicZone}
@@ -61,8 +61,8 @@ export function getPGEN()
61
61
  for (const gen of z.generators)
62
62
  {
63
63
  // name is deceptive, it works on negatives
64
- writeWord(pgendata, gen.generatorType);
65
- writeWord(pgendata, gen.generatorValue);
64
+ writeWord(pgenData, gen.generatorType);
65
+ writeWord(pgenData, gen.generatorValue);
66
66
  }
67
67
  };
68
68
  for (const preset of this.presets)
@@ -75,11 +75,25 @@ export function getPGEN()
75
75
  }
76
76
  }
77
77
  // terminal generator, is zero
78
- writeDword(pgendata, 0);
78
+ writeDword(pgenData, 0);
79
79
 
80
- return writeRIFFChunk(new RiffChunk(
80
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
81
+ const xpgenData = new IndexedByteArray(GEN_BYTE_SIZE);
82
+ writeDword(xpgenData, 0);
83
+
84
+ const pgen = writeRIFFChunk(new RiffChunk(
85
+ "pgen",
86
+ pgenData.length,
87
+ pgenData
88
+ ));
89
+ const xpgen = writeRIFFChunk(new RiffChunk(
81
90
  "pgen",
82
- pgendata.length,
83
- pgendata
91
+ xpgenData.length,
92
+ xpgenData
84
93
  ));
94
+ return {
95
+ pdta: pgen,
96
+ xdta: xpgen,
97
+ highestIndex: 0 // not applicable
98
+ };
85
99
  }