spessasynth_lib 3.22.13 → 3.23.2

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 (49) 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 +8 -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/README.md +2 -1
  17. package/midi_parser/midi_editor.js +0 -0
  18. package/package.json +1 -1
  19. package/soundfont/basic_soundfont/basic_preset.js +23 -12
  20. package/soundfont/basic_soundfont/basic_soundfont.js +2 -0
  21. package/soundfont/basic_soundfont/basic_zone.js +30 -5
  22. package/soundfont/basic_soundfont/generator.js +10 -4
  23. package/soundfont/basic_soundfont/riff_chunk.js +20 -5
  24. package/soundfont/basic_soundfont/write_dls/art2.js +136 -0
  25. package/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
  26. package/soundfont/basic_soundfont/write_dls/combine_zones.js +398 -0
  27. package/soundfont/basic_soundfont/write_dls/ins.js +103 -0
  28. package/soundfont/basic_soundfont/write_dls/lins.js +18 -0
  29. package/soundfont/basic_soundfont/write_dls/modulator_converter.js +324 -0
  30. package/soundfont/basic_soundfont/write_dls/rgn2.js +116 -0
  31. package/soundfont/basic_soundfont/write_dls/wave.js +88 -0
  32. package/soundfont/basic_soundfont/write_dls/write_dls.js +118 -0
  33. package/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
  34. package/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
  35. package/soundfont/basic_soundfont/write_sf2/igen.js +3 -3
  36. package/soundfont/basic_soundfont/write_sf2/pgen.js +2 -2
  37. package/soundfont/dls/articulator_converter.js +10 -0
  38. package/soundfont/dls/dls_sample.js +2 -2
  39. package/soundfont/dls/dls_soundfont.js +2 -1
  40. package/soundfont/dls/dls_zone.js +21 -18
  41. package/soundfont/dls/read_articulation.js +18 -30
  42. package/soundfont/dls/read_instrument.js +31 -1
  43. package/soundfont/dls/read_region.js +0 -4
  44. package/soundfont/dls/read_samples.js +17 -17
  45. package/synthetizer/key_modifier_manager.js +1 -1
  46. package/synthetizer/worklet_processor.min.js +13 -12
  47. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +0 -0
  48. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +1 -1
  49. package/utils/byte_functions/string.js +1 -1
@@ -87,6 +87,8 @@ export class BasicSoundFont {
87
87
  parsingError(error: string): void;
88
88
  destroySoundfont(): void;
89
89
  write: typeof write;
90
+ writeDLS: typeof writeDLS;
90
91
  }
91
92
  import { Modulator } from "./modulator.js";
92
93
  import { write } from "./write_sf2/write.js";
94
+ import { writeDLS } from "./write_dls/write_dls.js";
@@ -6,11 +6,13 @@
6
6
  export class BasicZone {
7
7
  /**
8
8
  * The zone's velocity range
9
+ * min -1 means that it is a default value
9
10
  * @type {SoundFontRange}
10
11
  */
11
12
  velRange: SoundFontRange;
12
13
  /**
13
14
  * The zone's key range
15
+ * min -1 means that it is a default value
14
16
  * @type {SoundFontRange}
15
17
  */
16
18
  keyRange: SoundFontRange;
@@ -29,6 +31,20 @@ export class BasicZone {
29
31
  * @type {Modulator[]}
30
32
  */
31
33
  modulators: Modulator[];
34
+ /**
35
+ * @returns {boolean}
36
+ */
37
+ get hasKeyRange(): boolean;
38
+ /**
39
+ * @returns {boolean}
40
+ */
41
+ get hasVelRange(): boolean;
42
+ /**
43
+ * @param generatorType {generatorTypes}
44
+ * @param notFoundValue {number}
45
+ * @returns {number}
46
+ */
47
+ getGeneratorValue(generatorType: generatorTypes, notFoundValue: number): number;
32
48
  }
33
49
  export type SoundFontRange = {
34
50
  /**
@@ -86,8 +86,9 @@ export class Generator {
86
86
  * Constructs a new generator
87
87
  * @param type {generatorTypes|number}
88
88
  * @param value {number}
89
+ * @param validate {boolean}
89
90
  */
90
- constructor(type?: generatorTypes | number, value?: number);
91
+ constructor(type?: generatorTypes | number, value?: number, validate?: boolean);
91
92
  /**
92
93
  * The generator's enum number
93
94
  * @type {generatorTypes|number}
@@ -15,9 +15,10 @@ export function writeRIFFChunk(chunk: RiffChunk, prepend?: IndexedByteArray): In
15
15
  * @param header {string}
16
16
  * @param data {Uint8Array}
17
17
  * @param addZeroByte {Boolean}
18
+ * @param isList {boolean}
18
19
  * @returns {IndexedByteArray}
19
20
  */
20
- export function writeRIFFOddSize(header: string, data: Uint8Array, addZeroByte?: boolean): IndexedByteArray;
21
+ export function writeRIFFOddSize(header: string, data: Uint8Array, addZeroByte?: boolean, isList?: boolean): IndexedByteArray;
21
22
  /**
22
23
  * @param collection {RiffChunk[]}
23
24
  * @param type {string}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @param zone {BasicInstrumentZone}
3
+ * @returns {IndexedByteArray}
4
+ */
5
+ export function writeArticulator(zone: BasicInstrumentZone): IndexedByteArray;
6
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,28 @@
1
+ export class Articulator {
2
+ constructor(source: any, control: any, destination: any, scale: any, transform: any);
3
+ /**
4
+ * @type {DLSSources}
5
+ */
6
+ source: DLSSources;
7
+ /**
8
+ * @type {DLSSources}
9
+ */
10
+ control: DLSSources;
11
+ /**
12
+ * @type {DLSDestinations}
13
+ */
14
+ destination: DLSDestinations;
15
+ /**
16
+ * @type {number}
17
+ */
18
+ scale: number;
19
+ /**
20
+ * @type {number}
21
+ */
22
+ transform: number;
23
+ /**
24
+ * @returns {IndexedByteArray}
25
+ */
26
+ writeArticulator(): IndexedByteArray;
27
+ }
28
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Combines preset zones
3
+ * @param preset {BasicPreset}
4
+ * @param globalize {boolean}
5
+ * @returns {BasicInstrumentZone[]}
6
+ */
7
+ export function combineZones(preset: BasicPreset, globalize?: boolean): BasicInstrumentZone[];
8
+ import { BasicInstrumentZone } from "../basic_zones.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @this {BasicSoundFont}
3
+ * @param preset {BasicPreset}
4
+ * @returns {IndexedByteArray}
5
+ */
6
+ export function writeIns(this: BasicSoundFont, preset: BasicPreset): IndexedByteArray;
7
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @this {BasicSoundFont}
3
+ * @returns {IndexedByteArray}
4
+ */
5
+ export function writeLins(this: BasicSoundFont): IndexedByteArray;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @param gen {Generator}
3
+ * @returns {Articulator|undefined}
4
+ */
5
+ export function getDLSArticulatorFromSf2Generator(gen: Generator): Articulator | undefined;
6
+ /**
7
+ * @param mod {Modulator}
8
+ * @returns {Articulator|undefined}
9
+ */
10
+ export function getDLSArticulatorFromSf2Modulator(mod: Modulator): Articulator | undefined;
11
+ import { Articulator } from "./articulator.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @param zone {BasicInstrumentZone}
3
+ * @param globalZone {BasicInstrumentZone}
4
+ * @this {BasicSoundFont}
5
+ * @returns {IndexedByteArray}
6
+ */
7
+ export function writeDLSRegion(this: BasicSoundFont, zone: BasicInstrumentZone, globalZone: BasicInstrumentZone): IndexedByteArray;
8
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @param sample {BasicSample}
3
+ * @returns {IndexedByteArray}
4
+ */
5
+ export function writeDLSSample(sample: BasicSample): IndexedByteArray;
6
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Write the soundfont as a .dls file. Experimental
3
+ * @this {BasicSoundFont}
4
+ * @returns {Uint8Array}
5
+ */
6
+ export function writeDLS(this: BasicSoundFont): Uint8Array;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @param sample {BasicSample}
3
+ * @param rootKey {number}
4
+ * @param tuning {number}
5
+ * @param attenuationCentibels {number} CENTIBELS, NO CORRECTION
6
+ * @param loopStart {number}
7
+ * @param loopEnd {number}
8
+ * @param loopingMode {number}
9
+ * @returns {IndexedByteArray}
10
+ */
11
+ export function writeWavesample(sample: BasicSample, rootKey: number, tuning: number, attenuationCentibels: number, loopStart: number, loopEnd: number, loopingMode: number): IndexedByteArray;
12
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @this {BasicSoundFont}
3
+ * @returns {{data: IndexedByteArray, indexes: number[] }}
4
+ */
5
+ export function writeWavePool(this: BasicSoundFont): {
6
+ data: IndexedByteArray;
7
+ indexes: number[];
8
+ };
package/README.md CHANGED
@@ -105,7 +105,7 @@ document.getElementById("button").onclick = async () =>
105
105
  - **Variable compression quality:** You choose between file size and quality!
106
106
  - **Compression preserving:** Avoid decompressing and recompressing uncompressed samples for minimal quality loss!
107
107
 
108
- #### Read and play DLS Level 1 or 2 files
108
+ #### Read and write DLS Level 1 or 2 files
109
109
  - Read DLS (DownLoadable Sounds) files as SF2 files!
110
110
  - **Works like a normal soundfont:** *Saving it as sf2 is still [just one function!](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class#write)*
111
111
  - Converts articulators to both **modulators** and **generators**!
@@ -113,6 +113,7 @@ document.getElementById("button").onclick = async () =>
113
113
  - **Covers special generator cases:** *such as modLfoToPitch*!
114
114
  - **Correct volume:** *looking at you, Viena and gm.sf2!*
115
115
  - Support built right into the synthesizer!
116
+ - **Convert SF2 to DLS:** [with limitations](https://github.com/spessasus/SpessaSynth/wiki/DLS-Conversion-Problem);
116
117
 
117
118
  ### Export MIDI as WAV
118
119
  - Save the MIDI file as WAV audio!
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.22.13",
3
+ "version": "3.23.2",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -148,9 +148,14 @@ export class BasicPreset
148
148
  return [];
149
149
  }
150
150
 
151
- function isInRange(min, max, number)
151
+ /**
152
+ * @param range {SoundFontRange}
153
+ * @param number {number}
154
+ * @returns {boolean}
155
+ */
156
+ function isInRange(range, number)
152
157
  {
153
- return number >= min && number <= max;
158
+ return number >= range.min && number <= range.max;
154
159
  }
155
160
 
156
161
  /**
@@ -182,51 +187,57 @@ export class BasicPreset
182
187
  */
183
188
  let globalPresetGenerators = this.presetZones[0].isGlobal ? [...this.presetZones[0].generators] : [];
184
189
 
190
+ /**
191
+ * @type {Modulator[]}
192
+ */
185
193
  let globalPresetModulators = this.presetZones[0].isGlobal ? [...this.presetZones[0].modulators] : [];
194
+ const globalKeyRange = this.presetZones[0].isGlobal ? this.presetZones[0].keyRange : { min: 0, max: 127 };
195
+ const globalVelRange = this.presetZones[0].isGlobal ? this.presetZones[0].velRange : { min: 0, max: 127 };
186
196
 
187
197
  // find the preset zones in range
188
198
  let presetZonesInRange = this.presetZones.filter(currentZone =>
189
199
  (
190
200
  isInRange(
191
- currentZone.keyRange.min,
192
- currentZone.keyRange.max,
201
+ currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
193
202
  midiNote
194
203
  )
195
204
  &&
196
205
  isInRange(
197
- currentZone.velRange.min,
198
- currentZone.velRange.max,
206
+ currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
199
207
  velocity
200
208
  )
201
209
  ) && !currentZone.isGlobal);
202
210
 
203
211
  presetZonesInRange.forEach(zone =>
204
212
  {
213
+ // global zone is already taken into account earlier
205
214
  if (zone.instrument.instrumentZones.length < 1)
206
215
  {
207
216
  return;
208
217
  }
209
218
  let presetGenerators = zone.generators;
210
219
  let presetModulators = zone.modulators;
220
+ const firstZone = zone.instrument.instrumentZones[0];
211
221
  /**
212
222
  * global zone is always first, so it or nothing
213
223
  * @type {Generator[]}
214
224
  */
215
- let globalInstrumentGenerators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].generators] : [];
216
- let globalInstrumentModulators = zone.instrument.instrumentZones[0].isGlobal ? [...zone.instrument.instrumentZones[0].modulators] : [];
225
+ let globalInstrumentGenerators = firstZone.isGlobal ? [...firstZone.generators] : [];
226
+ let globalInstrumentModulators = firstZone.isGlobal ? [...firstZone.modulators] : [];
227
+ const globalKeyRange = firstZone.isGlobal ? firstZone.keyRange : { min: 0, max: 127 };
228
+ const globalVelRange = firstZone.isGlobal ? firstZone.velRange : { min: 0, max: 127 };
229
+
217
230
 
218
231
  let instrumentZonesInRange = zone.instrument.instrumentZones
219
232
  .filter(currentZone =>
220
233
  (
221
234
  isInRange(
222
- currentZone.keyRange.min,
223
- currentZone.keyRange.max,
235
+ currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
224
236
  midiNote
225
237
  )
226
238
  &&
227
239
  isInRange(
228
- currentZone.velRange.min,
229
- currentZone.velRange.max,
240
+ currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
230
241
  velocity
231
242
  )
232
243
  ) && !currentZone.isGlobal
@@ -2,6 +2,7 @@ import { SpessaSynthWarn } from "../../utils/loggin.js";
2
2
  import { consoleColors } from "../../utils/other.js";
3
3
  import { write } from "./write_sf2/write.js";
4
4
  import { defaultModulators, Modulator } from "./modulator.js";
5
+ import { writeDLS } from "./write_dls/write_dls.js";
5
6
 
6
7
  class BasicSoundFont
7
8
  {
@@ -243,5 +244,6 @@ class BasicSoundFont
243
244
  }
244
245
 
245
246
  BasicSoundFont.prototype.write = write;
247
+ BasicSoundFont.prototype.writeDLS = writeDLS;
246
248
 
247
249
  export { BasicSoundFont };
@@ -8,32 +8,57 @@ export class BasicZone
8
8
  {
9
9
  /**
10
10
  * The zone's velocity range
11
+ * min -1 means that it is a default value
11
12
  * @type {SoundFontRange}
12
13
  */
13
- velRange = { min: 0, max: 127 };
14
+ velRange = { min: -1, max: 127 };
14
15
 
15
16
  /**
16
17
  * The zone's key range
18
+ * min -1 means that it is a default value
17
19
  * @type {SoundFontRange}
18
20
  */
19
- keyRange = { min: 0, max: 127 };
20
-
21
+ keyRange = { min: -1, max: 127 };
21
22
  /**
22
23
  * Indicates if the zone is global
23
24
  * @type {boolean}
24
25
  */
25
26
  isGlobal = false;
26
-
27
27
  /**
28
28
  * The zone's generators
29
29
  * @type {Generator[]}
30
30
  */
31
31
  generators = [];
32
-
33
32
  /**
34
33
  * The zone's modulators
35
34
  * @type {Modulator[]}
36
35
  */
37
36
  modulators = [];
37
+
38
+ /**
39
+ * @returns {boolean}
40
+ */
41
+ get hasKeyRange()
42
+ {
43
+ return this.keyRange.min !== -1;
44
+ }
45
+
46
+ /**
47
+ * @returns {boolean}
48
+ */
49
+ get hasVelRange()
50
+ {
51
+ return this.velRange.min !== -1;
52
+ }
53
+
54
+ /**
55
+ * @param generatorType {generatorTypes}
56
+ * @param notFoundValue {number}
57
+ * @returns {number}
58
+ */
59
+ getGeneratorValue(generatorType, notFoundValue)
60
+ {
61
+ return this.generators.find(g => g.generatorType === generatorType)?.generatorValue ?? notFoundValue;
62
+ }
38
63
  }
39
64
 
@@ -137,6 +137,7 @@ generatorLimits[generatorTypes.fineTune] = { min: -12700, max: 12700, def: 0 };
137
137
  generatorLimits[generatorTypes.scaleTuning] = { min: 0, max: 1200, def: 100 };
138
138
  generatorLimits[generatorTypes.exclusiveClass] = { min: 0, max: 99999, def: 0 };
139
139
  generatorLimits[generatorTypes.overridingRootKey] = { min: 0 - 1, max: 127, def: -1 };
140
+ generatorLimits[generatorTypes.sampleModes] = { min: 0, max: 3, def: 0 };
140
141
 
141
142
  export class Generator
142
143
  {
@@ -155,19 +156,24 @@ export class Generator
155
156
  * Constructs a new generator
156
157
  * @param type {generatorTypes|number}
157
158
  * @param value {number}
159
+ * @param validate {boolean}
158
160
  */
159
- constructor(type = generatorTypes.INVALID, value = 0)
161
+ constructor(type = generatorTypes.INVALID, value = 0, validate = true)
160
162
  {
161
163
  this.generatorType = type;
162
164
  if (value === undefined)
163
165
  {
164
166
  throw new Error("No value provided.");
165
167
  }
166
- const lim = generatorLimits[type];
167
168
  this.generatorValue = Math.round(value);
168
- if (lim !== undefined)
169
+ if (validate)
169
170
  {
170
- this.generatorValue = Math.max(lim.min, Math.min(lim.max, this.generatorValue));
171
+ const lim = generatorLimits[type];
172
+
173
+ if (lim !== undefined)
174
+ {
175
+ this.generatorValue = Math.max(lim.min, Math.min(lim.max, this.generatorValue));
176
+ }
171
177
  }
172
178
  }
173
179
  }
@@ -93,9 +93,10 @@ export function writeRIFFChunk(chunk, prepend = undefined)
93
93
  * @param header {string}
94
94
  * @param data {Uint8Array}
95
95
  * @param addZeroByte {Boolean}
96
+ * @param isList {boolean}
96
97
  * @returns {IndexedByteArray}
97
98
  */
98
- export function writeRIFFOddSize(header, data, addZeroByte = false)
99
+ export function writeRIFFOddSize(header, data, addZeroByte = false, isList = false)
99
100
  {
100
101
  if (addZeroByte)
101
102
  {
@@ -103,15 +104,29 @@ export function writeRIFFOddSize(header, data, addZeroByte = false)
103
104
  tempData.set(data, 0);
104
105
  data = tempData;
105
106
  }
106
- let finalSize = 8 + data.length;
107
+ let offset = 8;
108
+ let finalSize = offset + data.length;
109
+ let writtenSize = data.length;
107
110
  if (finalSize % 2 !== 0)
108
111
  {
109
112
  finalSize++;
110
113
  }
114
+ let headerWritten = header;
115
+ if (isList)
116
+ {
117
+ finalSize += 4;
118
+ writtenSize += 4;
119
+ offset += 4;
120
+ headerWritten = "LIST";
121
+ }
111
122
  const outArray = new IndexedByteArray(finalSize);
112
- writeStringAsBytes(outArray, header);
113
- writeDword(outArray, data.length);
114
- outArray.set(data, 8);
123
+ writeStringAsBytes(outArray, headerWritten);
124
+ writeDword(outArray, writtenSize);
125
+ if (isList)
126
+ {
127
+ writeStringAsBytes(outArray, header);
128
+ }
129
+ outArray.set(data, offset);
115
130
  return outArray;
116
131
  }
117
132
 
@@ -0,0 +1,136 @@
1
+ import { getDLSArticulatorFromSf2Generator, getDLSArticulatorFromSf2Modulator } from "./modulator_converter.js";
2
+ import { writeRIFFOddSize } from "../riff_chunk.js";
3
+ import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
4
+ import { Generator, generatorTypes } from "../generator.js";
5
+ import { writeDword } from "../../../utils/byte_functions/little_endian.js";
6
+ import { consoleColors } from "../../../utils/other.js";
7
+ import { SpessaSynthInfo, SpessaSynthWarn } from "../../../utils/loggin.js";
8
+
9
+ const invalidGeneratorTypes = new Set([
10
+ generatorTypes.sampleModes,
11
+ generatorTypes.initialAttenuation,
12
+ generatorTypes.keyRange,
13
+ generatorTypes.velRange,
14
+ generatorTypes.sampleID,
15
+ generatorTypes.fineTune,
16
+ generatorTypes.coarseTune,
17
+ generatorTypes.startAddrsOffset,
18
+ generatorTypes.startAddrsCoarseOffset,
19
+ generatorTypes.endAddrOffset,
20
+ generatorTypes.endAddrsCoarseOffset,
21
+ generatorTypes.startloopAddrsOffset,
22
+ generatorTypes.startloopAddrsCoarseOffset,
23
+ generatorTypes.endloopAddrsOffset,
24
+ generatorTypes.endloopAddrsCoarseOffset,
25
+ generatorTypes.overridingRootKey,
26
+ generatorTypes.exclusiveClass
27
+ ]);
28
+
29
+ /**
30
+ * @param zone {BasicInstrumentZone}
31
+ * @returns {IndexedByteArray}
32
+ */
33
+ export function writeArticulator(zone)
34
+ {
35
+ /**
36
+ * @returns {number}
37
+ */
38
+
39
+ // read_articulation.js:
40
+ // according to viena and another strange (with modulators) rendition of gm.dls in sf2,
41
+ // it shall be divided by -128
42
+ // and a strange correction needs to be applied to the real value:
43
+ // real + (60 / 128) * scale
44
+
45
+ // we invert this here
46
+ for (let i = 0; i < zone.generators.length; i++)
47
+ {
48
+ const relativeGenerator = zone.generators[i];
49
+ let absoluteCounterpart = undefined;
50
+ switch (relativeGenerator.generatorType)
51
+ {
52
+ default:
53
+ continue;
54
+
55
+ case generatorTypes.keyNumToVolEnvDecay:
56
+ absoluteCounterpart = generatorTypes.decayVolEnv;
57
+ break;
58
+ case generatorTypes.keyNumToVolEnvHold:
59
+ absoluteCounterpart = generatorTypes.holdVolEnv;
60
+ break;
61
+ case generatorTypes.keyNumToModEnvDecay:
62
+ absoluteCounterpart = generatorTypes.decayModEnv;
63
+ break;
64
+ case generatorTypes.keyNumToModEnvHold:
65
+ absoluteCounterpart = generatorTypes.holdModEnv;
66
+ }
67
+ let absoluteGenerator = zone.generators.find(g => g.generatorType === absoluteCounterpart);
68
+ if (absoluteGenerator === undefined)
69
+ {
70
+ // there's no absolute generator here.
71
+ continue;
72
+ }
73
+ const dlsRelative = relativeGenerator.generatorValue * -128;
74
+ const subtraction = (60 / 128) * dlsRelative;
75
+ const newAbsolute = absoluteGenerator.generatorValue - subtraction;
76
+
77
+ const iR = zone.generators.indexOf(relativeGenerator);
78
+ const iA = zone.generators.indexOf(absoluteGenerator);
79
+ zone.generators[iA] =
80
+ new Generator(absoluteCounterpart, newAbsolute, false);
81
+ zone.generators[iR] =
82
+ new Generator(relativeGenerator.generatorType, dlsRelative, false);
83
+ }
84
+ /**
85
+ * @type {Articulator[]}
86
+ */
87
+ const generators = zone.generators.reduce((arrs, g) =>
88
+ {
89
+ if (invalidGeneratorTypes.has(g.generatorType))
90
+ {
91
+ return arrs;
92
+ }
93
+ const art = getDLSArticulatorFromSf2Generator(g);
94
+ if (art !== undefined)
95
+ {
96
+ arrs.push(art);
97
+ SpessaSynthInfo("%cSucceeded converting to DLS Articulator!", consoleColors.recognized);
98
+
99
+ }
100
+ else
101
+ {
102
+ SpessaSynthWarn("Failed converting to DLS Articulator!");
103
+ }
104
+ return arrs;
105
+ }, []);
106
+ /**
107
+ * @type {Articulator[]}
108
+ */
109
+ const modulators = zone.modulators.reduce((arrs, m) =>
110
+ {
111
+ const art = getDLSArticulatorFromSf2Modulator(m);
112
+ if (art !== undefined)
113
+ {
114
+ arrs.push(art);
115
+ SpessaSynthInfo("%cSucceeded converting to DLS Articulator!", consoleColors.recognized);
116
+
117
+ }
118
+ else
119
+ {
120
+ SpessaSynthWarn("Failed converting to DLS Articulator!");
121
+ }
122
+ return arrs;
123
+ }, []);
124
+ generators.push(...modulators);
125
+
126
+ const art2Data = new IndexedByteArray(8);
127
+ writeDword(art2Data, 8); // cbSize
128
+ writeDword(art2Data, generators.length); // cbConnectionBlocks
129
+
130
+
131
+ const out = generators.map(a => a.writeArticulator());
132
+ return writeRIFFOddSize(
133
+ "art2",
134
+ combineArrays([art2Data, ...out])
135
+ );
136
+ }
@@ -0,0 +1,49 @@
1
+ import { IndexedByteArray } from "../../../utils/indexed_array.js";
2
+ import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
3
+
4
+ export class Articulator
5
+ {
6
+ /**
7
+ * @type {DLSSources}
8
+ */
9
+ source;
10
+ /**
11
+ * @type {DLSSources}
12
+ */
13
+ control;
14
+ /**
15
+ * @type {DLSDestinations}
16
+ */
17
+ destination;
18
+ /**
19
+ * @type {number}
20
+ */
21
+ scale;
22
+ /**
23
+ * @type {number}
24
+ */
25
+ transform;
26
+
27
+ constructor(source, control, destination, scale, transform)
28
+ {
29
+ this.source = source;
30
+ this.control = control;
31
+ this.destination = destination;
32
+ this.scale = scale;
33
+ this.transform = transform;
34
+ }
35
+
36
+ /**
37
+ * @returns {IndexedByteArray}
38
+ */
39
+ writeArticulator()
40
+ {
41
+ const out = new IndexedByteArray(12);
42
+ writeWord(out, this.source);
43
+ writeWord(out, this.control);
44
+ writeWord(out, this.destination);
45
+ writeWord(out, this.transform);
46
+ writeDword(out, this.scale << 16);
47
+ return out;
48
+ }
49
+ }