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.
- package/.idea/inspectionProfiles/Project_Default.xml +10 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_core.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +376 -0
- package/index.js +7 -0
- package/package.json +34 -0
- package/spessasynth_core/midi_parser/README.md +3 -0
- package/spessasynth_core/midi_parser/midi_loader.js +381 -0
- package/spessasynth_core/midi_parser/midi_message.js +231 -0
- package/spessasynth_core/sequencer/sequencer.js +192 -0
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
- package/spessasynth_core/soundfont/README.md +4 -0
- package/spessasynth_core/soundfont/chunk/generators.js +205 -0
- package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
- package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
- package/spessasynth_core/soundfont/chunk/presets.js +264 -0
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
- package/spessasynth_core/soundfont/chunk/samples.js +250 -0
- package/spessasynth_core/soundfont/chunk/zones.js +264 -0
- package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
- package/spessasynth_core/synthetizer/README.md +6 -0
- package/spessasynth_core/synthetizer/synthesizer.js +303 -0
- package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
- package/spessasynth_core/utils/README.md +4 -0
- package/spessasynth_core/utils/buffer_to_wav.js +70 -0
- package/spessasynth_core/utils/byte_functions.js +141 -0
- package/spessasynth_core/utils/loggin.js +79 -0
- package/spessasynth_core/utils/other.js +49 -0
- package/spessasynth_core/utils/shiftable_array.js +26 -0
- 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.
|