spessasynth_core 3.26.18 → 3.26.20

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/README.md CHANGED
@@ -79,7 +79,6 @@ npm install --save spessasynth_core
79
79
  - **Smart preloading:** Only preloads the samples used in the MIDI file for smooth playback *(down to key and velocity!)*
80
80
  - **Lyrics support:** Add karaoke to your program!
81
81
  - **Raw lyrics available:** Decode in any encoding! *(Kanji? No problem!)*
82
- - **Runs in Audio Thread as well:** Never blocks the main thread
83
82
  - **Loop points support:** Ensures seamless loops
84
83
 
85
84
  ### Read and Write SoundFont and MIDI Files with Ease
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.18",
3
+ "version": "3.26.20",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -37,12 +37,12 @@ export class BasicInstrument
37
37
  }
38
38
 
39
39
  /**
40
- * @param zone {BasicInstrumentZone}
40
+ * @param zones {BasicInstrumentZone}
41
41
  */
42
- addZone(zone)
42
+ addZones(...zones)
43
43
  {
44
- zone.useCount++;
45
- this.instrumentZones.push(zone);
44
+ zones.forEach(z => z.useCount++);
45
+ this.instrumentZones.push(...zones);
46
46
  }
47
47
 
48
48
  addUseCount()
@@ -75,6 +75,14 @@ export class BasicPreset
75
75
  this.parentSoundBank = parentSoundBank;
76
76
  }
77
77
 
78
+ /**
79
+ * @param zones {BasicPresetZone}
80
+ */
81
+ addZones(...zones)
82
+ {
83
+ this.presetZones.push(...zones);
84
+ }
85
+
78
86
  /**
79
87
  * @param allowXG {boolean}
80
88
  * @param allowSFX {boolean}
@@ -67,15 +67,47 @@ class BasicSoundBank
67
67
  isXGBank = false;
68
68
 
69
69
  /**
70
- * Creates a new basic soundfont template
70
+ * Creates a new basic soundfont template (or copies)
71
71
  * @param data {undefined|{presets: BasicPreset[], info: Object<string, string>}}
72
72
  */
73
73
  constructor(data = undefined)
74
74
  {
75
75
  if (data?.presets)
76
76
  {
77
- this.presets.push(...data.presets);
78
77
  this.soundFontInfo = data.info;
78
+ this.addPresets(...data.presets);
79
+ /**
80
+ * @type {BasicInstrument[]}
81
+ */
82
+ const instrumentList = [];
83
+ for (const preset of data.presets)
84
+ {
85
+ for (const zone of preset.presetZones)
86
+ {
87
+ if (!instrumentList.includes(zone.instrument))
88
+ {
89
+ instrumentList.push(zone.instrument);
90
+ }
91
+ }
92
+ }
93
+ this.addInstruments(...instrumentList);
94
+
95
+ /**
96
+ * @type {BasicSample[]}
97
+ */
98
+ const sampleList = [];
99
+
100
+ for (const instrument of instrumentList)
101
+ {
102
+ for (const zone of instrument.instrumentZones)
103
+ {
104
+ if (!sampleList.includes(zone.sample))
105
+ {
106
+ sampleList.push(zone.sample);
107
+ }
108
+ }
109
+ }
110
+ this.addSamples(...sampleList);
79
111
  }
80
112
  }
81
113
 
@@ -127,43 +159,74 @@ class BasicSoundBank
127
159
  {
128
160
  sample.sampleData[i] = (i / 128) * 2 - 1;
129
161
  }
130
- font.samples.push(sample);
162
+ font.addSamples(sample);
131
163
 
132
164
  const gZone = new BasicGlobalZone();
133
- gZone.generators.push(new Generator(generatorTypes.initialAttenuation, 375));
134
- gZone.generators.push(new Generator(generatorTypes.releaseVolEnv, -1000));
135
- gZone.generators.push(new Generator(generatorTypes.sampleModes, 1));
165
+ gZone.addGenerators(
166
+ new Generator(generatorTypes.initialAttenuation, 375),
167
+ new Generator(generatorTypes.releaseVolEnv, -1000),
168
+ new Generator(generatorTypes.sampleModes, 1)
169
+ );
136
170
 
137
171
  const zone1 = new BasicInstrumentZone();
138
172
  zone1.setSample(sample);
139
173
 
140
174
  const zone2 = new BasicInstrumentZone();
141
175
  zone2.setSample(sample);
142
- zone2.generators.push(new Generator(generatorTypes.fineTune, -9));
176
+ zone2.addGenerators(new Generator(generatorTypes.fineTune, -9));
143
177
 
144
178
 
145
179
  const inst = new BasicInstrument();
146
180
  inst.instrumentName = "Saw Wave";
147
181
  inst.globalZone = gZone;
148
- inst.addZone(zone1);
149
- inst.addZone(zone2);
150
- font.instruments.push(inst);
182
+ inst.addZones(zone1, zone2);
183
+ font.addInstruments(inst);
151
184
 
152
185
  const pZone = new BasicPresetZone();
153
186
  pZone.setInstrument(inst);
154
187
 
155
188
  const preset = new BasicPreset(font);
156
189
  preset.presetName = "Saw Wave";
157
- preset.presetZones.push(pZone);
158
- font.presets.push(preset);
190
+ preset.addZones(pZone);
191
+ font.addPresets(preset);
159
192
 
160
193
  font.soundFontInfo["ifil"] = "2.1";
161
194
  font.soundFontInfo["isng"] = "EMU8000";
162
195
  font.soundFontInfo["INAM"] = "Dummy";
163
- font._parseInternal();
196
+ font.flush();
164
197
  return font.write().buffer;
165
198
  }
166
199
 
200
+ /**
201
+ * @param preset {BasicPreset}
202
+ */
203
+ addPresets(...preset)
204
+ {
205
+ this.presets.push(...preset);
206
+ }
207
+
208
+ flush()
209
+ {
210
+ this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
211
+ this._parseInternal();
212
+ }
213
+
214
+ /**
215
+ * @param instrument {BasicInstrument}
216
+ */
217
+ addInstruments(...instrument)
218
+ {
219
+ this.instruments.push(...instrument);
220
+ }
221
+
222
+ /**
223
+ * @param sample {BasicSample}
224
+ */
225
+ addSamples(...sample)
226
+ {
227
+ this.samples.push(...sample);
228
+ }
229
+
167
230
  /**
168
231
  * parses the bank after loading is done
169
232
  * @protected
@@ -3,6 +3,8 @@
3
3
  * @property {number} min - the minimum midi note
4
4
  * @property {number} max - the maximum midi note
5
5
  */
6
+ import { generatorTypes } from "./generator_types.js";
7
+ import { Generator } from "./generator.js";
6
8
 
7
9
  export class BasicZone
8
10
  {
@@ -47,6 +49,76 @@ export class BasicZone
47
49
  return this.velRange.min !== -1;
48
50
  }
49
51
 
52
+ /**
53
+ * Adds at the start
54
+ * @param generator {Generator}
55
+ */
56
+ prependGenerator(generator)
57
+ {
58
+ this.generators.unshift(generator);
59
+ }
60
+
61
+ /**
62
+ * @param type {generatorTypes}
63
+ * @param value {number}
64
+ */
65
+ setGenerator(type, value)
66
+ {
67
+ switch (type)
68
+ {
69
+ case generatorTypes.sampleID:
70
+ throw new Error("Use setSample()");
71
+ case generatorTypes.instrument:
72
+ throw new Error("Use setInstrument()");
73
+
74
+ case generatorTypes.velRange:
75
+ case generatorTypes.keyRange:
76
+ throw new Error("Set the range manually");
77
+ }
78
+ let generator = this.generators.find(g => g.generatorType === type);
79
+ if (generator)
80
+ {
81
+ generator.generatorValue = value;
82
+ }
83
+ else
84
+ {
85
+ this.addGenerators(new Generator(type, value));
86
+ }
87
+ }
88
+
89
+ /**
90
+ * @param generators {Generator}
91
+ */
92
+ addGenerators(...generators)
93
+ {
94
+ generators.forEach(g =>
95
+ {
96
+ switch (g.generatorType)
97
+ {
98
+ default:
99
+ this.generators.push(g);
100
+ break;
101
+
102
+ case generatorTypes.velRange:
103
+ this.velRange.min = g.generatorValue & 0x7F;
104
+ this.velRange.max = (g.generatorValue >> 8) & 0x7F;
105
+ break;
106
+
107
+ case generatorTypes.keyRange:
108
+ this.keyRange.min = g.generatorValue & 0x7F;
109
+ this.keyRange.max = (g.generatorValue >> 8) & 0x7F;
110
+ }
111
+ });
112
+ }
113
+
114
+ /**
115
+ * @param modulators {Modulator}
116
+ */
117
+ addModulators(...modulators)
118
+ {
119
+ this.modulators.push(...modulators);
120
+ }
121
+
50
122
  /**
51
123
  * @param generatorType {generatorTypes}
52
124
  * @param notFoundValue {number}
@@ -68,15 +68,6 @@ export function addAndClampGenerator(generatorType, presetGens, instrumentGens)
68
68
  instruValue = instruGen.generatorValue;
69
69
  }
70
70
 
71
- let value = instruValue + presetValue;
72
-
73
- // Special case, initial attenuation.
74
- // Shall get clamped in the volume envelope,
75
- // so the modulators can be affected by negative generators (the "Brass" patch was problematic...)
76
- if (generatorType === generatorTypes.initialAttenuation)
77
- {
78
- return value;
79
- }
80
-
81
- return Math.max(limits.min, Math.min(limits.max, value));
71
+ // limits are applied in the compute_modulator function
72
+ return instruValue + presetValue;
82
73
  }
@@ -150,6 +150,7 @@ export class Modulator
150
150
  * @param destination {generatorTypes}
151
151
  * @param amount {number}
152
152
  * @param transformType {0|2}
153
+ * @param isEffectModulator {boolean}
153
154
  */
154
155
  constructor(sourceIndex,
155
156
  sourceCurveType,
@@ -163,7 +164,8 @@ export class Modulator
163
164
  secSrcDirection,
164
165
  destination,
165
166
  amount,
166
- transformType)
167
+ transformType,
168
+ isEffectModulator = false)
167
169
  {
168
170
  this.sourcePolarity = sourcePolarity;
169
171
  this.sourceDirection = sourceDirection;
@@ -180,6 +182,7 @@ export class Modulator
180
182
  this.modulatorDestination = destination;
181
183
  this.transformAmount = amount;
182
184
  this.transformType = transformType;
185
+ this.isEffectModulator = isEffectModulator;
183
186
 
184
187
 
185
188
  if (this.modulatorDestination > MAX_GENERATOR)
@@ -208,7 +211,8 @@ export class Modulator
208
211
  modulator.secSrcDirection,
209
212
  modulator.modulatorDestination,
210
213
  modulator.transformAmount,
211
- modulator.transformType
214
+ modulator.transformType,
215
+ modulator.isEffectModulator
212
216
  );
213
217
  }
214
218
 
@@ -322,7 +326,8 @@ export class Modulator
322
326
  this.secSrcDirection,
323
327
  this.modulatorDestination,
324
328
  this.transformAmount + modulator.transformAmount,
325
- this.transformType
329
+ this.transformType,
330
+ this.isEffectModulator
326
331
  );
327
332
  }
328
333
  }
@@ -308,7 +308,7 @@ export function combineZones(preset, globalize = true)
308
308
  // if the global value is the default value just remove it, no need to add it
309
309
  if (targetValue !== defaultForChecked)
310
310
  {
311
- globalZone.generators.push(new Generator(checkedType, targetValue));
311
+ globalZone.addGenerators(new Generator(checkedType, targetValue));
312
312
  }
313
313
  // remove from the zones
314
314
  finalZones.forEach(z =>
@@ -329,7 +329,7 @@ export function combineZones(preset, globalize = true)
329
329
  // Since we're globalizing, we need to add the default here.
330
330
  if (targetValue !== defaultForChecked)
331
331
  {
332
- z.generators.push(new Generator(checkedType, defaultForChecked));
332
+ z.addGenerators(new Generator(checkedType, defaultForChecked));
333
333
  }
334
334
  }
335
335
  });
@@ -360,7 +360,7 @@ export function combineZones(preset, globalize = true)
360
360
  }
361
361
  if (existsForAllZones === true)
362
362
  {
363
- globalZone.modulators.push(Modulator.copy(checkedModulator));
363
+ globalZone.addModulators(Modulator.copy(checkedModulator));
364
364
  // delete it from local zones.
365
365
  for (const zone of finalZones)
366
366
  {
@@ -18,16 +18,16 @@ export function getIGEN()
18
18
  igensize += inst.instrumentZones.reduce((sum, z) =>
19
19
  {
20
20
  // clear sample and range generators before determining the size
21
- z.generators = z.generators.filter(g =>
21
+ z.generators = (z.generators.filter(g =>
22
22
  g.generatorType !== generatorTypes.sampleID &&
23
23
  g.generatorType !== generatorTypes.keyRange &&
24
24
  g.generatorType !== generatorTypes.velRange
25
- );
25
+ ));
26
26
  // add sample and ranges if necessary
27
27
  // unshift vel then key (to make key first) and the sample is last
28
28
  if (z.hasVelRange)
29
29
  {
30
- z.generators.unshift(new Generator(
30
+ z.prependGenerator(new Generator(
31
31
  generatorTypes.velRange,
32
32
  z.velRange.max << 8 | Math.max(z.velRange.min, 0),
33
33
  false
@@ -35,14 +35,14 @@ export function getIGEN()
35
35
  }
36
36
  if (z.hasKeyRange)
37
37
  {
38
- z.generators.unshift(new Generator(
38
+ z.prependGenerator(new Generator(
39
39
  generatorTypes.keyRange,
40
40
  z.keyRange.max << 8 | Math.max(z.keyRange.min, 0),
41
41
  false
42
42
  ));
43
43
  }
44
44
  // add sample id
45
- z.generators.push(new Generator(
45
+ z.addGenerators(new Generator(
46
46
  generatorTypes.sampleID,
47
47
  this.samples.indexOf(z.sample),
48
48
  false
@@ -16,7 +16,7 @@ export function getIMOD()
16
16
  {
17
17
  imodsize += inst.globalZone.modulators.length * MOD_BYTE_SIZE;
18
18
  // start with one mod for global
19
- imodsize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * 10 + sum, 0);
19
+ imodsize += inst.instrumentZones.reduce((sum, z) => z.modulators.length * MOD_BYTE_SIZE + sum, 0);
20
20
  }
21
21
  const imoddata = new IndexedByteArray(imodsize);
22
22
 
@@ -46,7 +46,7 @@ export function getIMOD()
46
46
  }
47
47
 
48
48
  // terminal modulator, is zero
49
- writeLittleEndian(imoddata, 0, 10);
49
+ writeLittleEndian(imoddata, 0, MOD_BYTE_SIZE);
50
50
 
51
51
  return writeRIFFChunk(new RiffChunk(
52
52
  "imod",
@@ -28,7 +28,7 @@ export function getPGEN()
28
28
  // unshift vel then key and instrument is last
29
29
  if (z.hasVelRange)
30
30
  {
31
- z.generators.unshift(new Generator(
31
+ z.prependGenerator(new Generator(
32
32
  generatorTypes.velRange,
33
33
  z.velRange.max << 8 | Math.max(z.velRange.min, 0),
34
34
  false
@@ -36,14 +36,14 @@ export function getPGEN()
36
36
  }
37
37
  if (z.hasKeyRange)
38
38
  {
39
- z.generators.unshift(new Generator(
39
+ z.prependGenerator(new Generator(
40
40
  generatorTypes.keyRange,
41
41
  z.keyRange.max << 8 | Math.max(z.keyRange.min, 0),
42
42
  false
43
43
  ));
44
44
  }
45
45
  // write the instrument id
46
- z.generators.push(new Generator(
46
+ z.addGenerators(new Generator(
47
47
  generatorTypes.instrument,
48
48
  this.instruments.indexOf(z.instrument),
49
49
  false
@@ -46,7 +46,7 @@ export function getPMOD()
46
46
  }
47
47
 
48
48
  // terminal modulator, is zero
49
- writeLittleEndian(pmoddata, 0, 10);
49
+ writeLittleEndian(pmoddata, 0, MOD_BYTE_SIZE);
50
50
 
51
51
  return writeRIFFChunk(new RiffChunk(
52
52
  "pmod",
@@ -14,12 +14,16 @@ import { getPBAG } from "./pbag.js";
14
14
  import { getPHDR } from "./phdr.js";
15
15
  import { writeLittleEndian, writeWord } from "../../../utils/byte_functions/little_endian.js";
16
16
  import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
17
+ import { MOD_BYTE_SIZE } from "../modulator.js";
18
+ import { fillWithDefaults } from "../../../utils/fill_with_defaults.js";
17
19
  /**
18
20
  * @typedef {Object} SoundFont2WriteOptions
19
- * @property {boolean} compress - if the soundfont should be compressed with the Ogg Vorbis codec
20
- * @property {number} compressionQuality - the vorbis compression quality, from -0.1 to 1
21
- * @property {EncodeVorbisFunction|undefined} compressionFunction - the encode vorbis function.
22
- * Can be undefined if not compressed.
21
+ * @property {boolean|undefined} compress - if the soundfont should be compressed with the Ogg Vorbis codec
22
+ * @property {number|undefined} compressionQuality - the vorbis compression quality, from -0.1 to 1
23
+ * @property {EncodeVorbisFunction|undefined} compressionFunction -
24
+ * the encode vorbis function. Can be undefined if not compressed.
25
+ * @property {boolean|undefined} writeDefaultModulators - if the DMOD chunk should be written.
26
+ * Recommended.
23
27
  */
24
28
 
25
29
  /**
@@ -28,7 +32,8 @@ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from
28
32
  const DEFAULT_WRITE_OPTIONS = {
29
33
  compress: false,
30
34
  compressionQuality: 0.5,
31
- compressionFunction: undefined
35
+ compressionFunction: undefined,
36
+ writeDefaultModulators: true
32
37
  };
33
38
 
34
39
  /**
@@ -39,6 +44,7 @@ const DEFAULT_WRITE_OPTIONS = {
39
44
  */
40
45
  export function write(options = DEFAULT_WRITE_OPTIONS)
41
46
  {
47
+ options = fillWithDefaults(options, DEFAULT_WRITE_OPTIONS);
42
48
  if (options.compress)
43
49
  {
44
50
  if (typeof options.compressionFunction !== "function")
@@ -72,7 +78,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
72
78
  this.soundFontInfo["ifil"] = "3.0"; // set version to 3
73
79
  }
74
80
 
75
- if (this.defaultModulators.length > 0)
81
+ if (options?.writeDefaultModulators)
76
82
  {
77
83
  // trigger the DMOD write
78
84
  this.soundFontInfo["DMOD"] = `${this.defaultModulators.length} Modulators`;
@@ -108,7 +114,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
108
114
  consoleColors.recognized,
109
115
  consoleColors.info
110
116
  );
111
- let dmodsize = 10 + mods.length * 10;
117
+ let dmodsize = MOD_BYTE_SIZE + mods.length * MOD_BYTE_SIZE;
112
118
  const dmoddata = new IndexedByteArray(dmodsize);
113
119
  for (const mod of mods)
114
120
  {
@@ -120,7 +126,7 @@ export function write(options = DEFAULT_WRITE_OPTIONS)
120
126
  }
121
127
 
122
128
  // terminal modulator, is zero
123
- writeLittleEndian(dmoddata, 0, 10);
129
+ writeLittleEndian(dmoddata, 0, MOD_BYTE_SIZE);
124
130
 
125
131
  infoArrays.push(writeRIFFChunk(new RiffChunk(
126
132
  type,
@@ -114,8 +114,7 @@ class DLSSoundFont extends BasicSoundBank
114
114
  this.readDLSInstrumentList(instrumentListChunk);
115
115
 
116
116
  // sort presets
117
- this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
118
- this._parseInternal();
117
+ this.flush();
119
118
  SpessaSynthInfo(
120
119
  `%cParsing finished! %c"${this.soundFontInfo["INAM"] || "UNNAMED"}"%c has %c${this.presets.length} %cpresets,
121
120
  %c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
@@ -36,22 +36,21 @@ export class DLSZone extends BasicInstrumentZone
36
36
  {
37
37
  if (loopingMode !== 0)
38
38
  {
39
- this.generators.push(new Generator(generatorTypes.sampleModes, loopingMode));
39
+ this.addGenerators(new Generator(generatorTypes.sampleModes, loopingMode));
40
40
  }
41
- this.generators.push(new Generator(generatorTypes.initialAttenuation, attenuationCb));
42
- this.isGlobal = false;
41
+ this.addGenerators(new Generator(generatorTypes.initialAttenuation, attenuationCb));
43
42
 
44
43
  // correct tuning if needed
45
44
  samplePitchCorrection -= sample.samplePitchCorrection;
46
45
  const coarseTune = Math.trunc(samplePitchCorrection / 100);
47
46
  if (coarseTune !== 0)
48
47
  {
49
- this.generators.push(new Generator(generatorTypes.coarseTune, coarseTune));
48
+ this.addGenerators(new Generator(generatorTypes.coarseTune, coarseTune));
50
49
  }
51
50
  const fineTune = samplePitchCorrection - (coarseTune * 100);
52
51
  if (fineTune !== 0)
53
52
  {
54
- this.generators.push(new Generator(generatorTypes.fineTune, fineTune));
53
+ this.addGenerators(new Generator(generatorTypes.fineTune, fineTune));
55
54
  }
56
55
 
57
56
  // correct loop if needed
@@ -62,33 +61,32 @@ export class DLSZone extends BasicInstrumentZone
62
61
  if (diffStart !== 0)
63
62
  {
64
63
  const fine = diffStart % 32768;
65
- this.generators.push(new Generator(generatorTypes.startloopAddrsOffset, fine));
64
+ this.addGenerators(new Generator(generatorTypes.startloopAddrsOffset, fine));
66
65
  // coarse generator uses 32768 samples per step
67
66
  const coarse = Math.trunc(diffStart / 32768);
68
67
  if (coarse !== 0)
69
68
  {
70
- this.generators.push(new Generator(generatorTypes.startloopAddrsCoarseOffset, coarse));
69
+ this.addGenerators(new Generator(generatorTypes.startloopAddrsCoarseOffset, coarse));
71
70
  }
72
71
  }
73
72
  if (diffEnd !== 0)
74
73
  {
75
74
  const fine = diffEnd % 32768;
76
- this.generators.push(new Generator(generatorTypes.endloopAddrsOffset, fine));
75
+ this.addGenerators(new Generator(generatorTypes.endloopAddrsOffset, fine));
77
76
  // coarse generator uses 32768 samples per step
78
77
  const coarse = Math.trunc(diffEnd / 32768);
79
78
  if (coarse !== 0)
80
79
  {
81
- this.generators.push(new Generator(generatorTypes.endloopAddrsCoarseOffset, coarse));
80
+ this.addGenerators(new Generator(generatorTypes.endloopAddrsCoarseOffset, coarse));
82
81
  }
83
82
  }
84
83
  }
85
84
  // correct the key if needed
86
85
  if (sampleKey !== sample.samplePitch)
87
86
  {
88
- this.generators.push(new Generator(generatorTypes.overridingRootKey, sampleKey));
87
+ this.addGenerators(new Generator(generatorTypes.overridingRootKey, sampleKey));
89
88
  }
90
89
  // add sample ID
91
- this.generators.push(new Generator(generatorTypes.sampleID, sampleID));
92
90
  this.setSample(sample);
93
91
  }
94
92
  }
@@ -84,12 +84,12 @@ export function readDLSInstrument(chunk)
84
84
  // reverb
85
85
  if (globalZone.modulators.find(m => m.modulatorDestination === generatorTypes.reverbEffectsSend) === undefined)
86
86
  {
87
- globalZone.modulators.push(Modulator.copy(DEFAULT_DLS_REVERB));
87
+ globalZone.addModulators(Modulator.copy(DEFAULT_DLS_REVERB));
88
88
  }
89
89
  // chorus
90
90
  if (globalZone.modulators.find(m => m.modulatorDestination === generatorTypes.chorusEffectsSend) === undefined)
91
91
  {
92
- globalZone.modulators.push(Modulator.copy(DEFAULT_DLS_CHORUS));
92
+ globalZone.addModulators(Modulator.copy(DEFAULT_DLS_CHORUS));
93
93
  }
94
94
 
95
95
  // read regions
@@ -108,11 +108,11 @@ export function readDLSInstrument(chunk)
108
108
  const zone = this.readRegion(chunk);
109
109
  if (zone)
110
110
  {
111
- preset.DLSInstrument.addZone(zone);
111
+ preset.DLSInstrument.addZones(zone);
112
112
  }
113
113
  }
114
114
 
115
- this.presets.push(preset);
116
- this.instruments.push(preset.DLSInstrument);
115
+ this.addPresets(preset);
116
+ this.addInstruments(preset.DLSInstrument);
117
117
  SpessaSynthGroupEnd();
118
118
  }
@@ -16,8 +16,8 @@ export function readLart(lartChunk, lar2Chunk, zone)
16
16
  const art1 = readRIFFChunk(lartChunk.chunkData);
17
17
  this.verifyHeader(art1, "art1", "art2");
18
18
  const modsAndGens = readArticulation(art1, true);
19
- zone.generators.push(...modsAndGens.generators);
20
- zone.modulators.push(...modsAndGens.modulators);
19
+ zone.addGenerators(...modsAndGens.generators);
20
+ zone.addModulators(...modsAndGens.modulators);
21
21
  }
22
22
  }
23
23
 
@@ -28,8 +28,8 @@ export function readLart(lartChunk, lar2Chunk, zone)
28
28
  const art2 = readRIFFChunk(lar2Chunk.chunkData);
29
29
  this.verifyHeader(art2, "art2", "art1");
30
30
  const modsAndGens = readArticulation(art2, false);
31
- zone.generators.push(...modsAndGens.generators);
32
- zone.modulators.push(...modsAndGens.modulators);
31
+ zone.addGenerators(...modsAndGens.generators);
32
+ zone.addModulators(...modsAndGens.modulators);
33
33
  }
34
34
  }
35
35
  }
@@ -52,7 +52,7 @@ export function readRegion(chunk)
52
52
  const exclusive = readLittleEndian(regionHeader.chunkData, 2);
53
53
  if (exclusive !== 0)
54
54
  {
55
- zone.generators.push(new Generator(generatorTypes.exclusiveClass, exclusive));
55
+ zone.addGenerators(new Generator(generatorTypes.exclusiveClass, exclusive));
56
56
  }
57
57
 
58
58
  // lart
@@ -61,7 +61,6 @@ export function readRegion(chunk)
61
61
  this.readLart(lart, lar2, zone);
62
62
 
63
63
  // wsmp: wave sample chunk
64
- zone.isGlobal = false;
65
64
  const waveSampleChunk = regionChunks.find(c => c.header === "wsmp");
66
65
  // cbSize
67
66
  readLittleEndian(waveSampleChunk.chunkData, 4);
@@ -36,7 +36,7 @@ export class Instrument extends BasicInstrument
36
36
  const zone = zones[i];
37
37
  if (zone.hasSample())
38
38
  {
39
- this.addZone(zone);
39
+ this.addZones(zone);
40
40
  }
41
41
  else
42
42
  {
@@ -242,9 +242,8 @@ export class SoundFont2 extends BasicSoundBank
242
242
 
243
243
  let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, this.instruments);
244
244
 
245
- this.presets.push(...readPresets(presetHeadersChunk, presetZones, this));
246
- this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
247
- this._parseInternal();
245
+ this.addPresets(...readPresets(presetHeadersChunk, presetZones, this));
246
+ this.flush();
248
247
  SpessaSynthInfo(
249
248
  `%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
250
249
  %c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
@@ -46,7 +46,7 @@ export class InstrumentZone extends BasicInstrumentZone
46
46
  {
47
47
  throw new Error("Missing generator in instrument zone! The file may corrupted.");
48
48
  }
49
- this.generators.push(g);
49
+ this.addGenerators(g);
50
50
  }
51
51
  }
52
52
 
@@ -63,7 +63,7 @@ export class InstrumentZone extends BasicInstrumentZone
63
63
  {
64
64
  throw new Error("Missing modulator in instrument zone! The file may corrupted.");
65
65
  }
66
- this.modulators.push(m);
66
+ this.addModulators(m);
67
67
  }
68
68
  }
69
69
 
@@ -79,32 +79,6 @@ export class InstrumentZone extends BasicInstrumentZone
79
79
  this.setSample(samples[sampleID.generatorValue]);
80
80
  }
81
81
  }
82
-
83
- /**
84
- * Reads the keyRange of the zone
85
- */
86
- getKeyRange()
87
- {
88
- let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
89
- if (range)
90
- {
91
- this.keyRange.min = range.generatorValue & 0x7F;
92
- this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
93
- }
94
- }
95
-
96
- /**
97
- * reads the velolicty range of the zone
98
- */
99
- getVelRange()
100
- {
101
- let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
102
- if (range)
103
- {
104
- this.velRange.min = range.generatorValue & 0x7F;
105
- this.velRange.max = (range.generatorValue >> 8) & 0x7F;
106
- }
107
- }
108
82
  }
109
83
 
110
84
  /**
@@ -132,8 +106,6 @@ export function readInstrumentZones(zonesChunk, instrumentGenerators, instrument
132
106
  zones[zones.length - 1].getGenerators(instrumentGenerators);
133
107
  zones[zones.length - 1].getModulators(instrumentModulators);
134
108
  zones[zones.length - 1].getSample(instrumentSamples);
135
- zones[zones.length - 1].getKeyRange();
136
- zones[zones.length - 1].getVelRange();
137
109
  }
138
110
  zones.push(zone);
139
111
  }
@@ -179,7 +151,7 @@ export class PresetZone extends BasicPresetZone
179
151
  {
180
152
  throw new Error("Missing generator in preset zone! The file may corrupted.");
181
153
  }
182
- this.generators.push(g);
154
+ this.addGenerators(g);
183
155
  }
184
156
  }
185
157
 
@@ -196,7 +168,7 @@ export class PresetZone extends BasicPresetZone
196
168
  {
197
169
  throw new Error("Missing modulator in preset zone! The file may corrupted.");
198
170
  }
199
- this.modulators.push(m);
171
+ this.addModulators(m);
200
172
  }
201
173
  }
202
174
 
@@ -212,32 +184,6 @@ export class PresetZone extends BasicPresetZone
212
184
  this.setInstrument(instruments[instrumentID.generatorValue]);
213
185
  }
214
186
  }
215
-
216
- /**
217
- * Reads the keyRange of the zone
218
- */
219
- getKeyRange()
220
- {
221
- let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
222
- if (range)
223
- {
224
- this.keyRange.min = range.generatorValue & 0x7F;
225
- this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
226
- }
227
- }
228
-
229
- /**
230
- * reads the velolicty range of the zone
231
- */
232
- getVelRange()
233
- {
234
- let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
235
- if (range)
236
- {
237
- this.velRange.min = range.generatorValue & 0x7F;
238
- this.velRange.max = (range.generatorValue >> 8) & 0x7F;
239
- }
240
- }
241
187
  }
242
188
 
243
189
  /**
@@ -265,8 +211,6 @@ export function readPresetZones(zonesChunk, presetGenerators, presetModulators,
265
211
  zones[zones.length - 1].getGenerators(presetGenerators);
266
212
  zones[zones.length - 1].getModulators(presetModulators);
267
213
  zones[zones.length - 1].getInstrument(instruments);
268
- zones[zones.length - 1].getKeyRange();
269
- zones[zones.length - 1].getVelRange();
270
214
  }
271
215
  zones.push(zone);
272
216
  }
@@ -3,7 +3,6 @@ import { VolumeEnvelope } from "./volume_envelope.js";
3
3
  import { ModulationEnvelope } from "./modulation_envelope.js";
4
4
  import { Modulator, modulatorSources } from "../../../soundfont/basic_soundfont/modulator.js";
5
5
  import { NON_CC_INDEX_OFFSET } from "./controller_tables.js";
6
- import { SpessaSynthWarn } from "../../../utils/loggin.js";
7
6
  import { generatorLimits, generatorTypes } from "../../../soundfont/basic_soundfont/generator_types.js";
8
7
 
9
8
  /**
@@ -146,22 +145,23 @@ export function computeModulators(voice, sourceUsesCC = -1, sourceIndex = 0)
146
145
  modulatedGenerators.set(generators);
147
146
  modulators.forEach(mod =>
148
147
  {
149
- const limits = generatorLimits[mod.modulatorDestination];
150
- if (!limits)
151
- {
152
- SpessaSynthWarn(`Invalid modulator: ${mod.modulatorDestination}`);
153
- return;
154
- }
155
- const newValue = modulatedGenerators[mod.modulatorDestination] + computeModulator(
148
+ modulatedGenerators[mod.modulatorDestination] += computeModulator(
156
149
  this.midiControllers,
157
150
  mod,
158
151
  voice
159
152
  );
160
- modulatedGenerators[mod.modulatorDestination] = Math.max(
161
- limits.min,
162
- Math.min(newValue, limits.max)
163
- );
164
153
  });
154
+ // apply limits
155
+ for (let gen = 0; gen < modulatedGenerators.length; gen++)
156
+ {
157
+ const limit = generatorLimits[gen];
158
+ if (!limit)
159
+ {
160
+ // skip unused
161
+ continue;
162
+ }
163
+ modulatedGenerators[gen] = Math.min(limit.max, Math.max(limit.min, modulatedGenerators[gen]));
164
+ }
165
165
  VolumeEnvelope.recalculate(voice);
166
166
  ModulationEnvelope.recalculate(voice);
167
167
  return;
@@ -201,14 +201,15 @@ export function computeModulators(voice, sourceUsesCC = -1, sourceIndex = 0)
201
201
  {
202
202
  if (m.modulatorDestination === destination)
203
203
  {
204
- const limits = generatorLimits[mod.modulatorDestination];
205
- const newValue = modulatedGenerators[mod.modulatorDestination] + m.currentValue;
206
- modulatedGenerators[mod.modulatorDestination] = Math.max(
207
- limits.min,
208
- Math.min(newValue, limits.max)
209
- );
204
+ modulatedGenerators[destination] += m.currentValue;
210
205
  }
211
206
  });
207
+ // apply limits
208
+ const limits = generatorLimits[destination];
209
+ modulatedGenerators[destination] = Math.max(
210
+ limits.min,
211
+ Math.min(modulatedGenerators[destination], limits.max)
212
+ );
212
213
  computedDestinations.add(destination);
213
214
  }
214
215
  }
@@ -7,7 +7,8 @@ import { generatorTypes } from "../../../soundfont/basic_soundfont/generator_typ
7
7
 
8
8
  export const PAN_SMOOTHING_FACTOR = 0.05;
9
9
 
10
- export const REVERB_DIVIDER = 4600;
10
+ // optimized for spessasynth_lib's effects
11
+ export const REVERB_DIVIDER = 3070;
11
12
  export const CHORUS_DIVIDER = 2000;
12
13
  const HALF_PI = Math.PI / 2;
13
14
 
@@ -238,8 +238,4 @@ export function resetParameters()
238
238
  this.midiControllers[midiControllers.RPNMsb] = 127 << 7;
239
239
  this.resetGeneratorOverrides();
240
240
  this.resetGeneratorOffsets();
241
- SpessaSynthInfo(
242
- "%cResetting Registered and Non-Registered Parameters!",
243
- consoleColors.info
244
- );
245
241
  }