spessasynth_lib 3.16.5 → 3.20.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 (87) hide show
  1. package/@types/index.d.ts +5 -4
  2. package/@types/midi_parser/basic_midi.d.ts +125 -0
  3. package/@types/midi_parser/midi_builder.d.ts +69 -0
  4. package/@types/midi_parser/midi_data.d.ts +2 -2
  5. package/@types/midi_parser/midi_editor.d.ts +4 -4
  6. package/@types/midi_parser/midi_loader.d.ts +3 -100
  7. package/@types/midi_parser/midi_writer.d.ts +2 -2
  8. package/@types/midi_parser/rmidi_writer.d.ts +3 -3
  9. package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
  10. package/@types/sequencer/sequencer.d.ts +1 -1
  11. package/@types/soundfont/basic_soundfont/basic_sample.d.ts +2 -2
  12. package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
  13. package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
  14. package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
  15. package/@types/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
  16. package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
  17. package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
  18. package/@types/soundfont/dls/dls_preset.d.ts +13 -0
  19. package/@types/soundfont/dls/dls_sample.d.ts +18 -0
  20. package/@types/soundfont/dls/dls_soundfont.d.ts +31 -0
  21. package/@types/soundfont/dls/dls_sources.d.ts +22 -0
  22. package/@types/soundfont/dls/dls_zone.d.ts +22 -0
  23. package/@types/soundfont/dls/read_articulation.d.ts +12 -0
  24. package/@types/soundfont/dls/read_instrument.d.ts +5 -0
  25. package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
  26. package/@types/soundfont/dls/read_lart.d.ts +7 -0
  27. package/@types/soundfont/dls/read_region.d.ts +7 -0
  28. package/@types/soundfont/dls/read_samples.d.ts +5 -0
  29. package/@types/soundfont/load_soundfont.d.ts +6 -0
  30. package/@types/soundfont/read_sf2/generators.d.ts +18 -5
  31. package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
  32. package/@types/soundfont/soundfont.d.ts +2 -1
  33. package/@types/synthetizer/synthetizer.d.ts +2 -2
  34. package/@types/utils/byte_functions/little_endian.d.ts +1 -1
  35. package/README.md +27 -15
  36. package/index.js +6 -4
  37. package/midi_parser/basic_midi.js +146 -0
  38. package/midi_parser/midi_builder.js +281 -0
  39. package/midi_parser/midi_data.js +1 -1
  40. package/midi_parser/midi_editor.js +2 -2
  41. package/midi_parser/midi_loader.js +38 -56
  42. package/midi_parser/midi_writer.js +1 -1
  43. package/midi_parser/rmidi_writer.js +2 -2
  44. package/midi_parser/used_keys_loaded.js +1 -1
  45. package/package.json +1 -1
  46. package/sequencer/sequencer.js +1 -1
  47. package/sequencer/worklet_sequencer/song_control.js +3 -3
  48. package/sequencer/worklet_sequencer/worklet_sequencer.js +1 -1
  49. package/soundfont/README.md +6 -2
  50. package/soundfont/basic_soundfont/basic_sample.js +3 -3
  51. package/soundfont/basic_soundfont/basic_zone.js +28 -28
  52. package/soundfont/basic_soundfont/basic_zones.js +15 -19
  53. package/soundfont/basic_soundfont/riff_chunk.js +20 -4
  54. package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
  55. package/soundfont/dls/articulator_converter.js +311 -0
  56. package/soundfont/dls/dls_destinations.js +38 -0
  57. package/soundfont/dls/dls_preset.js +32 -0
  58. package/soundfont/dls/dls_sample.js +58 -0
  59. package/soundfont/dls/dls_soundfont.js +150 -0
  60. package/soundfont/dls/dls_sources.js +26 -0
  61. package/soundfont/dls/dls_zone.js +75 -0
  62. package/soundfont/dls/read_articulation.js +327 -0
  63. package/soundfont/dls/read_instrument.js +100 -0
  64. package/soundfont/dls/read_instrument_list.js +17 -0
  65. package/soundfont/dls/read_lart.js +35 -0
  66. package/soundfont/dls/read_region.js +129 -0
  67. package/soundfont/dls/read_samples.js +174 -0
  68. package/soundfont/load_soundfont.js +21 -0
  69. package/soundfont/read_sf2/generators.js +41 -6
  70. package/soundfont/read_sf2/instruments.js +2 -2
  71. package/soundfont/read_sf2/modulators.js +8 -8
  72. package/soundfont/read_sf2/presets.js +7 -7
  73. package/soundfont/read_sf2/samples.js +8 -8
  74. package/soundfont/read_sf2/zones.js +5 -5
  75. package/soundfont/soundfont.js +8 -3
  76. package/synthetizer/synthetizer.js +1 -1
  77. package/synthetizer/worklet_processor.min.js +10 -7
  78. package/synthetizer/worklet_system/main_processor.js +1 -2
  79. package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -3
  80. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
  81. package/utils/buffer_to_wav.js +5 -26
  82. package/utils/byte_functions/little_endian.js +1 -1
  83. /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
  84. /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
  85. /package/{midi_handler → external_midi}/README.md +0 -0
  86. /package/{midi_handler → external_midi}/midi_handler.js +0 -0
  87. /package/{midi_handler → external_midi}/web_midi_link.js +0 -0
@@ -2,16 +2,16 @@ import { BasicZone } from './basic_zone.js'
2
2
 
3
3
  export class BasicInstrumentZone extends BasicZone
4
4
  {
5
- constructor()
6
- {
7
- super()
8
- /**
9
- * Zone's sample. Undefined if global
10
- * @type {BasicSample|undefined}
11
- */
12
- this.sample = undefined
13
- this.useCount = 0
14
- }
5
+ /**
6
+ * Zone's sample. Undefined if global
7
+ * @type {BasicSample|undefined}
8
+ */
9
+ sample = undefined;
10
+ /**
11
+ * The zone's use count
12
+ * @type {number}
13
+ */
14
+ useCount = 0;
15
15
 
16
16
  deleteZone()
17
17
  {
@@ -26,15 +26,11 @@ export class BasicInstrumentZone extends BasicZone
26
26
 
27
27
  export class BasicPresetZone extends BasicZone
28
28
  {
29
- constructor()
30
- {
31
- super()
32
- /**
33
- * Zone's instrument. Undefined if global
34
- * @type {BasicInstrument|undefined}
35
- */
36
- this.instrument = undefined
37
- }
29
+ /**
30
+ * Zone's instrument. Undefined if global
31
+ * @type {BasicInstrument|undefined}
32
+ */
33
+ instrument = undefined
38
34
 
39
35
  deleteZone()
40
36
  {
@@ -1,5 +1,5 @@
1
1
  import { IndexedByteArray } from '../../utils/indexed_array.js'
2
- import { readBytesAsUintLittleEndian, writeDword } from '../../utils/byte_functions/little_endian.js'
2
+ import { readLittleEndian, writeDword } from '../../utils/byte_functions/little_endian.js'
3
3
  import { readBytesAsString, writeStringAsBytes } from '../../utils/byte_functions/string.js'
4
4
 
5
5
  /**
@@ -33,12 +33,11 @@ export class RiffChunk
33
33
  export function readRIFFChunk(dataArray, readData = true, forceShift = false) {
34
34
  let header = readBytesAsString(dataArray, 4)
35
35
 
36
- let size = readBytesAsUintLittleEndian(dataArray, 4)
36
+ let size = readLittleEndian(dataArray, 4)
37
37
  let chunkData = undefined
38
38
  if (readData)
39
39
  {
40
- chunkData = new IndexedByteArray(size)
41
- chunkData.set(dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + size))
40
+ chunkData = new IndexedByteArray(dataArray.buffer.slice(dataArray.currentIndex, dataArray.currentIndex + size))
42
41
  }
43
42
  if(readData || forceShift)
44
43
  {
@@ -112,4 +111,21 @@ export function writeRIFFOddSize(header, data, addZeroByte = false)
112
111
  writeDword(outArray, data.length);
113
112
  outArray.set(data, 8);
114
113
  return outArray;
114
+ }
115
+
116
+ /**
117
+ * @param collection {RiffChunk[]}
118
+ * @param type {string}
119
+ * @returns {RiffChunk|undefined}
120
+ */
121
+ export function findRIFFListType(collection, type)
122
+ {
123
+ return collection.find(c => {
124
+ if(c.header !== "LIST")
125
+ {
126
+ return false;
127
+ }
128
+ c.chunkData.currentIndex = 0;
129
+ return readBytesAsString(c.chunkData, 4) === type;
130
+ });
115
131
  }
@@ -9,7 +9,7 @@ import { getUsedProgramsAndKeys } from '../../../midi_parser/used_keys_loaded.js
9
9
 
10
10
  /**
11
11
  * @param soundfont {BasicSoundFont}
12
- * @param mid {MIDI}
12
+ * @param mid {BasicMIDI}
13
13
  * @returns {Uint8Array}
14
14
  */
15
15
  export function trimSoundfont(soundfont, mid)
@@ -0,0 +1,311 @@
1
+ import { DLSSources } from './dls_sources.js'
2
+ import { getModSourceEnum, Modulator, modulatorCurveTypes, modulatorSources } from '../read_sf2/modulators.js'
3
+ import { midiControllers } from '../../midi_parser/midi_message.js'
4
+ import { DLSDestinations } from './dls_destinations.js'
5
+ import { generatorTypes } from '../read_sf2/generators.js'
6
+
7
+ /**
8
+ * @param source {number}
9
+ * @returns {{enum: number, isCC: boolean}|undefined}
10
+ */
11
+ function getSF2SourceFromDLS(source)
12
+ {
13
+ let sourceEnum = undefined;
14
+ let isCC = false;
15
+ switch(source)
16
+ {
17
+ default:
18
+ case DLSSources.modLfo:
19
+ case DLSSources.vibratoLfo:
20
+ case DLSSources.coarseTune:
21
+ case DLSSources.fineTune:
22
+ case DLSSources.modEnv:
23
+ return undefined; // cannot be this in sf2
24
+
25
+ case DLSSources.keyNum:
26
+ sourceEnum = modulatorSources.noteOnKeyNum;
27
+ break;
28
+ case DLSSources.none:
29
+ sourceEnum = modulatorSources.noController;
30
+ break;
31
+ case DLSSources.modulationWheel:
32
+ sourceEnum = midiControllers.modulationWheel;
33
+ isCC = true;
34
+ break;
35
+ case DLSSources.pan:
36
+ sourceEnum = midiControllers.pan;
37
+ isCC = true;
38
+ break;
39
+ case DLSSources.reverb:
40
+ sourceEnum = midiControllers.effects1Depth;
41
+ isCC = true;
42
+ break;
43
+ case DLSSources.chorus:
44
+ sourceEnum = midiControllers.effects3Depth;
45
+ isCC = true;
46
+ break;
47
+ case DLSSources.expression:
48
+ sourceEnum = midiControllers.expressionController;
49
+ isCC = true;
50
+ break;
51
+ case DLSSources.volume:
52
+ sourceEnum = midiControllers.mainVolume;
53
+ isCC = true;
54
+ break;
55
+ case DLSSources.velocity:
56
+ sourceEnum = modulatorSources.noteOnVelocity;
57
+ break;
58
+ case DLSSources.polyPressure:
59
+ sourceEnum = modulatorSources.polyPressure;
60
+ break;
61
+ case DLSSources.channelPressure:
62
+ sourceEnum = modulatorSources.channelPressure;
63
+ break;
64
+ case DLSSources.pitchWheel:
65
+ sourceEnum = modulatorSources.pitchWheel;
66
+ break;
67
+ case DLSSources.pitchWheelRange:
68
+ sourceEnum = modulatorSources.pitchWheelRange;
69
+ break;
70
+ }
71
+ if(sourceEnum === undefined)
72
+ {
73
+ throw `not known?? ${source}`
74
+ }
75
+ return {enum: sourceEnum, isCC: isCC};
76
+ }
77
+
78
+ /**
79
+ * @param destination {number}
80
+ * @param amount {number}
81
+ * @returns {generatorTypes|{gen: generatorTypes, newAmount: number}} // transform amount to sf2 units
82
+ */
83
+ function getSF2GeneratorFromDLS(destination, amount)
84
+ {
85
+ switch (destination)
86
+ {
87
+ default:
88
+ case DLSDestinations.none:
89
+ return undefined;
90
+ case DLSDestinations.pan:
91
+ return generatorTypes.pan;
92
+ case DLSDestinations.gain:
93
+ return {gen: generatorTypes.initialAttenuation, newAmount: amount * -1};
94
+ case DLSDestinations.pitch:
95
+ return generatorTypes.fineTune;
96
+ case DLSDestinations.keyNum:
97
+ return generatorTypes.overridingRootKey;
98
+
99
+ // vol env
100
+ case DLSDestinations.volEnvDelay:
101
+ return generatorTypes.delayVolEnv;
102
+ case DLSDestinations.volEnvAttack:
103
+ return generatorTypes.attackVolEnv;
104
+ case DLSDestinations.volEnvHold:
105
+ return generatorTypes.holdVolEnv;
106
+ case DLSDestinations.volEnvDecay:
107
+ return generatorTypes.decayVolEnv;
108
+ case DLSDestinations.volEnvSustain:
109
+ return {gen: generatorTypes.sustainVolEnv, newAmount: 1000 - amount};
110
+ case DLSDestinations.volEnvRelease:
111
+ return generatorTypes.releaseVolEnv;
112
+
113
+ // mod env
114
+ case DLSDestinations.modEnvDelay:
115
+ return generatorTypes.delayModEnv;
116
+ case DLSDestinations.modEnvAttack:
117
+ return generatorTypes.attackModEnv;
118
+ case DLSDestinations.modEnvHold:
119
+ return generatorTypes.holdModEnv;
120
+ case DLSDestinations.modEnvDecay:
121
+ return generatorTypes.decayModEnv;
122
+ case DLSDestinations.modEnvSustain:
123
+ return {gen: generatorTypes.sustainModEnv, newAmount: (1000 - amount) / 10};
124
+ case DLSDestinations.modEnvRelease:
125
+ return generatorTypes.releaseModEnv;
126
+
127
+ case DLSDestinations.filterCutoff:
128
+ return generatorTypes.initialFilterFc;
129
+ case DLSDestinations.filterQ:
130
+ return generatorTypes.initialFilterQ;
131
+ case DLSDestinations.chorusSend:
132
+ return generatorTypes.chorusEffectsSend;
133
+ case DLSDestinations.reverbSend:
134
+ return generatorTypes.reverbEffectsSend;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * checks for combos such as mod lfo as source and pitch as destination which results in modLfoToPitch
140
+ * @param source {number}
141
+ * @param destination {number}
142
+ * @returns {generatorTypes} real destination
143
+ */
144
+ function checkForSpecialDLSCombo(source, destination)
145
+ {
146
+ if(source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
147
+ {
148
+ return generatorTypes.vibLfoToPitch;
149
+ }
150
+ else
151
+ if(source === DLSSources.modLfo && destination === DLSDestinations.pitch)
152
+ {
153
+ return generatorTypes.modLfoToPitch;
154
+ }
155
+ else
156
+ if(source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
157
+ {
158
+ return generatorTypes.modLfoToFilterFc;
159
+ }
160
+ else
161
+ if(source === DLSSources.modLfo && destination === DLSDestinations.gain)
162
+ {
163
+ return generatorTypes.modLfoToVolume;
164
+ }
165
+ else
166
+ if(source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
167
+ {
168
+ return generatorTypes.modEnvToFilterFc;
169
+ }
170
+ else
171
+ if(source === DLSSources.modEnv && destination === DLSDestinations.pitch)
172
+ {
173
+ return generatorTypes.modEnvToPitch;
174
+ }
175
+ else
176
+ {
177
+ return undefined;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * @param source {number}
183
+ * @param control {number}
184
+ * @param destination {number}
185
+ * @param transform {number}
186
+ * @param value {number}
187
+ * @returns {Modulator|undefined}
188
+ */
189
+ export function getSF2ModulatorFromArticulator(
190
+ source,
191
+ control,
192
+ destination,
193
+ transform,
194
+ value
195
+ )
196
+ {
197
+ // check for special combinations
198
+ const specialDestination = checkForSpecialDLSCombo(source, destination);
199
+ /**
200
+ * @type {generatorTypes}
201
+ */
202
+ let destinationGenerator;
203
+ /**
204
+ * @type {{enum: number, isCC: boolean}}
205
+ */
206
+ let sf2Source
207
+ let swapSources = false;
208
+ let isSourceNoController = false;
209
+ if(specialDestination === undefined)
210
+ {
211
+ // determine destination
212
+ const sf2GenDestination = getSF2GeneratorFromDLS(destination, value);
213
+ if(sf2GenDestination === undefined)
214
+ {
215
+ // cannot be a valid modulator
216
+ return undefined;
217
+ }
218
+ /**
219
+ * @type {generatorTypes}
220
+ */
221
+ destinationGenerator = sf2GenDestination;
222
+ if(sf2GenDestination.newAmount !== undefined)
223
+ {
224
+ value = sf2GenDestination.newAmount;
225
+ destinationGenerator = sf2GenDestination.gen;
226
+ }
227
+ sf2Source = getSF2SourceFromDLS(source);
228
+ if(sf2Source === undefined)
229
+ {
230
+ // cannot be a valid modulator
231
+ return undefined;
232
+ }
233
+ }
234
+ else
235
+ {
236
+ destinationGenerator = specialDestination;
237
+ swapSources = true;
238
+ sf2Source = {enum: modulatorSources.noController, isCC: false};
239
+ isSourceNoController = true;
240
+ }
241
+ let sf2SecondSource = getSF2SourceFromDLS(control);
242
+ if(sf2SecondSource === undefined)
243
+ {
244
+ // cannot be a valid modulator
245
+ return undefined;
246
+ }
247
+
248
+ // get transforms and final enums
249
+ let sourceEnumFinal;
250
+ if(isSourceNoController)
251
+ {
252
+ // we force it into this state because before it was some strange value,
253
+ // like vibrato lfo bipolar for example
254
+ // since we turn it into NoController -> vibLfoToPitch the result is the same and bipolar concontroller is technically 0
255
+ sourceEnumFinal = 0x0;
256
+ }
257
+ else
258
+ {
259
+ // output transform is ignored as it's not a thing in sfont format
260
+ // unless the curve type of source is linear, then output is copied
261
+ const outputTransform = transform & 0b1111;
262
+ // source curve type maps to desfont curve type in section 2.10, table 9
263
+ let sourceTransform = (transform >> 10) & 0b1111;
264
+ if(sourceTransform === modulatorCurveTypes.linear && outputTransform !== modulatorCurveTypes.linear)
265
+ {
266
+ sourceTransform = outputTransform;
267
+ }
268
+ const sourceIsBipolar = (transform >> 14) & 1;
269
+ let sourceIsNegative = (transform >> 15) & 1;
270
+ // special case: for attenuation, invert source
271
+ if(destinationGenerator === generatorTypes.initialAttenuation)
272
+ {
273
+ sourceIsNegative = !sourceIsNegative;
274
+ }
275
+ sourceEnumFinal = getModSourceEnum(
276
+ sourceTransform,
277
+ sourceIsBipolar,
278
+ sourceIsNegative,
279
+ sf2Source.isCC,
280
+ sf2Source.enum
281
+ );
282
+ }
283
+
284
+ const secSourceTransform = (transform >> 4) & 0b1111;
285
+ const secSourceIsBipolar = (transform >> 8) & 1;
286
+ const secSourceIsNegative = transform >> 9 & 1;
287
+ let secSourceEnumFinal = getModSourceEnum(
288
+ secSourceTransform,
289
+ secSourceIsBipolar,
290
+ secSourceIsNegative,
291
+ sf2SecondSource.isCC,
292
+ sf2SecondSource.enum
293
+ );
294
+
295
+ if(swapSources)
296
+ {
297
+ const temp = secSourceEnumFinal;
298
+ secSourceEnumFinal = sourceEnumFinal;
299
+ sourceEnumFinal = temp;
300
+ }
301
+
302
+ // return the modulator!
303
+ return new Modulator({
304
+ srcEnum: sourceEnumFinal,
305
+ secSrcEnum: secSourceEnumFinal,
306
+ dest: destinationGenerator,
307
+ transform: 0x0,
308
+ amt: value
309
+ });
310
+
311
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ *
3
+ * @enum {number}
4
+ */
5
+ export const DLSDestinations = {
6
+ none: 0x0,
7
+ gain: 0x1,
8
+ reserved: 0x2,
9
+ pitch: 0x3,
10
+ pan: 0x4,
11
+ keyNum: 0x5,
12
+ // nuh uh, the channel controllers are not supported!!!!
13
+ chorusSend: 0x80,
14
+ reverbSend: 0x81,
15
+
16
+ modLfoFreq: 0x104,
17
+ modLfoDelay: 0x105,
18
+
19
+ vibLfoFreq: 0x114,
20
+ vibLfoDelay: 0x115,
21
+
22
+ volEnvAttack: 0x206,
23
+ volEnvDecay: 0x207,
24
+ volEnvRelease: 0x209,
25
+ volEnvSustain: 0x20a,
26
+ volEnvDelay: 0x20b,
27
+ volEnvHold: 0x20c,
28
+
29
+ modEnvAttack: 0x30a,
30
+ modEnvDecay: 0x30b,
31
+ modEnvRelease: 0x30d,
32
+ modEnvSustain: 0x30e,
33
+ modEnvDelay: 0x30f,
34
+ modEnvHold: 0x310,
35
+
36
+ filterCutoff: 0x500,
37
+ filterQ: 0x501
38
+ }
@@ -0,0 +1,32 @@
1
+ import { BasicPreset } from '../basic_soundfont/basic_preset.js'
2
+ import { BasicPresetZone } from '../basic_soundfont/basic_zones.js'
3
+ import { BasicInstrument } from '../basic_soundfont/basic_instrument.js'
4
+
5
+ export class DLSPreset extends BasicPreset
6
+ {
7
+ /**
8
+ * Creates a new DLS preset
9
+ * @param ulBank {number}
10
+ * @param ulInstrument {number}
11
+ */
12
+ constructor(ulBank, ulInstrument)
13
+ {
14
+ super();
15
+ this.program = ulInstrument & 127;
16
+ this.bank = (ulBank >> 8) & 127;
17
+ const isDrums = ulBank >> 31;
18
+ if(isDrums)
19
+ {
20
+ // soundfont bank is 128 so we change it here
21
+ this.bank = 128;
22
+ }
23
+
24
+ this.DLSInstrument = new BasicInstrument();
25
+ this.DLSInstrument.addUseCount();
26
+
27
+ const zone = new BasicPresetZone();
28
+ zone.instrument = this.DLSInstrument;
29
+
30
+ this.presetZones = [zone];
31
+ }
32
+ }
@@ -0,0 +1,58 @@
1
+ import { BasicSample } from '../basic_soundfont/basic_sample.js'
2
+
3
+ export class DLSSample extends BasicSample
4
+ {
5
+ /**
6
+ * @param name {string}
7
+ * @param rate {number}
8
+ * @param pitch {number}
9
+ * @param pitchCorrection {number}
10
+ * @param loopStart {number} sample data points
11
+ * @param loopEnd {number} sample data points
12
+ * @param data {Float32Array}
13
+ */
14
+ constructor(
15
+ name,
16
+ rate,
17
+ pitch,
18
+ pitchCorrection,
19
+ loopStart,
20
+ loopEnd,
21
+ data
22
+ )
23
+ {
24
+ super(
25
+ name,
26
+ rate,
27
+ pitch,
28
+ pitchCorrection,
29
+ 0,
30
+ 1,
31
+ loopStart * 2,
32
+ (loopEnd - 1) * 2 // -1 sample because soundfont end is last sample and dls end is next sample
33
+ );
34
+ this.sampleData = data;
35
+ }
36
+
37
+ getAudioData()
38
+ {
39
+ return this.sampleData;
40
+ }
41
+
42
+ /**
43
+ * @type {Float32Array}
44
+ */
45
+ sampleData;
46
+
47
+ getRawData()
48
+ {
49
+ const uint8 = new Uint8Array(this.sampleData.length * 2);
50
+ for (let i = 0; i < this.sampleData.length; i++)
51
+ {
52
+ const sample = Math.floor(this.sampleData[i] * 32767);
53
+ uint8[i * 2] = sample & 0xFF; // lower byte
54
+ uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
55
+ }
56
+ return uint8;
57
+ }
58
+ }
@@ -0,0 +1,150 @@
1
+ import { BasicSoundFont } from '../basic_soundfont/basic_soundfont.js'
2
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
3
+ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from '../../utils/loggin.js'
4
+ import { consoleColors } from '../../utils/other.js'
5
+ import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
6
+ import { readBytesAsString } from '../../utils/byte_functions/string.js'
7
+ import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
8
+ import { readDLSInstrumentList } from './read_instrument_list.js'
9
+ import { readDLSInstrument } from './read_instrument.js'
10
+ import { readLart } from './read_lart.js'
11
+ import { readRegion } from './read_region.js'
12
+ import { readDLSSamples } from './read_samples.js'
13
+
14
+ class DLSSoundFont extends BasicSoundFont
15
+ {
16
+ /**
17
+ * Loads a new DLS (Downloadable sounds) soundfont
18
+ * @param buffer {ArrayBuffer}
19
+ */
20
+ constructor(buffer)
21
+ {
22
+ super();
23
+ this.dataArray = new IndexedByteArray(buffer);
24
+ SpessaSynthGroup("%cParsing DLS...", consoleColors.info);
25
+ if(!this.dataArray)
26
+ {
27
+ SpessaSynthGroupEnd();
28
+ throw new TypeError("No data!");
29
+ }
30
+
31
+ // read the main chunk
32
+ let firstChunk = readRIFFChunk(this.dataArray, false);
33
+ this.verifyHeader(firstChunk, "riff");
34
+ this.verifyText(readBytesAsString(this.dataArray,4).toLowerCase(), "dls ");
35
+
36
+ /**
37
+ * Read list
38
+ * @type {RiffChunk[]}
39
+ */
40
+ const chunks = [];
41
+ while(this.dataArray.currentIndex < this.dataArray.length)
42
+ {
43
+ chunks.push(readRIFFChunk(this.dataArray));
44
+ }
45
+
46
+ // mandatory
47
+ this.soundFontInfo["ifil"] = "2.1"; // always for dls
48
+ this.soundFontInfo["isng"] = "EMU8000";
49
+
50
+ // set some defaults
51
+ this.soundFontInfo["IPRD"] = "SpessaSynth DLS";
52
+ this.soundFontInfo["ICRD"] = new Date().toDateString();
53
+
54
+ // read info
55
+ const infoChunk = findRIFFListType(chunks, "INFO");
56
+ if(infoChunk)
57
+ {
58
+ while(infoChunk.chunkData.currentIndex < infoChunk.chunkData.length)
59
+ {
60
+ const infoPart = readRIFFChunk(infoChunk.chunkData);
61
+ this.soundFontInfo[infoPart.header] = readBytesAsString(infoPart.chunkData, infoPart.size);
62
+ }
63
+ }
64
+ this.soundFontInfo["ICMT"] = (this.soundFontInfo["ICMT"] || "") + "\nConverted from DLS to SF2 with SpessaSynth";
65
+ if(this.soundFontInfo["ISBJ"])
66
+ {
67
+ // merge it
68
+ this.soundFontInfo["ICMT"] += "\n" + this.soundFontInfo["ISBJ"];
69
+ delete this.soundFontInfo["ISBJ"];
70
+ }
71
+
72
+ for(const [info, value] of Object.entries(this.soundFontInfo))
73
+ {
74
+ SpessaSynthInfo(`%c"${info}": %c"${value}"`,
75
+ consoleColors.info,
76
+ consoleColors.recognized);
77
+ }
78
+
79
+ // read "colh"
80
+ let colhChunk = chunks.find(c => c.header === "colh");
81
+ if(!colhChunk)
82
+ {
83
+ SpessaSynthGroupEnd();
84
+ throw new Error("No colh chunk!");
85
+ }
86
+ this.instrumentAmount = readLittleEndian(colhChunk.chunkData, 4);
87
+ SpessaSynthInfo(`%cInstruments amount: %c${this.instrumentAmount}`,
88
+ consoleColors.info,
89
+ consoleColors.recognized);
90
+
91
+ // read wave list
92
+ let waveListChunk = findRIFFListType(chunks , "wvpl");
93
+ this.readDLSSamples(waveListChunk);
94
+
95
+ // read instrument list
96
+ let instrumentListChunk = findRIFFListType(chunks, "lins");
97
+ if(!instrumentListChunk)
98
+ {
99
+ SpessaSynthGroupEnd();
100
+ throw new Error("No lins chunk!");
101
+ }
102
+ this.readDLSInstrumentList(instrumentListChunk);
103
+
104
+ SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"] || "UNNAMED"}"%c has %c${this.presets.length} %cpresets,
105
+ %c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
106
+ consoleColors.info,
107
+ consoleColors.recognized,
108
+ consoleColors.info,
109
+ consoleColors.recognized,
110
+ consoleColors.info,
111
+ consoleColors.recognized,
112
+ consoleColors.info,
113
+ consoleColors.recognized,
114
+ consoleColors.info);
115
+ SpessaSynthGroupEnd();
116
+ }
117
+
118
+ /**
119
+ * @param chunk {RiffChunk}
120
+ * @param expected {string}
121
+ */
122
+ verifyHeader(chunk, expected)
123
+ {
124
+ if(chunk.header.toLowerCase() !== expected.toLowerCase())
125
+ {
126
+ SpessaSynthGroupEnd();
127
+ throw new SyntaxError(`Invalid DLS chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * @param text {string}
133
+ * @param expected {string}
134
+ */
135
+ verifyText(text, expected)
136
+ {
137
+ if(text.toLowerCase() !== expected.toLowerCase())
138
+ {
139
+ SpessaSynthGroupEnd();
140
+ throw new SyntaxError(`Invalid DLS soundfont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
141
+ }
142
+ }
143
+ }
144
+ DLSSoundFont.prototype.readDLSInstrumentList = readDLSInstrumentList;
145
+ DLSSoundFont.prototype.readDLSInstrument = readDLSInstrument;
146
+ DLSSoundFont.prototype.readRegion = readRegion;
147
+ DLSSoundFont.prototype.readLart = readLart;
148
+ DLSSoundFont.prototype.readDLSSamples = readDLSSamples;
149
+
150
+ export {DLSSoundFont}