spessasynth_core 3.26.28 → 3.26.30
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 +121 -103
- 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);
|
|
@@ -4,7 +4,7 @@ import { readLittleEndian, signedInt8 } from "../../utils/byte_functions/little_
|
|
|
4
4
|
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
5
5
|
import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
6
6
|
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
7
|
-
import { BasicSample } from "../basic_soundfont/basic_sample.js";
|
|
7
|
+
import { BasicSample, sampleTypes } from "../basic_soundfont/basic_sample.js";
|
|
8
8
|
|
|
9
9
|
export const SF3_BIT_FLIT = 0x10;
|
|
10
10
|
|
|
@@ -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
|
|
|
@@ -94,70 +136,62 @@ export class SoundFontSample extends BasicSample
|
|
|
94
136
|
{
|
|
95
137
|
return;
|
|
96
138
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Get raw data, whether it's compressed or not as we simply write it to the file
|
|
102
|
-
* @return {Uint8Array} either s16 or vorbis data
|
|
103
|
-
*/
|
|
104
|
-
getRawData()
|
|
105
|
-
{
|
|
106
|
-
const smplArr = this.sampleDataArray;
|
|
107
|
-
if (this.isCompressed)
|
|
139
|
+
const linkedSample = samplesArray[this.linkedSampleIndex];
|
|
140
|
+
if (!linkedSample)
|
|
108
141
|
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return this.compressedData;
|
|
112
|
-
}
|
|
113
|
-
const smplStart = smplArr.currentIndex;
|
|
114
|
-
return smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
|
|
142
|
+
SpessaSynthWarn(`Invalid linked sample for ${this.sampleName}. Setting to mono.`);
|
|
143
|
+
this.setSampleType(sampleTypes.monoSample);
|
|
115
144
|
}
|
|
116
145
|
else
|
|
117
146
|
{
|
|
118
|
-
|
|
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);
|
|
147
|
+
this.setLinkedSample(samplesArray[this.linkedSampleIndex], this.sampleType);
|
|
125
148
|
}
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
/**
|
|
152
|
+
* @private
|
|
129
153
|
* Decode binary vorbis into a float32 pcm
|
|
154
|
+
* @returns {Float32Array}
|
|
130
155
|
*/
|
|
131
156
|
decodeVorbis()
|
|
132
157
|
{
|
|
158
|
+
if (this.sampleData)
|
|
159
|
+
{
|
|
160
|
+
return this.sampleData;
|
|
161
|
+
}
|
|
133
162
|
if (this.sampleLength < 1)
|
|
134
163
|
{
|
|
135
164
|
// eos, do not do anything
|
|
136
|
-
return;
|
|
165
|
+
return new Float32Array(0);
|
|
137
166
|
}
|
|
138
167
|
// 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
168
|
// reset array and being decoding
|
|
143
|
-
this.sampleData = new Float32Array(0);
|
|
144
169
|
try
|
|
145
170
|
{
|
|
146
171
|
/**
|
|
147
172
|
* @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
|
|
148
173
|
*/
|
|
149
|
-
const vorbis = stbvorbis.decode(
|
|
150
|
-
|
|
151
|
-
if (
|
|
174
|
+
const vorbis = stbvorbis.decode(this.compressedData);
|
|
175
|
+
const decoded = vorbis.data[0];
|
|
176
|
+
if (decoded === undefined)
|
|
152
177
|
{
|
|
153
178
|
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
|
|
179
|
+
return new Float32Array(0);
|
|
154
180
|
}
|
|
181
|
+
// clip
|
|
182
|
+
// because vorbis can go above 1 sometimes
|
|
183
|
+
for (let i = 0; i < decoded.length; i++)
|
|
184
|
+
{
|
|
185
|
+
// magic number is 32,767 / 32,768
|
|
186
|
+
decoded[i] = Math.max(-1, Math.min(decoded[i], 0.999969482421875));
|
|
187
|
+
}
|
|
188
|
+
return decoded;
|
|
155
189
|
}
|
|
156
190
|
catch (e)
|
|
157
191
|
{
|
|
158
192
|
// do not error out, fill with silence
|
|
159
193
|
SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
|
|
160
|
-
|
|
194
|
+
return new Float32Array(this.sampleLoopEndIndex + 1);
|
|
161
195
|
}
|
|
162
196
|
}
|
|
163
197
|
|
|
@@ -167,8 +201,6 @@ export class SoundFontSample extends BasicSample
|
|
|
167
201
|
setAudioData(audioData)
|
|
168
202
|
{
|
|
169
203
|
super.setAudioData(audioData);
|
|
170
|
-
this.isSampleLoaded = true;
|
|
171
|
-
this.isDataRaw = false;
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
/**
|
|
@@ -177,48 +209,31 @@ export class SoundFontSample extends BasicSample
|
|
|
177
209
|
*/
|
|
178
210
|
getAudioData()
|
|
179
211
|
{
|
|
180
|
-
if (
|
|
212
|
+
if (this.sampleData)
|
|
181
213
|
{
|
|
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();
|
|
214
|
+
return this.sampleData;
|
|
201
215
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
* @returns {Float32Array}
|
|
207
|
-
*/
|
|
208
|
-
loadUncompressedData()
|
|
209
|
-
{
|
|
210
|
-
if (this.isCompressed)
|
|
216
|
+
// SF2Pack is decoded during load time
|
|
217
|
+
|
|
218
|
+
// start loading data if it is not loaded
|
|
219
|
+
if (this.sampleLength < 1)
|
|
211
220
|
{
|
|
212
|
-
SpessaSynthWarn(
|
|
213
|
-
return new Float32Array(
|
|
221
|
+
SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${this.sampleLength}`);
|
|
222
|
+
return new Float32Array(1);
|
|
214
223
|
}
|
|
215
224
|
|
|
225
|
+
if (this.isCompressed)
|
|
226
|
+
{
|
|
227
|
+
// SF3
|
|
228
|
+
// if compressed, decode
|
|
229
|
+
this.sampleData = this.decodeVorbis();
|
|
230
|
+
return this.sampleData;
|
|
231
|
+
}
|
|
232
|
+
// SF2
|
|
216
233
|
// read the sample data
|
|
217
234
|
let audioData = new Float32Array(this.sampleLength / 2);
|
|
218
|
-
const dataStartIndex = this.sampleDataArray.currentIndex;
|
|
219
235
|
let convertedSigned16 = new Int16Array(
|
|
220
|
-
this.
|
|
221
|
-
.buffer
|
|
236
|
+
this.sf2FileArrayHandle.buffer.slice(this.s16leStart, this.s16leEnd)
|
|
222
237
|
);
|
|
223
238
|
|
|
224
239
|
// convert to float
|
|
@@ -228,26 +243,28 @@ export class SoundFontSample extends BasicSample
|
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
this.sampleData = audioData;
|
|
231
|
-
this.isSampleLoaded = true;
|
|
232
246
|
return audioData;
|
|
247
|
+
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
/**
|
|
236
|
-
* @
|
|
251
|
+
* @param allowVorbis
|
|
252
|
+
* @returns {Uint8Array}
|
|
237
253
|
*/
|
|
238
|
-
|
|
254
|
+
getRawData(allowVorbis = true)
|
|
239
255
|
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
256
|
+
if (this.dataOverriden)
|
|
257
|
+
{
|
|
258
|
+
return this.encodeS16LE();
|
|
259
|
+
}
|
|
260
|
+
else
|
|
261
|
+
{
|
|
262
|
+
if (this.compressedData && allowVorbis)
|
|
263
|
+
{
|
|
264
|
+
return this.compressedData;
|
|
265
|
+
}
|
|
266
|
+
return this.sf2FileArrayHandle.slice(this.s16leStart, this.s16leEnd);
|
|
267
|
+
}
|
|
251
268
|
}
|
|
252
269
|
}
|
|
253
270
|
|
|
@@ -255,10 +272,10 @@ export class SoundFontSample extends BasicSample
|
|
|
255
272
|
* Reads the generatorTranslator from the shdr read
|
|
256
273
|
* @param sampleHeadersChunk {RiffChunk}
|
|
257
274
|
* @param smplChunkData {IndexedByteArray|Float32Array}
|
|
258
|
-
* @param
|
|
275
|
+
* @param linkSamples {boolean}
|
|
259
276
|
* @returns {SoundFontSample[]}
|
|
260
277
|
*/
|
|
261
|
-
export function readSamples(sampleHeadersChunk, smplChunkData,
|
|
278
|
+
export function readSamples(sampleHeadersChunk, smplChunkData, linkSamples = true)
|
|
262
279
|
{
|
|
263
280
|
/**
|
|
264
281
|
* @type {SoundFontSample[]}
|
|
@@ -267,7 +284,7 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
267
284
|
let index = 0;
|
|
268
285
|
while (sampleHeadersChunk.chunkData.length > sampleHeadersChunk.chunkData.currentIndex)
|
|
269
286
|
{
|
|
270
|
-
const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData
|
|
287
|
+
const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData);
|
|
271
288
|
samples.push(sample);
|
|
272
289
|
index++;
|
|
273
290
|
}
|
|
@@ -275,7 +292,10 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
275
292
|
samples.pop();
|
|
276
293
|
|
|
277
294
|
// link samples
|
|
278
|
-
|
|
295
|
+
if (linkSamples)
|
|
296
|
+
{
|
|
297
|
+
samples.forEach(s => s.getLinkedSample(samples));
|
|
298
|
+
}
|
|
279
299
|
|
|
280
300
|
return samples;
|
|
281
301
|
}
|
|
@@ -285,10 +305,9 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
285
305
|
* @param index {number}
|
|
286
306
|
* @param sampleHeaderData {IndexedByteArray}
|
|
287
307
|
* @param smplArrayData {IndexedByteArray|Float32Array}
|
|
288
|
-
* @param isDataRaw {boolean} true means binary 16-bit data, false means float32
|
|
289
308
|
* @returns {SoundFontSample}
|
|
290
309
|
*/
|
|
291
|
-
function readSample(index, sampleHeaderData, smplArrayData
|
|
310
|
+
function readSample(index, sampleHeaderData, smplArrayData)
|
|
292
311
|
{
|
|
293
312
|
|
|
294
313
|
// read the sample name
|
|
@@ -311,9 +330,9 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
|
|
|
311
330
|
|
|
312
331
|
// read the original sample pitch
|
|
313
332
|
let samplePitch = sampleHeaderData[sampleHeaderData.currentIndex++];
|
|
314
|
-
if (samplePitch
|
|
333
|
+
if (samplePitch > 127)
|
|
315
334
|
{
|
|
316
|
-
// if it's
|
|
335
|
+
// if it's out of range, then default to 60
|
|
317
336
|
samplePitch = 60;
|
|
318
337
|
}
|
|
319
338
|
|
|
@@ -338,7 +357,6 @@ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
|
|
|
338
357
|
sampleLink,
|
|
339
358
|
sampleType,
|
|
340
359
|
smplArrayData,
|
|
341
|
-
index
|
|
342
|
-
isDataRaw
|
|
360
|
+
index
|
|
343
361
|
);
|
|
344
362
|
}
|