spessasynth_core 3.26.23 → 3.26.25

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.23",
3
+ "version": "3.26.25",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
- * Sample link, currently unused here
49
- * @type {number}
63
+ * Linked sample, unused if mono
64
+ * @type {BasicSample|undefined}
50
65
  */
51
- sampleLink;
66
+ linkedSample;
52
67
 
53
68
  /**
54
- * Type of the sample, currently only used for SF3
55
- * @type {number}
69
+ * The type of the sample, it can indicate an SF3 sample
70
+ * @type {sampleTypes|number}
56
71
  */
57
72
  sampleType;
58
73
 
@@ -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 sampleLink {number} The sample's link, currently unused
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,21 @@ 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
- // https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format
125
- this.isCompressed = (sampleType & 0x10) > 0;
135
+ this.setSampleType(sampleType);
136
+ }
137
+
138
+ /**
139
+ * If the sample is linked to another sample
140
+ * @returns {boolean}
141
+ */
142
+ get isLinked()
143
+ {
144
+ return !this.isCompressed &&
145
+ (this.sampleType === sampleTypes.rightSample ||
146
+ this.sampleType === sampleTypes.leftSample ||
147
+ this.sampleType === sampleTypes.linkedSample);
126
148
  }
127
149
 
128
150
  /**
@@ -189,20 +211,88 @@ export class BasicSample
189
211
  }
190
212
  this.compressedData = encodeVorbis([audioData], 1, this.sampleRate, quality);
191
213
  // flag as compressed
192
- this.sampleType |= 0x10;
193
- this.isCompressed = true;
214
+ this.setSampleType(this.sampleType | 0x10);
194
215
  }
195
216
  catch (e)
196
217
  {
197
218
  SpessaSynthWarn(`Failed to compress ${this.sampleName}. Leaving as uncompressed!`);
198
- this.isCompressed = false;
199
219
  this.compressedData = undefined;
200
220
  // flag as uncompressed
201
- this.sampleType &= 0xEF;
221
+ this.setSampleType(this.sampleType & 0xEF);
222
+ }
223
+
224
+ }
225
+
226
+ /**
227
+ * @param type {sampleTypes|number}
228
+ */
229
+ setSampleType(type)
230
+ {
231
+ this.sampleType = type;
232
+ // https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format
233
+ this.isCompressed = (type & 0x10) > 0;
234
+ if (!this.isLinked)
235
+ {
236
+ // unlink the other sample
237
+ if (this.linkedSample)
238
+ {
239
+ this.linkedSample.linkedSample = undefined;
240
+ this.linkedSample.sampleType = type;
241
+ }
242
+
243
+ this.linkedSample = undefined;
244
+ }
245
+ if ((type & 0x8000) > 0 && this.linkedSample)
246
+ {
247
+ throw new Error("ROM samples cannot be linked.");
202
248
  }
203
249
 
204
250
  }
205
251
 
252
+ // noinspection JSUnusedGlobalSymbols
253
+ /**
254
+ * Unlinks a sample link
255
+ */
256
+ unlinkSample()
257
+ {
258
+ this.setSampleType(sampleTypes.monoSample);
259
+ }
260
+
261
+ // noinspection JSUnusedGlobalSymbols
262
+ /**
263
+ * Links a stereo sample
264
+ * @param sample {BasicSample}
265
+ * @param type {sampleTypes}
266
+ */
267
+ setLinkedSample(sample, type)
268
+ {
269
+ if (this.isCompressed)
270
+ {
271
+ throw new Error("Cannot link a compressed sample.");
272
+ }
273
+ this.linkedSample = sample;
274
+ sample.linkedSample = this;
275
+ if (type === sampleTypes.leftSample)
276
+ {
277
+ this.setSampleType(sampleTypes.leftSample);
278
+ sample.setSampleType(sampleTypes.rightSample);
279
+ }
280
+ else if (type === sampleTypes.rightSample)
281
+ {
282
+ this.setSampleType(sampleTypes.rightSample);
283
+ sample.setSampleType(sampleTypes.leftSample);
284
+ }
285
+ else if (type === sampleTypes.linkedSample)
286
+ {
287
+ this.setSampleType(sampleTypes.linkedSample);
288
+ sample.setSampleType(sampleTypes.linkedSample);
289
+ }
290
+ else
291
+ {
292
+ throw new Error("Invalid sample type: " + type);
293
+ }
294
+ }
295
+
206
296
  /**
207
297
  * @param instrument {BasicInstrument}
208
298
  */
@@ -226,6 +316,7 @@ export class BasicSample
226
316
 
227
317
  /**
228
318
  * @returns {Float32Array}
319
+ * @virtual
229
320
  */
230
321
  getAudioData()
231
322
  {
@@ -239,6 +330,7 @@ export class BasicSample
239
330
  // noinspection JSUnusedGlobalSymbols
240
331
  /**
241
332
  * @param audioData {Float32Array}
333
+ * @virtual
242
334
  */
243
335
  setAudioData(audioData)
244
336
  {
@@ -40,7 +40,8 @@ export function getSHDR(smplStartOffsets, smplEndOffsets)
40
40
  shdrData[shdrData.currentIndex++] = sample.samplePitch;
41
41
  shdrData[shdrData.currentIndex++] = sample.samplePitchCorrection;
42
42
  // sample link
43
- writeWord(shdrData, sample.sampleLink);
43
+ const sampleLinkIndex = this.samples.indexOf(sample.linkedSample);
44
+ writeWord(shdrData, Math.max(0, sampleLinkIndex));
44
45
  // sample type: write raw because we simply copy compressed samples
45
46
  writeWord(shdrData, sample.sampleType);
46
47
  });
@@ -37,7 +37,7 @@ const DEFAULT_WRITE_OPTIONS = {
37
37
  };
38
38
 
39
39
  /**
40
- * Write the soundfont as an .sf2 file. This method is DESTRUCTIVE
40
+ * Write the soundfont as an .sf2 file
41
41
  * @this {BasicSoundBank}
42
42
  * @param {SoundFont2WriteOptions} options
43
43
  * @returns {Uint8Array}
@@ -52,6 +52,14 @@ export class DLSSample extends BasicSample
52
52
  return this.sampleData;
53
53
  }
54
54
 
55
+ /**
56
+ * @param audioData {Float32Array}
57
+ */
58
+ setAudioData(audioData)
59
+ {
60
+ super.setAudioData(audioData);
61
+ }
62
+
55
63
  getRawData()
56
64
  {
57
65
  if (this.isCompressed)
@@ -8,6 +8,12 @@ import { BasicSample } from "../basic_soundfont/basic_sample.js";
8
8
 
9
9
  export class SoundFontSample extends BasicSample
10
10
  {
11
+ /**
12
+ * Linked sample index for retrieving linked samples in sf2
13
+ * @type {number}
14
+ */
15
+ linkedSampleIndex;
16
+
11
17
  /**
12
18
  * Creates a sample
13
19
  * @param sampleName {string}
@@ -18,7 +24,7 @@ export class SoundFontSample extends BasicSample
18
24
  * @param sampleRate {number}
19
25
  * @param samplePitch {number}
20
26
  * @param samplePitchCorrection {number}
21
- * @param sampleLink {number}
27
+ * @param linkedSampleIndex {number}
22
28
  * @param sampleType {number}
23
29
  * @param smplArr {IndexedByteArray|Float32Array}
24
30
  * @param sampleIndex {number} initial sample index when loading the sfont
@@ -34,7 +40,7 @@ export class SoundFontSample extends BasicSample
34
40
  sampleRate,
35
41
  samplePitch,
36
42
  samplePitchCorrection,
37
- sampleLink,
43
+ linkedSampleIndex,
38
44
  sampleType,
39
45
  smplArr,
40
46
  sampleIndex,
@@ -46,7 +52,6 @@ export class SoundFontSample extends BasicSample
46
52
  sampleRate,
47
53
  samplePitch,
48
54
  samplePitchCorrection,
49
- sampleLink,
50
55
  sampleType,
51
56
  sampleLoopStartIndex - (sampleStartIndex / 2),
52
57
  sampleLoopEndIndex - (sampleStartIndex / 2)
@@ -69,6 +74,19 @@ export class SoundFontSample extends BasicSample
69
74
  this.sampleLength = 99999999; // set to 999,999 before we decode it
70
75
  }
71
76
  this.isDataRaw = isDataRaw;
77
+ this.linkedSampleIndex = linkedSampleIndex;
78
+ }
79
+
80
+ /**
81
+ * @param samplesArray {BasicSample[]}
82
+ */
83
+ getLinkedSample(samplesArray)
84
+ {
85
+ if (this.isCompressed || this.linkedSample || !this.isLinked)
86
+ {
87
+ return;
88
+ }
89
+ this.setLinkedSample(samplesArray[this.linkedSampleIndex], this.sampleType);
72
90
  }
73
91
 
74
92
  /**
@@ -135,6 +153,16 @@ export class SoundFontSample extends BasicSample
135
153
  }
136
154
  }
137
155
 
156
+ /**
157
+ * @param audioData {Float32Array}
158
+ */
159
+ setAudioData(audioData)
160
+ {
161
+ super.setAudioData(audioData);
162
+ this.isSampleLoaded = true;
163
+ this.isDataRaw = false;
164
+ }
165
+
138
166
  /**
139
167
  * Loads the audio data and stores it for reuse
140
168
  * @returns {Float32Array} The audioData
@@ -237,6 +265,10 @@ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = t
237
265
  }
238
266
  // remove EOS
239
267
  samples.pop();
268
+
269
+ // link samples
270
+ samples.forEach(s => s.getLinkedSample(samples));
271
+
240
272
  return samples;
241
273
  }
242
274
 
@@ -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 = [];