spessasynth_lib 0.0.1

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 (154) hide show
  1. package/.idea/modules.xml +8 -0
  2. package/.idea/spessasynth_lib.iml +12 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/copy_version.sh +38 -0
  5. package/index.js +73 -0
  6. package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  7. package/package/@types/index.d.ts +34 -0
  8. package/package/@types/midi_handler/midi_handler.d.ts +39 -0
  9. package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
  10. package/package/@types/midi_parser/midi_data.d.ts +95 -0
  11. package/package/@types/midi_parser/midi_editor.d.ts +45 -0
  12. package/package/@types/midi_parser/midi_loader.d.ts +100 -0
  13. package/package/@types/midi_parser/midi_message.d.ts +154 -0
  14. package/package/@types/midi_parser/midi_writer.d.ts +6 -0
  15. package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  16. package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  17. package/package/@types/sequencer/sequencer.d.ts +180 -0
  18. package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  19. package/package/@types/soundfont/read/generators.d.ts +98 -0
  20. package/package/@types/soundfont/read/instruments.d.ts +50 -0
  21. package/package/@types/soundfont/read/modulators.d.ts +73 -0
  22. package/package/@types/soundfont/read/presets.d.ts +87 -0
  23. package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  24. package/package/@types/soundfont/read/samples.d.ts +134 -0
  25. package/package/@types/soundfont/read/zones.d.ts +141 -0
  26. package/package/@types/soundfont/soundfont.d.ts +76 -0
  27. package/package/@types/soundfont/write/ibag.d.ts +6 -0
  28. package/package/@types/soundfont/write/igen.d.ts +6 -0
  29. package/package/@types/soundfont/write/imod.d.ts +6 -0
  30. package/package/@types/soundfont/write/inst.d.ts +6 -0
  31. package/package/@types/soundfont/write/pbag.d.ts +6 -0
  32. package/package/@types/soundfont/write/pgen.d.ts +6 -0
  33. package/package/@types/soundfont/write/phdr.d.ts +6 -0
  34. package/package/@types/soundfont/write/pmod.d.ts +6 -0
  35. package/package/@types/soundfont/write/sdta.d.ts +11 -0
  36. package/package/@types/soundfont/write/shdr.d.ts +8 -0
  37. package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  38. package/package/@types/soundfont/write/write.d.ts +21 -0
  39. package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  40. package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  41. package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  42. package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  43. package/package/@types/synthetizer/synthetizer.d.ts +294 -0
  44. package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
  45. package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  46. package/package/@types/synthetizer/worklet_url.d.ts +5 -0
  47. package/package/@types/utils/buffer_to_wav.d.ts +8 -0
  48. package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  49. package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  50. package/package/@types/utils/byte_functions/string.d.ts +22 -0
  51. package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  52. package/package/@types/utils/indexed_array.d.ts +21 -0
  53. package/package/@types/utils/loggin.d.ts +26 -0
  54. package/package/@types/utils/other.d.ts +32 -0
  55. package/package/LICENSE +26 -0
  56. package/package/README.md +84 -0
  57. package/package/externals/NOTICE +9 -0
  58. package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  59. package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  60. package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  61. package/package/externals/stbvorbis_sync/LICENSE +202 -0
  62. package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  63. package/package/index.js +73 -0
  64. package/package/midi_handler/README.md +3 -0
  65. package/package/midi_handler/midi_handler.js +118 -0
  66. package/package/midi_handler/web_midi_link.js +41 -0
  67. package/package/midi_parser/README.md +3 -0
  68. package/package/midi_parser/midi_data.js +121 -0
  69. package/package/midi_parser/midi_editor.js +557 -0
  70. package/package/midi_parser/midi_loader.js +502 -0
  71. package/package/midi_parser/midi_message.js +234 -0
  72. package/package/midi_parser/midi_writer.js +95 -0
  73. package/package/midi_parser/rmidi_writer.js +271 -0
  74. package/package/midi_parser/used_keys_loaded.js +172 -0
  75. package/package/package.json +43 -0
  76. package/package/sequencer/README.md +23 -0
  77. package/package/sequencer/sequencer.js +439 -0
  78. package/package/sequencer/worklet_sequencer/events.js +92 -0
  79. package/package/sequencer/worklet_sequencer/play.js +309 -0
  80. package/package/sequencer/worklet_sequencer/process_event.js +167 -0
  81. package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
  82. package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  83. package/package/sequencer/worklet_sequencer/song_control.js +193 -0
  84. package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  85. package/package/soundfont/README.md +8 -0
  86. package/package/soundfont/read/generators.js +212 -0
  87. package/package/soundfont/read/instruments.js +125 -0
  88. package/package/soundfont/read/modulators.js +249 -0
  89. package/package/soundfont/read/presets.js +300 -0
  90. package/package/soundfont/read/riff_chunk.js +81 -0
  91. package/package/soundfont/read/samples.js +398 -0
  92. package/package/soundfont/read/zones.js +310 -0
  93. package/package/soundfont/soundfont.js +357 -0
  94. package/package/soundfont/write/ibag.js +39 -0
  95. package/package/soundfont/write/igen.js +75 -0
  96. package/package/soundfont/write/imod.js +46 -0
  97. package/package/soundfont/write/inst.js +34 -0
  98. package/package/soundfont/write/pbag.js +39 -0
  99. package/package/soundfont/write/pgen.js +77 -0
  100. package/package/soundfont/write/phdr.js +42 -0
  101. package/package/soundfont/write/pmod.js +46 -0
  102. package/package/soundfont/write/sdta.js +72 -0
  103. package/package/soundfont/write/shdr.js +54 -0
  104. package/package/soundfont/write/soundfont_trimmer.js +169 -0
  105. package/package/soundfont/write/write.js +180 -0
  106. package/package/synthetizer/README.md +6 -0
  107. package/package/synthetizer/audio_effects/effects_config.js +21 -0
  108. package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  109. package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  110. package/package/synthetizer/audio_effects/reverb.js +24 -0
  111. package/package/synthetizer/synth_event_handler.js +156 -0
  112. package/package/synthetizer/synthetizer.js +766 -0
  113. package/package/synthetizer/worklet_processor.min.js +13 -0
  114. package/package/synthetizer/worklet_system/README.md +6 -0
  115. package/package/synthetizer/worklet_system/main_processor.js +363 -0
  116. package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
  117. package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  118. package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
  119. package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
  120. package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  121. package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  122. package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  123. package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  124. package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  125. package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  126. package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  127. package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  128. package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  129. package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  130. package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  131. package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
  132. package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  133. package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  134. package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  135. package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  136. package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  137. package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  138. package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  139. package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  140. package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  141. package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  142. package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  143. package/package/synthetizer/worklet_url.js +5 -0
  144. package/package/utils/README.md +4 -0
  145. package/package/utils/buffer_to_wav.js +101 -0
  146. package/package/utils/byte_functions/big_endian.js +28 -0
  147. package/package/utils/byte_functions/little_endian.js +74 -0
  148. package/package/utils/byte_functions/string.js +97 -0
  149. package/package/utils/byte_functions/variable_length_quantity.js +37 -0
  150. package/package/utils/encode_vorbis.js +30 -0
  151. package/package/utils/indexed_array.js +41 -0
  152. package/package/utils/loggin.js +79 -0
  153. package/package/utils/other.js +54 -0
  154. package/package.json +43 -0
@@ -0,0 +1,310 @@
1
+ import {readBytesAsUintLittleEndian} from "../../utils/byte_functions/little_endian.js";
2
+ import {IndexedByteArray} from "../../utils/indexed_array.js";
3
+ import {RiffChunk} from "./riff_chunk.js";
4
+ import {Generator, generatorTypes} from "./generators.js";
5
+ import {Sample} from "./samples.js";
6
+ import {Instrument} from "./instruments.js";
7
+ import {Modulator} from "./modulators.js";
8
+
9
+ /**
10
+ * zones.js
11
+ * purpose: reads instrumend and preset zones from soundfont and gets their respective samples and generators and modulators
12
+ */
13
+
14
+ export class InstrumentZone {
15
+ /**
16
+ * Creates a zone (instrument)
17
+ * @param dataArray {IndexedByteArray}
18
+ * @param index {number}
19
+ */
20
+ constructor(dataArray, index)
21
+ {
22
+ this.generatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
23
+ this.modulatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
24
+ this.modulatorZoneSize = 0;
25
+ this.generatorZoneSize = 0;
26
+ this.zoneID = index;
27
+ this.keyRange = {min: 0, max: 127};
28
+ this.velRange = {min: 0, max: 127}
29
+ this.isGlobal = true;
30
+ this.useCount = 0;
31
+
32
+ /**
33
+ * @type {Generator[]}
34
+ */
35
+ this.generators = [];
36
+ /**
37
+ * @type {Modulator[]}
38
+ */
39
+ this.modulators = [];
40
+ }
41
+
42
+ deleteZone()
43
+ {
44
+ this.useCount--;
45
+ if(this.isGlobal)
46
+ {
47
+ return;
48
+ }
49
+ this.sample.useCount--;
50
+ }
51
+
52
+ setZoneSize(modulatorZoneSize, generatorZoneSize)
53
+ {
54
+ this.modulatorZoneSize = modulatorZoneSize;
55
+ this.generatorZoneSize = generatorZoneSize;
56
+ }
57
+
58
+ /**
59
+ * grab the generators
60
+ * @param generators {Generator[]}
61
+ */
62
+ getGenerators(generators)
63
+ {
64
+ for(let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
65
+ {
66
+ this.generators.push(generators[i]);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * grab the modulators
72
+ * @param modulators {Modulator[]}
73
+ */
74
+ getModulators(modulators)
75
+ {
76
+ for(let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
77
+ {
78
+ this.modulators.push(modulators[i]);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Loads the zone's sample
84
+ * @param samples {Sample[]}
85
+ */
86
+ getSample(samples)
87
+ {
88
+ let sampleID = this.generators.find(g => g.generatorType === generatorTypes.sampleID);
89
+ if (sampleID)
90
+ {
91
+ this.sample = samples[sampleID.generatorValue];
92
+ this.isGlobal = false;
93
+ this.sample.useCount++;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Reads the keyRange of the zone
99
+ */
100
+ getKeyRange()
101
+ {
102
+ let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
103
+ if(range)
104
+ {
105
+ this.keyRange.min = range.generatorValue & 0x7F;
106
+ this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * reads the velolicty range of the zone
112
+ */
113
+ getVelRange()
114
+ {
115
+ let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
116
+ if(range)
117
+ {
118
+ this.velRange.min = range.generatorValue & 0x7F;
119
+ this.velRange.max = (range.generatorValue >> 8) & 0x7F;
120
+ }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Reads the given instrument zone read
126
+ * @param zonesChunk {RiffChunk}
127
+ * @param instrumentGenerators {Generator[]}
128
+ * @param instrumentModulators {Modulator[]}
129
+ * @param instrumentSamples {Sample[]}
130
+ * @returns {InstrumentZone[]}
131
+ */
132
+ export function readInstrumentZones(zonesChunk, instrumentGenerators, instrumentModulators, instrumentSamples)
133
+ {
134
+ /**
135
+ * @type {InstrumentZone[]}
136
+ */
137
+ let zones = [];
138
+ let index = 0;
139
+ while(zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
140
+ {
141
+ let zone = new InstrumentZone(zonesChunk.chunkData, index);
142
+ if(zones.length > 0)
143
+ {
144
+ let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
145
+ let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
146
+ zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
147
+ zones[zones.length - 1].getGenerators(instrumentGenerators);
148
+ zones[zones.length - 1].getModulators(instrumentModulators);
149
+ zones[zones.length - 1].getSample(instrumentSamples);
150
+ zones[zones.length - 1].getKeyRange();
151
+ zones[zones.length - 1].getVelRange();
152
+ }
153
+ zones.push(zone);
154
+ index++;
155
+ }
156
+ if(zones.length > 1)
157
+ {
158
+ // remove terminal
159
+ zones.pop();
160
+ }
161
+ return zones;
162
+ }
163
+
164
+ export class
165
+ PresetZone
166
+ {
167
+ /**
168
+ * Creates a zone (preset)
169
+ * @param dataArray {IndexedByteArray}
170
+ * @param index {number}
171
+ */
172
+ constructor(dataArray, index) {
173
+ this.generatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
174
+ this.modulatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
175
+ this.modulatorZoneSize = 0;
176
+ this.generatorZoneSize = 0;
177
+ this.zoneID = index;
178
+ this.keyRange = {min: 0, max: 127};
179
+ this.velRange = {min: 0, max: 127}
180
+ this.isGlobal = true;
181
+ /**
182
+ * @type {Generator[]}
183
+ */
184
+ this.generators = [];
185
+ /**
186
+ * @type {Modulator[]}
187
+ */
188
+ this.modulators = [];
189
+ }
190
+
191
+ setZoneSize(modulatorZoneSize, generatorZoneSize)
192
+ {
193
+ this.modulatorZoneSize = modulatorZoneSize;
194
+ this.generatorZoneSize = generatorZoneSize;
195
+ }
196
+
197
+ deleteZone()
198
+ {
199
+ if(this.isGlobal)
200
+ {
201
+ return;
202
+ }
203
+ this.instrument.removeUseCount();
204
+ }
205
+
206
+ /**
207
+ * grab the generators
208
+ * @param generators {Generator[]}
209
+ */
210
+ getGenerators(generators)
211
+ {
212
+ for(let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
213
+ {
214
+ this.generators.push(generators[i]);
215
+ }
216
+ }
217
+
218
+ /**
219
+ * grab the modulators
220
+ * @param modulators {Modulator[]}
221
+ */
222
+ getModulators(modulators)
223
+ {
224
+ for(let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
225
+ {
226
+ this.modulators.push(modulators[i]);
227
+ }
228
+ }
229
+
230
+ /**
231
+ * grab the instrument
232
+ * @param instruments {Instrument[]}
233
+ */
234
+ getInstrument(instruments)
235
+ {
236
+ let instrumentID = this.generators.find(g => g.generatorType === generatorTypes.instrument);
237
+ if(instrumentID)
238
+ {
239
+ this.instrument = instruments[instrumentID.generatorValue];
240
+ this.instrument.addUseCount();
241
+ this.isGlobal = false;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Reads the keyRange of the zone
247
+ */
248
+ getKeyRange()
249
+ {
250
+ let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
251
+ if(range)
252
+ {
253
+ this.keyRange.min = range.generatorValue & 0x7F;
254
+ this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
255
+ }
256
+ }
257
+
258
+ /**
259
+ * reads the velolicty range of the zone
260
+ */
261
+ getVelRange()
262
+ {
263
+ let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
264
+ if(range)
265
+ {
266
+ this.velRange.min = range.generatorValue & 0x7F;
267
+ this.velRange.max = (range.generatorValue >> 8) & 0x7F;
268
+ }
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Reads the given preset zone read
274
+ * @param zonesChunk {RiffChunk}
275
+ * @param presetGenerators {Generator[]}
276
+ * @param instruments {Instrument[]}
277
+ * @param presetModulators {Modulator[]}
278
+ * @returns {PresetZone[]}
279
+ */
280
+ export function readPresetZones(zonesChunk, presetGenerators, presetModulators, instruments)
281
+ {
282
+ /**
283
+ * @type {PresetZone[]}
284
+ */
285
+ let zones = [];
286
+ let index = 0;
287
+ while(zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
288
+ {
289
+ let zone = new PresetZone(zonesChunk.chunkData, index);
290
+ if(zones.length > 0)
291
+ {
292
+ let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
293
+ let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
294
+ zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
295
+ zones[zones.length - 1].getGenerators(presetGenerators);
296
+ zones[zones.length - 1].getModulators(presetModulators);
297
+ zones[zones.length - 1].getInstrument(instruments);
298
+ zones[zones.length - 1].getKeyRange();
299
+ zones[zones.length - 1].getVelRange();
300
+ }
301
+ zones.push(zone);
302
+ index++;
303
+ }
304
+ if(zones.length > 1)
305
+ {
306
+ // remove terminal
307
+ zones.pop();
308
+ }
309
+ return zones;
310
+ }
@@ -0,0 +1,357 @@
1
+ import { IndexedByteArray } from '../utils/indexed_array.js'
2
+ import {readSamples} from "./read/samples.js";
3
+ import { readBytesAsUintLittleEndian } from '../utils/byte_functions/little_endian.js'
4
+ import { readGenerators, Generator } from './read/generators.js'
5
+ import {readInstrumentZones, InstrumentZone, readPresetZones} from "./read/zones.js";
6
+ import {Preset, readPresets} from "./read/presets.js";
7
+ import {readInstruments, Instrument} from "./read/instruments.js";
8
+ import {readModulators, Modulator} from "./read/modulators.js";
9
+ import { readRIFFChunk, RiffChunk } from './read/riff_chunk.js'
10
+ import { consoleColors } from '../utils/other.js'
11
+ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
12
+ import { readBytesAsString } from '../utils/byte_functions/string.js'
13
+ import { write } from './write/write.js'
14
+
15
+ /**
16
+ * soundfont.js
17
+ * purpose: parses a soundfont2 file
18
+ */
19
+
20
+ class SoundFont2
21
+ {
22
+ /**
23
+ * Initializes a new SoundFont2 Parser and parses the given data array
24
+ * @param arrayBuffer {ArrayBuffer|{presets: Preset[], info: Object<string, string>}}
25
+ */
26
+ constructor(arrayBuffer) {
27
+ if(arrayBuffer.presets)
28
+ {
29
+ this.presets = arrayBuffer.presets;
30
+ this.soundFontInfo = arrayBuffer.info;
31
+ return;
32
+ }
33
+ this.dataArray = new IndexedByteArray(arrayBuffer);
34
+ SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
35
+ if(!this.dataArray)
36
+ {
37
+ SpessaSynthGroupEnd();
38
+ throw new TypeError("No data!");
39
+ }
40
+
41
+ // read the main read
42
+ let firstChunk = readRIFFChunk(this.dataArray, false);
43
+ this.verifyHeader(firstChunk, "riff");
44
+
45
+ this.verifyText(readBytesAsString(this.dataArray,4), "sfbk");
46
+
47
+ // INFO
48
+ let infoChunk = readRIFFChunk(this.dataArray);
49
+ this.verifyHeader(infoChunk, "list");
50
+ readBytesAsString(infoChunk.chunkData, 4);
51
+
52
+ /**
53
+ * @type {Object<string, string>}
54
+ */
55
+ this.soundFontInfo = {};
56
+
57
+ while(infoChunk.chunkData.length > infoChunk.chunkData.currentIndex) {
58
+ let chunk = readRIFFChunk(infoChunk.chunkData);
59
+ let text;
60
+ // special case: ifil
61
+ switch (chunk.header.toLowerCase())
62
+ {
63
+ case "ifil":
64
+ case "iver":
65
+ text = `${readBytesAsUintLittleEndian(chunk.chunkData, 2)}.${readBytesAsUintLittleEndian(chunk.chunkData, 2)}`;
66
+ break;
67
+
68
+ case "icmt":
69
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length, undefined, false);
70
+ break;
71
+
72
+ default:
73
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
74
+ }
75
+
76
+ SpessaSynthInfo(`%c"${chunk.header}": %c"${text}"`,
77
+ consoleColors.info,
78
+ consoleColors.recognized);
79
+ this.soundFontInfo[chunk.header] = text;
80
+ }
81
+
82
+ // SDTA
83
+ const sdtaChunk = readRIFFChunk(this.dataArray, false);
84
+ this.verifyHeader(sdtaChunk, "list")
85
+ this.verifyText(readBytesAsString(this.dataArray, 4), "sdta");
86
+
87
+ // smpl
88
+ SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn)
89
+ let sampleDataChunk = readRIFFChunk(this.dataArray, false);
90
+ this.verifyHeader(sampleDataChunk, "smpl");
91
+ this.sampleDataStartIndex = this.dataArray.currentIndex;
92
+
93
+ SpessaSynthInfo(`%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`,
94
+ consoleColors.info,
95
+ consoleColors.value);
96
+ this.dataArray.currentIndex += sdtaChunk.size - 12;
97
+
98
+ // PDTA
99
+ SpessaSynthInfo("%cLoading preset data chunk...", consoleColors.warn)
100
+ let presetChunk = readRIFFChunk(this.dataArray);
101
+ this.verifyHeader(presetChunk, "list");
102
+ readBytesAsString(presetChunk.chunkData, 4);
103
+
104
+ // read the hydra chunks
105
+ const presetHeadersChunk = readRIFFChunk(presetChunk.chunkData);
106
+ this.verifyHeader(presetHeadersChunk, "phdr");
107
+
108
+ const presetZonesChunk = readRIFFChunk(presetChunk.chunkData);
109
+ this.verifyHeader(presetZonesChunk, "pbag");
110
+
111
+ const presetModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
112
+ this.verifyHeader(presetModulatorsChunk, "pmod");
113
+
114
+ const presetGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
115
+ this.verifyHeader(presetGeneratorsChunk, "pgen");
116
+
117
+ const presetInstrumentsChunk = readRIFFChunk(presetChunk.chunkData);
118
+ this.verifyHeader(presetInstrumentsChunk, "inst");
119
+
120
+ const presetInstrumentZonesChunk = readRIFFChunk(presetChunk.chunkData);
121
+ this.verifyHeader(presetInstrumentZonesChunk, "ibag");
122
+
123
+ const presetInstrumentModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
124
+ this.verifyHeader(presetInstrumentModulatorsChunk, "imod");
125
+
126
+ const presetInstrumentGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
127
+ this.verifyHeader(presetInstrumentGeneratorsChunk, "igen");
128
+
129
+ const presetSamplesChunk = readRIFFChunk(presetChunk.chunkData);
130
+ this.verifyHeader(presetSamplesChunk, "shdr");
131
+
132
+ /**
133
+ * read all the samples
134
+ * (the current index points to start of the smpl read)
135
+ */
136
+ this.dataArray.currentIndex = this.sampleDataStartIndex
137
+ this.samples = readSamples(presetSamplesChunk, this.dataArray);
138
+
139
+ /**
140
+ * read all the instrument generators
141
+ * @type {Generator[]}
142
+ */
143
+ let instrumentGenerators = readGenerators(presetInstrumentGeneratorsChunk);
144
+
145
+ /**
146
+ * read all the instrument modulators
147
+ * @type {Modulator[]}
148
+ */
149
+ let instrumentModulators = readModulators(presetInstrumentModulatorsChunk);
150
+
151
+ /**
152
+ * read all the instrument zones
153
+ * @type {InstrumentZone[]}
154
+ */
155
+ let instrumentZones = readInstrumentZones(presetInstrumentZonesChunk,
156
+ instrumentGenerators,
157
+ instrumentModulators,
158
+ this.samples);
159
+
160
+ /**
161
+ * read all the instruments
162
+ * @type {Instrument[]}
163
+ */
164
+ this.instruments = readInstruments(presetInstrumentsChunk, instrumentZones);
165
+
166
+ /**
167
+ * read all the preset generators
168
+ * @type {Generator[]}
169
+ */
170
+ let presetGenerators = readGenerators(presetGeneratorsChunk);
171
+
172
+ /**
173
+ * Read all the preset modulatorrs
174
+ * @type {Modulator[]}
175
+ */
176
+ let presetModulators = readModulators(presetModulatorsChunk);
177
+
178
+ let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, this.instruments);
179
+ /**
180
+ * Finally, read all the presets
181
+ * @type {Preset[]}
182
+ */
183
+ this.presets = readPresets(presetHeadersChunk, presetZones);
184
+ this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
185
+ // preload the first preset
186
+ SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
187
+ %c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
188
+ consoleColors.info,
189
+ consoleColors.recognized,
190
+ consoleColors.info,
191
+ consoleColors.recognized,
192
+ consoleColors.info,
193
+ consoleColors.recognized,
194
+ consoleColors.info,
195
+ consoleColors.recognized,
196
+ consoleColors.info);
197
+ SpessaSynthGroupEnd();
198
+ }
199
+
200
+ removeUnusedElements()
201
+ {
202
+ this.instruments.forEach(i => {
203
+ if(i.useCount < 1)
204
+ {
205
+ i.instrumentZones.forEach(z => {if(!z.isGlobal) z.sample.useCount--});
206
+ }
207
+ })
208
+ this.instruments = this.instruments.filter(i => i.useCount > 0);
209
+ this.samples = this.samples.filter(s => s.useCount > 0);
210
+ }
211
+
212
+ /**
213
+ * @param instrument {Instrument}
214
+ */
215
+ deleteInstrument(instrument)
216
+ {
217
+ if(instrument.useCount > 0)
218
+ {
219
+ throw new Error(`Cannot delete an instrument that has ${instrument.useCount} usages.`);
220
+ }
221
+ this.instruments.splice(this.instruments.indexOf(instrument), 1);
222
+ instrument.deleteInstrument();
223
+ this.removeUnusedElements();
224
+ }
225
+
226
+ /**
227
+ * @param sample {Sample}
228
+ */
229
+ deleteSample(sample)
230
+ {
231
+ if(sample.useCount > 0)
232
+ {
233
+ throw new Error(`Cannot delete sample that has ${sample.useCount} usages.`);
234
+ }
235
+ this.samples.splice(this.samples.indexOf(sample), 1);
236
+ this.removeUnusedElements();
237
+ }
238
+
239
+ /**
240
+ * @param preset {Preset}
241
+ */
242
+ deletePreset(preset)
243
+ {
244
+ preset.deletePreset();
245
+ this.presets.splice(this.presets.indexOf(preset), 1);
246
+ this.removeUnusedElements();
247
+ }
248
+
249
+ /**
250
+ * @param chunk {RiffChunk}
251
+ * @param expected {string}
252
+ */
253
+ verifyHeader(chunk, expected)
254
+ {
255
+ if(chunk.header.toLowerCase() !== expected.toLowerCase())
256
+ {
257
+ SpessaSynthGroupEnd();
258
+ throw new SyntaxError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * @param text {string}
264
+ * @param expected {string}
265
+ */
266
+ verifyText(text, expected)
267
+ {
268
+ if(text.toLowerCase() !== expected.toLowerCase())
269
+ {
270
+ SpessaSynthGroupEnd();
271
+ throw new SyntaxError(`Invalid soundFont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Get the appropriate preset
277
+ * @param bankNr {number}
278
+ * @param presetNr {number}
279
+ * @returns {Preset}
280
+ */
281
+ getPreset(bankNr, presetNr)
282
+ {
283
+ let preset = this.presets.find(p => p.bank === bankNr && p.program === presetNr);
284
+ if (!preset)
285
+ {
286
+ preset = this.presets.find(p => p.program === presetNr && p.bank !== 128);
287
+ if(bankNr === 128)
288
+ {
289
+ preset = this.presets.find(p => p.bank === 128 && p.program === presetNr);
290
+ if(!preset)
291
+ {
292
+ preset = this.presets.find(p => p.bank === 128);
293
+ }
294
+ }
295
+ if(preset)
296
+ {
297
+ SpessaSynthWarn(`%cPreset ${bankNr}.${presetNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
298
+ consoleColors.warn,
299
+ consoleColors.recognized);
300
+ }
301
+ }
302
+ if(!preset)
303
+ {
304
+ SpessaSynthWarn(`Preset ${presetNr} not found. Defaulting to`, this.presets[0].presetName);
305
+ preset = this.presets[0];
306
+ }
307
+ return preset;
308
+ }
309
+
310
+ /**
311
+ * gets preset by name
312
+ * @param presetName {string}
313
+ * @returns {Preset}
314
+ */
315
+ getPresetByName(presetName)
316
+ {
317
+ let preset = this.presets.find(p => p.presetName === presetName);
318
+ if(!preset)
319
+ {
320
+ SpessaSynthWarn("Preset not found. Defaulting to:", this.presets[0].presetName);
321
+ preset = this.presets[0];
322
+ }
323
+ return preset;
324
+ }
325
+
326
+
327
+ /**
328
+ * Merges soundfonts with the given order. Keep in mind that the info read is copied from the first one
329
+ * @param soundfonts {...SoundFont2} the soundfonts to merge, the first overwrites the last
330
+ * @returns {SoundFont2}
331
+ */
332
+ static mergeSoundfonts(...soundfonts)
333
+ {
334
+ const mainSf = soundfonts.shift();
335
+ /**
336
+ * @type {Preset[]}
337
+ */
338
+ const presets = mainSf.presets;
339
+ while(soundfonts.length)
340
+ {
341
+ const newPresets = soundfonts.shift().presets;
342
+ newPresets.forEach(newPreset => {
343
+ if(
344
+ presets.find(existingPreset => existingPreset.bank === newPreset.bank && existingPreset.program === newPreset.program) === undefined
345
+ )
346
+ {
347
+ presets.push(newPreset);
348
+ }
349
+ })
350
+ }
351
+
352
+ return new SoundFont2({presets: presets, info: mainSf.soundFontInfo});
353
+ }
354
+ }
355
+ SoundFont2.prototype.write = write;
356
+
357
+ export { SoundFont2 }
@@ -0,0 +1,39 @@
1
+ import { IndexedByteArray } from '../../utils/indexed_array.js'
2
+ import { writeWord } from '../../utils/byte_functions/little_endian.js'
3
+ import { RiffChunk, writeRIFFChunk } from '../read/riff_chunk.js'
4
+
5
+ /**
6
+ * @this {SoundFont2}
7
+ * @returns {IndexedByteArray}
8
+ */
9
+ export function getIBAG()
10
+ {
11
+ // write all ibags with their start indexes as they were changed in getIGEN() and getIMOD()
12
+ const ibagsize = this.instruments.reduce((sum, i) => i.instrumentZones.length * 4 + sum, 4);
13
+ const ibagdata = new IndexedByteArray(ibagsize);
14
+ let zoneID = 0;
15
+ let generatorIndex = 0;
16
+ let modulatorIndex = 0;
17
+ for(const inst of this.instruments)
18
+ {
19
+ inst.instrumentZoneIndex = zoneID;
20
+ for(const ibag of inst.instrumentZones)
21
+ {
22
+ ibag.zoneID = zoneID;
23
+ writeWord(ibagdata, generatorIndex);
24
+ writeWord(ibagdata, modulatorIndex);
25
+ generatorIndex += ibag.generators.length;
26
+ modulatorIndex += ibag.modulators.length;
27
+ zoneID++;
28
+ }
29
+ }
30
+ // write the terminal IBAG
31
+ writeWord(ibagdata, generatorIndex);
32
+ writeWord(ibagdata, modulatorIndex);
33
+
34
+ return writeRIFFChunk(new RiffChunk(
35
+ "ibag",
36
+ ibagdata.length,
37
+ ibagdata
38
+ ));
39
+ }