spessasynth_core 3.26.27 → 3.26.29

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 +21 -2
  3. package/src/soundfont/basic_soundfont/basic_instrument_zone.js +1 -1
  4. package/src/soundfont/basic_soundfont/basic_sample.js +48 -17
  5. package/src/soundfont/basic_soundfont/basic_soundbank.js +18 -15
  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 +115 -106
  25. package/src/soundfont/read_sf2/soundfont.js +198 -56
  26. package/src/soundfont/read_sf2/zones.js +28 -0
@@ -2,7 +2,7 @@ import { IndexedByteArray } from "../../utils/indexed_array.js";
2
2
  import { readSamples } from "./samples.js";
3
3
  import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
4
4
  import { readGenerators } from "./generators.js";
5
- import { readPresetZones } from "./preset_zones.js";
5
+ import { applyPresetZones } from "./preset_zones.js";
6
6
  import { readPresets } from "./presets.js";
7
7
  import { readInstruments } from "./instruments.js";
8
8
  import { readModulators } from "./modulators.js";
@@ -14,7 +14,8 @@ import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js"
14
14
  import { BasicSoundBank } from "../basic_soundfont/basic_soundbank.js";
15
15
  import { Generator } from "../basic_soundfont/generator.js";
16
16
  import { Modulator } from "../basic_soundfont/modulator.js";
17
- import { InstrumentZone, readInstrumentZones } from "./instrument_zones.js";
17
+ import { applyInstrumentZones, InstrumentZone } from "./instrument_zones.js";
18
+ import { readZoneIndexes } from "./zones.js";
18
19
 
19
20
  /**
20
21
  * soundfont.js
@@ -45,19 +46,19 @@ export class SoundFont2 extends BasicSoundBank
45
46
  {
46
47
  console.warn("Using the constructor directly is deprecated. Use loadSoundFont instead.");
47
48
  }
48
- this.dataArray = new IndexedByteArray(arrayBuffer);
49
+ const mainFileArray = new IndexedByteArray(arrayBuffer);
49
50
  SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
50
- if (!this.dataArray)
51
+ if (!mainFileArray)
51
52
  {
52
53
  SpessaSynthGroupEnd();
53
54
  this.parsingError("No data provided!");
54
55
  }
55
56
 
56
57
  // read the main read
57
- let firstChunk = readRIFFChunk(this.dataArray, false);
58
+ let firstChunk = readRIFFChunk(mainFileArray, false);
58
59
  this.verifyHeader(firstChunk, "riff");
59
60
 
60
- const type = readBytesAsString(this.dataArray, 4).toLowerCase();
61
+ const type = readBytesAsString(mainFileArray, 4).toLowerCase();
61
62
  if (type !== "sfbk" && type !== "sfpk")
62
63
  {
63
64
  SpessaSynthGroupEnd();
@@ -71,9 +72,20 @@ export class SoundFont2 extends BasicSoundBank
71
72
  const isSF2Pack = type === "sfpk";
72
73
 
73
74
  // INFO
74
- let infoChunk = readRIFFChunk(this.dataArray);
75
+ let infoChunk = readRIFFChunk(mainFileArray);
75
76
  this.verifyHeader(infoChunk, "list");
76
- readBytesAsString(infoChunk.chunkData, 4);
77
+ const infoString = readBytesAsString(infoChunk.chunkData, 4);
78
+ if (infoString !== "INFO")
79
+ {
80
+ SpessaSynthGroupEnd();
81
+ throw new SyntaxError(`Invalid soundFont! Expected "INFO" or "${infoString}"`);
82
+ }
83
+
84
+ /**
85
+ * @type {RiffChunk|undefined}
86
+ */
87
+ let xdtaChunk = undefined;
88
+
77
89
 
78
90
  while (infoChunk.chunkData.length > infoChunk.chunkData.currentIndex)
79
91
  {
@@ -104,6 +116,16 @@ export class SoundFont2 extends BasicSoundBank
104
116
  this.soundFontInfo[chunk.header] = text;
105
117
  break;
106
118
 
119
+ case "list":
120
+ // possible xdta
121
+ const listType = readBytesAsString(chunk.chunkData, 4);
122
+ if (listType === "xdta")
123
+ {
124
+ SpessaSynthInfo("%cExtended SF2 found!", consoleColors.recognized);
125
+ xdtaChunk = chunk;
126
+ }
127
+ break;
128
+
107
129
  default:
108
130
  text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
109
131
  this.soundFontInfo[chunk.header] = text;
@@ -115,15 +137,44 @@ export class SoundFont2 extends BasicSoundBank
115
137
  consoleColors.recognized
116
138
  );
117
139
  }
140
+ // https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
141
+ const isExtended = xdtaChunk !== undefined;
142
+ /**
143
+ * @type {{
144
+ * phdr: RiffChunk,
145
+ * pbag: RiffChunk,
146
+ * pmod: RiffChunk,
147
+ * pgen: RiffChunk,
148
+ * inst: RiffChunk,
149
+ * ibag: RiffChunk,
150
+ * imod: RiffChunk,
151
+ * igen: RiffChunk,
152
+ * shdr: RiffChunk,
153
+ * }}
154
+ */
155
+ let xChunks = {};
156
+ if (isExtended)
157
+ {
158
+ // read the hydra chunks
159
+ xChunks.phdr = readRIFFChunk(xdtaChunk.chunkData);
160
+ xChunks.pbag = readRIFFChunk(xdtaChunk.chunkData);
161
+ xChunks.pmod = readRIFFChunk(xdtaChunk.chunkData);
162
+ xChunks.pgen = readRIFFChunk(xdtaChunk.chunkData);
163
+ xChunks.inst = readRIFFChunk(xdtaChunk.chunkData);
164
+ xChunks.ibag = readRIFFChunk(xdtaChunk.chunkData);
165
+ xChunks.imod = readRIFFChunk(xdtaChunk.chunkData);
166
+ xChunks.igen = readRIFFChunk(xdtaChunk.chunkData);
167
+ xChunks.shdr = readRIFFChunk(xdtaChunk.chunkData);
168
+ }
118
169
 
119
170
  // SDTA
120
- const sdtaChunk = readRIFFChunk(this.dataArray, false);
171
+ const sdtaChunk = readRIFFChunk(mainFileArray, false);
121
172
  this.verifyHeader(sdtaChunk, "list");
122
- this.verifyText(readBytesAsString(this.dataArray, 4), "sdta");
173
+ this.verifyText(readBytesAsString(mainFileArray, 4), "sdta");
123
174
 
124
175
  // smpl
125
176
  SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn);
126
- let sampleDataChunk = readRIFFChunk(this.dataArray, false);
177
+ let sampleDataChunk = readRIFFChunk(mainFileArray, false);
127
178
  this.verifyHeader(sampleDataChunk, "smpl");
128
179
  /**
129
180
  * @type {IndexedByteArray|Float32Array}
@@ -141,9 +192,9 @@ export class SoundFont2 extends BasicSoundBank
141
192
  /**
142
193
  * @type {Float32Array}
143
194
  */
144
- sampleData = stbvorbis.decode(this.dataArray.buffer.slice(
145
- this.dataArray.currentIndex,
146
- this.dataArray.currentIndex + sdtaChunk.size - 12
195
+ sampleData = stbvorbis.decode(mainFileArray.buffer.slice(
196
+ mainFileArray.currentIndex,
197
+ mainFileArray.currentIndex + sdtaChunk.size - 12
147
198
  )).data[0];
148
199
  }
149
200
  catch (e)
@@ -162,8 +213,8 @@ export class SoundFont2 extends BasicSoundBank
162
213
  /**
163
214
  * @type {IndexedByteArray}
164
215
  */
165
- sampleData = this.dataArray;
166
- this.sampleDataStartIndex = this.dataArray.currentIndex;
216
+ sampleData = mainFileArray;
217
+ this.sampleDataStartIndex = mainFileArray.currentIndex;
167
218
  }
168
219
 
169
220
  SpessaSynthInfo(
@@ -171,68 +222,128 @@ export class SoundFont2 extends BasicSoundBank
171
222
  consoleColors.info,
172
223
  consoleColors.value
173
224
  );
174
- this.dataArray.currentIndex += sdtaChunk.size - 12;
225
+ mainFileArray.currentIndex += sdtaChunk.size - 12;
175
226
 
176
227
  // PDTA
177
228
  SpessaSynthInfo("%cLoading preset data chunk...", consoleColors.warn);
178
- let presetChunk = readRIFFChunk(this.dataArray);
229
+ let presetChunk = readRIFFChunk(mainFileArray);
179
230
  this.verifyHeader(presetChunk, "list");
180
231
  readBytesAsString(presetChunk.chunkData, 4);
181
232
 
182
233
  // read the hydra chunks
183
- const pHdrChunk = readRIFFChunk(presetChunk.chunkData);
184
- this.verifyHeader(pHdrChunk, "phdr");
234
+ const phdrChunk = readRIFFChunk(presetChunk.chunkData);
235
+ this.verifyHeader(phdrChunk, "phdr");
185
236
 
186
- const pBagChunk = readRIFFChunk(presetChunk.chunkData);
187
- this.verifyHeader(pBagChunk, "pbag");
237
+ const pbagChunk = readRIFFChunk(presetChunk.chunkData);
238
+ this.verifyHeader(pbagChunk, "pbag");
188
239
 
189
- const pModChunk = readRIFFChunk(presetChunk.chunkData);
190
- this.verifyHeader(pModChunk, "pmod");
240
+ const pmodChunk = readRIFFChunk(presetChunk.chunkData);
241
+ this.verifyHeader(pmodChunk, "pmod");
191
242
 
192
- const pGenChunk = readRIFFChunk(presetChunk.chunkData);
193
- this.verifyHeader(pGenChunk, "pgen");
243
+ const pgenChunk = readRIFFChunk(presetChunk.chunkData);
244
+ this.verifyHeader(pgenChunk, "pgen");
194
245
 
195
246
  const instChunk = readRIFFChunk(presetChunk.chunkData);
196
247
  this.verifyHeader(instChunk, "inst");
197
248
 
198
- const iBagChunk = readRIFFChunk(presetChunk.chunkData);
199
- this.verifyHeader(iBagChunk, "ibag");
249
+ const ibagChunk = readRIFFChunk(presetChunk.chunkData);
250
+ this.verifyHeader(ibagChunk, "ibag");
200
251
 
201
- const iModChunk = readRIFFChunk(presetChunk.chunkData);
202
- this.verifyHeader(iModChunk, "imod");
252
+ const imodChunk = readRIFFChunk(presetChunk.chunkData);
253
+ this.verifyHeader(imodChunk, "imod");
203
254
 
204
- const iGenChunk = readRIFFChunk(presetChunk.chunkData);
205
- this.verifyHeader(iGenChunk, "igen");
255
+ const igenChunk = readRIFFChunk(presetChunk.chunkData);
256
+ this.verifyHeader(igenChunk, "igen");
206
257
 
207
- const sHdrChunk = readRIFFChunk(presetChunk.chunkData);
208
- this.verifyHeader(sHdrChunk, "shdr");
258
+ const shdrChunk = readRIFFChunk(presetChunk.chunkData);
259
+ this.verifyHeader(shdrChunk, "shdr");
209
260
 
210
261
  /**
211
262
  * read all the samples
212
263
  * (the current index points to start of the smpl read)
213
264
  */
214
- this.dataArray.currentIndex = this.sampleDataStartIndex;
215
- this.samples.push(...readSamples(sHdrChunk, sampleData, !isSF2Pack));
265
+ mainFileArray.currentIndex = this.sampleDataStartIndex;
266
+ const samples = readSamples(shdrChunk, sampleData, !isExtended);
267
+
268
+ if (isExtended)
269
+ {
270
+ // apply extensions to samples
271
+ const xSamples = readSamples(xChunks.shdr, new Float32Array(1), false);
272
+ if (xSamples.length === samples.length)
273
+ {
274
+ samples.forEach((s, i) =>
275
+ {
276
+ s.sampleName += xSamples[i].sampleName;
277
+ s.linkedSampleIndex |= xSamples[i].linkedSampleIndex << 16;
278
+ });
279
+ }
280
+
281
+ }
282
+ // trim names
283
+ samples.forEach(s => s.sampleName = s.sampleName.trim());
284
+ this.samples.push(...samples);
216
285
 
217
286
  /**
218
287
  * read all the instrument generators
219
288
  * @type {Generator[]}
220
289
  */
221
- let instrumentGenerators = readGenerators(iGenChunk);
290
+ let instrumentGenerators = readGenerators(igenChunk);
222
291
 
223
292
  /**
224
293
  * read all the instrument modulators
225
294
  * @type {Modulator[]}
226
295
  */
227
- let instrumentModulators = readModulators(iModChunk);
296
+ let instrumentModulators = readModulators(imodChunk);
297
+
298
+ const instruments = readInstruments(instChunk);
299
+
300
+ if (isExtended)
301
+ {
302
+ // apply extensions to instruments
303
+ const xInst = readInstruments(xChunks.inst);
304
+ if (xInst.length === instruments.length)
305
+ {
306
+ instruments.forEach((inst, i) =>
307
+ {
308
+ inst.instrumentName += xInst[i].instrumentName;
309
+ inst.zoneStartIndex |= xInst[i].zoneStartIndex;
310
+ });
311
+ // adjust zone counts
312
+ instruments.forEach((inst, i) =>
313
+ {
314
+ if (i < instruments.length - 1)
315
+ {
316
+ inst.zonesCount = instruments[i + 1].zoneStartIndex - inst.zoneStartIndex;
317
+ }
318
+ });
319
+ }
320
+
321
+ }
322
+ // trim names
323
+ instruments.forEach(i => i.instrumentName = i.instrumentName.trim());
324
+ this.instruments.push(...instruments);
325
+
326
+ const ibagIndexes = readZoneIndexes(ibagChunk);
327
+
328
+ if (isExtended)
329
+ {
330
+ const extraIndexes = readZoneIndexes(xChunks.ibag);
331
+ for (let i = 0; i < ibagIndexes.mod.length; i++)
332
+ {
333
+ ibagIndexes.mod[i] |= extraIndexes.mod[i] << 16;
334
+ }
335
+ for (let i = 0; i < ibagIndexes.gen.length; i++)
336
+ {
337
+ ibagIndexes.gen[i] |= extraIndexes.gen[i] << 16;
338
+ }
339
+ }
228
340
 
229
- this.instruments = readInstruments(instChunk);
230
341
  /**
231
342
  * read all the instrument zones (and apply them)
232
343
  * @type {InstrumentZone[]}
233
344
  */
234
- readInstrumentZones(
235
- iBagChunk,
345
+ applyInstrumentZones(
346
+ ibagIndexes,
236
347
  instrumentGenerators,
237
348
  instrumentModulators,
238
349
  this.samples,
@@ -243,17 +354,59 @@ export class SoundFont2 extends BasicSoundBank
243
354
  * read all the preset generators
244
355
  * @type {Generator[]}
245
356
  */
246
- let presetGenerators = readGenerators(pGenChunk);
357
+ let presetGenerators = readGenerators(pgenChunk);
247
358
 
248
359
  /**
249
360
  * Read all the preset modulatorrs
250
361
  * @type {Modulator[]}
251
362
  */
252
- let presetModulators = readModulators(pModChunk);
363
+ let presetModulators = readModulators(pmodChunk);
364
+
365
+ const presets = readPresets(phdrChunk, this);
366
+
367
+ if (isExtended)
368
+ {
369
+ // apply extensions to presets
370
+ const xPreset = readPresets(xChunks.phdr, this);
371
+ if (xPreset.length === presets.length)
372
+ {
373
+ presets.forEach((pres, i) =>
374
+ {
375
+ pres.presetName += xPreset[i].presetName;
376
+ pres.zoneStartIndex |= xPreset[i].zoneStartIndex;
377
+ });
378
+ // adjust zone counts
379
+ presets.forEach((preset, i) =>
380
+ {
381
+ if (i < instruments.length - 1)
382
+ {
383
+ preset.zonesCount = presets[i + 1].zoneStartIndex - preset.zoneStartIndex;
384
+ }
385
+ });
386
+ }
387
+
388
+ }
389
+
390
+ // trim names
391
+ presets.forEach(p => p.presetName === p.presetName.trim());
392
+ this.addPresets(...presets);
253
393
 
254
- this.addPresets(...readPresets(pHdrChunk, this));
394
+ const pbagIndexes = readZoneIndexes(pbagChunk);
255
395
 
256
- readPresetZones(pBagChunk, presetGenerators, presetModulators, this.instruments, this.presets);
396
+ if (isExtended)
397
+ {
398
+ const extraIndexes = readZoneIndexes(xChunks.pbag);
399
+ for (let i = 0; i < pbagIndexes.mod.length; i++)
400
+ {
401
+ pbagIndexes.mod[i] |= extraIndexes.mod[i] << 16;
402
+ }
403
+ for (let i = 0; i < pbagIndexes.gen.length; i++)
404
+ {
405
+ pbagIndexes.gen[i] |= extraIndexes.gen[i] << 16;
406
+ }
407
+ }
408
+
409
+ applyPresetZones(pbagIndexes, presetGenerators, presetModulators, this.instruments, this.presets);
257
410
  this.flush();
258
411
  SpessaSynthInfo(
259
412
  `%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
@@ -269,11 +422,6 @@ export class SoundFont2 extends BasicSoundBank
269
422
  consoleColors.info
270
423
  );
271
424
  SpessaSynthGroupEnd();
272
-
273
- if (isSF2Pack)
274
- {
275
- delete this.dataArray;
276
- }
277
425
  }
278
426
 
279
427
  /**
@@ -301,10 +449,4 @@ export class SoundFont2 extends BasicSoundBank
301
449
  this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
302
450
  }
303
451
  }
304
-
305
- destroySoundBank()
306
- {
307
- super.destroySoundBank();
308
- delete this.dataArray;
309
- }
310
452
  }
@@ -0,0 +1,28 @@
1
+ import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
2
+
3
+ /**
4
+ *
5
+ * @param zonesChunk {RiffChunk} both pbag and ibag work
6
+ * @returns {{mod: number[], gen: number[]}}
7
+ */
8
+ export function readZoneIndexes(zonesChunk)
9
+ {
10
+ /**
11
+ * @type {number[]}
12
+ */
13
+ const modStartIndexes = [];
14
+ /**
15
+ * @type {number[]}
16
+ */
17
+ const genStartIndexes = [];
18
+
19
+ while (zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
20
+ {
21
+ genStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
22
+ modStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
23
+ }
24
+ return {
25
+ mod: modStartIndexes,
26
+ gen: genStartIndexes
27
+ };
28
+ }