spessasynth_core 3.26.20 → 3.26.22

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_core",
3
- "version": "3.26.20",
3
+ "version": "3.26.22",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,9 +22,12 @@
22
22
  "midi",
23
23
  "rmi",
24
24
  "midi-player",
25
+ "midi-file",
26
+ "midi-editor",
25
27
  "player",
26
28
  "soundfont2",
27
- "soundfont3"
29
+ "soundfont3",
30
+ "audio-to-wav"
28
31
  ],
29
32
  "author": {
30
33
  "name": "spessasus",
@@ -2,15 +2,5 @@ import { BasicZone } from "./basic_zone.js";
2
2
 
3
3
  export class BasicGlobalZone extends BasicZone
4
4
  {
5
-
6
- /**
7
- * @param zone {BasicZone}
8
- */
9
- copyFrom(zone)
10
- {
11
- this.keyRange = { ...zone.keyRange };
12
- this.velRange = { ...zone.velRange };
13
- this.generators = [...zone.generators];
14
- this.modulators = [...zone.modulators];
15
- }
5
+ // nothing here, just a different instance...
16
6
  }
@@ -1,4 +1,5 @@
1
1
  import { BasicGlobalZone } from "./basic_global_zone.js";
2
+ import { BasicInstrumentZone } from "./basic_instrument_zone.js";
2
3
 
3
4
  export class BasicInstrument
4
5
  {
@@ -22,42 +23,53 @@ export class BasicInstrument
22
23
  globalZone = new BasicGlobalZone();
23
24
 
24
25
  /**
25
- * Instrument's use count, used for trimming
26
- * @type {number}
27
- * @private
26
+ * Instrument's linked presets (the presets that use it)
27
+ * note that duplicates are allowed since one preset can use the same instrument multople times
28
+ * @type {BasicPreset[]}
28
29
  */
29
- _useCount = 0;
30
+ linkedPresets = [];
30
31
 
31
32
  /**
32
33
  * @returns {number}
33
34
  */
34
35
  get useCount()
35
36
  {
36
- return this._useCount;
37
+ return this.linkedPresets.length;
37
38
  }
38
39
 
39
40
  /**
40
- * @param zones {BasicInstrumentZone}
41
+ * @returns {BasicInstrumentZone}
41
42
  */
42
- addZones(...zones)
43
+ createZone()
43
44
  {
44
- zones.forEach(z => z.useCount++);
45
- this.instrumentZones.push(...zones);
45
+ const zone = new BasicInstrumentZone(this);
46
+ this.instrumentZones.push(zone);
47
+ return zone;
46
48
  }
47
49
 
48
- addUseCount()
50
+ /**
51
+ * @param preset {BasicPreset}
52
+ */
53
+ linkTo(preset)
49
54
  {
50
- this._useCount++;
51
- this.instrumentZones.forEach(z => z.useCount++);
55
+ this.linkedPresets.push(preset);
56
+ this.instrumentZones.forEach(z => z.useCount = this.linkedPresets.length);
52
57
  }
53
58
 
54
- removeUseCount()
59
+ /**
60
+ * @param preset {BasicPreset}
61
+ */
62
+ unlinkFrom(preset)
55
63
  {
56
- this._useCount--;
57
- this.instrumentZones.forEach(z => z.useCount--);
64
+ const index = this.linkedPresets.indexOf(preset);
65
+ if (index < 0)
66
+ {
67
+ throw new Error(`Cannot unlink ${preset.presetName} from ${this.instrumentName}: not linked.`);
68
+ }
69
+ this.linkedPresets.splice(index, 1);
58
70
  }
59
71
 
60
- deleteZones()
72
+ deleteAllZones()
61
73
  {
62
74
  this.instrumentZones.forEach(z => z.deleteZone());
63
75
  this.instrumentZones.length = 0;
@@ -2,17 +2,32 @@ import { BasicZone } from "./basic_zone.js";
2
2
 
3
3
  export class BasicInstrumentZone extends BasicZone
4
4
  {
5
+ /**
6
+ * The parent instrument.
7
+ * @type {BasicInstrument}
8
+ */
9
+ parentInstrument;
10
+
5
11
  /**
6
12
  * Zone's sample.
7
13
  * @type {BasicSample}
8
14
  */
9
15
  sample;
10
-
11
16
  /**
12
- * For tracking on the individual zone level, since multiple presets can refer to the same instrument
17
+ * For tracking on the individual zone level, since multiple presets can refer to the same instrument.
13
18
  * @type {number}
14
19
  */
15
- useCount = 0;
20
+ useCount;
21
+
22
+ /**
23
+ * @param instrument {BasicInstrument}
24
+ */
25
+ constructor(instrument)
26
+ {
27
+ super();
28
+ this.parentInstrument = instrument;
29
+ this.useCount = instrument.linkedPresets.length;
30
+ }
16
31
 
17
32
  /**
18
33
  * @param sample {BasicSample}
@@ -20,16 +35,23 @@ export class BasicInstrumentZone extends BasicZone
20
35
  setSample(sample)
21
36
  {
22
37
  this.sample = sample;
23
- this.sample.useCount++;
38
+ sample.linkTo(this.parentInstrument);
24
39
  }
25
40
 
26
41
  deleteZone()
27
42
  {
28
- this.sample.useCount--;
43
+ this.sample.unlinkFrom(this.parentInstrument);
29
44
  }
30
45
 
31
- hasSample()
46
+ /**
47
+ * @param zone {BasicZone}
48
+ */
49
+ copyFrom(zone)
32
50
  {
33
- return !!this.sample;
51
+ super.copyFrom(zone);
52
+ if (zone instanceof BasicInstrumentZone)
53
+ {
54
+ this.sample = zone.sample;
55
+ }
34
56
  }
35
57
  }
@@ -10,6 +10,7 @@ import { Modulator } from "./modulator.js";
10
10
  import { isXGDrums } from "../../utils/xg_hacks.js";
11
11
 
12
12
  import { BasicGlobalZone } from "./basic_global_zone.js";
13
+ import { BasicPresetZone } from "./basic_preset_zone.js";
13
14
 
14
15
  export class BasicPreset
15
16
  {
@@ -75,14 +76,6 @@ export class BasicPreset
75
76
  this.parentSoundBank = parentSoundBank;
76
77
  }
77
78
 
78
- /**
79
- * @param zones {BasicPresetZone}
80
- */
81
- addZones(...zones)
82
- {
83
- this.presetZones.push(...zones);
84
- }
85
-
86
79
  /**
87
80
  * @param allowXG {boolean}
88
81
  * @param allowSFX {boolean}
@@ -113,6 +106,16 @@ export class BasicPreset
113
106
  this.presetZones.splice(index, 1);
114
107
  }
115
108
 
109
+ /**
110
+ * @returns {BasicPresetZone}
111
+ */
112
+ createZone()
113
+ {
114
+ const z = new BasicPresetZone(this);
115
+ this.presetZones.push(z);
116
+ return z;
117
+ }
118
+
116
119
  // noinspection JSUnusedGlobalSymbols
117
120
  /**
118
121
  * Preloads all samples (async)
@@ -3,14 +3,29 @@ import { BasicZone } from "./basic_zone.js";
3
3
  export class BasicPresetZone extends BasicZone
4
4
  {
5
5
  /**
6
- * Zone's instrument
6
+ * The parent preset.
7
+ * @type {BasicPreset}
8
+ */
9
+ parentPreset;
10
+
11
+ /**
12
+ * Zone's instrument.
7
13
  * @type {BasicInstrument}
8
14
  */
9
15
  instrument;
10
16
 
17
+ /**
18
+ * @param preset {BasicPreset}
19
+ */
20
+ constructor(preset)
21
+ {
22
+ super();
23
+ this.parentPreset = preset;
24
+ }
25
+
11
26
  deleteZone()
12
27
  {
13
- this.instrument.removeUseCount();
28
+ this.instrument.unlinkFrom(this.parentPreset);
14
29
  }
15
30
 
16
31
  /**
@@ -19,12 +34,18 @@ export class BasicPresetZone extends BasicZone
19
34
  setInstrument(instrument)
20
35
  {
21
36
  this.instrument = instrument;
22
- this.instrument.addUseCount();
37
+ this.instrument.linkTo(this.parentPreset);
23
38
  }
24
39
 
25
-
26
- hasInstrument()
40
+ /**
41
+ * @param zone {BasicZone}
42
+ */
43
+ copyFrom(zone)
27
44
  {
28
- return !!this.instrument;
45
+ super.copyFrom(zone);
46
+ if (zone instanceof BasicPresetZone)
47
+ {
48
+ this.instrument = zone.instrument;
49
+ }
29
50
  }
30
51
  }
@@ -79,13 +79,12 @@ export class BasicSample
79
79
  * @type {Uint8Array}
80
80
  */
81
81
  compressedData = undefined;
82
-
83
82
  /**
84
- * The sample's use count
85
- * @type {number}
83
+ * Sample's linked instruments (the instruments that use it)
84
+ * note that duplicates are allowed since one instrument can use the same sample multople times
85
+ * @type {BasicInstrument[]}
86
86
  */
87
- useCount = 0;
88
-
87
+ linkedInstruments = [];
89
88
  /**
90
89
  * The sample's audio data
91
90
  * @type {Float32Array}
@@ -126,6 +125,14 @@ export class BasicSample
126
125
  this.isCompressed = (sampleType & 0x10) > 0;
127
126
  }
128
127
 
128
+ /**
129
+ * The sample's use count
130
+ * @type {number}
131
+ */
132
+ get useCount()
133
+ {
134
+ return this.linkedInstruments.length;
135
+ }
129
136
 
130
137
  /**
131
138
  * @returns {Uint8Array|IndexedByteArray}
@@ -196,11 +203,47 @@ export class BasicSample
196
203
 
197
204
  }
198
205
 
206
+ /**
207
+ * @param instrument {BasicInstrument}
208
+ */
209
+ linkTo(instrument)
210
+ {
211
+ this.linkedInstruments.push(instrument);
212
+ }
213
+
214
+ /**
215
+ * @param instrument {BasicInstrument}
216
+ */
217
+ unlinkFrom(instrument)
218
+ {
219
+ const index = this.linkedInstruments.indexOf(instrument);
220
+ if (index < 0)
221
+ {
222
+ throw new Error(`Cannot unlink ${instrument.instrumentName} from ${this.sampleName}: not linked.`);
223
+ }
224
+ this.linkedInstruments.splice(index, 1);
225
+ }
226
+
199
227
  /**
200
228
  * @returns {Float32Array}
201
229
  */
202
230
  getAudioData()
203
231
  {
232
+ if (!this.sampleData)
233
+ {
234
+ throw new Error("Error! Sample data is undefined. Is the method overriden properly?");
235
+ }
204
236
  return this.sampleData;
205
237
  }
238
+
239
+ // noinspection JSUnusedGlobalSymbols
240
+ /**
241
+ * @param audioData {Float32Array}
242
+ */
243
+ setAudioData(audioData)
244
+ {
245
+ this.isCompressed = false;
246
+ delete this.compressedData;
247
+ this.sampleData = audioData;
248
+ }
206
249
  }
@@ -10,13 +10,11 @@ import { write } from "./write_sf2/write.js";
10
10
  import { defaultModulators, Modulator } from "./modulator.js";
11
11
  import { writeDLS } from "./write_dls/write_dls.js";
12
12
  import { BasicSample } from "./basic_sample.js";
13
- import { BasicPresetZone } from "./basic_preset_zone.js";
14
13
  import { Generator } from "./generator.js";
15
14
  import { BasicInstrument } from "./basic_instrument.js";
16
15
  import { BasicPreset } from "./basic_preset.js";
17
16
  import { isXGDrums } from "../../utils/xg_hacks.js";
18
17
  import { generatorTypes } from "./generator_types.js";
19
- import { BasicInstrumentZone } from "./basic_instrument_zone.js";
20
18
  import { BasicGlobalZone } from "./basic_global_zone.js";
21
19
 
22
20
  /**
@@ -168,26 +166,25 @@ class BasicSoundBank
168
166
  new Generator(generatorTypes.sampleModes, 1)
169
167
  );
170
168
 
171
- const zone1 = new BasicInstrumentZone();
169
+ const inst = new BasicInstrument();
170
+ inst.instrumentName = "Saw Wave";
171
+ inst.globalZone = gZone;
172
+
173
+ const zone1 = inst.createZone();
172
174
  zone1.setSample(sample);
173
175
 
174
- const zone2 = new BasicInstrumentZone();
176
+ const zone2 = inst.createZone();
175
177
  zone2.setSample(sample);
176
178
  zone2.addGenerators(new Generator(generatorTypes.fineTune, -9));
177
179
 
178
-
179
- const inst = new BasicInstrument();
180
- inst.instrumentName = "Saw Wave";
181
- inst.globalZone = gZone;
182
- inst.addZones(zone1, zone2);
183
180
  font.addInstruments(inst);
184
181
 
185
- const pZone = new BasicPresetZone();
186
- pZone.setInstrument(inst);
187
182
 
188
183
  const preset = new BasicPreset(font);
189
184
  preset.presetName = "Saw Wave";
190
- preset.addZones(pZone);
185
+ const pZone = preset.createZone();
186
+ pZone.setInstrument(inst);
187
+
191
188
  font.addPresets(preset);
192
189
 
193
190
  font.soundFontInfo["ifil"] = "2.1";
@@ -207,7 +204,14 @@ class BasicSoundBank
207
204
 
208
205
  flush()
209
206
  {
210
- this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
207
+ this.presets.sort((a, b) =>
208
+ {
209
+ if (a.bank !== b.bank)
210
+ {
211
+ return a.bank - b.bank;
212
+ }
213
+ return a.program - b.program;
214
+ });
211
215
  this._parseInternal();
212
216
  }
213
217
 
@@ -443,7 +447,7 @@ class BasicSoundBank
443
447
  {
444
448
  if (i.useCount < 1)
445
449
  {
446
- i.deleteZones();
450
+ i.deleteAllZones();
447
451
  }
448
452
  });
449
453
  this.instruments = this.instruments.filter(i => i.useCount > 0);
@@ -460,7 +464,7 @@ class BasicSoundBank
460
464
  throw new Error(`Cannot delete an instrument that has ${instrument.useCount} usages.`);
461
465
  }
462
466
  this.instruments.splice(this.instruments.indexOf(instrument), 1);
463
- instrument.deleteZones();
467
+ instrument.deleteAllZones();
464
468
  }
465
469
 
466
470
  /**
@@ -58,6 +58,7 @@ export class BasicZone
58
58
  this.generators.unshift(generator);
59
59
  }
60
60
 
61
+ // noinspection JSUnusedGlobalSymbols
61
62
  /**
62
63
  * @param type {generatorTypes}
63
64
  * @param value {number}
@@ -128,5 +129,16 @@ export class BasicZone
128
129
  {
129
130
  return this.generators.find(g => g.generatorType === generatorType)?.generatorValue ?? notFoundValue;
130
131
  }
132
+
133
+ /**
134
+ * @param zone {BasicZone}
135
+ */
136
+ copyFrom(zone)
137
+ {
138
+ this.generators = [...zone.generators];
139
+ this.modulators = [...zone.modulators];
140
+ this.velRange = { ...zone.velRange };
141
+ this.keyRange = { ...zone.keyRange };
142
+ }
131
143
  }
132
144
 
@@ -1,8 +1,7 @@
1
1
  import { Modulator } from "../modulator.js";
2
2
  import { Generator } from "../generator.js";
3
3
  import { generatorLimits, generatorTypes } from "../generator_types.js";
4
- import { BasicInstrumentZone } from "../basic_instrument_zone.js";
5
- import { BasicGlobalZone } from "../basic_global_zone.js";
4
+ import { BasicInstrument } from "../basic_instrument.js";
6
5
 
7
6
  const notGlobalizedTypes = new Set([
8
7
  generatorTypes.velRange,
@@ -32,7 +31,7 @@ const notGlobalizedTypes = new Set([
32
31
  * Combines preset zones
33
32
  * @param preset {BasicPreset}
34
33
  * @param globalize {boolean}
35
- * @returns {{global: BasicGlobalZone, zones: BasicInstrumentZone[]}}
34
+ * @returns {BasicInstrument}
36
35
  */
37
36
  export function combineZones(preset, globalize = true)
38
37
  {
@@ -64,10 +63,7 @@ export function combineZones(preset, globalize = true)
64
63
  main.push(...adder.filter(m => !main.find(mm => Modulator.isIdentical(m, mm))));
65
64
  }
66
65
 
67
- /**
68
- * @type {BasicInstrumentZone[]}
69
- */
70
- const finalZones = [];
66
+ const outputInstrument = new BasicInstrument();
71
67
 
72
68
  /**
73
69
  * @type {Generator[]}
@@ -77,7 +73,6 @@ export function combineZones(preset, globalize = true)
77
73
  * @type {Modulator[]}
78
74
  */
79
75
  const globalPresetModulators = [];
80
-
81
76
  // find the global zone and apply ranges, generators, and modulators
82
77
  const globalPresetZone = preset.globalZone;
83
78
  globalPresetGenerators.push(...globalPresetZone.generators);
@@ -209,7 +204,7 @@ export function combineZones(preset, globalize = true)
209
204
  );
210
205
 
211
206
  // create the zone and copy over values
212
- const zone = new BasicInstrumentZone();
207
+ const zone = outputInstrument.createZone();
213
208
  zone.keyRange = instZoneKeyRange;
214
209
  zone.velRange = instZoneVelRange;
215
210
  if (zone.keyRange.min === 0 && zone.keyRange.max === 127)
@@ -221,12 +216,11 @@ export function combineZones(preset, globalize = true)
221
216
  zone.velRange.min = -1;
222
217
  }
223
218
  zone.setSample(instZone.sample);
224
- zone.generators = finalGenList;
225
- zone.modulators = finalModList;
226
- finalZones.push(zone);
219
+ zone.addGenerators(...finalGenList);
220
+ zone.addModulators(...finalModList);
227
221
  }
228
222
  }
229
- const globalZone = new BasicGlobalZone();
223
+ const globalZone = outputInstrument.globalZone;
230
224
  if (globalize)
231
225
  {
232
226
  // create a global zone and add repeating generators to it
@@ -245,7 +239,7 @@ export function combineZones(preset, globalize = true)
245
239
  let occurencesForValues = {};
246
240
  const defaultForChecked = generatorLimits[checkedType]?.def || 0;
247
241
  occurencesForValues[defaultForChecked] = 0;
248
- for (const z of finalZones)
242
+ for (const z of outputInstrument.instrumentZones)
249
243
  {
250
244
  const gen = z.generators.find(g => g.generatorType === checkedType);
251
245
  if (gen)
@@ -311,7 +305,7 @@ export function combineZones(preset, globalize = true)
311
305
  globalZone.addGenerators(new Generator(checkedType, targetValue));
312
306
  }
313
307
  // remove from the zones
314
- finalZones.forEach(z =>
308
+ outputInstrument.instrumentZones.forEach(z =>
315
309
  {
316
310
  const gen = z.generators.findIndex(g =>
317
311
  g.generatorType === checkedType);
@@ -337,12 +331,12 @@ export function combineZones(preset, globalize = true)
337
331
  }
338
332
 
339
333
  // globalize only modulators that exist in all zones
340
- const firstZone = finalZones[0];
334
+ const firstZone = outputInstrument.instrumentZones[0];
341
335
  const modulators = firstZone.modulators.map(m => Modulator.copy(m));
342
336
  for (const checkedModulator of modulators)
343
337
  {
344
338
  let existsForAllZones = true;
345
- for (const zone of finalZones)
339
+ for (const zone of outputInstrument.instrumentZones)
346
340
  {
347
341
  if (!existsForAllZones)
348
342
  {
@@ -362,7 +356,7 @@ export function combineZones(preset, globalize = true)
362
356
  {
363
357
  globalZone.addModulators(Modulator.copy(checkedModulator));
364
358
  // delete it from local zones.
365
- for (const zone of finalZones)
359
+ for (const zone of outputInstrument.instrumentZones)
366
360
  {
367
361
  const modulator = zone.modulators.find(m => Modulator.isIdentical(m, checkedModulator));
368
362
  // Check if the amount is correct.
@@ -376,8 +370,5 @@ export function combineZones(preset, globalize = true)
376
370
  }
377
371
  }
378
372
  }
379
- return {
380
- zones: finalZones,
381
- global: globalZone
382
- };
373
+ return outputInstrument;
383
374
  }
@@ -22,7 +22,9 @@ export function writeIns(preset)
22
22
  consoleColors.info
23
23
  );
24
24
  // combine preset and instrument zones into a single instrument zone (region) list
25
- const { global, zones } = combineZones(preset);
25
+ const inst = combineZones(preset);
26
+ const global = inst.globalZone;
27
+ const zones = inst.instrumentZones;
26
28
 
27
29
 
28
30
  // insh: instrument header
@@ -0,0 +1,20 @@
1
+ import { BasicInstrument } from "../basic_soundfont/basic_instrument.js";
2
+ import { DLSZone } from "./dls_zone.js";
3
+
4
+ export class DLSInstrument extends BasicInstrument
5
+ {
6
+ constructor()
7
+ {
8
+ super();
9
+ }
10
+
11
+ /**
12
+ * @returns {DLSZone}
13
+ */
14
+ createZone()
15
+ {
16
+ const z = new DLSZone(this);
17
+ this.instrumentZones.push(z);
18
+ return z;
19
+ }
20
+ }
@@ -1,9 +1,13 @@
1
1
  import { BasicPreset } from "../basic_soundfont/basic_preset.js";
2
- import { BasicPresetZone } from "../basic_soundfont/basic_preset_zone.js";
3
- import { BasicInstrument } from "../basic_soundfont/basic_instrument.js";
2
+ import { DLSInstrument } from "./dls_instrument.js";
4
3
 
5
4
  export class DLSPreset extends BasicPreset
6
5
  {
6
+ /**
7
+ * @type {DLSInstrument}
8
+ */
9
+ dlsInstrument = new DLSInstrument();
10
+
7
11
  /**
8
12
  * Creates a new DLS preset
9
13
  * @param dls {BasicSoundBank}
@@ -33,11 +37,7 @@ export class DLSPreset extends BasicPreset
33
37
  this.bank = 128;
34
38
  }
35
39
 
36
- this.DLSInstrument = new BasicInstrument();
37
-
38
- const zone = new BasicPresetZone();
39
- zone.setInstrument(this.DLSInstrument);
40
-
41
- this.presetZones = [zone];
40
+ const zone = this.createZone();
41
+ zone.setInstrument(this.dlsInstrument);
42
42
  }
43
43
  }
@@ -5,14 +5,11 @@ import { BasicInstrumentZone } from "../basic_soundfont/basic_instrument_zone.js
5
5
  export class DLSZone extends BasicInstrumentZone
6
6
  {
7
7
  /**
8
- * @param keyRange {SoundFontRange}
9
- * @param velRange {SoundFontRange}
8
+ * @param inst {BasicInstrument}
10
9
  */
11
- constructor(keyRange, velRange)
10
+ constructor(inst)
12
11
  {
13
- super();
14
- this.keyRange = keyRange;
15
- this.velRange = velRange;
12
+ super(inst);
16
13
  }
17
14
 
18
15
  /**
@@ -86,7 +83,7 @@ export class DLSZone extends BasicInstrumentZone
86
83
  {
87
84
  this.addGenerators(new Generator(generatorTypes.overridingRootKey, sampleKey));
88
85
  }
89
- // add sample ID
86
+ // add sample
90
87
  this.setSample(sample);
91
88
  }
92
89
  }