spessasynth_lib 3.14.2 → 3.15.0

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.
@@ -9,6 +9,7 @@
9
9
  * @property {string|undefined} comment - the coment of the file
10
10
  * @property {string|undefined} creationDate - the creation date of the file
11
11
  * @property {string|undefined} copyright - the copyright of the file
12
+ * @property {string|unescape} midiEncoding - the encoding of the inner MIDI file
12
13
  */
13
14
  /**
14
15
  * Writes an RMIDI file
@@ -34,6 +35,7 @@ export namespace RMIDINFOChunks {
34
35
  let engineer: string;
35
36
  let software: string;
36
37
  let encoding: string;
38
+ let midiEncoding: string;
37
39
  let bankOffset: string;
38
40
  }
39
41
  export type RMIDMetadata = {
@@ -73,5 +75,9 @@ export type RMIDMetadata = {
73
75
  * - the copyright of the file
74
76
  */
75
77
  copyright: string | undefined;
78
+ /**
79
+ * - the encoding of the inner MIDI file
80
+ */
81
+ midiEncoding: string | typeof unescape;
76
82
  };
77
83
  import { IndexedByteArray } from '../utils/indexed_array.js';
@@ -31,7 +31,7 @@ export class Preset {
31
31
  instrumentGenerators: Generator[];
32
32
  presetGenerators: Generator[];
33
33
  modulators: Modulator[];
34
- sample: Sample;
34
+ sample: LoadedSample;
35
35
  sampleID: number;
36
36
  }[][][];
37
37
  library: number;
@@ -63,7 +63,7 @@ export class Preset {
63
63
  * instrumentGenerators: Generator[],
64
64
  * presetGenerators: Generator[],
65
65
  * modulators: Modulator[],
66
- * sample: Sample,
66
+ * sample: LoadedSample,
67
67
  * sampleID: number,
68
68
  * }} SampleAndGenerators
69
69
  */
@@ -77,11 +77,11 @@ export class Preset {
77
77
  instrumentGenerators: Generator[];
78
78
  presetGenerators: Generator[];
79
79
  modulators: Modulator[];
80
- sample: Sample;
80
+ sample: LoadedSample;
81
81
  sampleID: number;
82
82
  }[];
83
83
  }
84
84
  import { RiffChunk } from "./riff_chunk.js";
85
85
  import { PresetZone } from "./zones.js";
86
86
  import { Generator } from './generators.js';
87
- import { Sample } from "./samples.js";
87
+ import { LoadedSample } from "./samples.js";
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Reads the generatorTranslator from the shdr read
3
3
  * @param sampleHeadersChunk {RiffChunk}
4
- * @param smplChunkData {IndexedByteArray}
5
- * @returns {Sample[]}
4
+ * @param smplChunkData {IndexedByteArray|Float32Array}
5
+ * @param isSmplDataRaw {boolean}
6
+ * @returns {LoadedSample[]}
6
7
  */
7
- export function readSamples(sampleHeadersChunk: RiffChunk, smplChunkData: IndexedByteArray): Sample[];
8
+ export function readSamples(sampleHeadersChunk: RiffChunk, smplChunkData: IndexedByteArray | Float32Array, isSmplDataRaw?: boolean): LoadedSample[];
8
9
  /**
9
10
  * samples.js
10
11
  * purpose: parses soundfont samples, resamples if needed.
@@ -87,7 +88,7 @@ export class BasicSample {
87
88
  */
88
89
  getAudioData(): Float32Array;
89
90
  }
90
- export class Sample extends BasicSample {
91
+ export class LoadedSample extends BasicSample {
91
92
  /**
92
93
  * Creates a sample
93
94
  * @param sampleName {string}
@@ -100,18 +101,21 @@ export class Sample extends BasicSample {
100
101
  * @param samplePitchCorrection {number}
101
102
  * @param sampleLink {number}
102
103
  * @param sampleType {number}
103
- * @param smplArr {IndexedByteArray}
104
+ * @param smplArr {IndexedByteArray|Float32Array}
104
105
  * @param sampleIndex {number} initial sample index when loading the sfont
106
+ * @param isDataRaw {boolean} if false, the data is decoded as float32.
107
+ * Used for SF2Pack support
105
108
  */
106
- constructor(sampleName: string, sampleStartIndex: number, sampleEndIndex: number, sampleLoopStartIndex: number, sampleLoopEndIndex: number, sampleRate: number, samplePitch: number, samplePitchCorrection: number, sampleLink: number, sampleType: number, smplArr: IndexedByteArray, sampleIndex: number);
109
+ constructor(sampleName: string, sampleStartIndex: number, sampleEndIndex: number, sampleLoopStartIndex: number, sampleLoopEndIndex: number, sampleRate: number, samplePitch: number, samplePitchCorrection: number, sampleLink: number, sampleType: number, smplArr: IndexedByteArray | Float32Array, sampleIndex: number, isDataRaw: boolean);
107
110
  sampleStartIndex: number;
108
111
  sampleEndIndex: number;
109
112
  isSampleLoaded: boolean;
110
113
  sampleID: number;
111
114
  useCount: number;
112
115
  sampleLength: number;
113
- sampleDataArray: IndexedByteArray;
116
+ sampleDataArray: Float32Array | IndexedByteArray;
114
117
  sampleData: Float32Array;
118
+ isDataRaw: boolean;
115
119
  /**
116
120
  * Get raw data, whether it's compressed or not as we simply write it to the file
117
121
  * @return {Uint8Array}
@@ -128,7 +132,7 @@ export class Sample extends BasicSample {
128
132
  /**
129
133
  * @returns {Float32Array}
130
134
  */
131
- loadBufferData(): Float32Array;
135
+ getUncompressedReadyData(): Float32Array;
132
136
  }
133
137
  import { RiffChunk } from "./riff_chunk.js";
134
138
  import { IndexedByteArray } from "../../utils/indexed_array.js";
@@ -3,10 +3,10 @@
3
3
  * @param zonesChunk {RiffChunk}
4
4
  * @param instrumentGenerators {Generator[]}
5
5
  * @param instrumentModulators {Modulator[]}
6
- * @param instrumentSamples {Sample[]}
6
+ * @param instrumentSamples {LoadedSample[]}
7
7
  * @returns {InstrumentZone[]}
8
8
  */
9
- export function readInstrumentZones(zonesChunk: RiffChunk, instrumentGenerators: Generator[], instrumentModulators: Modulator[], instrumentSamples: Sample[]): InstrumentZone[];
9
+ export function readInstrumentZones(zonesChunk: RiffChunk, instrumentGenerators: Generator[], instrumentModulators: Modulator[], instrumentSamples: LoadedSample[]): InstrumentZone[];
10
10
  /**
11
11
  * Reads the given preset zone read
12
12
  * @param zonesChunk {RiffChunk}
@@ -64,10 +64,10 @@ export class InstrumentZone {
64
64
  getModulators(modulators: Modulator[]): void;
65
65
  /**
66
66
  * Loads the zone's sample
67
- * @param samples {Sample[]}
67
+ * @param samples {LoadedSample[]}
68
68
  */
69
- getSample(samples: Sample[]): void;
70
- sample: Sample;
69
+ getSample(samples: LoadedSample[]): void;
70
+ sample: LoadedSample;
71
71
  /**
72
72
  * Reads the keyRange of the zone
73
73
  */
@@ -136,6 +136,6 @@ export class PresetZone {
136
136
  import { RiffChunk } from "./riff_chunk.js";
137
137
  import { Generator } from "./generators.js";
138
138
  import { Modulator } from "./modulators.js";
139
- import { Sample } from "./samples.js";
139
+ import { LoadedSample } from "./samples.js";
140
140
  import { Instrument } from "./instruments.js";
141
141
  import { IndexedByteArray } from "../../utils/indexed_array.js";
@@ -25,7 +25,7 @@ export class SoundFont2 {
25
25
  };
26
26
  dataArray: IndexedByteArray;
27
27
  sampleDataStartIndex: number;
28
- samples: import("./read/samples.js").Sample[];
28
+ samples: import("./read/samples.js").LoadedSample[];
29
29
  /**
30
30
  * read all the instruments
31
31
  * @type {Instrument[]}
@@ -37,9 +37,9 @@ export class SoundFont2 {
37
37
  */
38
38
  deleteInstrument(instrument: Instrument): void;
39
39
  /**
40
- * @param sample {Sample}
40
+ * @param sample {LoadedSample}
41
41
  */
42
- deleteSample(sample: Sample): void;
42
+ deleteSample(sample: LoadedSample): void;
43
43
  /**
44
44
  * @param preset {Preset}
45
45
  */
package/README.md CHANGED
@@ -47,10 +47,11 @@ document.getElementById("button").onclick = async () => {
47
47
  - Runs in a **separate thread** for maximum performance
48
48
  - Supported by all modern browsers
49
49
  - **Unlimited channel count:** Your CPU is the limit!
50
- - **Various MIDI Standards Support:**
50
+ - **Excellent MIDI Standards Support:**
51
51
  - **MIDI Controller Support:** Default supported controllers [here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-controllers)
52
52
  - **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#midi-tuning-standard)
53
53
  - [Full **RPN** and limited **NRPN** support](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-registered-parameters)
54
+ - **MIDI Tuning Standard Support:** [more info here](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#midi-tuning-standard)
54
55
  - Supports some [**Roland GS** and **Yamaha XG** system exclusives](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Implementation#supported-system-exclusives)
55
56
 
56
57
  - **High-performance mode:** Play Rush E! _note: may kill your browser ;)_
@@ -75,17 +76,21 @@ document.getElementById("button").onclick = async () => {
75
76
  - **Loop detection:** Automatically detects loops in MIDIs (e.g., from _Touhou Project_)
76
77
  - **First note detection:** Skip unnecessary silence at the start by jumping to the first note!
77
78
  - **Easy saving:** Save with just [one function!](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#writemidifile)
78
- #### Read and write [RMID files with embedded SF2 soundfonts](https://github.com/spessasus/SpessaSynth/wiki/About-RMIDI)
79
+ -
80
+ #### Read and write [RMID files with embedded SF2 soundfonts](https://github.com/spessasus/sf2-rmidi-specification#readme)
81
+ - **[Level 4](https://github.com/spessasus/sf2-rmidi-specification#level-4) compliance:** Reads and writes *everything!*
79
82
  - **Compression and trimming support:** Reduce a MIDI file with a 1GB soundfont to **as small as 5MB**!
80
83
  - **Automatic bank shifting and validation:** Every soundfont *just works!*
81
84
  - **Metadata support:** Add title, artist, album name and cover and more! And of course read them too! *(In any encoding!)*
82
85
  - **Compatible with [Falcosoft Midi Player 6!](https://falcosoft.hu/softwares.html#midiplayer)**
83
86
  - **Easy saving:** [As simple as saving a MIDI file!](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#writermidi)
87
+ -
84
88
  #### Read and write SoundFont2 files
85
89
  - **Easy info access:** Just an [object of strings!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#soundfontinfo)
86
90
  - **Smart trimming:** Trim the SoundFont to only include samples used in the MIDI *(down to key and velocity!)*
87
91
  - **sf3 conversion:** Compress SoundFont2 files to SoundFont3 with variable quality!
88
92
  - **Easy saving:** Also just [one function!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#write)
93
+ -
89
94
  #### Read and write SoundFont3 files
90
95
  - Same features as SoundFont2 but with now with **Ogg Vorbis compression!**
91
96
  - **Variable compression quality:** You choose between file size and quality!
@@ -62,7 +62,7 @@ class MIDI{
62
62
  binaryData.currentIndex -= 4;
63
63
  if(initialString === "RIFF")
64
64
  {
65
- // possibly an RMID file (https://github.com/spessasus/SpessaSynth/wiki/About-RMIDI)
65
+ // possibly an RMID file (https://github.com/spessasus/sf2-rmidi-specification#readme)
66
66
  // skip size
67
67
  binaryData.currentIndex += 8;
68
68
  const rmid = readBytesAsString(binaryData, 4, undefined, false);
@@ -23,6 +23,7 @@ export const RMIDINFOChunks = {
23
23
  engineer: "IENG",
24
24
  software: "ISFT",
25
25
  encoding: "IENC",
26
+ midiEncoding: "MENC",
26
27
  bankOffset: "DBNK"
27
28
  }
28
29
 
@@ -40,6 +41,7 @@ const DEFAULT_COPYRIGHT = "Created using SpessaSynth";
40
41
  * @property {string|undefined} comment - the coment of the file
41
42
  * @property {string|undefined} creationDate - the creation date of the file
42
43
  * @property {string|undefined} copyright - the copyright of the file
44
+ * @property {string|unescape} midiEncoding - the encoding of the inner MIDI file
43
45
  */
44
46
 
45
47
  /**
@@ -62,7 +64,7 @@ export function writeRMIDI(soundfontBinary, mid, soundfont, bankOffset = 0, enco
62
64
  consoleColors.value);
63
65
  SpessaSynthInfo("metadata", metadata);
64
66
  SpessaSynthInfo("Initial bank offset", mid.bankOffset);
65
- // add offset to bank. See wiki About-RMIDI
67
+ // add offset to bank. See https://github.com/spessasus/sf2-rmidi-specification#readme
66
68
  // also fix presets that don't exists since midiplayer6 doesn't seem to default to 0 when nonextistent...
67
69
  let system = "gm";
68
70
  /**
@@ -444,10 +446,19 @@ export function writeRMIDI(soundfontBinary, mid, soundfont, bankOffset = 0, enco
444
446
  const DBNK = new IndexedByteArray(2);
445
447
  writeLittleEndian(DBNK, bankOffset, 2);
446
448
  infoContent.push(writeRIFFOddSize(RMIDINFOChunks.bankOffset, DBNK));
449
+ // midi encoding
450
+ if(metadata.midiEncoding !== undefined)
451
+ {
452
+ infoContent.push(
453
+ writeRIFFOddSize(RMIDINFOChunks.midiEncoding, encoder.encode(metadata.midiEncoding))
454
+ );
455
+ encoding = FORCED_ENCODING;
456
+ }
447
457
  // encoding
448
458
  infoContent.push(writeRIFFOddSize(RMIDINFOChunks.encoding, getStringBytes(encoding)));
449
- const infodata = combineArrays(infoContent);
450
459
 
460
+ // combine and write out
461
+ const infodata = combineArrays(infoContent);
451
462
  const rmiddata = combineArrays([
452
463
  getStringBytes("RMID"),
453
464
  writeRIFFOddSize(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.14.2",
3
+ "version": "3.15.0",
4
4
  "description": "No compromise MIDI and SoundFont2 Synthesizer library",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -1,7 +1,7 @@
1
1
  import {RiffChunk} from "./riff_chunk.js";
2
2
  import {PresetZone} from "./zones.js";
3
3
  import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
4
- import {Sample} from "./samples.js";
4
+ import {LoadedSample} from "./samples.js";
5
5
  import { Generator, generatorTypes } from './generators.js'
6
6
  import { defaultModulators } from './modulators.js'
7
7
  import { readBytesAsString } from '../../utils/byte_functions/string.js'
@@ -114,7 +114,7 @@ export class Preset {
114
114
  * instrumentGenerators: Generator[],
115
115
  * presetGenerators: Generator[],
116
116
  * modulators: Modulator[],
117
- * sample: Sample,
117
+ * sample: LoadedSample,
118
118
  * sampleID: number,
119
119
  * }} SampleAndGenerators
120
120
  */
@@ -111,7 +111,8 @@ export class BasicSample
111
111
  return;
112
112
  }
113
113
  // compress, always mono!
114
- try {
114
+ try
115
+ {
115
116
  this.compressedData = encodeVorbis([this.getAudioData()], 1, this.sampleRate, quality);
116
117
  // flag as compressed
117
118
  this.sampleType |= 0x10;
@@ -138,7 +139,7 @@ export class BasicSample
138
139
  }
139
140
  }
140
141
 
141
- export class Sample extends BasicSample
142
+ export class LoadedSample extends BasicSample
142
143
  {
143
144
  /**
144
145
  * Creates a sample
@@ -152,8 +153,10 @@ export class Sample extends BasicSample
152
153
  * @param samplePitchCorrection {number}
153
154
  * @param sampleLink {number}
154
155
  * @param sampleType {number}
155
- * @param smplArr {IndexedByteArray}
156
+ * @param smplArr {IndexedByteArray|Float32Array}
156
157
  * @param sampleIndex {number} initial sample index when loading the sfont
158
+ * @param isDataRaw {boolean} if false, the data is decoded as float32.
159
+ * Used for SF2Pack support
157
160
  */
158
161
  constructor(sampleName,
159
162
  sampleStartIndex,
@@ -166,7 +169,8 @@ export class Sample extends BasicSample
166
169
  sampleLink,
167
170
  sampleType,
168
171
  smplArr,
169
- sampleIndex
172
+ sampleIndex,
173
+ isDataRaw,
170
174
  )
171
175
  {
172
176
  super(
@@ -197,7 +201,7 @@ export class Sample extends BasicSample
197
201
  this.sampleLoopEndIndex += this.sampleStartIndex;
198
202
  this.sampleLength = 99999999; // set to 999999 before we decode it
199
203
  }
200
-
204
+ this.isDataRaw = isDataRaw;
201
205
  }
202
206
 
203
207
  /**
@@ -218,6 +222,10 @@ export class Sample extends BasicSample
218
222
  }
219
223
  else
220
224
  {
225
+ if(!this.isDataRaw)
226
+ {
227
+ throw new Error("Writing SF2Pack samples is not supported.");
228
+ }
221
229
  const dataStartIndex = smplArr.currentIndex;
222
230
  return smplArr.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex)
223
231
  }
@@ -255,7 +263,24 @@ export class Sample extends BasicSample
255
263
  if (!this.isSampleLoaded)
256
264
  {
257
265
  // start loading data if not loaded
258
- return this.loadBufferData();
266
+ if (this.sampleLength < 1)
267
+ {
268
+ // eos, do not do anything
269
+ return new Float32Array(1);
270
+ }
271
+
272
+ if(this.isCompressed)
273
+ {
274
+ // if compressed, decode
275
+ this.decodeVorbis();
276
+ this.isSampleLoaded = true;
277
+ return this.sampleData;
278
+ }
279
+ else if(!this.isDataRaw)
280
+ {
281
+ return this.getUncompressedReadyData();
282
+ }
283
+ return this.loadUncompressedData();
259
284
  }
260
285
  return this.sampleData;
261
286
  }
@@ -293,40 +318,36 @@ export class Sample extends BasicSample
293
318
  /**
294
319
  * @returns {Float32Array}
295
320
  */
296
- loadBufferData()
321
+ getUncompressedReadyData()
297
322
  {
298
- if (this.sampleLength < 1)
299
- {
300
- // eos, do not do anything
301
- return new Float32Array(1);
302
- }
303
-
304
- if(this.isCompressed)
305
- {
306
- this.decodeVorbis();
307
- this.isSampleLoaded = true;
308
- return this.sampleData;
309
- }
310
- return this.loadUncompressedData();
323
+ /**
324
+ * read the sample data
325
+ * @type {Float32Array}
326
+ */
327
+ let audioData = this.sampleDataArray.slice(this.sampleStartIndex / 2, this.sampleEndIndex / 2);
328
+ this.sampleData = audioData;
329
+ this.isSampleLoaded = true;
330
+ return audioData;
311
331
  }
312
332
  }
313
333
 
314
334
  /**
315
335
  * Reads the generatorTranslator from the shdr read
316
336
  * @param sampleHeadersChunk {RiffChunk}
317
- * @param smplChunkData {IndexedByteArray}
318
- * @returns {Sample[]}
337
+ * @param smplChunkData {IndexedByteArray|Float32Array}
338
+ * @param isSmplDataRaw {boolean}
339
+ * @returns {LoadedSample[]}
319
340
  */
320
- export function readSamples(sampleHeadersChunk, smplChunkData)
341
+ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = true)
321
342
  {
322
343
  /**
323
- * @type {Sample[]}
344
+ * @type {LoadedSample[]}
324
345
  */
325
346
  let samples = [];
326
347
  let index = 0;
327
348
  while(sampleHeadersChunk.chunkData.length > sampleHeadersChunk.chunkData.currentIndex)
328
349
  {
329
- const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData);
350
+ const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData, isSmplDataRaw);
330
351
  samples.push(sample);
331
352
  index++;
332
353
  }
@@ -342,10 +363,11 @@ export function readSamples(sampleHeadersChunk, smplChunkData)
342
363
  * Reads it into a sample
343
364
  * @param index {number}
344
365
  * @param sampleHeaderData {IndexedByteArray}
345
- * @param smplArrayData {IndexedByteArray}
346
- * @returns {Sample}
366
+ * @param smplArrayData {IndexedByteArray|Float32Array}
367
+ * @param isDataRaw {boolean} true means binary 16 bit data, false means float32
368
+ * @returns {LoadedSample}
347
369
  */
348
- function readSample(index, sampleHeaderData, smplArrayData) {
370
+ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw) {
349
371
 
350
372
  // read the sample name
351
373
  let sampleName = readBytesAsString(sampleHeaderData, 20);
@@ -383,7 +405,7 @@ function readSample(index, sampleHeaderData, smplArrayData) {
383
405
 
384
406
 
385
407
 
386
- return new Sample(sampleName,
408
+ return new LoadedSample(sampleName,
387
409
  sampleStartIndex,
388
410
  sampleEndIndex,
389
411
  sampleLoopStartIndex,
@@ -394,5 +416,6 @@ function readSample(index, sampleHeaderData, smplArrayData) {
394
416
  sampleLink,
395
417
  sampleType,
396
418
  smplArrayData,
397
- index);
419
+ index,
420
+ isDataRaw);
398
421
  }
@@ -2,7 +2,7 @@ import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_end
2
2
  import {IndexedByteArray} from "../../utils/indexed_array.js";
3
3
  import {RiffChunk} from "./riff_chunk.js";
4
4
  import {Generator, generatorTypes} from "./generators.js";
5
- import {Sample} from "./samples.js";
5
+ import {LoadedSample} from "./samples.js";
6
6
  import {Instrument} from "./instruments.js";
7
7
  import {Modulator} from "./modulators.js";
8
8
 
@@ -81,7 +81,7 @@ export class InstrumentZone {
81
81
 
82
82
  /**
83
83
  * Loads the zone's sample
84
- * @param samples {Sample[]}
84
+ * @param samples {LoadedSample[]}
85
85
  */
86
86
  getSample(samples)
87
87
  {
@@ -126,7 +126,7 @@ export class InstrumentZone {
126
126
  * @param zonesChunk {RiffChunk}
127
127
  * @param instrumentGenerators {Generator[]}
128
128
  * @param instrumentModulators {Modulator[]}
129
- * @param instrumentSamples {Sample[]}
129
+ * @param instrumentSamples {LoadedSample[]}
130
130
  * @returns {InstrumentZone[]}
131
131
  */
132
132
  export function readInstrumentZones(zonesChunk, instrumentGenerators, instrumentModulators, instrumentSamples)
@@ -11,6 +11,7 @@ import { consoleColors } from '../utils/other.js'
11
11
  import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
12
12
  import { readBytesAsString } from '../utils/byte_functions/string.js'
13
13
  import { write } from './write/write.js'
14
+ import { stbvorbis } from "../externals/stbvorbis_sync/stbvorbis_sync.min.js";
14
15
 
15
16
  /**
16
17
  * soundfont.js
@@ -42,7 +43,13 @@ class SoundFont2
42
43
  let firstChunk = readRIFFChunk(this.dataArray, false);
43
44
  this.verifyHeader(firstChunk, "riff");
44
45
 
45
- this.verifyText(readBytesAsString(this.dataArray,4), "sfbk");
46
+ const type = readBytesAsString(this.dataArray,4).toLowerCase();
47
+ if(type !== "sfbk" && type !== "sfpk")
48
+ {
49
+ SpessaSynthGroupEnd();
50
+ throw new SyntaxError(`Invalid soundFont! Expected "sfbk" or "sfpk" got "${type}"`);
51
+ }
52
+ const isSF2Pack = type === "sfpk";
46
53
 
47
54
  // INFO
48
55
  let infoChunk = readRIFFChunk(this.dataArray);
@@ -88,7 +95,39 @@ class SoundFont2
88
95
  SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn)
89
96
  let sampleDataChunk = readRIFFChunk(this.dataArray, false);
90
97
  this.verifyHeader(sampleDataChunk, "smpl");
91
- this.sampleDataStartIndex = this.dataArray.currentIndex;
98
+ /**
99
+ * @type {IndexedByteArray|Float32Array}
100
+ */
101
+ let sampleData;
102
+ // SF2Pack: the entire data is compressed
103
+ if(isSF2Pack)
104
+ {
105
+ SpessaSynthInfo("%cSF2Pack detected, attempting to decode the smpl chunk...",
106
+ consoleColors.info);
107
+ try
108
+ {
109
+ /**
110
+ * @type {Float32Array}
111
+ */
112
+ sampleData = stbvorbis.decode(this.dataArray.buffer.slice(this.dataArray.currentIndex, this.dataArray.currentIndex + sdtaChunk.size - 12)).data[0];
113
+ }
114
+ catch (e)
115
+ {
116
+ SpessaSynthGroupEnd();
117
+ throw new Error(`SF2Pack Ogg Vorbis decode error: ${e}`);
118
+ }
119
+ SpessaSynthInfo(`%cDecoded the smpl chunk! Length: %c${sampleData.length}`,
120
+ consoleColors.info,
121
+ consoleColors.value);
122
+ }
123
+ else
124
+ {
125
+ /**
126
+ * @type {IndexedByteArray}
127
+ */
128
+ sampleData = this.dataArray;
129
+ this.sampleDataStartIndex = this.dataArray.currentIndex;
130
+ }
92
131
 
93
132
  SpessaSynthInfo(`%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`,
94
133
  consoleColors.info,
@@ -133,8 +172,8 @@ class SoundFont2
133
172
  * read all the samples
134
173
  * (the current index points to start of the smpl read)
135
174
  */
136
- this.dataArray.currentIndex = this.sampleDataStartIndex
137
- this.samples = readSamples(presetSamplesChunk, this.dataArray);
175
+ this.dataArray.currentIndex = this.sampleDataStartIndex;
176
+ this.samples = readSamples(presetSamplesChunk, sampleData, !isSF2Pack);
138
177
 
139
178
  /**
140
179
  * read all the instrument generators
@@ -195,6 +234,11 @@ class SoundFont2
195
234
  consoleColors.recognized,
196
235
  consoleColors.info);
197
236
  SpessaSynthGroupEnd();
237
+
238
+ if(isSF2Pack)
239
+ {
240
+ delete this.dataArray;
241
+ }
198
242
  }
199
243
 
200
244
  removeUnusedElements()
@@ -224,7 +268,7 @@ class SoundFont2
224
268
  }
225
269
 
226
270
  /**
227
- * @param sample {Sample}
271
+ * @param sample {LoadedSample}
228
272
  */
229
273
  deleteSample(sample)
230
274
  {