spessasynth_lib 3.14.5 → 3.15.1

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.
@@ -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
  */
@@ -88,7 +88,7 @@ class MIDI{
88
88
  if(currentChunk.header === "RIFF")
89
89
  {
90
90
  const type = readBytesAsString(currentChunk.chunkData, 4);
91
- if(type === "sfbk")
91
+ if(type === "sfbk" || type === "sfpk")
92
92
  {
93
93
  SpessaSynthInfo("%cFound embedded soundfont!", consoleColors.recognized);
94
94
  this.embeddedSoundFont = binaryData.slice(startIndex, startIndex + currentChunk.size).buffer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.14.5",
3
+ "version": "3.15.1",
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
  {