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 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.24",
3
+ "version": "3.26.26",
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
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 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,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
- // https://github.com/FluidSynth/fluidsynth/wiki/SoundFont3Format
125
- this.isCompressed = (sampleType & 0x10) > 0;
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 |= 0x10;
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 &= 0xEF;
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
- writeWord(shdrData, sample.sampleLink);
44
- // sample type: write raw because we simply copy compressed samples
45
- writeWord(shdrData, sample.sampleType);
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
@@ -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}
@@ -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 sampleLink {number}
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
- sampleLink,
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 = [];