spessasynth_core 3.26.28 → 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 +16 -1
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +1 -1
- package/src/soundfont/basic_soundfont/basic_sample.js +39 -17
- package/src/soundfont/basic_soundfont/basic_soundbank.js +3 -3
- 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,6 +67,20 @@ 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--);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
deleteUnusedZones()
|
|
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
|
+
});
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
deleteInstrument()
|
|
@@ -86,6 +100,7 @@ export class BasicInstrument
|
|
|
86
100
|
deleteZone(index)
|
|
87
101
|
{
|
|
88
102
|
const zone = this.instrumentZones[index];
|
|
103
|
+
zone.useCount -= 1;
|
|
89
104
|
if (zone.useCount < 1)
|
|
90
105
|
{
|
|
91
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
|
}
|
|
@@ -329,8 +333,25 @@ export class BasicSample
|
|
|
329
333
|
return this.sampleData;
|
|
330
334
|
}
|
|
331
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
|
+
|
|
332
352
|
// noinspection JSUnusedGlobalSymbols
|
|
333
353
|
/**
|
|
354
|
+
* REPLACES the audio data
|
|
334
355
|
* @param audioData {Float32Array}
|
|
335
356
|
* @virtual
|
|
336
357
|
*/
|
|
@@ -339,5 +360,6 @@ export class BasicSample
|
|
|
339
360
|
this.isCompressed = false;
|
|
340
361
|
delete this.compressedData;
|
|
341
362
|
this.sampleData = audioData;
|
|
363
|
+
this.dataOverriden = true;
|
|
342
364
|
}
|
|
343
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
|
|
@@ -444,12 +443,13 @@ class BasicSoundBank
|
|
|
444
443
|
{
|
|
445
444
|
this.instruments = this.instruments.filter(i =>
|
|
446
445
|
{
|
|
446
|
+
i.deleteUnusedZones();
|
|
447
447
|
const deletable = i.useCount < 1;
|
|
448
448
|
if (deletable)
|
|
449
449
|
{
|
|
450
450
|
i.deleteInstrument();
|
|
451
451
|
}
|
|
452
|
-
return deletable;
|
|
452
|
+
return !deletable;
|
|
453
453
|
});
|
|
454
454
|
this.samples = this.samples.filter(s =>
|
|
455
455
|
{
|
|
@@ -458,7 +458,7 @@ class BasicSoundBank
|
|
|
458
458
|
{
|
|
459
459
|
s.deleteSample();
|
|
460
460
|
}
|
|
461
|
-
return deletable;
|
|
461
|
+
return !deletable;
|
|
462
462
|
});
|
|
463
463
|
}
|
|
464
464
|
|
|
@@ -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
|
}
|
|
@@ -7,17 +7,17 @@ import { generatorTypes } from "../generator_types.js";
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @this {BasicSoundBank}
|
|
10
|
-
* @returns {
|
|
10
|
+
* @returns {ReturnedExtendedSf2Chunks}
|
|
11
11
|
*/
|
|
12
12
|
export function getPGEN()
|
|
13
13
|
{
|
|
14
14
|
// almost identical to igen, except the correct instrument instead of sample gen
|
|
15
15
|
// goes through all preset zones and writes generators sequentially (add 4 for terminal)
|
|
16
|
-
let
|
|
16
|
+
let pgenSize = GEN_BYTE_SIZE;
|
|
17
17
|
for (const preset of this.presets)
|
|
18
18
|
{
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
pgenSize += preset.globalZone.generators.length * GEN_BYTE_SIZE;
|
|
20
|
+
pgenSize += preset.presetZones.reduce((size, z) =>
|
|
21
21
|
{
|
|
22
22
|
// clear instrument and range generators before determining the size
|
|
23
23
|
z.generators = z.generators.filter(g =>
|
|
@@ -51,7 +51,7 @@ export function getPGEN()
|
|
|
51
51
|
return z.generators.length * GEN_BYTE_SIZE + size;
|
|
52
52
|
}, 0);
|
|
53
53
|
}
|
|
54
|
-
const
|
|
54
|
+
const pgenData = new IndexedByteArray(pgenSize);
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* @param z {BasicZone}
|
|
@@ -61,8 +61,8 @@ export function getPGEN()
|
|
|
61
61
|
for (const gen of z.generators)
|
|
62
62
|
{
|
|
63
63
|
// name is deceptive, it works on negatives
|
|
64
|
-
writeWord(
|
|
65
|
-
writeWord(
|
|
64
|
+
writeWord(pgenData, gen.generatorType);
|
|
65
|
+
writeWord(pgenData, gen.generatorValue);
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
for (const preset of this.presets)
|
|
@@ -75,11 +75,25 @@ export function getPGEN()
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
// terminal generator, is zero
|
|
78
|
-
writeDword(
|
|
78
|
+
writeDword(pgenData, 0);
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// https://github.com/spessasus/soundfont-proposals/blob/main/extended_limits.md
|
|
81
|
+
const xpgenData = new IndexedByteArray(GEN_BYTE_SIZE);
|
|
82
|
+
writeDword(xpgenData, 0);
|
|
83
|
+
|
|
84
|
+
const pgen = writeRIFFChunk(new RiffChunk(
|
|
85
|
+
"pgen",
|
|
86
|
+
pgenData.length,
|
|
87
|
+
pgenData
|
|
88
|
+
));
|
|
89
|
+
const xpgen = writeRIFFChunk(new RiffChunk(
|
|
81
90
|
"pgen",
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
xpgenData.length,
|
|
92
|
+
xpgenData
|
|
84
93
|
));
|
|
94
|
+
return {
|
|
95
|
+
pdta: pgen,
|
|
96
|
+
xdta: xpgen,
|
|
97
|
+
highestIndex: 0 // not applicable
|
|
98
|
+
};
|
|
85
99
|
}
|