spessasynth_core 1.0.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 (55) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/spessasynth_core.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +376 -0
  7. package/index.js +7 -0
  8. package/package.json +34 -0
  9. package/spessasynth_core/midi_parser/README.md +3 -0
  10. package/spessasynth_core/midi_parser/midi_loader.js +381 -0
  11. package/spessasynth_core/midi_parser/midi_message.js +231 -0
  12. package/spessasynth_core/sequencer/sequencer.js +192 -0
  13. package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
  14. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
  15. package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
  16. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
  17. package/spessasynth_core/soundfont/README.md +4 -0
  18. package/spessasynth_core/soundfont/chunk/generators.js +205 -0
  19. package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
  20. package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
  21. package/spessasynth_core/soundfont/chunk/presets.js +264 -0
  22. package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
  23. package/spessasynth_core/soundfont/chunk/samples.js +250 -0
  24. package/spessasynth_core/soundfont/chunk/zones.js +264 -0
  25. package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
  26. package/spessasynth_core/synthetizer/README.md +6 -0
  27. package/spessasynth_core/synthetizer/synthesizer.js +303 -0
  28. package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
  29. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
  30. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
  31. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
  32. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
  33. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
  34. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
  35. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
  36. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  37. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
  38. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  39. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
  40. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  41. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  42. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
  43. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  44. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
  45. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  46. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
  47. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
  48. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
  49. package/spessasynth_core/utils/README.md +4 -0
  50. package/spessasynth_core/utils/buffer_to_wav.js +70 -0
  51. package/spessasynth_core/utils/byte_functions.js +141 -0
  52. package/spessasynth_core/utils/loggin.js +79 -0
  53. package/spessasynth_core/utils/other.js +49 -0
  54. package/spessasynth_core/utils/shiftable_array.js +26 -0
  55. package/spessasynth_core/utils/stbvorbis_sync.js +1877 -0
@@ -0,0 +1,264 @@
1
+ import {readBytesAsUintLittleEndian} from "../../utils/byte_functions.js";
2
+ import {ShiftableByteArray} from "../../utils/shiftable_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 {ShiftableByteArray}
18
+ */
19
+ constructor(dataArray) {
20
+ this.generatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
21
+ this.modulatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
22
+ this.modulatorZoneSize = 0;
23
+ this.generatorZoneSize = 0;
24
+ this.keyRange = {min: 0, max: 127};
25
+ this.velRange = {min: 0, max: 127}
26
+ this.isGlobal = true;
27
+ /**
28
+ * @type {Generator[]}
29
+ */
30
+ this.generators = [];
31
+ /**
32
+ * @type {Modulator[]}
33
+ */
34
+ this.modulators = [];
35
+ }
36
+
37
+ setZoneSize(modulatorZoneSize, generatorZoneSize)
38
+ {
39
+ this.modulatorZoneSize = modulatorZoneSize;
40
+ this.generatorZoneSize = generatorZoneSize;
41
+ }
42
+
43
+ /**
44
+ * grab the generators
45
+ * @param generators {Generator[]}
46
+ */
47
+ getGenerators(generators)
48
+ {
49
+ for(let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
50
+ {
51
+ this.generators.push(generators[i]);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * grab the modulators
57
+ * @param modulators {Modulator[]}
58
+ */
59
+ getModulators(modulators)
60
+ {
61
+ for(let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
62
+ {
63
+ this.modulators.push(modulators[i]);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Loads the zone's sample
69
+ * @param samples {Sample[]}
70
+ */
71
+ getSample(samples) {
72
+ let sampleID = this.generators.find(g => g.generatorType === generatorTypes.sampleID);
73
+ if (sampleID)
74
+ {
75
+ this.sample = samples[sampleID.generatorValue];
76
+ this.isGlobal = false;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Reads the keyRange of the zone
82
+ */
83
+ getKeyRange()
84
+ {
85
+ let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
86
+ if(range)
87
+ {
88
+ this.keyRange.min = range.generatorValue & 0x7F;
89
+ this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * reads the velolicty range of the zone
95
+ */
96
+ getVelRange()
97
+ {
98
+ let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
99
+ if(range)
100
+ {
101
+ this.velRange.min = range.generatorValue & 0x7F;
102
+ this.velRange.max = (range.generatorValue >> 8) & 0x7F;
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Reads the given instrument zone chunk
109
+ * @param zonesChunk {RiffChunk}
110
+ * @param instrumentGenerators {Generator[]}
111
+ * @param instrumentModulators {Modulator[]}
112
+ * @param instrumentSamples {Sample[]}
113
+ * @returns {InstrumentZone[]}
114
+ */
115
+ export function readInstrumentZones(zonesChunk, instrumentGenerators, instrumentModulators, instrumentSamples)
116
+ {
117
+ /**
118
+ * @type {InstrumentZone[]}
119
+ */
120
+ let zones = [];
121
+ while(zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
122
+ {
123
+ let zone = new InstrumentZone(zonesChunk.chunkData);
124
+ if(zones.length > 0)
125
+ {
126
+ let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
127
+ let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
128
+ zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
129
+ zones[zones.length - 1].getGenerators(instrumentGenerators);
130
+ zones[zones.length - 1].getModulators(instrumentModulators);
131
+ zones[zones.length - 1].getSample(instrumentSamples);
132
+ zones[zones.length - 1].getKeyRange();
133
+ zones[zones.length - 1].getVelRange();
134
+ }
135
+ zones.push(zone);
136
+ }
137
+ return zones;
138
+ }
139
+
140
+ export class PresetZone {
141
+ /**
142
+ * Creates a zone (preset)
143
+ * @param dataArray {ShiftableByteArray}
144
+ */
145
+ constructor(dataArray) {
146
+ this.generatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
147
+ this.modulatorZoneStartIndex = readBytesAsUintLittleEndian(dataArray, 2);
148
+ this.modulatorZoneSize = 0;
149
+ this.generatorZoneSize = 0;
150
+ this.keyRange = {min: 0, max: 127};
151
+ this.velRange = {min: 0, max: 127}
152
+ this.isGlobal = true;
153
+ /**
154
+ * @type {Generator[]}
155
+ */
156
+ this.generators = [];
157
+ /**
158
+ * @type {Modulator[]}
159
+ */
160
+ this.modulators = [];
161
+ }
162
+
163
+ setZoneSize(modulatorZoneSize, generatorZoneSize)
164
+ {
165
+ this.modulatorZoneSize = modulatorZoneSize;
166
+ this.generatorZoneSize = generatorZoneSize;
167
+ }
168
+
169
+ /**
170
+ * grab the generators
171
+ * @param generators {Generator[]}
172
+ */
173
+ getGenerators(generators)
174
+ {
175
+ for(let i = this.generatorZoneStartIndex; i < this.generatorZoneStartIndex + this.generatorZoneSize; i++)
176
+ {
177
+ this.generators.push(generators[i]);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * grab the modulators
183
+ * @param modulators {Modulator[]}
184
+ */
185
+ getModulators(modulators)
186
+ {
187
+ for(let i = this.modulatorZoneStartIndex; i < this.modulatorZoneStartIndex + this.modulatorZoneSize; i++)
188
+ {
189
+ this.modulators.push(modulators[i]);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * grab the instrument
195
+ * @param instruments {Instrument[]}
196
+ */
197
+ getInstrument(instruments)
198
+ {
199
+ let instrumentID = this.generators.find(g => g.generatorType === generatorTypes.instrument);
200
+ if(instrumentID) {
201
+ this.instrument = instruments[instrumentID.generatorValue];
202
+ this.isGlobal = false;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Reads the keyRange of the zone
208
+ */
209
+ getKeyRange()
210
+ {
211
+ let range = this.generators.find(g => g.generatorType === generatorTypes.keyRange);
212
+ if(range)
213
+ {
214
+ this.keyRange.min = range.generatorValue & 0x7F;
215
+ this.keyRange.max = (range.generatorValue >> 8) & 0x7F;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * reads the velolicty range of the zone
221
+ */
222
+ getVelRange()
223
+ {
224
+ let range = this.generators.find(g => g.generatorType === generatorTypes.velRange);
225
+ if(range)
226
+ {
227
+ this.velRange.min = range.generatorValue & 0x7F;
228
+ this.velRange.max = (range.generatorValue >> 8) & 0x7F;
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Reads the given preset zone chunk
235
+ * @param zonesChunk {RiffChunk}
236
+ * @param presetGenerators {Generator[]}
237
+ * @param instruments {Instrument[]}
238
+ * @param presetModulators {Modulator[]}
239
+ * @returns {PresetZone[]}
240
+ */
241
+ export function readPresetZones(zonesChunk, presetGenerators, presetModulators, instruments)
242
+ {
243
+ /**
244
+ * @type {PresetZone[]}
245
+ */
246
+ let zones = [];
247
+ while(zonesChunk.chunkData.length > zonesChunk.chunkData.currentIndex)
248
+ {
249
+ let zone = new PresetZone(zonesChunk.chunkData);
250
+ if(zones.length > 0)
251
+ {
252
+ let modulatorZoneSize = zone.modulatorZoneStartIndex - zones[zones.length - 1].modulatorZoneStartIndex;
253
+ let generatorZoneSize = zone.generatorZoneStartIndex - zones[zones.length - 1].generatorZoneStartIndex;
254
+ zones[zones.length - 1].setZoneSize(modulatorZoneSize, generatorZoneSize);
255
+ zones[zones.length - 1].getGenerators(presetGenerators);
256
+ zones[zones.length - 1].getModulators(presetModulators);
257
+ zones[zones.length - 1].getInstrument(instruments);
258
+ zones[zones.length - 1].getKeyRange();
259
+ zones[zones.length - 1].getVelRange();
260
+ }
261
+ zones.push(zone);
262
+ }
263
+ return zones;
264
+ }
@@ -0,0 +1,301 @@
1
+ import {ShiftableByteArray} from "../utils/shiftable_array.js";
2
+ import {readSamples} from "./chunk/samples.js";
3
+ import { readBytesAsString, readBytesAsUintLittleEndian } from '../utils/byte_functions.js'
4
+ import {readGenerators, Generator} from "./chunk/generators.js";
5
+ import {readInstrumentZones, InstrumentZone, readPresetZones} from "./chunk/zones.js";
6
+ import {Preset, readPresets} from "./chunk/presets.js";
7
+ import {readInstruments, Instrument} from "./chunk/instruments.js";
8
+ import {readModulators, Modulator} from "./chunk/modulators.js";
9
+ import { readRIFFChunk, RiffChunk } from './chunk/riff_chunk.js'
10
+ import { consoleColors } from '../utils/other.js'
11
+ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
12
+
13
+ /**
14
+ * soundfont_parser.js
15
+ * purpose: parses a soundfont2 file
16
+ */
17
+
18
+ export class SoundFont2
19
+ {
20
+ /**
21
+ * Initializes a new SoundFont2 Parser and parses the given data array
22
+ * @param arrayBuffer {Buffer|ArrayBufferLike|{presets: Preset[], info: Object<string, string>}}
23
+ */
24
+ constructor(arrayBuffer) {
25
+ if(arrayBuffer.presets)
26
+ {
27
+ this.presets = arrayBuffer.presets;
28
+ this.soundFontInfo = arrayBuffer.info;
29
+ return;
30
+ }
31
+ this.dataArray = new ShiftableByteArray(arrayBuffer);
32
+ SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
33
+ if(!this.dataArray)
34
+ {
35
+ SpessaSynthGroupEnd();
36
+ throw new TypeError("No data!");
37
+ }
38
+
39
+ // read the main chunk
40
+ let firstChunk = readRIFFChunk(this.dataArray, false);
41
+ this.verifyHeader(firstChunk, "riff");
42
+
43
+ this.verifyText(readBytesAsString(this.dataArray,4), "sfbk");
44
+
45
+ // INFO
46
+ let infoChunk = readRIFFChunk(this.dataArray);
47
+ this.verifyHeader(infoChunk, "list");
48
+ readBytesAsString(infoChunk.chunkData, 4);
49
+
50
+ /**
51
+ * @type {Object<string, string>}
52
+ */
53
+ this.soundFontInfo = {};
54
+
55
+ while(infoChunk.chunkData.length > infoChunk.chunkData.currentIndex) {
56
+ let chunk = readRIFFChunk(infoChunk.chunkData);
57
+ let text;
58
+ // special case: ifil
59
+ switch (chunk.header.toLowerCase())
60
+ {
61
+ case "ifil":
62
+ text = `${readBytesAsUintLittleEndian(chunk.chunkData, 2)}.${readBytesAsUintLittleEndian(chunk.chunkData, 2)}`;
63
+ break;
64
+
65
+ case "icmt":
66
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length, undefined, false);
67
+ break;
68
+
69
+ default:
70
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
71
+ }
72
+
73
+ SpessaSynthInfo(`%c"${chunk.header}": %c"${text}"`,
74
+ consoleColors.info,
75
+ consoleColors.recognized);
76
+ this.soundFontInfo[chunk.header] = text;
77
+ }
78
+
79
+ // SDTA
80
+ const sdtaChunk = readRIFFChunk(this.dataArray, false);
81
+ this.verifyHeader(sdtaChunk, "list")
82
+ this.verifyText(readBytesAsString(this.dataArray, 4), "sdta");
83
+
84
+ // smpl
85
+ SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn)
86
+ let sampleDataChunk = readRIFFChunk(this.dataArray, false);
87
+ this.verifyHeader(sampleDataChunk, "smpl");
88
+ this.sampleDataStartIndex = this.dataArray.currentIndex;
89
+
90
+ SpessaSynthInfo(`%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`,
91
+ consoleColors.info,
92
+ consoleColors.value);
93
+ this.dataArray.currentIndex += sdtaChunk.size - 12;
94
+
95
+ // PDTA
96
+ SpessaSynthInfo("%cLoading preset data chunk...", consoleColors.warn)
97
+ let presetChunk = readRIFFChunk(this.dataArray);
98
+ this.verifyHeader(presetChunk, "list");
99
+ readBytesAsString(presetChunk.chunkData, 4);
100
+
101
+ // read the hydra chunks
102
+ const presetHeadersChunk = readRIFFChunk(presetChunk.chunkData);
103
+ this.verifyHeader(presetHeadersChunk, "phdr");
104
+
105
+ const presetZonesChunk = readRIFFChunk(presetChunk.chunkData);
106
+ this.verifyHeader(presetZonesChunk, "pbag");
107
+
108
+ const presetModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
109
+ this.verifyHeader(presetModulatorsChunk, "pmod");
110
+
111
+ const presetGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
112
+ this.verifyHeader(presetGeneratorsChunk, "pgen");
113
+
114
+ const presetInstrumentsChunk = readRIFFChunk(presetChunk.chunkData);
115
+ this.verifyHeader(presetInstrumentsChunk, "inst");
116
+
117
+ const presetInstrumentZonesChunk = readRIFFChunk(presetChunk.chunkData);
118
+ this.verifyHeader(presetInstrumentZonesChunk, "ibag");
119
+
120
+ const presetInstrumentModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
121
+ this.verifyHeader(presetInstrumentModulatorsChunk, "imod");
122
+
123
+ const presetInstrumentGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
124
+ this.verifyHeader(presetInstrumentGeneratorsChunk, "igen");
125
+
126
+ const presetSamplesChunk = readRIFFChunk(presetChunk.chunkData);
127
+ this.verifyHeader(presetSamplesChunk, "shdr");
128
+
129
+ /**
130
+ * read all the samples
131
+ * (the current index points to start of the smpl chunk)
132
+ */
133
+ this.dataArray.currentIndex = this.sampleDataStartIndex
134
+ this.samples = readSamples(presetSamplesChunk, this.dataArray);
135
+
136
+ /**
137
+ * read all the instrument generators
138
+ * @type {Generator[]}
139
+ */
140
+ let instrumentGenerators = readGenerators(presetInstrumentGeneratorsChunk);
141
+
142
+ /**
143
+ * read all the instrument modulators
144
+ * @type {Modulator[]}
145
+ */
146
+ let instrumentModulators = readModulators(presetInstrumentModulatorsChunk);
147
+
148
+ /**
149
+ * read all the instrument zones
150
+ * @type {InstrumentZone[]}
151
+ */
152
+ let instrumentZones = readInstrumentZones(presetInstrumentZonesChunk,
153
+ instrumentGenerators,
154
+ instrumentModulators,
155
+ this.samples);
156
+
157
+ /**
158
+ * read all the instruments
159
+ * @type {Instrument[]}
160
+ */
161
+ let instruments = readInstruments(presetInstrumentsChunk, instrumentZones);
162
+
163
+ /**
164
+ * read all the preset generators
165
+ * @type {Generator[]}
166
+ */
167
+ let presetGenerators = readGenerators(presetGeneratorsChunk);
168
+
169
+ /**
170
+ * Read all the preset modulatorrs
171
+ * @type {Modulator[]}
172
+ */
173
+ let presetModulators = readModulators(presetModulatorsChunk);
174
+
175
+ let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, instruments);
176
+
177
+ /**
178
+ * Finally, read all the presets
179
+ * @type {Preset[]}
180
+ */
181
+ this.presets = readPresets(presetHeadersChunk, presetZones);
182
+ this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
183
+ // preload the first preset
184
+ SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
185
+ %c${instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
186
+ consoleColors.info,
187
+ consoleColors.recognized,
188
+ consoleColors.info,
189
+ consoleColors.recognized,
190
+ consoleColors.info,
191
+ consoleColors.recognized,
192
+ consoleColors.info,
193
+ consoleColors.recognized,
194
+ consoleColors.info);
195
+ SpessaSynthGroupEnd();
196
+ }
197
+
198
+ /**
199
+ * @param chunk {RiffChunk}
200
+ * @param expected {string}
201
+ */
202
+ verifyHeader(chunk, expected)
203
+ {
204
+ if(chunk.header.toLowerCase() !== expected.toLowerCase())
205
+ {
206
+ SpessaSynthGroupEnd();
207
+ throw new SyntaxError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * @param text {string}
213
+ * @param expected {string}
214
+ */
215
+ verifyText(text, expected)
216
+ {
217
+ if(text.toLowerCase() !== expected.toLowerCase())
218
+ {
219
+ SpessaSynthGroupEnd();
220
+ throw new SyntaxError(`Invalid soundFont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Get the appropriate preset
226
+ * @param bankNr {number}
227
+ * @param presetNr {number}
228
+ * @returns {Preset}
229
+ */
230
+ getPreset(bankNr, presetNr) {
231
+ let preset = this.presets.find(p => p.bank === bankNr && p.program === presetNr);
232
+ if (!preset)
233
+ {
234
+ preset = this.presets.find(p => p.program === presetNr && p.bank !== 128);
235
+ if(bankNr === 128)
236
+ {
237
+ preset = this.presets.find(p => p.bank === 128 && p.program === presetNr);
238
+ if(!preset)
239
+ {
240
+ preset = this.presets.find(p => p.bank === 128);
241
+ }
242
+ }
243
+ if(preset) {
244
+ SpessaSynthWarn(`%cPreset ${bankNr}.${presetNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
245
+ consoleColors.warn,
246
+ consoleColors.recognized);
247
+ }
248
+ }
249
+ if(!preset)
250
+ {
251
+ SpessaSynthWarn(`Preset ${presetNr} not found. Defaulting to`, this.presets[0].presetName);
252
+ preset = this.presets[0];
253
+ }
254
+ return preset;
255
+ }
256
+
257
+ /**
258
+ * gets preset by name
259
+ * @param presetName {string}
260
+ * @returns {Preset}
261
+ */
262
+ getPresetByName(presetName)
263
+ {
264
+ let preset = this.presets.find(p => p.presetName === presetName);
265
+ if(!preset)
266
+ {
267
+ SpessaSynthWarn("Preset not found. Defaulting to:", this.presets[0].presetName);
268
+ preset = this.presets[0];
269
+ }
270
+ return preset;
271
+ }
272
+
273
+
274
+ /**
275
+ * Merges soundfonts with the given order. Keep in mind that the info chunk is copied from the first one
276
+ * @param soundfonts {...SoundFont2} the soundfonts to merge, the first overwrites the last
277
+ * @returns {SoundFont2}
278
+ */
279
+ static mergeSoundfonts(...soundfonts)
280
+ {
281
+ const mainSf = soundfonts.shift();
282
+ /**
283
+ * @type {Preset[]}
284
+ */
285
+ const presets = mainSf.presets;
286
+ while(soundfonts.length)
287
+ {
288
+ const newPresets = soundfonts.shift().presets;
289
+ newPresets.forEach(newPreset => {
290
+ if(
291
+ presets.find(existingPreset => existingPreset.bank === newPreset.bank && existingPreset.program === newPreset.program) === undefined
292
+ )
293
+ {
294
+ presets.push(newPreset);
295
+ }
296
+ })
297
+ }
298
+
299
+ return new SoundFont2({presets: presets, info: mainSf.soundFontInfo});
300
+ }
301
+ }
@@ -0,0 +1,6 @@
1
+ ## This is the main synthesizer folder.
2
+ The code here is responsible for making the actual sound.
3
+ This is the heart of the SpessaSynth library.
4
+ Here it's divided into 2 types:
5
+ - `native_system` - the old synthesis system with AudioBuffers, only periodically maintained, lacking many features and not supporting modulators. It will be used when the AudioWorklet API cannot be used.
6
+ - `worklet_system` - the current synthesis system with AudioWorklets, has all the features and is actively maintained and expanded.