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.
- package/package.json +1 -1
- package/src/soundfont/basic_soundfont/basic_instrument.js +21 -2
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +1 -1
- package/src/soundfont/basic_soundfont/basic_sample.js +48 -17
- package/src/soundfont/basic_soundfont/basic_soundbank.js +18 -15
- package/src/soundfont/basic_soundfont/riff_chunk.js +4 -1
- package/src/soundfont/basic_soundfont/write_dls/wave.js +1 -1
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +28 -10
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +26 -11
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +28 -14
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +28 -10
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +26 -11
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +25 -11
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +45 -20
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +28 -14
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +28 -5
- package/src/soundfont/basic_soundfont/write_sf2/write.js +53 -14
- package/src/soundfont/dls/dls_sample.js +179 -18
- package/src/soundfont/dls/read_samples.js +7 -123
- package/src/soundfont/read_sf2/instrument_zones.js +4 -17
- package/src/soundfont/read_sf2/instruments.js +1 -1
- package/src/soundfont/read_sf2/preset_zones.js +6 -19
- package/src/soundfont/read_sf2/presets.js +0 -1
- package/src/soundfont/read_sf2/samples.js +115 -106
- package/src/soundfont/read_sf2/soundfont.js +198 -56
- package/src/soundfont/read_sf2/zones.js +28 -0
package/package.json
CHANGED
|
@@ -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
|
|
56
|
+
this.instrumentZones.forEach(z => z.useCount++);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/**
|
|
@@ -67,10 +67,28 @@ 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--);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
deleteUnusedZones()
|
|
73
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
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
deleteInstrument()
|
|
87
|
+
{
|
|
88
|
+
if (this.useCount > 0)
|
|
89
|
+
{
|
|
90
|
+
throw new Error(`Cannot delete an instrument that has ${this.useCount} usages.`);
|
|
91
|
+
}
|
|
74
92
|
this.instrumentZones.forEach(z => z.deleteZone());
|
|
75
93
|
this.instrumentZones.length = 0;
|
|
76
94
|
}
|
|
@@ -82,6 +100,7 @@ export class BasicInstrument
|
|
|
82
100
|
deleteZone(index)
|
|
83
101
|
{
|
|
84
102
|
const zone = this.instrumentZones[index];
|
|
103
|
+
zone.useCount -= 1;
|
|
85
104
|
if (zone.useCount < 1)
|
|
86
105
|
{
|
|
87
106
|
zone.deleteZone();
|
|
@@ -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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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.
|
|
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
|
|
222
|
+
delete this.compressedData;
|
|
219
223
|
// flag as uncompressed
|
|
220
|
-
this.
|
|
224
|
+
this.isCompressed = false;
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
}
|
|
@@ -246,6 +250,15 @@ export class BasicSample
|
|
|
246
250
|
|
|
247
251
|
}
|
|
248
252
|
|
|
253
|
+
deleteSample()
|
|
254
|
+
{
|
|
255
|
+
if (this.useCount > 0)
|
|
256
|
+
{
|
|
257
|
+
throw new Error(`Cannot delete sample that has ${this.useCount} usages.`);
|
|
258
|
+
}
|
|
259
|
+
this.unlinkSample();
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
// noinspection JSUnusedGlobalSymbols
|
|
250
263
|
/**
|
|
251
264
|
* Unlinks a sample link
|
|
@@ -320,8 +333,25 @@ export class BasicSample
|
|
|
320
333
|
return this.sampleData;
|
|
321
334
|
}
|
|
322
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
|
+
|
|
323
352
|
// noinspection JSUnusedGlobalSymbols
|
|
324
353
|
/**
|
|
354
|
+
* REPLACES the audio data
|
|
325
355
|
* @param audioData {Float32Array}
|
|
326
356
|
* @virtual
|
|
327
357
|
*/
|
|
@@ -330,5 +360,6 @@ export class BasicSample
|
|
|
330
360
|
this.isCompressed = false;
|
|
331
361
|
delete this.compressedData;
|
|
332
362
|
this.sampleData = audioData;
|
|
363
|
+
this.dataOverriden = true;
|
|
333
364
|
}
|
|
334
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
|
|
@@ -442,15 +441,25 @@ class BasicSoundBank
|
|
|
442
441
|
|
|
443
442
|
removeUnusedElements()
|
|
444
443
|
{
|
|
445
|
-
this.instruments.
|
|
444
|
+
this.instruments = this.instruments.filter(i =>
|
|
446
445
|
{
|
|
447
|
-
|
|
446
|
+
i.deleteUnusedZones();
|
|
447
|
+
const deletable = i.useCount < 1;
|
|
448
|
+
if (deletable)
|
|
448
449
|
{
|
|
449
|
-
i.
|
|
450
|
+
i.deleteInstrument();
|
|
450
451
|
}
|
|
452
|
+
return !deletable;
|
|
453
|
+
});
|
|
454
|
+
this.samples = this.samples.filter(s =>
|
|
455
|
+
{
|
|
456
|
+
const deletable = s.useCount < 1;
|
|
457
|
+
if (deletable)
|
|
458
|
+
{
|
|
459
|
+
s.deleteSample();
|
|
460
|
+
}
|
|
461
|
+
return !deletable;
|
|
451
462
|
});
|
|
452
|
-
this.instruments = this.instruments.filter(i => i.useCount > 0);
|
|
453
|
-
this.samples = this.samples.filter(s => s.useCount > 0);
|
|
454
463
|
}
|
|
455
464
|
|
|
456
465
|
/**
|
|
@@ -458,12 +467,8 @@ class BasicSoundBank
|
|
|
458
467
|
*/
|
|
459
468
|
deleteInstrument(instrument)
|
|
460
469
|
{
|
|
461
|
-
|
|
462
|
-
{
|
|
463
|
-
throw new Error(`Cannot delete an instrument that has ${instrument.useCount} usages.`);
|
|
464
|
-
}
|
|
470
|
+
instrument.deleteInstrument();
|
|
465
471
|
this.instruments.splice(this.instruments.indexOf(instrument), 1);
|
|
466
|
-
instrument.deleteAllZones();
|
|
467
472
|
}
|
|
468
473
|
|
|
469
474
|
/**
|
|
@@ -480,10 +485,7 @@ class BasicSoundBank
|
|
|
480
485
|
*/
|
|
481
486
|
deleteSample(sample)
|
|
482
487
|
{
|
|
483
|
-
|
|
484
|
-
{
|
|
485
|
-
throw new Error(`Cannot delete sample that has ${sample.useCount} usages.`);
|
|
486
|
-
}
|
|
488
|
+
sample.deleteSample();
|
|
487
489
|
this.samples.splice(this.samples.indexOf(sample), 1);
|
|
488
490
|
}
|
|
489
491
|
|
|
@@ -615,6 +617,7 @@ class BasicSoundBank
|
|
|
615
617
|
delete this.presets;
|
|
616
618
|
delete this.instruments;
|
|
617
619
|
delete this.samples;
|
|
620
|
+
delete this.soundFontInfo;
|
|
618
621
|
}
|
|
619
622
|
}
|
|
620
623
|
|
|
@@ -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 =
|
|
45
|
+
chunkData = dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + size);
|
|
43
46
|
}
|
|
44
47
|
if (readData || forceShift)
|
|
45
48
|
{
|
|
@@ -6,18 +6,20 @@ const BAG_SIZE = 4;
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @this {BasicSoundBank}
|
|
9
|
-
* @returns {
|
|
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
|
|
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
|
|
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
|
-
|
|
29
|
-
writeWord(
|
|
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(
|
|
44
|
-
writeWord(
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
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 {
|
|
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
|
|
14
|
+
let igenSize = GEN_BYTE_SIZE;
|
|
15
15
|
for (const inst of this.instruments)
|
|
16
16
|
{
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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(
|
|
64
|
-
writeWord(
|
|
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(
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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 {
|
|
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
|
|
14
|
+
let imodSize = MOD_BYTE_SIZE; // terminal
|
|
15
15
|
for (const inst of this.instruments)
|
|
16
16
|
{
|
|
17
|
-
|
|
17
|
+
imodSize += inst.globalZone.modulators.length * MOD_BYTE_SIZE;
|
|
18
18
|
// start with one mod for global
|
|
19
|
-
|
|
19
|
+
imodSize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * MOD_BYTE_SIZE + sum, 0);
|
|
20
20
|
}
|
|
21
|
-
const
|
|
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(
|
|
31
|
-
writeWord(
|
|
32
|
-
writeWord(
|
|
33
|
-
writeWord(
|
|
34
|
-
writeWord(
|
|
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(
|
|
49
|
+
writeLittleEndian(imodData, 0, MOD_BYTE_SIZE);
|
|
50
50
|
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 {
|
|
10
|
+
* @returns {ReturnedExtendedSf2Chunks}
|
|
11
11
|
*/
|
|
12
12
|
export function getINST()
|
|
13
13
|
{
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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(
|
|
21
|
-
|
|
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(
|
|
26
|
-
|
|
29
|
+
writeStringAsBytes(instData, "EOI", 20);
|
|
30
|
+
writeStringAsBytes(xinstData, "EOI", 20);
|
|
31
|
+
writeWord(instData, instrumentStart & 0xFFFF);
|
|
32
|
+
writeWord(xinstData, instrumentStart >> 16);
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
const inst = writeRIFFChunk(new RiffChunk(
|
|
29
35
|
"inst",
|
|
30
|
-
|
|
31
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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(
|
|
27
|
-
writeWord(
|
|
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(
|
|
43
|
-
writeWord(
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
57
|
+
xpbagData.length,
|
|
58
|
+
xpbagData
|
|
49
59
|
));
|
|
60
|
+
return {
|
|
61
|
+
pdta: pbag,
|
|
62
|
+
xdta: xbag,
|
|
63
|
+
highestIndex: Math.max(generatorIndex, modulatorIndex)
|
|
64
|
+
};
|
|
50
65
|
}
|