spessasynth_core 3.26.24 → 3.26.26
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/index.js +2 -1
- package/package.json +1 -1
- package/src/soundfont/basic_soundfont/basic_sample.js +100 -17
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +10 -3
- package/src/soundfont/basic_soundfont/write_sf2/write.js +1 -1
- package/src/soundfont/read_sf2/samples.js +33 -3
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +10 -0
package/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { SynthesizerSnapshot } from "./src/synthetizer/audio_engine/snapshot/syn
|
|
|
19
19
|
import { ChannelSnapshot } from "./src/synthetizer/audio_engine/snapshot/channel_snapshot.js";
|
|
20
20
|
|
|
21
21
|
import { BasicSoundBank } from "./src/soundfont/basic_soundfont/basic_soundbank.js";
|
|
22
|
-
import { BasicSample } from "./src/soundfont/basic_soundfont/basic_sample.js";
|
|
22
|
+
import { BasicSample, sampleTypes } from "./src/soundfont/basic_soundfont/basic_sample.js";
|
|
23
23
|
import { BasicPresetZone } from "./src/soundfont/basic_soundfont/basic_preset_zone.js";
|
|
24
24
|
import { BasicInstrument } from "./src/soundfont/basic_soundfont/basic_instrument.js";
|
|
25
25
|
import { BasicPreset } from "./src/soundfont/basic_soundfont/basic_preset.js";
|
|
@@ -103,6 +103,7 @@ export {
|
|
|
103
103
|
generatorTypes,
|
|
104
104
|
DLSSources,
|
|
105
105
|
DLSDestinations,
|
|
106
|
+
sampleTypes,
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
// MIDI
|
package/package.json
CHANGED
|
@@ -8,6 +8,21 @@ import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
|
8
8
|
// should be reasonable for most cases
|
|
9
9
|
const RESAMPLE_RATE = 48000;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* @enum {number}
|
|
13
|
+
*/
|
|
14
|
+
export const sampleTypes = {
|
|
15
|
+
monoSample: 1,
|
|
16
|
+
rightSample: 2,
|
|
17
|
+
leftSample: 4,
|
|
18
|
+
linkedSample: 8,
|
|
19
|
+
romMonoSample: 32769,
|
|
20
|
+
romRightSample: 32770,
|
|
21
|
+
romLeftSample: 32772,
|
|
22
|
+
romLinkedSample: 32776
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
|
|
11
26
|
/**
|
|
12
27
|
* @typedef {function} EncodeVorbisFunction
|
|
13
28
|
* @param channelAudioData {Float32Array[]}
|
|
@@ -45,14 +60,14 @@ export class BasicSample
|
|
|
45
60
|
samplePitchCorrection;
|
|
46
61
|
|
|
47
62
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @type {
|
|
63
|
+
* Linked sample, unused if mono
|
|
64
|
+
* @type {BasicSample|undefined}
|
|
50
65
|
*/
|
|
51
|
-
|
|
66
|
+
linkedSample;
|
|
52
67
|
|
|
53
68
|
/**
|
|
54
|
-
*
|
|
55
|
-
* @type {
|
|
69
|
+
* The type of the sample
|
|
70
|
+
* @type {sampleTypes}
|
|
56
71
|
*/
|
|
57
72
|
sampleType;
|
|
58
73
|
|
|
@@ -69,7 +84,7 @@ export class BasicSample
|
|
|
69
84
|
sampleLoopEndIndex;
|
|
70
85
|
|
|
71
86
|
/**
|
|
72
|
-
* Indicates if the sample is compressed
|
|
87
|
+
* Indicates if the sample is compressed using vorbis SF3
|
|
73
88
|
* @type {boolean}
|
|
74
89
|
*/
|
|
75
90
|
isCompressed;
|
|
@@ -97,8 +112,7 @@ export class BasicSample
|
|
|
97
112
|
* @param sampleRate {number} The sample's rate in Hz
|
|
98
113
|
* @param samplePitch {number} The sample's pitch as a MIDI note number
|
|
99
114
|
* @param samplePitchCorrection {number} The sample's pitch correction in cents
|
|
100
|
-
* @param
|
|
101
|
-
* @param sampleType {number} The sample's type, an enum
|
|
115
|
+
* @param sampleType {sampleTypes|number} The sample's type, an enum that can indicate SF3
|
|
102
116
|
* @param loopStart {number} The sample's loop start relative to the sample start in sample points
|
|
103
117
|
* @param loopEnd {number} The sample's loop end relative to the sample start in sample points
|
|
104
118
|
*/
|
|
@@ -107,7 +121,6 @@ export class BasicSample
|
|
|
107
121
|
sampleRate,
|
|
108
122
|
samplePitch,
|
|
109
123
|
samplePitchCorrection,
|
|
110
|
-
sampleLink,
|
|
111
124
|
sampleType,
|
|
112
125
|
loopStart,
|
|
113
126
|
loopEnd
|
|
@@ -117,12 +130,20 @@ export class BasicSample
|
|
|
117
130
|
this.sampleRate = sampleRate;
|
|
118
131
|
this.samplePitch = samplePitch;
|
|
119
132
|
this.samplePitchCorrection = samplePitchCorrection;
|
|
120
|
-
this.sampleLink = sampleLink;
|
|
121
|
-
this.sampleType = sampleType;
|
|
122
133
|
this.sampleLoopStartIndex = loopStart;
|
|
123
134
|
this.sampleLoopEndIndex = loopEnd;
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
this.sampleType = sampleType;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* If the sample is linked to another sample
|
|
140
|
+
* @returns {boolean}
|
|
141
|
+
*/
|
|
142
|
+
get isLinked()
|
|
143
|
+
{
|
|
144
|
+
return this.sampleType === sampleTypes.rightSample ||
|
|
145
|
+
this.sampleType === sampleTypes.leftSample ||
|
|
146
|
+
this.sampleType === sampleTypes.linkedSample;
|
|
126
147
|
}
|
|
127
148
|
|
|
128
149
|
/**
|
|
@@ -189,20 +210,82 @@ export class BasicSample
|
|
|
189
210
|
}
|
|
190
211
|
this.compressedData = encodeVorbis([audioData], 1, this.sampleRate, quality);
|
|
191
212
|
// flag as compressed
|
|
192
|
-
this.sampleType
|
|
193
|
-
this.isCompressed = true;
|
|
213
|
+
this.setSampleType(this.sampleType | 0x10);
|
|
194
214
|
}
|
|
195
215
|
catch (e)
|
|
196
216
|
{
|
|
197
217
|
SpessaSynthWarn(`Failed to compress ${this.sampleName}. Leaving as uncompressed!`);
|
|
198
|
-
this.isCompressed = false;
|
|
199
218
|
this.compressedData = undefined;
|
|
200
219
|
// flag as uncompressed
|
|
201
|
-
this.sampleType
|
|
220
|
+
this.setSampleType(this.sampleType & 0xEF);
|
|
202
221
|
}
|
|
203
222
|
|
|
204
223
|
}
|
|
205
224
|
|
|
225
|
+
/**
|
|
226
|
+
* @param type {sampleTypes|number}
|
|
227
|
+
*/
|
|
228
|
+
setSampleType(type)
|
|
229
|
+
{
|
|
230
|
+
this.sampleType = type;
|
|
231
|
+
if (!this.isLinked)
|
|
232
|
+
{
|
|
233
|
+
// unlink the other sample
|
|
234
|
+
if (this.linkedSample)
|
|
235
|
+
{
|
|
236
|
+
this.linkedSample.linkedSample = undefined;
|
|
237
|
+
this.linkedSample.sampleType = type;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.linkedSample = undefined;
|
|
241
|
+
}
|
|
242
|
+
if ((type & 0x8000) > 0)
|
|
243
|
+
{
|
|
244
|
+
throw new Error("ROM samples are not supported.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// noinspection JSUnusedGlobalSymbols
|
|
250
|
+
/**
|
|
251
|
+
* Unlinks a sample link
|
|
252
|
+
*/
|
|
253
|
+
unlinkSample()
|
|
254
|
+
{
|
|
255
|
+
this.setSampleType(sampleTypes.monoSample);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// noinspection JSUnusedGlobalSymbols
|
|
259
|
+
/**
|
|
260
|
+
* Links a stereo sample
|
|
261
|
+
* @param sample {BasicSample} the sample to link to
|
|
262
|
+
* @param type {sampleTypes} either left, right or linked
|
|
263
|
+
*/
|
|
264
|
+
setLinkedSample(sample, type)
|
|
265
|
+
{
|
|
266
|
+
this.linkedSample = sample;
|
|
267
|
+
sample.linkedSample = this;
|
|
268
|
+
if (type === sampleTypes.leftSample)
|
|
269
|
+
{
|
|
270
|
+
this.setSampleType(sampleTypes.leftSample);
|
|
271
|
+
sample.setSampleType(sampleTypes.rightSample);
|
|
272
|
+
}
|
|
273
|
+
else if (type === sampleTypes.rightSample)
|
|
274
|
+
{
|
|
275
|
+
this.setSampleType(sampleTypes.rightSample);
|
|
276
|
+
sample.setSampleType(sampleTypes.leftSample);
|
|
277
|
+
}
|
|
278
|
+
else if (type === sampleTypes.linkedSample)
|
|
279
|
+
{
|
|
280
|
+
this.setSampleType(sampleTypes.linkedSample);
|
|
281
|
+
sample.setSampleType(sampleTypes.linkedSample);
|
|
282
|
+
}
|
|
283
|
+
else
|
|
284
|
+
{
|
|
285
|
+
throw new Error("Invalid sample type: " + type);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
206
289
|
/**
|
|
207
290
|
* @param instrument {BasicInstrument}
|
|
208
291
|
*/
|
|
@@ -2,6 +2,7 @@ import { IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
|
2
2
|
import { writeStringAsBytes } from "../../../utils/byte_functions/string.js";
|
|
3
3
|
import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
|
|
4
4
|
import { RiffChunk, writeRIFFChunk } from "../riff_chunk.js";
|
|
5
|
+
import { SF3_BIT_FLIT } from "../../read_sf2/samples.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @this {BasicSoundBank}
|
|
@@ -40,9 +41,15 @@ export function getSHDR(smplStartOffsets, smplEndOffsets)
|
|
|
40
41
|
shdrData[shdrData.currentIndex++] = sample.samplePitch;
|
|
41
42
|
shdrData[shdrData.currentIndex++] = sample.samplePitchCorrection;
|
|
42
43
|
// sample link
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const sampleLinkIndex = this.samples.indexOf(sample.linkedSample);
|
|
45
|
+
writeWord(shdrData, Math.max(0, sampleLinkIndex));
|
|
46
|
+
// sample type: add byte if compressed
|
|
47
|
+
let type = sample.sampleType;
|
|
48
|
+
if (sample.isCompressed)
|
|
49
|
+
{
|
|
50
|
+
type |= SF3_BIT_FLIT;
|
|
51
|
+
}
|
|
52
|
+
writeWord(shdrData, type);
|
|
46
53
|
});
|
|
47
54
|
|
|
48
55
|
// write EOS and zero everything else
|
|
@@ -6,8 +6,16 @@ import { SpessaSynthWarn } from "../../utils/loggin.js";
|
|
|
6
6
|
import { readBytesAsString } from "../../utils/byte_functions/string.js";
|
|
7
7
|
import { BasicSample } from "../basic_soundfont/basic_sample.js";
|
|
8
8
|
|
|
9
|
+
export const SF3_BIT_FLIT = 0x10;
|
|
10
|
+
|
|
9
11
|
export class SoundFontSample extends BasicSample
|
|
10
12
|
{
|
|
13
|
+
/**
|
|
14
|
+
* Linked sample index for retrieving linked samples in sf2
|
|
15
|
+
* @type {number}
|
|
16
|
+
*/
|
|
17
|
+
linkedSampleIndex;
|
|
18
|
+
|
|
11
19
|
/**
|
|
12
20
|
* Creates a sample
|
|
13
21
|
* @param sampleName {string}
|
|
@@ -18,7 +26,7 @@ export class SoundFontSample extends BasicSample
|
|
|
18
26
|
* @param sampleRate {number}
|
|
19
27
|
* @param samplePitch {number}
|
|
20
28
|
* @param samplePitchCorrection {number}
|
|
21
|
-
* @param
|
|
29
|
+
* @param linkedSampleIndex {number}
|
|
22
30
|
* @param sampleType {number}
|
|
23
31
|
* @param smplArr {IndexedByteArray|Float32Array}
|
|
24
32
|
* @param sampleIndex {number} initial sample index when loading the sfont
|
|
@@ -34,23 +42,28 @@ export class SoundFontSample extends BasicSample
|
|
|
34
42
|
sampleRate,
|
|
35
43
|
samplePitch,
|
|
36
44
|
samplePitchCorrection,
|
|
37
|
-
|
|
45
|
+
linkedSampleIndex,
|
|
38
46
|
sampleType,
|
|
39
47
|
smplArr,
|
|
40
48
|
sampleIndex,
|
|
41
49
|
isDataRaw
|
|
42
50
|
)
|
|
43
51
|
{
|
|
52
|
+
// read sf3
|
|
53
|
+
// https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format
|
|
54
|
+
const compressed = (sampleType & SF3_BIT_FLIT) > 0;
|
|
55
|
+
// remove the compression flag
|
|
56
|
+
sampleType &= ~SF3_BIT_FLIT;
|
|
44
57
|
super(
|
|
45
58
|
sampleName,
|
|
46
59
|
sampleRate,
|
|
47
60
|
samplePitch,
|
|
48
61
|
samplePitchCorrection,
|
|
49
|
-
sampleLink,
|
|
50
62
|
sampleType,
|
|
51
63
|
sampleLoopStartIndex - (sampleStartIndex / 2),
|
|
52
64
|
sampleLoopEndIndex - (sampleStartIndex / 2)
|
|
53
65
|
);
|
|
66
|
+
this.isCompressed = compressed;
|
|
54
67
|
this.sampleName = sampleName;
|
|
55
68
|
// in bytes
|
|
56
69
|
this.sampleStartIndex = sampleStartIndex;
|
|
@@ -69,6 +82,19 @@ export class SoundFontSample extends BasicSample
|
|
|
69
82
|
this.sampleLength = 99999999; // set to 999,999 before we decode it
|
|
70
83
|
}
|
|
71
84
|
this.isDataRaw = isDataRaw;
|
|
85
|
+
this.linkedSampleIndex = linkedSampleIndex;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param samplesArray {BasicSample[]}
|
|
90
|
+
*/
|
|
91
|
+
getLinkedSample(samplesArray)
|
|
92
|
+
{
|
|
93
|
+
if (this.linkedSample || !this.isLinked)
|
|
94
|
+
{
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.setLinkedSample(samplesArray[this.linkedSampleIndex], this.sampleType);
|
|
72
98
|
}
|
|
73
99
|
|
|
74
100
|
/**
|
|
@@ -247,6 +273,10 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
|
|
|
247
273
|
}
|
|
248
274
|
// remove EOS
|
|
249
275
|
samples.pop();
|
|
276
|
+
|
|
277
|
+
// link samples
|
|
278
|
+
samples.forEach(s => s.getLinkedSample(samples));
|
|
279
|
+
|
|
250
280
|
return samples;
|
|
251
281
|
}
|
|
252
282
|
|
|
@@ -102,6 +102,16 @@ export function resetAllControllers(log = true)
|
|
|
102
102
|
LSB: lsb
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
// restore channel pressure
|
|
107
|
+
if (this.midiAudioChannels[channelNumber].lockedControllers[NON_CC_INDEX_OFFSET + modulatorSources.channelPressure] === false)
|
|
108
|
+
{
|
|
109
|
+
const val = this.midiAudioChannels[channelNumber].midiControllers[NON_CC_INDEX_OFFSET + modulatorSources.channelPressure] >> 7;
|
|
110
|
+
this.callEvent("channelpressure", {
|
|
111
|
+
channel: channelNumber,
|
|
112
|
+
pressure: val
|
|
113
|
+
});
|
|
114
|
+
}
|
|
105
115
|
}
|
|
106
116
|
this.tunings = [];
|
|
107
117
|
this.tunings = [];
|