spessasynth_lib 3.22.13 → 3.23.0

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.
Files changed (48) hide show
  1. package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +2 -0
  2. package/@types/soundfont/basic_soundfont/basic_zone.d.ts +16 -0
  3. package/@types/soundfont/basic_soundfont/generator.d.ts +2 -1
  4. package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +2 -1
  5. package/@types/soundfont/basic_soundfont/write_dls/art2.d.ts +6 -0
  6. package/@types/soundfont/basic_soundfont/write_dls/articulator.d.ts +28 -0
  7. package/@types/soundfont/basic_soundfont/write_dls/combine_zones.d.ts +8 -0
  8. package/@types/soundfont/basic_soundfont/write_dls/ins.d.ts +7 -0
  9. package/@types/soundfont/basic_soundfont/write_dls/lins.d.ts +5 -0
  10. package/@types/soundfont/basic_soundfont/write_dls/modulator_converter.d.ts +11 -0
  11. package/@types/soundfont/basic_soundfont/write_dls/rgn2.d.ts +7 -0
  12. package/@types/soundfont/basic_soundfont/write_dls/wave.d.ts +6 -0
  13. package/@types/soundfont/basic_soundfont/write_dls/write_dls.d.ts +6 -0
  14. package/@types/soundfont/basic_soundfont/write_dls/wsmp.d.ts +12 -0
  15. package/@types/soundfont/basic_soundfont/write_dls/wvpl.d.ts +8 -0
  16. package/midi_parser/midi_editor.js +0 -0
  17. package/package.json +1 -1
  18. package/soundfont/basic_soundfont/basic_preset.js +23 -12
  19. package/soundfont/basic_soundfont/basic_soundfont.js +2 -0
  20. package/soundfont/basic_soundfont/basic_zone.js +30 -5
  21. package/soundfont/basic_soundfont/generator.js +10 -4
  22. package/soundfont/basic_soundfont/riff_chunk.js +20 -5
  23. package/soundfont/basic_soundfont/write_dls/art2.js +136 -0
  24. package/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
  25. package/soundfont/basic_soundfont/write_dls/combine_zones.js +398 -0
  26. package/soundfont/basic_soundfont/write_dls/ins.js +103 -0
  27. package/soundfont/basic_soundfont/write_dls/lins.js +18 -0
  28. package/soundfont/basic_soundfont/write_dls/modulator_converter.js +324 -0
  29. package/soundfont/basic_soundfont/write_dls/rgn2.js +101 -0
  30. package/soundfont/basic_soundfont/write_dls/wave.js +74 -0
  31. package/soundfont/basic_soundfont/write_dls/write_dls.js +118 -0
  32. package/soundfont/basic_soundfont/write_dls/wsmp.js +74 -0
  33. package/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
  34. package/soundfont/basic_soundfont/write_sf2/igen.js +3 -3
  35. package/soundfont/basic_soundfont/write_sf2/pgen.js +2 -2
  36. package/soundfont/dls/articulator_converter.js +10 -0
  37. package/soundfont/dls/dls_sample.js +2 -2
  38. package/soundfont/dls/dls_soundfont.js +2 -1
  39. package/soundfont/dls/dls_zone.js +21 -18
  40. package/soundfont/dls/read_articulation.js +18 -30
  41. package/soundfont/dls/read_instrument.js +31 -1
  42. package/soundfont/dls/read_region.js +0 -4
  43. package/soundfont/dls/read_samples.js +17 -17
  44. package/synthetizer/key_modifier_manager.js +1 -1
  45. package/synthetizer/worklet_processor.min.js +13 -12
  46. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +0 -0
  47. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +1 -1
  48. package/utils/byte_functions/string.js +1 -1
@@ -0,0 +1,32 @@
1
+ import { writeDLSSample } from "./wave.js";
2
+ import { writeRIFFOddSize } from "../riff_chunk.js";
3
+ import { combineArrays } from "../../../utils/indexed_array.js";
4
+
5
+ /**
6
+ * @this {BasicSoundFont}
7
+ * @returns {{data: IndexedByteArray, indexes: number[] }}
8
+ */
9
+ export function writeWavePool()
10
+ {
11
+ let currentIndex = 0;
12
+ const offsets = [];
13
+ /**
14
+ * @type {IndexedByteArray[]}
15
+ */
16
+ const samples = this.samples.map(s =>
17
+ {
18
+ const out = writeDLSSample(s);
19
+ offsets.push(currentIndex);
20
+ currentIndex += out.length;
21
+ return out;
22
+ });
23
+ return {
24
+ data: writeRIFFOddSize(
25
+ "wvpl",
26
+ combineArrays(samples),
27
+ false,
28
+ true
29
+ ),
30
+ indexes: offsets
31
+ };
32
+ }
@@ -23,19 +23,19 @@ export function getIGEN()
23
23
  g.generatorType !== generatorTypes.velRange
24
24
  );
25
25
  // add sample and ranges if needed
26
- // unshift vel then key ( to make key first) and instrument is last
26
+ // unshift vel then key ( to make key first) and instrument is last
27
27
  if (z.velRange.max !== 127 || z.velRange.min !== 0)
28
28
  {
29
29
  z.generators.unshift({
30
30
  generatorType: generatorTypes.velRange,
31
- generatorValue: z.velRange.max << 8 | z.velRange.min
31
+ generatorValue: z.velRange.max << 8 | Math.max(z.velRange.min, 0)
32
32
  });
33
33
  }
34
34
  if (z.keyRange.max !== 127 || z.keyRange.min !== 0)
35
35
  {
36
36
  z.generators.unshift({
37
37
  generatorType: generatorTypes.keyRange,
38
- generatorValue: z.keyRange.max << 8 | z.keyRange.min
38
+ generatorValue: z.keyRange.max << 8 | Math.max(z.keyRange.min, 0)
39
39
  });
40
40
  }
41
41
  if (!z.isGlobal)
@@ -28,14 +28,14 @@ export function getPGEN()
28
28
  {
29
29
  z.generators.unshift({
30
30
  generatorType: generatorTypes.velRange,
31
- generatorValue: z.velRange.max << 8 | z.velRange.min
31
+ generatorValue: z.velRange.max << 8 | Math.max(z.velRange.min, 0)
32
32
  });
33
33
  }
34
34
  if (z.keyRange.max !== 127 || z.keyRange.min !== 0)
35
35
  {
36
36
  z.generators.unshift({
37
37
  generatorType: generatorTypes.keyRange,
38
- generatorValue: z.keyRange.max << 8 | z.keyRange.min
38
+ generatorValue: z.keyRange.max << 8 | Math.max(z.keyRange.min, 0)
39
39
  });
40
40
  }
41
41
  if (!z.isGlobal)
@@ -5,6 +5,7 @@ import { DLSDestinations } from "./dls_destinations.js";
5
5
 
6
6
  import { generatorTypes } from "../basic_soundfont/generator.js";
7
7
  import { consoleColors } from "../../utils/other.js";
8
+ import { SpessaSynthWarn } from "../../utils/loggin.js";
8
9
 
9
10
  /**
10
11
  * @param source {number}
@@ -147,26 +148,32 @@ function checkForSpecialDLSCombo(source, destination)
147
148
  {
148
149
  if (source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
149
150
  {
151
+ // vibrato lfo to pitch
150
152
  return generatorTypes.vibLfoToPitch;
151
153
  }
152
154
  else if (source === DLSSources.modLfo && destination === DLSDestinations.pitch)
153
155
  {
156
+ // mod lfo to pitch
154
157
  return generatorTypes.modLfoToPitch;
155
158
  }
156
159
  else if (source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
157
160
  {
161
+ // mod lfo to filter
158
162
  return generatorTypes.modLfoToFilterFc;
159
163
  }
160
164
  else if (source === DLSSources.modLfo && destination === DLSDestinations.gain)
161
165
  {
166
+ // mod lfo to volume
162
167
  return generatorTypes.modLfoToVolume;
163
168
  }
164
169
  else if (source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
165
170
  {
171
+ // mod envelope to filter
166
172
  return generatorTypes.modEnvToFilterFc;
167
173
  }
168
174
  else if (source === DLSSources.modEnv && destination === DLSDestinations.pitch)
169
175
  {
176
+ // mod envelope to pitch
170
177
  return generatorTypes.modEnvToPitch;
171
178
  }
172
179
  else
@@ -253,6 +260,7 @@ export function getSF2ModulatorFromArticulator(
253
260
  if (sf2GenDestination === undefined)
254
261
  {
255
262
  // cannot be a valid modulator
263
+ SpessaSynthWarn(`Invalid destination: ${destination}`);
256
264
  return undefined;
257
265
  }
258
266
  /**
@@ -268,6 +276,7 @@ export function getSF2ModulatorFromArticulator(
268
276
  if (sf2Source === undefined)
269
277
  {
270
278
  // cannot be a valid modulator
279
+ SpessaSynthWarn(`Invalid source: ${source}`);
271
280
  return undefined;
272
281
  }
273
282
  }
@@ -282,6 +291,7 @@ export function getSF2ModulatorFromArticulator(
282
291
  if (sf2SecondSource === undefined)
283
292
  {
284
293
  // cannot be a valid modulator
294
+ SpessaSynthWarn(`Invalid control: ${control}`);
285
295
  return undefined;
286
296
  }
287
297
 
@@ -41,7 +41,7 @@ export class DLSSample extends BasicSample
41
41
  0,
42
42
  1,
43
43
  loopStart,
44
- loopEnd - 1 // -1 sample because soundfont end is last sample and dls end is next sample
44
+ loopEnd
45
45
  );
46
46
  this.sampleData = data;
47
47
  this.sampleDbAttenuation = sampleDbAttenuation;
@@ -65,7 +65,7 @@ export class DLSSample extends BasicSample
65
65
  const uint8 = new Uint8Array(this.sampleData.length * 2);
66
66
  for (let i = 0; i < this.sampleData.length; i++)
67
67
  {
68
- const sample = Math.floor(this.sampleData[i] * 32767);
68
+ const sample = Math.floor(this.sampleData[i] * 32768);
69
69
  uint8[i * 2] = sample & 0xFF; // lower byte
70
70
  uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
71
71
  }
@@ -63,13 +63,14 @@ class DLSSoundFont extends BasicSoundFont
63
63
  this.soundFontInfo[infoPart.header] = readBytesAsString(infoPart.chunkData, infoPart.size);
64
64
  }
65
65
  }
66
- this.soundFontInfo["ICMT"] = (this.soundFontInfo["ICMT"] || "(No description)") + "\nConverted from DLS to SF2 with SpessaSynth";
66
+ this.soundFontInfo["ICMT"] = this.soundFontInfo["ICMT"] || "(No description)";
67
67
  if (this.soundFontInfo["ISBJ"])
68
68
  {
69
69
  // merge it
70
70
  this.soundFontInfo["ICMT"] += "\n" + this.soundFontInfo["ISBJ"];
71
71
  delete this.soundFontInfo["ISBJ"];
72
72
  }
73
+ this.soundFontInfo["ICMT"] += "\nConverted from DLS to SF2 with SpessaSynth";
73
74
 
74
75
  for (const [info, value] of Object.entries(this.soundFontInfo))
75
76
  {
@@ -55,28 +55,31 @@ export class DLSZone extends BasicInstrumentZone
55
55
  }
56
56
 
57
57
  // correct loop if needed
58
- const diffStart = loop.start - sample.sampleLoopStartIndex;
59
- const diffEnd = loop.end - sample.sampleLoopEndIndex;
60
- if (diffStart !== 0)
58
+ if (loopingMode !== 0)
61
59
  {
62
- const fine = diffStart % 32768;
63
- this.generators.push(new Generator(generatorTypes.startloopAddrsOffset, fine));
64
- // coarse generator uses 32768 samples per step
65
- const coarse = Math.trunc(diffStart / 32768);
66
- if (coarse !== 0)
60
+ const diffStart = loop.start - sample.sampleLoopStartIndex;
61
+ const diffEnd = loop.end - sample.sampleLoopEndIndex;
62
+ if (diffStart !== 0)
67
63
  {
68
- this.generators.push(new Generator(generatorTypes.startloopAddrsCoarseOffset, coarse));
64
+ const fine = diffStart % 32768;
65
+ this.generators.push(new Generator(generatorTypes.startloopAddrsOffset, fine));
66
+ // coarse generator uses 32768 samples per step
67
+ const coarse = Math.trunc(diffStart / 32768);
68
+ if (coarse !== 0)
69
+ {
70
+ this.generators.push(new Generator(generatorTypes.startloopAddrsCoarseOffset, coarse));
71
+ }
69
72
  }
70
- }
71
- if (diffEnd !== 0)
72
- {
73
- const fine = diffEnd % 32768;
74
- this.generators.push(new Generator(generatorTypes.endloopAddrsOffset, fine));
75
- // coarse generator uses 32768 samples per step
76
- const coarse = Math.trunc(diffEnd / 32768);
77
- if (coarse !== 0)
73
+ if (diffEnd !== 0)
78
74
  {
79
- this.generators.push(new Generator(generatorTypes.endloopAddrsCoarseOffset, coarse));
75
+ const fine = diffEnd % 32768;
76
+ this.generators.push(new Generator(generatorTypes.endloopAddrsOffset, fine));
77
+ // coarse generator uses 32768 samples per step
78
+ const coarse = Math.trunc(diffEnd / 32768);
79
+ if (coarse !== 0)
80
+ {
81
+ this.generators.push(new Generator(generatorTypes.endloopAddrsCoarseOffset, coarse));
82
+ }
80
83
  }
81
84
  }
82
85
  // correct key if needed
@@ -84,18 +84,20 @@ export function readArticulation(chunk, disableVibrato)
84
84
  generator = new Generator(generatorTypes.attackVolEnv, value);
85
85
  break;
86
86
  case DLSDestinations.volEnvHold:
87
- generator = new Generator(generatorTypes.holdVolEnv, value);
87
+ // do not validate because keyNumToSomething
88
+ generator = new Generator(generatorTypes.holdVolEnv, value, false);
88
89
  break;
89
90
  case DLSDestinations.volEnvDecay:
90
- generator = new Generator(generatorTypes.decayVolEnv, value);
91
+ // do not validate because keyNumToSomething
92
+ generator = new Generator(generatorTypes.decayVolEnv, value, false);
91
93
  break;
92
94
  case DLSDestinations.volEnvRelease:
93
95
  generator = new Generator(generatorTypes.releaseVolEnv, value);
94
96
  break;
95
97
  case DLSDestinations.volEnvSustain:
96
98
  // gain seems to be (1000 - value) / 10 = sustain dB
97
- const sustainDb = (1000 - value) / 10;
98
- generator = new Generator(generatorTypes.sustainVolEnv, sustainDb * 10);
99
+ const sustainCb = 1000 - value;
100
+ generator = new Generator(generatorTypes.sustainVolEnv, sustainCb);
99
101
  break;
100
102
 
101
103
  // mod env
@@ -106,10 +108,12 @@ export function readArticulation(chunk, disableVibrato)
106
108
  generator = new Generator(generatorTypes.attackModEnv, value);
107
109
  break;
108
110
  case DLSDestinations.modEnvHold:
109
- generator = new Generator(generatorTypes.holdModEnv, value);
111
+ // do not validate because keyNumToSomething
112
+ generator = new Generator(generatorTypes.holdModEnv, value, false);
110
113
  break;
111
114
  case DLSDestinations.modEnvDecay:
112
- generator = new Generator(generatorTypes.decayModEnv, value);
115
+ // do not validate because keyNumToSomething
116
+ generator = new Generator(generatorTypes.decayModEnv, value, false);
113
117
  break;
114
118
  case DLSDestinations.modEnvRelease:
115
119
  generator = new Generator(generatorTypes.releaseModEnv, value);
@@ -181,6 +185,14 @@ export function readArticulation(chunk, disableVibrato)
181
185
  {
182
186
  generators.push(new Generator(generatorTypes.modEnvToFilterFc, value));
183
187
  }
188
+ else
189
+ // scale tuning (key number to pitch)
190
+ if (source === DLSSources.keyNum && destination === DLSDestinations.pitch)
191
+ {
192
+ // this is just a soundfont generator, but the amount must be changed
193
+ // 12800 means the regular scale (100)
194
+ generators.push(new Generator(generatorTypes.scaleTuning, value / 128));
195
+ }
184
196
  else
185
197
  // key to vol env hold
186
198
  if (source === DLSSources.keyNum && destination === DLSDestinations.volEnvHold)
@@ -289,30 +301,6 @@ export function readArticulation(chunk, disableVibrato)
289
301
  }
290
302
  }
291
303
 
292
- // override reverb and chorus with 1000 instead of 200 (if not overriden)
293
- // reverb
294
- if (modulators.find(m => m.modulatorDestination === generatorTypes.reverbEffectsSend) === undefined)
295
- {
296
- modulators.push(new Modulator({
297
- srcEnum: 0x00DB,
298
- dest: generatorTypes.reverbEffectsSend,
299
- amt: 1000,
300
- secSrcEnum: 0x0,
301
- transform: 0
302
- }));
303
- }
304
- // chorus
305
- if (modulators.find(m => m.modulatorDestination === generatorTypes.chorusEffectsSend) === undefined)
306
- {
307
- modulators.push(new Modulator({
308
- srcEnum: 0x00DD,
309
- dest: generatorTypes.chorusEffectsSend,
310
- amt: 1000,
311
- secSrcEnum: 0x0,
312
- transform: 0
313
- }));
314
- }
315
-
316
304
  // it seems that dls 1 does not have vibrato lfo, so we shall disable it
317
305
  if (disableVibrato)
318
306
  {
@@ -5,6 +5,8 @@ import { findRIFFListType, readRIFFChunk } from "../basic_soundfont/riff_chunk.j
5
5
  import { SpessaSynthGroup, SpessaSynthGroupEnd } from "../../utils/loggin.js";
6
6
  import { BasicInstrumentZone } from "../basic_soundfont/basic_zones.js";
7
7
  import { consoleColors } from "../../utils/other.js";
8
+ import { generatorLimits, generatorTypes } from "../basic_soundfont/generator.js";
9
+ import { Modulator } from "../basic_soundfont/modulator.js";
8
10
 
9
11
  /**
10
12
  * @this {DLSSoundFont}
@@ -73,7 +75,35 @@ export function readDLSInstrument(chunk)
73
75
  // read articulators
74
76
  const globalLart = findRIFFListType(chunks, "lart");
75
77
  const globalLar2 = findRIFFListType(chunks, "lar2");
76
- this.readLart(globalLart, globalLar2, globalZone);
78
+ if (globalLar2 !== undefined || globalLart !== undefined)
79
+ {
80
+ this.readLart(globalLart, globalLar2, globalZone);
81
+ }
82
+ // remove generators with default values
83
+ globalZone.generators = globalZone.generators.filter(g => g.generatorValue !== generatorLimits[g.generatorType].def);
84
+ // override reverb and chorus with 1000 instead of 200 (if not overriden)
85
+ // reverb
86
+ if (globalZone.modulators.find(m => m.modulatorDestination === generatorTypes.reverbEffectsSend) === undefined)
87
+ {
88
+ globalZone.modulators.push(new Modulator({
89
+ srcEnum: 0x00DB,
90
+ dest: generatorTypes.reverbEffectsSend,
91
+ amt: 1000,
92
+ secSrcEnum: 0x0,
93
+ transform: 0
94
+ }));
95
+ }
96
+ // chorus
97
+ if (globalZone.modulators.find(m => m.modulatorDestination === generatorTypes.chorusEffectsSend) === undefined)
98
+ {
99
+ globalZone.modulators.push(new Modulator({
100
+ srcEnum: 0x00DD,
101
+ dest: generatorTypes.chorusEffectsSend,
102
+ amt: 1000,
103
+ secSrcEnum: 0x0,
104
+ transform: 0
105
+ }));
106
+ }
77
107
  preset.DLSInstrument.instrumentZones.push(globalZone);
78
108
 
79
109
  // read regions
@@ -64,10 +64,6 @@ export function readRegion(chunk)
64
64
  waveSampleChunk.chunkData[waveSampleChunk.chunkData.currentIndex++]
65
65
  );
66
66
 
67
- const pitchCorrectionSemitones = Math.trunc(pitchCorrection / 100);
68
- originalKey += pitchCorrectionSemitones;
69
- pitchCorrection -= pitchCorrectionSemitones * 100;
70
-
71
67
  // gain correction: Each unit of gain represents 1/655360 dB
72
68
  // it is set after linking the sample
73
69
  const gainCorrection = readLittleEndian(waveSampleChunk.chunkData, 4);
@@ -179,6 +179,22 @@ export function readDLSSamples(waveListChunk)
179
179
 
180
180
  }
181
181
 
182
+ // read sample name
183
+ const waveInfo = findRIFFListType(waveChunks, "INFO");
184
+ let sampleName = `Unnamed ${sampleID}`;
185
+ if (waveInfo)
186
+ {
187
+ let infoChunk = readRIFFChunk(waveInfo.chunkData);
188
+ while (infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length)
189
+ {
190
+ infoChunk = readRIFFChunk(waveInfo.chunkData);
191
+ }
192
+ if (infoChunk.header === "INAM")
193
+ {
194
+ sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim();
195
+ }
196
+ }
197
+
182
198
  // sane defaults
183
199
  let sampleKey = 60;
184
200
  let samplePitch = 0;
@@ -227,22 +243,6 @@ export function readDLSSamples(waveListChunk)
227
243
  SpessaSynthWarn("No wsmp chunk in wave... using sane defaults.");
228
244
  }
229
245
 
230
- // read sample name
231
- const waveInfo = findRIFFListType(waveChunks, "INFO");
232
- let sampleName = `Unnamed ${sampleID}`;
233
- if (waveInfo)
234
- {
235
- let infoChunk = readRIFFChunk(waveInfo.chunkData);
236
- while (infoChunk.header !== "INAM" && waveInfo.chunkData.currentIndex < waveInfo.chunkData.length)
237
- {
238
- infoChunk = readRIFFChunk(waveInfo.chunkData);
239
- }
240
- if (infoChunk.header === "INAM")
241
- {
242
- sampleName = readBytesAsString(infoChunk.chunkData, infoChunk.size).trim();
243
- }
244
- }
245
-
246
246
  if (failed)
247
247
  {
248
248
  console.error(`Failed to load '${sampleName}': Unsupported format: (${waveFormat})`);
@@ -254,7 +254,7 @@ export function readDLSSamples(waveListChunk)
254
254
  sampleKey,
255
255
  samplePitch,
256
256
  sampleLoopStart,
257
- sampleData.length,
257
+ sampleLoopEnd,
258
258
  sampleData,
259
259
  sampleDbAttenuation
260
260
  ));
@@ -41,7 +41,7 @@ export class KeyModifierManager
41
41
  */
42
42
  addModifier(channel, midiNote, options)
43
43
  {
44
- const velocity = options?.velocity || -1;
44
+ const velocity = options?.velocity ?? -1;
45
45
  const program = options?.patch?.program ?? -1;
46
46
  const bank = options?.patch?.bank ?? -1;
47
47
  this._sendToWorklet(