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
|
@@ -10,101 +10,6 @@ import { consoleColors } from "../../utils/other.js";
|
|
|
10
10
|
import { readLittleEndian, signedInt16 } from "../../utils/byte_functions/little_endian.js";
|
|
11
11
|
import { DLSSample } from "./dls_sample.js";
|
|
12
12
|
|
|
13
|
-
const W_FORMAT_TAG = {
|
|
14
|
-
PCM: 0x01,
|
|
15
|
-
ALAW: 0x6
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param dataChunk {RiffChunk}
|
|
20
|
-
* @param bytesPerSample {number}
|
|
21
|
-
* @returns {Float32Array}
|
|
22
|
-
*/
|
|
23
|
-
function readPCM(dataChunk, bytesPerSample)
|
|
24
|
-
{
|
|
25
|
-
const maxSampleValue = Math.pow(2, bytesPerSample * 8 - 1); // Max value for the sample
|
|
26
|
-
const maxUnsigned = Math.pow(2, bytesPerSample * 8);
|
|
27
|
-
|
|
28
|
-
let normalizationFactor;
|
|
29
|
-
let isUnsigned = false;
|
|
30
|
-
|
|
31
|
-
if (bytesPerSample === 1)
|
|
32
|
-
{
|
|
33
|
-
normalizationFactor = 255; // For 8-bit normalize from 0-255
|
|
34
|
-
isUnsigned = true;
|
|
35
|
-
}
|
|
36
|
-
else
|
|
37
|
-
{
|
|
38
|
-
normalizationFactor = maxSampleValue; // For 16-bit normalize from -32,768 to 32,767
|
|
39
|
-
}
|
|
40
|
-
const sampleLength = dataChunk.size / bytesPerSample;
|
|
41
|
-
const sampleData = new Float32Array(sampleLength);
|
|
42
|
-
for (let i = 0; i < sampleData.length; i++)
|
|
43
|
-
{
|
|
44
|
-
// read
|
|
45
|
-
let sample = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
46
|
-
// turn into signed
|
|
47
|
-
if (isUnsigned)
|
|
48
|
-
{
|
|
49
|
-
// normalize unsigned 8-bit sample
|
|
50
|
-
sampleData[i] = (sample / normalizationFactor) - 0.5;
|
|
51
|
-
}
|
|
52
|
-
else
|
|
53
|
-
{
|
|
54
|
-
// normalize signed 16-bit sample
|
|
55
|
-
if (sample >= maxSampleValue)
|
|
56
|
-
{
|
|
57
|
-
sample -= maxUnsigned;
|
|
58
|
-
}
|
|
59
|
-
sampleData[i] = sample / normalizationFactor;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return sampleData;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* @param dataChunk {RiffChunk}
|
|
67
|
-
* @param bytesPerSample {number}
|
|
68
|
-
* @returns {Float32Array}
|
|
69
|
-
*/
|
|
70
|
-
function readALAW(dataChunk, bytesPerSample)
|
|
71
|
-
{
|
|
72
|
-
const sampleLength = dataChunk.size / bytesPerSample;
|
|
73
|
-
const sampleData = new Float32Array(sampleLength);
|
|
74
|
-
for (let i = 0; i < sampleData.length; i++)
|
|
75
|
-
{
|
|
76
|
-
// read
|
|
77
|
-
const input = readLittleEndian(dataChunk.chunkData, bytesPerSample);
|
|
78
|
-
|
|
79
|
-
// https://en.wikipedia.org/wiki/G.711#A-law
|
|
80
|
-
// re-toggle toggled bits
|
|
81
|
-
let sample = input ^ 0x55;
|
|
82
|
-
|
|
83
|
-
// remove sign bit
|
|
84
|
-
sample &= 0x7F;
|
|
85
|
-
|
|
86
|
-
// extract exponent
|
|
87
|
-
let exponent = sample >> 4;
|
|
88
|
-
// extract mantissa
|
|
89
|
-
let mantissa = sample & 0xF;
|
|
90
|
-
if (exponent > 0)
|
|
91
|
-
{
|
|
92
|
-
mantissa += 16; // add leading '1', if exponent > 0
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
mantissa = (mantissa << 4) + 0x8;
|
|
96
|
-
if (exponent > 1)
|
|
97
|
-
{
|
|
98
|
-
mantissa = mantissa << (exponent - 1);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const s16sample = input > 127 ? mantissa : -mantissa;
|
|
102
|
-
|
|
103
|
-
// convert to float
|
|
104
|
-
sampleData[i] = s16sample / 32678;
|
|
105
|
-
}
|
|
106
|
-
return sampleData;
|
|
107
|
-
}
|
|
108
13
|
|
|
109
14
|
/**
|
|
110
15
|
* @this {DLSSoundFont}
|
|
@@ -138,7 +43,7 @@ export function readDLSSamples(waveListChunk)
|
|
|
138
43
|
throw new Error("No fmt chunk in the wave file!");
|
|
139
44
|
}
|
|
140
45
|
// https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/shared/mmreg.h#L2108
|
|
141
|
-
const
|
|
46
|
+
const wFormatTag = readLittleEndian(fmtChunk.chunkData, 2);
|
|
142
47
|
const channelsAmount = readLittleEndian(fmtChunk.chunkData, 2);
|
|
143
48
|
if (channelsAmount !== 1)
|
|
144
49
|
{
|
|
@@ -153,30 +58,11 @@ export function readDLSSamples(waveListChunk)
|
|
|
153
58
|
const wBitsPerSample = readLittleEndian(fmtChunk.chunkData, 2);
|
|
154
59
|
const bytesPerSample = wBitsPerSample / 8;
|
|
155
60
|
|
|
156
|
-
// read the data
|
|
157
|
-
let failed = false;
|
|
158
61
|
const dataChunk = waveChunks.find(c => c.header === "data");
|
|
159
62
|
if (!dataChunk)
|
|
160
63
|
{
|
|
161
64
|
this.parsingError("No data chunk in the WAVE chunk!");
|
|
162
65
|
}
|
|
163
|
-
let sampleData;
|
|
164
|
-
switch (waveFormat)
|
|
165
|
-
{
|
|
166
|
-
default:
|
|
167
|
-
failed = true;
|
|
168
|
-
sampleData = new Float32Array(dataChunk.size / bytesPerSample);
|
|
169
|
-
break;
|
|
170
|
-
|
|
171
|
-
case W_FORMAT_TAG.PCM:
|
|
172
|
-
sampleData = readPCM(dataChunk, bytesPerSample);
|
|
173
|
-
break;
|
|
174
|
-
|
|
175
|
-
case W_FORMAT_TAG.ALAW:
|
|
176
|
-
sampleData = readALAW(dataChunk, bytesPerSample);
|
|
177
|
-
break;
|
|
178
|
-
|
|
179
|
-
}
|
|
180
66
|
|
|
181
67
|
// read sample name
|
|
182
68
|
const waveInfo = findRIFFListType(waveChunks, "INFO");
|
|
@@ -198,7 +84,8 @@ export function readDLSSamples(waveListChunk)
|
|
|
198
84
|
let sampleKey = 60;
|
|
199
85
|
let samplePitch = 0;
|
|
200
86
|
let sampleLoopStart = 0;
|
|
201
|
-
|
|
87
|
+
const sampleLength = dataChunk.size / bytesPerSample;
|
|
88
|
+
let sampleLoopEnd = sampleLength - 1;
|
|
202
89
|
let sampleDbAttenuation = 0;
|
|
203
90
|
|
|
204
91
|
// read wsmp
|
|
@@ -242,11 +129,6 @@ export function readDLSSamples(waveListChunk)
|
|
|
242
129
|
SpessaSynthWarn("No wsmp chunk in wave... using sane defaults.");
|
|
243
130
|
}
|
|
244
131
|
|
|
245
|
-
if (failed)
|
|
246
|
-
{
|
|
247
|
-
console.error(`Failed to load '${sampleName}': Unsupported format: (${waveFormat})`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
132
|
this.samples.push(new DLSSample(
|
|
251
133
|
sampleName,
|
|
252
134
|
sampleRate,
|
|
@@ -254,8 +136,10 @@ export function readDLSSamples(waveListChunk)
|
|
|
254
136
|
samplePitch,
|
|
255
137
|
sampleLoopStart,
|
|
256
138
|
sampleLoopEnd,
|
|
257
|
-
|
|
258
|
-
|
|
139
|
+
sampleDbAttenuation,
|
|
140
|
+
dataChunk,
|
|
141
|
+
wFormatTag,
|
|
142
|
+
bytesPerSample
|
|
259
143
|
));
|
|
260
144
|
|
|
261
145
|
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { BasicInstrumentZone } from "../basic_soundfont/basic_instrument_zone.js";
|
|
6
6
|
import { generatorTypes } from "../basic_soundfont/generator_types.js";
|
|
7
|
-
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
8
7
|
|
|
9
8
|
export class InstrumentZone extends BasicInstrumentZone
|
|
10
9
|
{
|
|
@@ -33,28 +32,16 @@ export class InstrumentZone extends BasicInstrumentZone
|
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
34
|
* Reads the given instrument zone
|
|
36
|
-
* @param
|
|
35
|
+
* @param indexes {{mod: number[], gen: number[]}}
|
|
37
36
|
* @param instrumentGenerators {Generator[]}
|
|
38
37
|
* @param instrumentModulators {Modulator[]}
|
|
39
38
|
* @param samples {BasicSample[]}
|
|
40
39
|
* @param instruments {Instrument[]}
|
|
41
40
|
*/
|
|
42
|
-
export function
|
|
41
|
+
export function applyInstrumentZones(indexes, instrumentGenerators, instrumentModulators, samples, instruments)
|
|
43
42
|
{
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*/
|
|
47
|
-
const modStartIndexes = [];
|
|
48
|
-
/**
|
|
49
|
-
* @type {number[]}
|
|
50
|
-
*/
|
|
51
|
-
const genStartIndexes = [];
|
|
52
|
-
|
|
53
|
-
while (zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
|
|
54
|
-
{
|
|
55
|
-
genStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
|
|
56
|
-
modStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
|
|
57
|
-
}
|
|
43
|
+
const genStartIndexes = indexes.gen;
|
|
44
|
+
const modStartIndexes = indexes.mod;
|
|
58
45
|
|
|
59
46
|
let modIndex = 0;
|
|
60
47
|
let genIndex = 0;
|
|
@@ -28,7 +28,7 @@ export class Instrument extends BasicInstrument
|
|
|
28
28
|
constructor(instrumentChunk)
|
|
29
29
|
{
|
|
30
30
|
super();
|
|
31
|
-
this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20)
|
|
31
|
+
this.instrumentName = readBytesAsString(instrumentChunk.chunkData, 20);
|
|
32
32
|
this.zoneStartIndex = readLittleEndian(instrumentChunk.chunkData, 2);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
|
|
2
|
-
import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
|
|
3
1
|
import { BasicPresetZone } from "../basic_soundfont/basic_preset_zone.js";
|
|
4
2
|
import { Generator } from "../basic_soundfont/generator.js";
|
|
5
3
|
import { Modulator } from "../basic_soundfont/modulator.js";
|
|
@@ -35,30 +33,19 @@ export class PresetZone extends BasicPresetZone
|
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
|
|
36
|
+
|
|
38
37
|
/**
|
|
39
|
-
* Reads the given preset zone
|
|
40
|
-
* @param
|
|
38
|
+
* Reads the given preset zone
|
|
39
|
+
* @param indexes {{mod: number[], gen: number[]}}
|
|
41
40
|
* @param presetGens {Generator[]}
|
|
42
41
|
* @param instruments {BasicInstrument[]}
|
|
43
42
|
* @param presetMods {Modulator[]}
|
|
44
43
|
* @param presets {Preset[]}
|
|
45
44
|
*/
|
|
46
|
-
export function
|
|
45
|
+
export function applyPresetZones(indexes, presetGens, presetMods, instruments, presets)
|
|
47
46
|
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
const modStartIndexes = [];
|
|
52
|
-
/**
|
|
53
|
-
* @type {number[]}
|
|
54
|
-
*/
|
|
55
|
-
const genStartIndexes = [];
|
|
56
|
-
|
|
57
|
-
while (zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
|
|
58
|
-
{
|
|
59
|
-
genStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
|
|
60
|
-
modStartIndexes.push(readLittleEndian(zonesChunk.chunkData, 2));
|
|
61
|
-
}
|
|
47
|
+
const genStartIndexes = indexes.gen;
|
|
48
|
+
const modStartIndexes = indexes.mod;
|
|
62
49
|
|
|
63
50
|
let modIndex = 0;
|
|
64
51
|
let genIndex = 0;
|
|
@@ -29,7 +29,6 @@ export class Preset extends BasicPreset
|
|
|
29
29
|
{
|
|
30
30
|
super(sf2);
|
|
31
31
|
this.presetName = readBytesAsString(presetChunk.chunkData, 20)
|
|
32
|
-
.trim()
|
|
33
32
|
.replace(/\d{3}:\d{3}/, ""); // remove those pesky "000:001"
|
|
34
33
|
|
|
35
34
|
this.program = readLittleEndian(presetChunk.chunkData, 2);
|
|
@@ -16,6 +16,23 @@ export class SoundFontSample extends BasicSample
|
|
|
16
16
|
*/
|
|
17
17
|
linkedSampleIndex;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* The handle to the core sf2 file for dynamic sample reading
|
|
21
|
+
* @type {Uint8Array}
|
|
22
|
+
*/
|
|
23
|
+
sf2FileArrayHandle;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Start index of the sample in the file byte array
|
|
27
|
+
* @type {number}
|
|
28
|
+
*/
|
|
29
|
+
s16leStart = 0;
|
|
30
|
+
/**
|
|
31
|
+
* End index of the sample in the file byte array
|
|
32
|
+
* @type {number}
|
|
33
|
+
*/
|
|
34
|
+
s16leEnd = 0;
|
|
35
|
+
|
|
19
36
|
/**
|
|
20
37
|
* Creates a sample
|
|
21
38
|
* @param sampleName {string}
|
|
@@ -28,9 +45,8 @@ export class SoundFontSample extends BasicSample
|
|
|
28
45
|
* @param samplePitchCorrection {number}
|
|
29
46
|
* @param linkedSampleIndex {number}
|
|
30
47
|
* @param sampleType {number}
|
|
31
|
-
* @param
|
|
48
|
+
* @param sampleDataArray {IndexedByteArray|Float32Array}
|
|
32
49
|
* @param sampleIndex {number} initial sample index when loading the sfont
|
|
33
|
-
* @param isDataRaw {boolean} if false, the data is decoded as float32.
|
|
34
50
|
* Used for SF2Pack support
|
|
35
51
|
*/
|
|
36
52
|
constructor(
|
|
@@ -44,9 +60,8 @@ export class SoundFontSample extends BasicSample
|
|
|
44
60
|
samplePitchCorrection,
|
|
45
61
|
linkedSampleIndex,
|
|
46
62
|
sampleType,
|
|
47
|
-
|
|
48
|
-
sampleIndex
|
|
49
|
-
isDataRaw
|
|
63
|
+
sampleDataArray,
|
|
64
|
+
sampleIndex
|
|
50
65
|
)
|
|
51
66
|
{
|
|
52
67
|
// read sf3
|
|
@@ -68,20 +83,47 @@ export class SoundFontSample extends BasicSample
|
|
|
68
83
|
// in bytes
|
|
69
84
|
this.sampleStartIndex = sampleStartIndex;
|
|
70
85
|
this.sampleEndIndex = sampleEndIndex;
|
|
71
|
-
this.isSampleLoaded = false;
|
|
72
86
|
this.sampleID = sampleIndex;
|
|
73
87
|
// in bytes
|
|
74
88
|
this.sampleLength = this.sampleEndIndex - this.sampleStartIndex;
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
const smplStart = sampleDataArray.currentIndex;
|
|
90
|
+
|
|
91
|
+
// three data types in:
|
|
92
|
+
// SF2 (s16le)
|
|
93
|
+
// SF3 (vorbis)
|
|
94
|
+
// SF2Pack (
|
|
77
95
|
if (this.isCompressed)
|
|
78
96
|
{
|
|
79
97
|
// correct loop points
|
|
80
98
|
this.sampleLoopStartIndex += this.sampleStartIndex / 2;
|
|
81
99
|
this.sampleLoopEndIndex += this.sampleStartIndex / 2;
|
|
82
100
|
this.sampleLength = 99999999; // set to 999,999 before we decode it
|
|
101
|
+
|
|
102
|
+
// copy the compressed data, it can be preserved during writing
|
|
103
|
+
this.compressedData = sampleDataArray.slice(
|
|
104
|
+
this.sampleStartIndex / 2 + smplStart,
|
|
105
|
+
this.sampleEndIndex / 2 + smplStart
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
{
|
|
110
|
+
if (sampleDataArray instanceof Float32Array)
|
|
111
|
+
{
|
|
112
|
+
// float32 array from SF2pack, copy directly
|
|
113
|
+
this.sampleData = sampleDataArray.slice(
|
|
114
|
+
this.sampleStartIndex / 2,
|
|
115
|
+
this.sampleEndIndex / 2
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
else
|
|
119
|
+
{
|
|
120
|
+
// regular sf2 s16le
|
|
121
|
+
this.s16leStart = smplStart + this.sampleStartIndex;
|
|
122
|
+
this.s16leEnd = smplStart + this.sampleEndIndex;
|
|
123
|
+
this.sf2FileArrayHandle = sampleDataArray;
|
|
124
|
+
}
|
|
125
|
+
|
|
83
126
|
}
|
|
84
|
-
this.isDataRaw = isDataRaw;
|
|
85
127
|
this.linkedSampleIndex = linkedSampleIndex;
|
|
86
128
|
}
|
|
87
129
|
|
|
@@ -98,66 +140,49 @@ export class SoundFontSample extends BasicSample
|
|
|
98
140
|
}
|
|
99
141
|
|
|
100
142
|
/**
|
|
101
|
-
*
|
|
102
|
-
* @return {Uint8Array} either s16 or vorbis data
|
|
103
|
-
*/
|
|
104
|
-
getRawData()
|
|
105
|
-
{
|
|
106
|
-
const smplArr = this.sampleDataArray;
|
|
107
|
-
if (this.isCompressed)
|
|
108
|
-
{
|
|
109
|
-
if (this.compressedData)
|
|
110
|
-
{
|
|
111
|
-
return this.compressedData;
|
|
112
|
-
}
|
|
113
|
-
const smplStart = smplArr.currentIndex;
|
|
114
|
-
return smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
115
|
-
}
|
|
116
|
-
else
|
|
117
|
-
{
|
|
118
|
-
if (!this.isDataRaw)
|
|
119
|
-
{
|
|
120
|
-
// encode the f32 into s16 manually
|
|
121
|
-
super.getRawData();
|
|
122
|
-
}
|
|
123
|
-
const dataStartIndex = smplArr.currentIndex;
|
|
124
|
-
return smplArr.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
143
|
+
* @private
|
|
129
144
|
* Decode binary vorbis into a float32 pcm
|
|
145
|
+
* @returns {Float32Array}
|
|
130
146
|
*/
|
|
131
147
|
decodeVorbis()
|
|
132
148
|
{
|
|
149
|
+
if (this.sampleData)
|
|
150
|
+
{
|
|
151
|
+
return this.sampleData;
|
|
152
|
+
}
|
|
133
153
|
if (this.sampleLength < 1)
|
|
134
154
|
{
|
|
135
155
|
// eos, do not do anything
|
|
136
|
-
return;
|
|
156
|
+
return new Float32Array(0);
|
|
137
157
|
}
|
|
138
158
|
// get the compressed byte stream
|
|
139
|
-
const smplArr = this.sampleDataArray;
|
|
140
|
-
const smplStart = smplArr.currentIndex;
|
|
141
|
-
const buff = smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
142
159
|
// reset array and being decoding
|
|
143
|
-
this.sampleData = new Float32Array(0);
|
|
144
160
|
try
|
|
145
161
|
{
|
|
146
162
|
/**
|
|
147
163
|
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
148
164
|
*/
|
|
149
|
-
const vorbis = stbvorbis.decode(
|
|
150
|
-
|
|
151
|
-
if (
|
|
165
|
+
const vorbis = stbvorbis.decode(this.compressedData);
|
|
166
|
+
const decoded = vorbis.data[0];
|
|
167
|
+
if (decoded === undefined)
|
|
152
168
|
{
|
|
153
169
|
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
|
|
170
|
+
return new Float32Array(0);
|
|
154
171
|
}
|
|
172
|
+
// clip
|
|
173
|
+
// because vorbis can go above 1 sometimes
|
|
174
|
+
for (let i = 0; i < decoded.length; i++)
|
|
175
|
+
{
|
|
176
|
+
// magic number is 32,767 / 32,768
|
|
177
|
+
decoded[i] = Math.max(-1, Math.min(decoded[i], 0.999969482421875));
|
|
178
|
+
}
|
|
179
|
+
return decoded;
|
|
155
180
|
}
|
|
156
181
|
catch (e)
|
|
157
182
|
{
|
|
158
183
|
// do not error out, fill with silence
|
|
159
184
|
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
|
|
160
|
-
|
|
185
|
+
return new Float32Array(this.sampleLoopEndIndex + 1);
|
|
161
186
|
}
|
|
162
187
|
}
|
|
163
188
|
|
|
@@ -167,8 +192,6 @@ export class SoundFontSample extends BasicSample
|
|
|
167
192
|
setAudioData(audioData)
|
|
168
193
|
{
|
|
169
194
|
super.setAudioData(audioData);
|
|
170
|
-
this.isSampleLoaded = true;
|
|
171
|
-
this.isDataRaw = false;
|
|
172
195
|
}
|
|
173
196
|
|
|
174
197
|
/**
|
|
@@ -177,48 +200,31 @@ export class SoundFontSample extends BasicSample
|
|
|
177
200
|
*/
|
|
178
201
|
getAudioData()
|
|
179
202
|
{
|
|
180
|
-
if (
|
|
203
|
+
if (this.sampleData)
|
|
181
204
|
{
|
|
182
|
-
|
|
183
|
-
if (this.sampleLength < 1)
|
|
184
|
-
{
|
|
185
|
-
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${this.sampleLength}`);
|
|
186
|
-
return new Float32Array(1);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (this.isCompressed)
|
|
190
|
-
{
|
|
191
|
-
// if compressed, decode
|
|
192
|
-
this.decodeVorbis();
|
|
193
|
-
this.isSampleLoaded = true;
|
|
194
|
-
return this.sampleData;
|
|
195
|
-
}
|
|
196
|
-
else if (!this.isDataRaw)
|
|
197
|
-
{
|
|
198
|
-
return this.getUncompressedReadyData();
|
|
199
|
-
}
|
|
200
|
-
return this.loadUncompressedData();
|
|
205
|
+
return this.sampleData;
|
|
201
206
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
* @returns {Float32Array}
|
|
207
|
-
*/
|
|
208
|
-
loadUncompressedData()
|
|
209
|
-
{
|
|
210
|
-
if (this.isCompressed)
|
|
207
|
+
// SF2Pack is decoded during load time
|
|
208
|
+
|
|
209
|
+
// start loading data if it is not loaded
|
|
210
|
+
if (this.sampleLength < 1)
|
|
211
211
|
{
|
|
212
|
-
SpessaSynthWarn(
|
|
213
|
-
return new Float32Array(
|
|
212
|
+
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${this.sampleLength}`);
|
|
213
|
+
return new Float32Array(1);
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
if (this.isCompressed)
|
|
217
|
+
{
|
|
218
|
+
// SF3
|
|
219
|
+
// if compressed, decode
|
|
220
|
+
this.sampleData = this.decodeVorbis();
|
|
221
|
+
return this.sampleData;
|
|
222
|
+
}
|
|
223
|
+
// SF2
|
|
216
224
|
// read the sample data
|
|
217
225
|
let audioData = new Float32Array(this.sampleLength / 2);
|
|
218
|
-
const dataStartIndex = this.sampleDataArray.currentIndex;
|
|
219
226
|
let convertedSigned16 = new Int16Array(
|
|
220
|
-
this.
|
|
221
|
-
.buffer
|
|
227
|
+
this.sf2FileArrayHandle.buffer.slice(this.s16leStart, this.s16leEnd)
|
|
222
228
|
);
|
|
223
229
|
|
|
224
230
|
// convert to float
|
|
@@ -228,26 +234,28 @@ export class SoundFontSample extends BasicSample
|
|
|
228
234
|
}
|
|
229
235
|
|
|
230
236
|
this.sampleData = audioData;
|
|
231
|
-
this.isSampleLoaded = true;
|
|
232
237
|
return audioData;
|
|
238
|
+
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
/**
|
|
236
|
-
* @
|
|
242
|
+
* @param allowVorbis
|
|
243
|
+
* @returns {Uint8Array}
|
|
237
244
|
*/
|
|
238
|
-
|
|
245
|
+
getRawData(allowVorbis = true)
|
|
239
246
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
if (this.dataOverriden)
|
|
248
|
+
{
|
|
249
|
+
return this.encodeS16LE();
|
|
250
|
+
}
|
|
251
|
+
else
|
|
252
|
+
{
|
|
253
|
+
if (this.compressedData && allowVorbis)
|
|
254
|
+
{
|
|
255
|
+
return this.compressedData;
|
|
256
|
+
}
|
|
257
|
+
return this.sf2FileArrayHandle.slice(this.s16leStart, this.s16leEnd);
|
|
258
|
+
}
|
|
251
259
|
}
|
|
252
260
|
}
|
|
253
261
|
|
|
@@ -255,10 +263,10 @@ export class SoundFontSample extends BasicSample
|
|
|
255
263
|
* Reads the generatorTranslator from the shdr read
|
|
256
264
|
* @param sampleHeadersChunk {RiffChunk}
|
|
257
265
|
* @param smplChunkData {IndexedByteArray|Float32Array}
|
|
258
|
-
* @param
|
|
266
|
+
* @param linkSamples {boolean}
|
|
259
267
|
* @returns {SoundFontSample[]}
|
|
260
268
|
*/
|
|
261
|
-
export function readSamples(sampleHeadersChunk, smplChunkData,
|
|
269
|
+
export function readSamples(sampleHeadersChunk, smplChunkData, linkSamples = true)
|
|
262
270
|
{
|
|
263
271
|
/**
|
|
264
272
|
* @type {SoundFontSample[]}
|
|
@@ -267,7 +275,7 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
267
275
|
let index = 0;
|
|
268
276
|
while (sampleHeadersChunk.chunkData.length > sampleHeadersChunk.chunkData.currentIndex)
|
|
269
277
|
{
|
|
270
|
-
const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData
|
|
278
|
+
const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData);
|
|
271
279
|
samples.push(sample);
|
|
272
280
|
index++;
|
|
273
281
|
}
|
|
@@ -275,7 +283,10 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
275
283
|
samples.pop();
|
|
276
284
|
|
|
277
285
|
// link samples
|
|
278
|
-
|
|
286
|
+
if (linkSamples)
|
|
287
|
+
{
|
|
288
|
+
samples.forEach(s => s.getLinkedSample(samples));
|
|
289
|
+
}
|
|
279
290
|
|
|
280
291
|
return samples;
|
|
281
292
|
}
|
|
@@ -285,10 +296,9 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
285
296
|
* @param index {number}
|
|
286
297
|
* @param sampleHeaderData {IndexedByteArray}
|
|
287
298
|
* @param smplArrayData {IndexedByteArray|Float32Array}
|
|
288
|
-
* @param isDataRaw {boolean} true means binary 16-bit data, false means float32
|
|
289
299
|
* @returns {SoundFontSample}
|
|
290
300
|
*/
|
|
291
|
-
function readSample(index, sampleHeaderData, smplArrayData
|
|
301
|
+
function readSample(index, sampleHeaderData, smplArrayData)
|
|
292
302
|
{
|
|
293
303
|
|
|
294
304
|
// read the sample name
|
|
@@ -311,9 +321,9 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
|
|
|
311
321
|
|
|
312
322
|
// read the original sample pitch
|
|
313
323
|
let samplePitch = sampleHeaderData[sampleHeaderData.currentIndex++];
|
|
314
|
-
if (samplePitch
|
|
324
|
+
if (samplePitch > 127)
|
|
315
325
|
{
|
|
316
|
-
// if it's
|
|
326
|
+
// if it's out of range, then default to 60
|
|
317
327
|
samplePitch = 60;
|
|
318
328
|
}
|
|
319
329
|
|
|
@@ -338,7 +348,6 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
|
|
|
338
348
|
sampleLink,
|
|
339
349
|
sampleType,
|
|
340
350
|
smplArrayData,
|
|
341
|
-
index
|
|
342
|
-
isDataRaw
|
|
351
|
+
index
|
|
343
352
|
);
|
|
344
353
|
}
|