spessasynth_core 3.26.32 → 3.26.33
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/package.json +1 -1
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +0 -12
- package/src/soundfont/basic_soundfont/basic_preset_zone.js +0 -12
- package/src/soundfont/basic_soundfont/basic_sample.js +77 -16
- package/src/soundfont/basic_soundfont/basic_soundbank.js +117 -17
- package/src/soundfont/basic_soundfont/basic_zone.js +2 -1
- package/src/soundfont/basic_soundfont/write_sf2/write.js +1 -2
- package/src/soundfont/dls/dls_sample.js +6 -12
- package/src/soundfont/read_sf2/samples.js +30 -85
package/package.json
CHANGED
|
@@ -42,16 +42,4 @@ export class BasicInstrumentZone extends BasicZone
|
|
|
42
42
|
{
|
|
43
43
|
this.sample.unlinkFrom(this.parentInstrument);
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @param zone {BasicZone}
|
|
48
|
-
*/
|
|
49
|
-
copyFrom(zone)
|
|
50
|
-
{
|
|
51
|
-
super.copyFrom(zone);
|
|
52
|
-
if (zone instanceof BasicInstrumentZone)
|
|
53
|
-
{
|
|
54
|
-
this.sample = zone.sample;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
45
|
}
|
|
@@ -36,16 +36,4 @@ export class BasicPresetZone extends BasicZone
|
|
|
36
36
|
this.instrument = instrument;
|
|
37
37
|
this.instrument.linkTo(this.parentPreset);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param zone {BasicZone}
|
|
42
|
-
*/
|
|
43
|
-
copyFrom(zone)
|
|
44
|
-
{
|
|
45
|
-
super.copyFrom(zone);
|
|
46
|
-
if (zone instanceof BasicPresetZone)
|
|
47
|
-
{
|
|
48
|
-
this.instrument = zone.instrument;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
39
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* samples.js
|
|
3
|
-
* purpose: parses soundfont samples, resamples if needed.
|
|
4
|
-
* loads sample data, handles async loading of sf3 compressed samples
|
|
5
|
-
*/
|
|
6
1
|
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
7
2
|
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
3
|
+
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
8
4
|
|
|
9
5
|
// should be reasonable for most cases
|
|
10
6
|
const RESAMPLE_RATE = 48000;
|
|
@@ -87,7 +83,7 @@ export class BasicSample
|
|
|
87
83
|
* Indicates if the sample is compressed using vorbis SF3
|
|
88
84
|
* @type {boolean}
|
|
89
85
|
*/
|
|
90
|
-
isCompressed;
|
|
86
|
+
isCompressed = false;
|
|
91
87
|
|
|
92
88
|
/**
|
|
93
89
|
* The compressed sample data if the sample has been compressed
|
|
@@ -102,7 +98,7 @@ export class BasicSample
|
|
|
102
98
|
linkedInstruments = [];
|
|
103
99
|
/**
|
|
104
100
|
* The sample's audio data
|
|
105
|
-
* @type {Float32Array}
|
|
101
|
+
* @type {Float32Array|undefined}
|
|
106
102
|
*/
|
|
107
103
|
sampleData = undefined;
|
|
108
104
|
|
|
@@ -169,6 +165,10 @@ export class BasicSample
|
|
|
169
165
|
*/
|
|
170
166
|
getRawData(allowVorbis = true)
|
|
171
167
|
{
|
|
168
|
+
if (this.isCompressed && allowVorbis && !this.dataOverriden)
|
|
169
|
+
{
|
|
170
|
+
return this.compressedData;
|
|
171
|
+
}
|
|
172
172
|
return this.encodeS16LE();
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -210,11 +210,8 @@ export class BasicSample
|
|
|
210
210
|
this.resampleData(RESAMPLE_RATE);
|
|
211
211
|
audioData = this.getAudioData();
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.isCompressed = true;
|
|
216
|
-
// allow the data to be copied from the compressedData chunk during the write operation
|
|
217
|
-
this.dataOverriden = false;
|
|
213
|
+
const compressed = encodeVorbis([audioData], 1, this.sampleRate, quality);
|
|
214
|
+
this.setCompressedData(compressed);
|
|
218
215
|
}
|
|
219
216
|
catch (e)
|
|
220
217
|
{
|
|
@@ -320,17 +317,69 @@ export class BasicSample
|
|
|
320
317
|
this.linkedInstruments.splice(index, 1);
|
|
321
318
|
}
|
|
322
319
|
|
|
320
|
+
|
|
323
321
|
/**
|
|
322
|
+
* @private
|
|
323
|
+
* Decode binary vorbis into a float32 pcm
|
|
324
|
+
* @returns {Float32Array}
|
|
325
|
+
*/
|
|
326
|
+
decodeVorbis()
|
|
327
|
+
{
|
|
328
|
+
if (this.sampleData)
|
|
329
|
+
{
|
|
330
|
+
return this.sampleData;
|
|
331
|
+
}
|
|
332
|
+
// get the compressed byte stream
|
|
333
|
+
// reset array and being decoding
|
|
334
|
+
try
|
|
335
|
+
{
|
|
336
|
+
/**
|
|
337
|
+
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
338
|
+
*/
|
|
339
|
+
const vorbis = stbvorbis.decode(this.compressedData);
|
|
340
|
+
const decoded = vorbis.data[0];
|
|
341
|
+
if (decoded === undefined)
|
|
342
|
+
{
|
|
343
|
+
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
|
|
344
|
+
return new Float32Array(0);
|
|
345
|
+
}
|
|
346
|
+
// clip
|
|
347
|
+
// because vorbis can go above 1 sometimes
|
|
348
|
+
for (let i = 0; i < decoded.length; i++)
|
|
349
|
+
{
|
|
350
|
+
// magic number is 32,767 / 32,768
|
|
351
|
+
decoded[i] = Math.max(-1, Math.min(decoded[i], 0.999969482421875));
|
|
352
|
+
}
|
|
353
|
+
return decoded;
|
|
354
|
+
}
|
|
355
|
+
catch (e)
|
|
356
|
+
{
|
|
357
|
+
// do not error out, fill with silence
|
|
358
|
+
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
|
|
359
|
+
return new Float32Array(this.sampleLoopEndIndex + 1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get the float32 audio data.
|
|
365
|
+
* Note that this either decodes the compressed data or passes the ready sampleData.
|
|
366
|
+
* If neither are set then it will throw an error!
|
|
324
367
|
* @returns {Float32Array}
|
|
325
|
-
* @virtual
|
|
326
368
|
*/
|
|
327
369
|
getAudioData()
|
|
328
370
|
{
|
|
329
|
-
if (
|
|
371
|
+
if (this.sampleData)
|
|
330
372
|
{
|
|
331
|
-
|
|
373
|
+
return this.sampleData;
|
|
332
374
|
}
|
|
333
|
-
|
|
375
|
+
if (this.isCompressed)
|
|
376
|
+
{
|
|
377
|
+
// SF3
|
|
378
|
+
// if compressed, decode
|
|
379
|
+
this.sampleData = this.decodeVorbis();
|
|
380
|
+
return this.sampleData;
|
|
381
|
+
}
|
|
382
|
+
throw new Error("Sample data is undefined for a BasicSample instance.");
|
|
334
383
|
}
|
|
335
384
|
|
|
336
385
|
/**
|
|
@@ -372,4 +421,16 @@ export class BasicSample
|
|
|
372
421
|
this.sampleData = audioData;
|
|
373
422
|
this.dataOverriden = true;
|
|
374
423
|
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Replaces the audio with a compressed data sample
|
|
427
|
+
* @param data {Uint8Array}
|
|
428
|
+
*/
|
|
429
|
+
setCompressedData(data)
|
|
430
|
+
{
|
|
431
|
+
this.sampleData = undefined;
|
|
432
|
+
this.compressedData = data;
|
|
433
|
+
this.isCompressed = true;
|
|
434
|
+
this.dataOverriden = false;
|
|
435
|
+
}
|
|
375
436
|
}
|
|
@@ -39,18 +39,21 @@ class BasicSoundBank
|
|
|
39
39
|
/**
|
|
40
40
|
* The soundfont's presets
|
|
41
41
|
* @type {BasicPreset[]}
|
|
42
|
+
* @readonly
|
|
42
43
|
*/
|
|
43
44
|
presets = [];
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* The soundfont's samples
|
|
47
48
|
* @type {BasicSample[]}
|
|
49
|
+
* @readonly
|
|
48
50
|
*/
|
|
49
51
|
samples = [];
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
54
|
* The soundfont's instruments
|
|
53
55
|
* @type {BasicInstrument[]}
|
|
56
|
+
* @readonly
|
|
54
57
|
*/
|
|
55
58
|
instruments = [];
|
|
56
59
|
|
|
@@ -202,40 +205,137 @@ class BasicSoundBank
|
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
/**
|
|
205
|
-
* @param
|
|
208
|
+
* @param presets {BasicPreset}
|
|
206
209
|
*/
|
|
207
|
-
addPresets(...
|
|
210
|
+
addPresets(...presets)
|
|
208
211
|
{
|
|
209
|
-
this.presets.push(...
|
|
212
|
+
this.presets.push(...presets);
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
|
|
215
|
+
/**
|
|
216
|
+
* @param instruments {BasicInstrument}
|
|
217
|
+
*/
|
|
218
|
+
addInstruments(...instruments)
|
|
213
219
|
{
|
|
214
|
-
this.
|
|
220
|
+
this.instruments.push(...instruments);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param samples {BasicSample}
|
|
225
|
+
*/
|
|
226
|
+
addSamples(...samples)
|
|
227
|
+
{
|
|
228
|
+
this.samples.push(...samples);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Clones samples into this bank
|
|
233
|
+
* @param sample {BasicSample} samples to copy
|
|
234
|
+
* @returns {BasicSample} copied sample, if a sample exists with that name, it is returned instead
|
|
235
|
+
*/
|
|
236
|
+
cloneSample(sample)
|
|
237
|
+
{
|
|
238
|
+
const duplicate = this.samples.find(s => s.sampleName === sample.sampleName);
|
|
239
|
+
if (duplicate)
|
|
215
240
|
{
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
return duplicate;
|
|
242
|
+
}
|
|
243
|
+
const newSample = new BasicSample(
|
|
244
|
+
sample.sampleName,
|
|
245
|
+
sample.sampleRate,
|
|
246
|
+
sample.samplePitch,
|
|
247
|
+
sample.samplePitchCorrection,
|
|
248
|
+
sample.sampleType,
|
|
249
|
+
sample.sampleLoopStartIndex,
|
|
250
|
+
sample.sampleLoopEndIndex
|
|
251
|
+
);
|
|
252
|
+
if (sample.isCompressed)
|
|
253
|
+
{
|
|
254
|
+
newSample.setCompressedData(sample.compressedData.slice());
|
|
255
|
+
console.log(sample.compressedData.length);
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
{
|
|
259
|
+
newSample.setAudioData(sample.getAudioData());
|
|
260
|
+
}
|
|
261
|
+
this.addSamples(newSample);
|
|
262
|
+
if (sample.linkedSample)
|
|
263
|
+
{
|
|
264
|
+
const clonedLinked = this.cloneSample(sample.linkedSample);
|
|
265
|
+
newSample.setLinkedSample(clonedLinked, newSample.sampleType);
|
|
266
|
+
}
|
|
267
|
+
return newSample;
|
|
223
268
|
}
|
|
224
269
|
|
|
270
|
+
|
|
225
271
|
/**
|
|
272
|
+
* Clones an instruments into this bank
|
|
226
273
|
* @param instrument {BasicInstrument}
|
|
274
|
+
* @returns {BasicInstrument} the copied instrument, if an instrument exists with that name, it is returned instead
|
|
227
275
|
*/
|
|
228
|
-
|
|
276
|
+
cloneInstrument(instrument)
|
|
229
277
|
{
|
|
230
|
-
this.instruments.
|
|
278
|
+
const duplicate = this.instruments.find(i => i.instrumentName === instrument.instrumentName);
|
|
279
|
+
if (duplicate)
|
|
280
|
+
{
|
|
281
|
+
return duplicate;
|
|
282
|
+
}
|
|
283
|
+
const newInstrument = new BasicInstrument();
|
|
284
|
+
newInstrument.instrumentName = instrument.instrumentName;
|
|
285
|
+
newInstrument.globalZone.copyFrom(instrument.globalZone);
|
|
286
|
+
for (const zone of instrument.instrumentZones)
|
|
287
|
+
{
|
|
288
|
+
const copiedZone = newInstrument.createZone();
|
|
289
|
+
copiedZone.copyFrom(zone);
|
|
290
|
+
copiedZone.setSample(this.cloneSample(zone.sample));
|
|
291
|
+
}
|
|
292
|
+
this.addInstruments(newInstrument);
|
|
293
|
+
return newInstrument;
|
|
231
294
|
}
|
|
232
295
|
|
|
296
|
+
// noinspection JSUnusedGlobalSymbols
|
|
233
297
|
/**
|
|
234
|
-
*
|
|
298
|
+
* Clones presets into this sound bank
|
|
299
|
+
* @param preset {BasicPreset}
|
|
300
|
+
* @returns {BasicPreset} the copied preset, if a preset exists with that name, it is returned instead
|
|
235
301
|
*/
|
|
236
|
-
|
|
302
|
+
clonePreset(preset)
|
|
303
|
+
{
|
|
304
|
+
const duplicate = this.presets.find(p => p.presetName === preset.presetName);
|
|
305
|
+
if (duplicate)
|
|
306
|
+
{
|
|
307
|
+
return duplicate;
|
|
308
|
+
}
|
|
309
|
+
const newPreset = new BasicPreset(this);
|
|
310
|
+
newPreset.presetName = preset.presetName;
|
|
311
|
+
newPreset.bank = preset.bank;
|
|
312
|
+
newPreset.program = preset.program;
|
|
313
|
+
newPreset.library = preset.library;
|
|
314
|
+
newPreset.genre = preset.genre;
|
|
315
|
+
newPreset.morphology = preset.morphology;
|
|
316
|
+
newPreset.globalZone.copyFrom(preset.globalZone);
|
|
317
|
+
for (const zone of preset.presetZones)
|
|
318
|
+
{
|
|
319
|
+
const copiedZone = newPreset.createZone();
|
|
320
|
+
copiedZone.copyFrom(zone);
|
|
321
|
+
copiedZone.setInstrument(this.cloneInstrument(zone.instrument));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.addPresets(newPreset);
|
|
325
|
+
return newPreset;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
flush()
|
|
237
329
|
{
|
|
238
|
-
this.
|
|
330
|
+
this.presets.sort((a, b) =>
|
|
331
|
+
{
|
|
332
|
+
if (a.bank !== b.bank)
|
|
333
|
+
{
|
|
334
|
+
return a.bank - b.bank;
|
|
335
|
+
}
|
|
336
|
+
return a.program - b.program;
|
|
337
|
+
});
|
|
338
|
+
this._parseInternal();
|
|
239
339
|
}
|
|
240
340
|
|
|
241
341
|
/**
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { generatorTypes } from "./generator_types.js";
|
|
7
7
|
import { Generator } from "./generator.js";
|
|
8
|
+
import { Modulator } from "./modulator.js";
|
|
8
9
|
|
|
9
10
|
export class BasicZone
|
|
10
11
|
{
|
|
@@ -136,7 +137,7 @@ export class BasicZone
|
|
|
136
137
|
copyFrom(zone)
|
|
137
138
|
{
|
|
138
139
|
this.generators = [...zone.generators];
|
|
139
|
-
this.modulators =
|
|
140
|
+
this.modulators = zone.modulators.map(m => Modulator.copy(m));
|
|
140
141
|
this.velRange = { ...zone.velRange };
|
|
141
142
|
this.keyRange = { ...zone.keyRange };
|
|
142
143
|
}
|
|
@@ -84,7 +84,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
|
84
84
|
*/
|
|
85
85
|
const infoArrays = [];
|
|
86
86
|
this.soundFontInfo["ISFT"] = "SpessaSynth"; // ( ͡° ͜ʖ ͡°)
|
|
87
|
-
if (options?.compress)
|
|
87
|
+
if (options?.compress || this.samples.some(s => s.isCompressed))
|
|
88
88
|
{
|
|
89
89
|
this.soundFontInfo["ifil"] = "3.0"; // set version to 3
|
|
90
90
|
}
|
|
@@ -113,7 +113,6 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
|
|
|
113
113
|
}
|
|
114
114
|
else if (type === "DMOD")
|
|
115
115
|
{
|
|
116
|
-
|
|
117
116
|
const mods = this.defaultModulators;
|
|
118
117
|
SpessaSynthInfo(
|
|
119
118
|
`%cWriting %c${mods.length}%c default modulators...`,
|
|
@@ -223,21 +223,15 @@ export class DLSSample extends BasicSample
|
|
|
223
223
|
|
|
224
224
|
getRawData(allowVorbis = true)
|
|
225
225
|
{
|
|
226
|
-
if (this.dataOverriden)
|
|
226
|
+
if (this.dataOverriden || this.isCompressed)
|
|
227
227
|
{
|
|
228
|
-
return
|
|
228
|
+
return super.getRawData();
|
|
229
229
|
}
|
|
230
|
-
|
|
230
|
+
if (this.wFormatTag === W_FORMAT_TAG.PCM && this.bytesPerSample === 2)
|
|
231
231
|
{
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
return this.compressedData;
|
|
235
|
-
}
|
|
236
|
-
if (this.wFormatTag === W_FORMAT_TAG.PCM && this.bytesPerSample === 2)
|
|
237
|
-
{
|
|
238
|
-
return this.rawData;
|
|
239
|
-
}
|
|
240
|
-
return this.encodeS16LE();
|
|
232
|
+
// copy straight away
|
|
233
|
+
return this.rawData;
|
|
241
234
|
}
|
|
235
|
+
return this.encodeS16LE();
|
|
242
236
|
}
|
|
243
237
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
2
2
|
import { IndexedByteArray } from "../../utils/indexed_array.js";
|
|
3
3
|
import { readLittleEndian, signedInt8 } from "../../utils/byte_functions/little_endian.js";
|
|
4
|
-
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
5
4
|
import { SpessaSynthInfo, SpessaSynthWarn } from "../../utils/loggin.js";
|
|
6
5
|
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
7
6
|
import { BasicSample, sampleTypes } from "../basic_soundfont/basic_sample.js";
|
|
8
7
|
import { consoleColors } from "../../utils/other.js";
|
|
9
8
|
|
|
9
|
+
/**
|
|
10
|
+
* samples.js
|
|
11
|
+
* purpose: parses soundfont samples
|
|
12
|
+
*/
|
|
13
|
+
|
|
10
14
|
export const SF3_BIT_FLIT = 0x10;
|
|
11
15
|
|
|
12
16
|
export class SoundFontSample extends BasicSample
|
|
@@ -82,11 +86,9 @@ export class SoundFontSample extends BasicSample
|
|
|
82
86
|
this.isCompressed = compressed;
|
|
83
87
|
this.sampleName = sampleName;
|
|
84
88
|
// in bytes
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
89
|
+
this.startByteOffset = sampleStartIndex;
|
|
90
|
+
this.endByteOffset = sampleEndIndex;
|
|
87
91
|
this.sampleID = sampleIndex;
|
|
88
|
-
// in bytes
|
|
89
|
-
this.sampleLength = this.sampleEndIndex - this.sampleStartIndex;
|
|
90
92
|
const smplStart = sampleDataArray.currentIndex;
|
|
91
93
|
|
|
92
94
|
// three data types in:
|
|
@@ -96,14 +98,13 @@ export class SoundFontSample extends BasicSample
|
|
|
96
98
|
if (this.isCompressed)
|
|
97
99
|
{
|
|
98
100
|
// correct loop points
|
|
99
|
-
this.sampleLoopStartIndex += this.
|
|
100
|
-
this.sampleLoopEndIndex += this.
|
|
101
|
-
this.sampleLength = 99999999; // set to 999,999 before we decode it
|
|
101
|
+
this.sampleLoopStartIndex += this.startByteOffset / 2;
|
|
102
|
+
this.sampleLoopEndIndex += this.startByteOffset / 2;
|
|
102
103
|
|
|
103
104
|
// copy the compressed data, it can be preserved during writing
|
|
104
105
|
this.compressedData = sampleDataArray.slice(
|
|
105
|
-
this.
|
|
106
|
-
this.
|
|
106
|
+
this.startByteOffset / 2 + smplStart,
|
|
107
|
+
this.endByteOffset / 2 + smplStart
|
|
107
108
|
);
|
|
108
109
|
}
|
|
109
110
|
else
|
|
@@ -112,15 +113,15 @@ export class SoundFontSample extends BasicSample
|
|
|
112
113
|
{
|
|
113
114
|
// float32 array from SF2pack, copy directly
|
|
114
115
|
this.sampleData = sampleDataArray.slice(
|
|
115
|
-
this.
|
|
116
|
-
this.
|
|
116
|
+
this.startByteOffset / 2,
|
|
117
|
+
this.endByteOffset / 2
|
|
117
118
|
);
|
|
118
119
|
}
|
|
119
120
|
else
|
|
120
121
|
{
|
|
121
122
|
// regular sf2 s16le
|
|
122
|
-
this.s16leStart = smplStart + this.
|
|
123
|
-
this.s16leEnd = smplStart + this.
|
|
123
|
+
this.s16leStart = smplStart + this.startByteOffset;
|
|
124
|
+
this.s16leEnd = smplStart + this.endByteOffset;
|
|
124
125
|
this.sf2FileArrayHandle = sampleDataArray;
|
|
125
126
|
}
|
|
126
127
|
|
|
@@ -150,53 +151,6 @@ export class SoundFontSample extends BasicSample
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
/**
|
|
154
|
-
* @private
|
|
155
|
-
* Decode binary vorbis into a float32 pcm
|
|
156
|
-
* @returns {Float32Array}
|
|
157
|
-
*/
|
|
158
|
-
decodeVorbis()
|
|
159
|
-
{
|
|
160
|
-
if (this.sampleData)
|
|
161
|
-
{
|
|
162
|
-
return this.sampleData;
|
|
163
|
-
}
|
|
164
|
-
if (this.sampleLength < 1)
|
|
165
|
-
{
|
|
166
|
-
// eos, do not do anything
|
|
167
|
-
return new Float32Array(0);
|
|
168
|
-
}
|
|
169
|
-
// get the compressed byte stream
|
|
170
|
-
// reset array and being decoding
|
|
171
|
-
try
|
|
172
|
-
{
|
|
173
|
-
/**
|
|
174
|
-
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
175
|
-
*/
|
|
176
|
-
const vorbis = stbvorbis.decode(this.compressedData);
|
|
177
|
-
const decoded = vorbis.data[0];
|
|
178
|
-
if (decoded === undefined)
|
|
179
|
-
{
|
|
180
|
-
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
|
|
181
|
-
return new Float32Array(0);
|
|
182
|
-
}
|
|
183
|
-
// clip
|
|
184
|
-
// because vorbis can go above 1 sometimes
|
|
185
|
-
for (let i = 0; i < decoded.length; i++)
|
|
186
|
-
{
|
|
187
|
-
// magic number is 32,767 / 32,768
|
|
188
|
-
decoded[i] = Math.max(-1, Math.min(decoded[i], 0.999969482421875));
|
|
189
|
-
}
|
|
190
|
-
return decoded;
|
|
191
|
-
}
|
|
192
|
-
catch (e)
|
|
193
|
-
{
|
|
194
|
-
// do not error out, fill with silence
|
|
195
|
-
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
|
|
196
|
-
return new Float32Array(this.sampleLoopEndIndex + 1);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
154
|
/**
|
|
201
155
|
* @param audioData {Float32Array}
|
|
202
156
|
*/
|
|
@@ -216,24 +170,24 @@ export class SoundFontSample extends BasicSample
|
|
|
216
170
|
return this.sampleData;
|
|
217
171
|
}
|
|
218
172
|
// SF2Pack is decoded during load time
|
|
173
|
+
// SF3 is decoded in BasicSample
|
|
174
|
+
if (this.isCompressed)
|
|
175
|
+
{
|
|
176
|
+
return super.getAudioData();
|
|
177
|
+
}
|
|
219
178
|
|
|
220
179
|
// start loading data if it is not loaded
|
|
221
|
-
|
|
180
|
+
const byteLength = this.endByteOffset - this.startByteOffset;
|
|
181
|
+
if (byteLength < 1)
|
|
222
182
|
{
|
|
223
|
-
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${
|
|
183
|
+
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${byteLength}`);
|
|
224
184
|
return new Float32Array(1);
|
|
225
185
|
}
|
|
226
186
|
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
// SF3
|
|
230
|
-
// if compressed, decode
|
|
231
|
-
this.sampleData = this.decodeVorbis();
|
|
232
|
-
return this.sampleData;
|
|
233
|
-
}
|
|
187
|
+
|
|
234
188
|
// SF2
|
|
235
189
|
// read the sample data
|
|
236
|
-
let audioData = new Float32Array(
|
|
190
|
+
let audioData = new Float32Array(byteLength / 2);
|
|
237
191
|
let convertedSigned16 = new Int16Array(
|
|
238
192
|
this.sf2FileArrayHandle.buffer.slice(this.s16leStart, this.s16leEnd)
|
|
239
193
|
);
|
|
@@ -255,22 +209,13 @@ export class SoundFontSample extends BasicSample
|
|
|
255
209
|
*/
|
|
256
210
|
getRawData(allowVorbis = true)
|
|
257
211
|
{
|
|
258
|
-
if (this.dataOverriden)
|
|
212
|
+
if (this.dataOverriden || this.compressedData)
|
|
259
213
|
{
|
|
260
|
-
return
|
|
261
|
-
|
|
262
|
-
else
|
|
263
|
-
{
|
|
264
|
-
if (this.compressedData)
|
|
265
|
-
{
|
|
266
|
-
if (allowVorbis)
|
|
267
|
-
{
|
|
268
|
-
return this.compressedData;
|
|
269
|
-
}
|
|
270
|
-
return this.encodeS16LE();
|
|
271
|
-
}
|
|
272
|
-
return this.sf2FileArrayHandle.slice(this.s16leStart, this.s16leEnd);
|
|
214
|
+
// return vorbis or encode manually
|
|
215
|
+
return super.getRawData();
|
|
273
216
|
}
|
|
217
|
+
// copy the smpl directly
|
|
218
|
+
return this.sf2FileArrayHandle.slice(this.s16leStart, this.s16leEnd);
|
|
274
219
|
}
|
|
275
220
|
}
|
|
276
221
|
|