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.
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_lib.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/copy_version.sh +38 -0
- package/index.js +73 -0
- package/package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
- package/package/@types/index.d.ts +34 -0
- package/package/@types/midi_handler/midi_handler.d.ts +39 -0
- package/package/@types/midi_handler/web_midi_link.d.ts +12 -0
- package/package/@types/midi_parser/midi_data.d.ts +95 -0
- package/package/@types/midi_parser/midi_editor.d.ts +45 -0
- package/package/@types/midi_parser/midi_loader.d.ts +100 -0
- package/package/@types/midi_parser/midi_message.d.ts +154 -0
- package/package/@types/midi_parser/midi_writer.d.ts +6 -0
- package/package/@types/midi_parser/rmidi_writer.d.ts +9 -0
- package/package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
- package/package/@types/sequencer/sequencer.d.ts +180 -0
- package/package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
- package/package/@types/soundfont/read/generators.d.ts +98 -0
- package/package/@types/soundfont/read/instruments.d.ts +50 -0
- package/package/@types/soundfont/read/modulators.d.ts +73 -0
- package/package/@types/soundfont/read/presets.d.ts +87 -0
- package/package/@types/soundfont/read/riff_chunk.d.ts +31 -0
- package/package/@types/soundfont/read/samples.d.ts +134 -0
- package/package/@types/soundfont/read/zones.d.ts +141 -0
- package/package/@types/soundfont/soundfont.d.ts +76 -0
- package/package/@types/soundfont/write/ibag.d.ts +6 -0
- package/package/@types/soundfont/write/igen.d.ts +6 -0
- package/package/@types/soundfont/write/imod.d.ts +6 -0
- package/package/@types/soundfont/write/inst.d.ts +6 -0
- package/package/@types/soundfont/write/pbag.d.ts +6 -0
- package/package/@types/soundfont/write/pgen.d.ts +6 -0
- package/package/@types/soundfont/write/phdr.d.ts +6 -0
- package/package/@types/soundfont/write/pmod.d.ts +6 -0
- package/package/@types/soundfont/write/sdta.d.ts +11 -0
- package/package/@types/soundfont/write/shdr.d.ts +8 -0
- package/package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
- package/package/@types/soundfont/write/write.d.ts +21 -0
- package/package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
- package/package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
- package/package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
- package/package/@types/synthetizer/synth_event_handler.d.ts +161 -0
- package/package/@types/synthetizer/synthetizer.d.ts +294 -0
- package/package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +89 -0
- package/package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
- package/package/@types/synthetizer/worklet_url.d.ts +5 -0
- package/package/@types/utils/buffer_to_wav.d.ts +8 -0
- package/package/@types/utils/byte_functions/big_endian.d.ts +13 -0
- package/package/@types/utils/byte_functions/little_endian.d.ts +35 -0
- package/package/@types/utils/byte_functions/string.d.ts +22 -0
- package/package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
- package/package/@types/utils/indexed_array.d.ts +21 -0
- package/package/@types/utils/loggin.d.ts +26 -0
- package/package/@types/utils/other.d.ts +32 -0
- package/package/LICENSE +26 -0
- package/package/README.md +84 -0
- package/package/externals/NOTICE +9 -0
- package/package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
- package/package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
- package/package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
- package/package/externals/stbvorbis_sync/LICENSE +202 -0
- package/package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
- package/package/index.js +73 -0
- package/package/midi_handler/README.md +3 -0
- package/package/midi_handler/midi_handler.js +118 -0
- package/package/midi_handler/web_midi_link.js +41 -0
- package/package/midi_parser/README.md +3 -0
- package/package/midi_parser/midi_data.js +121 -0
- package/package/midi_parser/midi_editor.js +557 -0
- package/package/midi_parser/midi_loader.js +502 -0
- package/package/midi_parser/midi_message.js +234 -0
- package/package/midi_parser/midi_writer.js +95 -0
- package/package/midi_parser/rmidi_writer.js +271 -0
- package/package/midi_parser/used_keys_loaded.js +172 -0
- package/package/package.json +43 -0
- package/package/sequencer/README.md +23 -0
- package/package/sequencer/sequencer.js +439 -0
- package/package/sequencer/worklet_sequencer/events.js +92 -0
- package/package/sequencer/worklet_sequencer/play.js +309 -0
- package/package/sequencer/worklet_sequencer/process_event.js +167 -0
- package/package/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
- package/package/sequencer/worklet_sequencer/song_control.js +193 -0
- package/package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
- package/package/soundfont/README.md +8 -0
- package/package/soundfont/read/generators.js +212 -0
- package/package/soundfont/read/instruments.js +125 -0
- package/package/soundfont/read/modulators.js +249 -0
- package/package/soundfont/read/presets.js +300 -0
- package/package/soundfont/read/riff_chunk.js +81 -0
- package/package/soundfont/read/samples.js +398 -0
- package/package/soundfont/read/zones.js +310 -0
- package/package/soundfont/soundfont.js +357 -0
- package/package/soundfont/write/ibag.js +39 -0
- package/package/soundfont/write/igen.js +75 -0
- package/package/soundfont/write/imod.js +46 -0
- package/package/soundfont/write/inst.js +34 -0
- package/package/soundfont/write/pbag.js +39 -0
- package/package/soundfont/write/pgen.js +77 -0
- package/package/soundfont/write/phdr.js +42 -0
- package/package/soundfont/write/pmod.js +46 -0
- package/package/soundfont/write/sdta.js +72 -0
- package/package/soundfont/write/shdr.js +54 -0
- package/package/soundfont/write/soundfont_trimmer.js +169 -0
- package/package/soundfont/write/write.js +180 -0
- package/package/synthetizer/README.md +6 -0
- package/package/synthetizer/audio_effects/effects_config.js +21 -0
- package/package/synthetizer/audio_effects/fancy_chorus.js +120 -0
- package/package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
- package/package/synthetizer/audio_effects/reverb.js +24 -0
- package/package/synthetizer/synth_event_handler.js +156 -0
- package/package/synthetizer/synthetizer.js +766 -0
- package/package/synthetizer/worklet_processor.min.js +13 -0
- package/package/synthetizer/worklet_system/README.md +6 -0
- package/package/synthetizer/worklet_system/main_processor.js +363 -0
- package/package/synthetizer/worklet_system/message_protocol/handle_message.js +197 -0
- package/package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
- package/package/synthetizer/worklet_system/message_protocol/worklet_message.js +121 -0
- package/package/synthetizer/worklet_system/minify_processor.sh +4 -0
- package/package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
- package/package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
- package/package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
- package/package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
- package/package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
- package/package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
- package/package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
- package/package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
- package/package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
- package/package/synthetizer/worklet_system/worklet_processor.js +9 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
- package/package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
- package/package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
- package/package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
- package/package/synthetizer/worklet_url.js +5 -0
- package/package/utils/README.md +4 -0
- package/package/utils/buffer_to_wav.js +101 -0
- package/package/utils/byte_functions/big_endian.js +28 -0
- package/package/utils/byte_functions/little_endian.js +74 -0
- package/package/utils/byte_functions/string.js +97 -0
- package/package/utils/byte_functions/variable_length_quantity.js +37 -0
- package/package/utils/encode_vorbis.js +30 -0
- package/package/utils/indexed_array.js +41 -0
- package/package/utils/loggin.js +79 -0
- package/package/utils/other.js +54 -0
- 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
|
+
}
|