spessasynth_lib 3.17.0 → 3.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/soundfont/basic_soundfont/basic_sample.d.ts +2 -2
- package/@types/soundfont/basic_soundfont/basic_zone.d.ts +12 -12
- package/@types/soundfont/basic_soundfont/basic_zones.d.ts +4 -0
- package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +6 -0
- package/@types/soundfont/dls/articulator_converter.d.ts +10 -0
- package/@types/soundfont/dls/dls_destinations.d.ts +29 -0
- package/@types/soundfont/dls/dls_preset.d.ts +7 -5
- package/@types/soundfont/dls/dls_sample.d.ts +18 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +9 -2
- package/@types/soundfont/dls/dls_sources.d.ts +22 -0
- package/@types/soundfont/dls/dls_zone.d.ts +22 -0
- package/@types/soundfont/dls/read_articulation.d.ts +12 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +2 -2
- package/@types/soundfont/dls/read_lart.d.ts +7 -0
- package/@types/soundfont/dls/read_region.d.ts +7 -0
- package/@types/soundfont/dls/read_samples.d.ts +5 -0
- package/@types/soundfont/read_sf2/generators.d.ts +18 -5
- package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
- package/README.md +10 -0
- package/midi_parser/midi_loader.js +30 -3
- package/package.json +1 -1
- package/soundfont/README.md +6 -2
- package/soundfont/basic_soundfont/basic_sample.js +3 -3
- package/soundfont/basic_soundfont/basic_zone.js +28 -28
- package/soundfont/basic_soundfont/basic_zones.js +15 -19
- package/soundfont/basic_soundfont/riff_chunk.js +18 -2
- package/soundfont/dls/articulator_converter.js +311 -0
- package/soundfont/dls/dls_destinations.js +38 -0
- package/soundfont/dls/dls_preset.js +15 -8
- package/soundfont/dls/dls_sample.js +58 -0
- package/soundfont/dls/dls_soundfont.js +68 -11
- package/soundfont/dls/dls_sources.js +26 -0
- package/soundfont/dls/dls_zone.js +75 -0
- package/soundfont/dls/read_articulation.js +327 -0
- package/soundfont/dls/read_instrument.js +82 -4
- package/soundfont/dls/read_instrument_list.js +6 -6
- package/soundfont/dls/read_lart.js +35 -0
- package/soundfont/dls/read_region.js +129 -0
- package/soundfont/dls/read_samples.js +174 -0
- package/soundfont/read_sf2/generators.js +41 -6
- package/soundfont/read_sf2/modulators.js +3 -3
- package/synthetizer/worklet_processor.min.js +10 -8
- package/utils/buffer_to_wav.js +5 -26
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
2
|
+
import { DLSDestinations } from './dls_destinations.js'
|
|
3
|
+
import { Generator, generatorTypes } from '../read_sf2/generators.js'
|
|
4
|
+
import { DLSSources } from './dls_sources.js'
|
|
5
|
+
import { Modulator } from '../read_sf2/modulators.js'
|
|
6
|
+
import { getSF2ModulatorFromArticulator } from './articulator_converter.js'
|
|
7
|
+
import { SpessaSynthInfo, SpessaSynthWarn } from '../../utils/loggin.js'
|
|
8
|
+
import { consoleColors } from '../../utils/other.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param source {number}
|
|
12
|
+
* @param control {number}
|
|
13
|
+
* @param destination {number}
|
|
14
|
+
* @param value {number}
|
|
15
|
+
* @param transform {number}
|
|
16
|
+
*/
|
|
17
|
+
function modulatorConverterDebug(
|
|
18
|
+
source,
|
|
19
|
+
control,
|
|
20
|
+
destination,
|
|
21
|
+
value,
|
|
22
|
+
transform
|
|
23
|
+
)
|
|
24
|
+
{
|
|
25
|
+
const type = Object.keys(DLSDestinations).find(k => DLSDestinations[k] === destination);
|
|
26
|
+
const srcType = Object.keys(DLSSources).find(k => DLSSources[k] === source);
|
|
27
|
+
const ctrlType = Object.keys(DLSSources).find(k => DLSSources[k] === control);
|
|
28
|
+
const typeString = type ? type : destination.toString(16);
|
|
29
|
+
const srcString = srcType ? srcType : source.toString(16);
|
|
30
|
+
const ctrlString = ctrlType ? ctrlType : control.toString(16);
|
|
31
|
+
SpessaSynthInfo(
|
|
32
|
+
`%cAttempting to convert the following DLS Articulator to SF2 Modulator:
|
|
33
|
+
Source: %c${srcString}%c
|
|
34
|
+
Control: %c${ctrlString}%c
|
|
35
|
+
Destination: %c${typeString}%c
|
|
36
|
+
Amount: %c${value}%c
|
|
37
|
+
Transform: %c${transform}%c...`,
|
|
38
|
+
consoleColors.info,
|
|
39
|
+
consoleColors.recognized,
|
|
40
|
+
consoleColors.info,
|
|
41
|
+
consoleColors.recognized,
|
|
42
|
+
consoleColors.info,
|
|
43
|
+
consoleColors.recognized,
|
|
44
|
+
consoleColors.info,
|
|
45
|
+
consoleColors.recognized,
|
|
46
|
+
consoleColors.info,
|
|
47
|
+
consoleColors.recognized,
|
|
48
|
+
consoleColors.info);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Reads the articulator chunk
|
|
53
|
+
* @param chunk {RiffChunk}
|
|
54
|
+
* @param disableVibrato {boolean} it seems that dls 1 does not have vibrato lfo, so we shall disable it
|
|
55
|
+
* @returns {{modulators: Modulator[], generators: Generator[]}}
|
|
56
|
+
*/
|
|
57
|
+
export function readArticulation(chunk, disableVibrato)
|
|
58
|
+
{
|
|
59
|
+
const artData = chunk.chunkData;
|
|
60
|
+
/**
|
|
61
|
+
* @type {Generator[]}
|
|
62
|
+
*/
|
|
63
|
+
const generators = [];
|
|
64
|
+
/**
|
|
65
|
+
* @type {Modulator[]}
|
|
66
|
+
*/
|
|
67
|
+
const modulators = [];
|
|
68
|
+
|
|
69
|
+
// cbSize (ignore)
|
|
70
|
+
readLittleEndian(artData, 4);
|
|
71
|
+
const connectionsAmount = readLittleEndian(artData, 4);
|
|
72
|
+
for (let i = 0; i < connectionsAmount; i++)
|
|
73
|
+
{
|
|
74
|
+
// read the block
|
|
75
|
+
const source = readLittleEndian(artData, 2);
|
|
76
|
+
const control = readLittleEndian(artData, 2);
|
|
77
|
+
const destination = readLittleEndian(artData, 2);
|
|
78
|
+
const transform = readLittleEndian(artData, 2);
|
|
79
|
+
const value = readLittleEndian(artData, 4) >> 16; // convert it to 16 bit as soundfont uses that
|
|
80
|
+
|
|
81
|
+
// interpret this somehow...
|
|
82
|
+
// if source and control are both zero, it's a generator
|
|
83
|
+
if(source === 0 && control === 0 && transform === 0)
|
|
84
|
+
{
|
|
85
|
+
/**
|
|
86
|
+
* @type {Generator}
|
|
87
|
+
*/
|
|
88
|
+
let generator;
|
|
89
|
+
switch(destination)
|
|
90
|
+
{
|
|
91
|
+
case DLSDestinations.pan:
|
|
92
|
+
generator = new Generator(generatorTypes.pan, value); // turn percent into tenths of percent
|
|
93
|
+
break;
|
|
94
|
+
case DLSDestinations.gain:
|
|
95
|
+
generator = new Generator(generatorTypes.initialAttenuation, -value * 10 / 0.4); // turn to centibels and apply emu correction
|
|
96
|
+
break;
|
|
97
|
+
case DLSDestinations.filterCutoff:
|
|
98
|
+
generator = new Generator(generatorTypes.initialFilterFc, value);
|
|
99
|
+
break;
|
|
100
|
+
case DLSDestinations.filterQ:
|
|
101
|
+
generator = new Generator(generatorTypes.initialFilterQ, value);
|
|
102
|
+
break;
|
|
103
|
+
|
|
104
|
+
// mod lfo raw values it seems
|
|
105
|
+
case DLSDestinations.modLfoFreq:
|
|
106
|
+
generator = new Generator(generatorTypes.freqModLFO, value);
|
|
107
|
+
break;
|
|
108
|
+
case DLSDestinations.modLfoDelay:
|
|
109
|
+
generator = new Generator(generatorTypes.delayModLFO, value);
|
|
110
|
+
break;
|
|
111
|
+
case DLSDestinations.vibLfoFreq:
|
|
112
|
+
generator = new Generator(generatorTypes.freqVibLFO, value);
|
|
113
|
+
break;
|
|
114
|
+
case DLSDestinations.vibLfoDelay:
|
|
115
|
+
generator = new Generator(generatorTypes.delayVibLFO, value);
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
// vol env: all times are timecents like sf2
|
|
119
|
+
case DLSDestinations.volEnvDelay:
|
|
120
|
+
generator = new Generator(generatorTypes.delayVolEnv, value);
|
|
121
|
+
break;
|
|
122
|
+
case DLSDestinations.volEnvAttack:
|
|
123
|
+
generator = new Generator(generatorTypes.attackVolEnv, value);
|
|
124
|
+
break;
|
|
125
|
+
case DLSDestinations.volEnvHold:
|
|
126
|
+
generator = new Generator(generatorTypes.holdVolEnv, value);
|
|
127
|
+
break;
|
|
128
|
+
case DLSDestinations.volEnvDecay:
|
|
129
|
+
generator = new Generator(generatorTypes.decayVolEnv, value);
|
|
130
|
+
break;
|
|
131
|
+
case DLSDestinations.volEnvRelease:
|
|
132
|
+
generator = new Generator(generatorTypes.releaseVolEnv, value);
|
|
133
|
+
break;
|
|
134
|
+
case DLSDestinations.volEnvSustain:
|
|
135
|
+
// gain seems to be (1000 - value) / 10 = sustain dB
|
|
136
|
+
const sustainDb = (1000 - value) / 10;
|
|
137
|
+
generator = new Generator(generatorTypes.sustainVolEnv, sustainDb * 10);
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
// mod env
|
|
141
|
+
case DLSDestinations.modEnvDelay:
|
|
142
|
+
generator = new Generator(generatorTypes.delayModEnv, value);
|
|
143
|
+
break;
|
|
144
|
+
case DLSDestinations.modEnvAttack:
|
|
145
|
+
generator = new Generator(generatorTypes.attackModEnv, value);
|
|
146
|
+
break;
|
|
147
|
+
case DLSDestinations.modEnvHold:
|
|
148
|
+
generator = new Generator(generatorTypes.holdModEnv, value);
|
|
149
|
+
break;
|
|
150
|
+
case DLSDestinations.modEnvDecay:
|
|
151
|
+
generator = new Generator(generatorTypes.decayModEnv, value);
|
|
152
|
+
break;
|
|
153
|
+
case DLSDestinations.modEnvRelease:
|
|
154
|
+
generator = new Generator(generatorTypes.releaseModEnv, value);
|
|
155
|
+
break;
|
|
156
|
+
case DLSDestinations.modEnvSustain:
|
|
157
|
+
// dls uses 1%, desfont uses 0.1%
|
|
158
|
+
const percentageSustain = 1000 - value;
|
|
159
|
+
generator = new Generator(generatorTypes.sustainModEnv, percentageSustain);
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case DLSDestinations.reverbSend:
|
|
163
|
+
generator = new Generator(generatorTypes.reverbEffectsSend, value);
|
|
164
|
+
break;
|
|
165
|
+
case DLSDestinations.chorusSend:
|
|
166
|
+
generator = new Generator(generatorTypes.chorusEffectsSend, value);
|
|
167
|
+
break;
|
|
168
|
+
case DLSDestinations.pitch:
|
|
169
|
+
// split it up
|
|
170
|
+
const semi = Math.floor(value / 100);
|
|
171
|
+
const cents = Math.floor(value - semi * 100);
|
|
172
|
+
generator = new Generator(generatorTypes.fineTune, cents);
|
|
173
|
+
generators.push(new Generator(generatorTypes.coarseTune, semi));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
if(generator)
|
|
177
|
+
{
|
|
178
|
+
generators.push(generator);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else
|
|
182
|
+
// if not, modulator??
|
|
183
|
+
{
|
|
184
|
+
let isGenerator = true;
|
|
185
|
+
// a few special cases which are generators:
|
|
186
|
+
if(control === DLSSources.none)
|
|
187
|
+
{
|
|
188
|
+
// mod lfo to pitch
|
|
189
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.pitch)
|
|
190
|
+
{
|
|
191
|
+
generators.push(new Generator(generatorTypes.modLfoToPitch, value));
|
|
192
|
+
}
|
|
193
|
+
else
|
|
194
|
+
// mod lfo to volume
|
|
195
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.gain)
|
|
196
|
+
{
|
|
197
|
+
generators.push(new Generator(generatorTypes.modLfoToVolume, value));
|
|
198
|
+
}
|
|
199
|
+
else
|
|
200
|
+
// mod lfo to filter
|
|
201
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
|
|
202
|
+
{
|
|
203
|
+
generators.push(new Generator(generatorTypes.modLfoToFilterFc, value));
|
|
204
|
+
}
|
|
205
|
+
else
|
|
206
|
+
// vib lfo to pitch
|
|
207
|
+
if(source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
|
|
208
|
+
{
|
|
209
|
+
generators.push(new Generator(generatorTypes.vibLfoToPitch, value));
|
|
210
|
+
}
|
|
211
|
+
else
|
|
212
|
+
// mod env to pitch
|
|
213
|
+
if(source === DLSSources.modEnv && destination === DLSDestinations.pitch)
|
|
214
|
+
{
|
|
215
|
+
generators.push(new Generator(generatorTypes.modEnvToPitch, value));
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
// mod env to filter
|
|
219
|
+
if(source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
|
|
220
|
+
{
|
|
221
|
+
generators.push(new Generator(generatorTypes.modEnvToFilterFc, value));
|
|
222
|
+
}
|
|
223
|
+
else
|
|
224
|
+
// key to vol env hold
|
|
225
|
+
if(source === DLSSources.keyNum && destination === DLSDestinations.volEnvHold)
|
|
226
|
+
{
|
|
227
|
+
// according to viena and another strange (with modulators) rendition of gm.dls in sf2, this is how it should be done???
|
|
228
|
+
generators.push(new Generator(generatorTypes.keyNumToVolEnvHold, value / -127));
|
|
229
|
+
}
|
|
230
|
+
else
|
|
231
|
+
// key to vol env decay
|
|
232
|
+
if(source === DLSSources.keyNum && destination === DLSDestinations.volEnvDecay)
|
|
233
|
+
{
|
|
234
|
+
generators.push(new Generator(generatorTypes.keyNumToVolEnvDecay, value / -127));
|
|
235
|
+
}
|
|
236
|
+
else
|
|
237
|
+
// key to mod env hold
|
|
238
|
+
if(source === DLSSources.keyNum && destination === DLSDestinations.modEnvHold)
|
|
239
|
+
{
|
|
240
|
+
generators.push(new Generator(generatorTypes.keyNumToModEnvHold, value / -127));
|
|
241
|
+
}
|
|
242
|
+
else
|
|
243
|
+
// key to mod env decay
|
|
244
|
+
if(source === DLSSources.keyNum && destination === DLSDestinations.modEnvDecay)
|
|
245
|
+
{
|
|
246
|
+
generators.push(new Generator(generatorTypes.keyNumToModEnvDecay, value / -127));
|
|
247
|
+
}
|
|
248
|
+
else
|
|
249
|
+
{
|
|
250
|
+
isGenerator = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
else
|
|
255
|
+
{
|
|
256
|
+
isGenerator = false;
|
|
257
|
+
}
|
|
258
|
+
if(isGenerator === false)
|
|
259
|
+
{
|
|
260
|
+
// UNCOMMENT TO ENABLE DEBUG
|
|
261
|
+
// modulatorConverterDebug(source, control, destination, value, transform)
|
|
262
|
+
// convert it to modulator
|
|
263
|
+
const mod = getSF2ModulatorFromArticulator(
|
|
264
|
+
source,
|
|
265
|
+
control,
|
|
266
|
+
destination,
|
|
267
|
+
transform,
|
|
268
|
+
value
|
|
269
|
+
);
|
|
270
|
+
if(mod)
|
|
271
|
+
{
|
|
272
|
+
// some articulators cannot be turned into modulators, that's why this check is a thing
|
|
273
|
+
modulators.push(mod);
|
|
274
|
+
SpessaSynthInfo("%cSucceeded converting to SF2 Modulator!", consoleColors.recognized)
|
|
275
|
+
}
|
|
276
|
+
else
|
|
277
|
+
{
|
|
278
|
+
SpessaSynthWarn("Failed converting to SF2 Modulator!")
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// override reverb and chorus with 1000 instead of 200
|
|
285
|
+
modulators.push(
|
|
286
|
+
new Modulator({
|
|
287
|
+
srcEnum: 0x00DB,
|
|
288
|
+
dest: generatorTypes.reverbEffectsSend,
|
|
289
|
+
amt: 1000,
|
|
290
|
+
secSrcEnum: 0x0,
|
|
291
|
+
transform: 0
|
|
292
|
+
}),
|
|
293
|
+
// chorus effects to send
|
|
294
|
+
new Modulator({
|
|
295
|
+
srcEnum: 0x00DD,
|
|
296
|
+
dest: generatorTypes.chorusEffectsSend,
|
|
297
|
+
amt: 1000,
|
|
298
|
+
secSrcEnum: 0x0,
|
|
299
|
+
transform: 0
|
|
300
|
+
}),
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
// it seems that dls 1 does not have vibrato lfo, so we shall disable it
|
|
304
|
+
if(disableVibrato)
|
|
305
|
+
{
|
|
306
|
+
modulators.push(
|
|
307
|
+
// mod to vib
|
|
308
|
+
new Modulator({
|
|
309
|
+
srcEnum: 0x0081,
|
|
310
|
+
dest: generatorTypes.vibLfoToPitch,
|
|
311
|
+
amt: 0,
|
|
312
|
+
secSrcEnum: 0x0,
|
|
313
|
+
transform: 0
|
|
314
|
+
}),
|
|
315
|
+
// press to vib
|
|
316
|
+
new Modulator({
|
|
317
|
+
srcEnum: 0x000D,
|
|
318
|
+
dest: generatorTypes.vibLfoToPitch,
|
|
319
|
+
amt: 0,
|
|
320
|
+
secSrcEnum: 0x0,
|
|
321
|
+
transform: 0
|
|
322
|
+
})
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {modulators: modulators, generators: generators}
|
|
327
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
2
2
|
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
3
3
|
import { DLSPreset } from './dls_preset.js'
|
|
4
|
-
import { readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
4
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
5
|
+
import { SpessaSynthGroup, SpessaSynthGroupEnd } from '../../utils/loggin.js'
|
|
6
|
+
import { BasicInstrumentZone } from '../basic_soundfont/basic_zones.js'
|
|
7
|
+
import { consoleColors } from '../../utils/other.js'
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* @this {DLSSoundFont}
|
|
@@ -11,12 +14,87 @@ export function readDLSInstrument(chunk)
|
|
|
11
14
|
{
|
|
12
15
|
this.verifyHeader(chunk, "LIST");
|
|
13
16
|
this.verifyText(readBytesAsString(chunk.chunkData, 4), "ins ");
|
|
17
|
+
/**
|
|
18
|
+
* @type {RiffChunk[]}
|
|
19
|
+
*/
|
|
20
|
+
const chunks = [];
|
|
21
|
+
while(chunk.chunkData.length > chunk.chunkData.currentIndex)
|
|
22
|
+
{
|
|
23
|
+
chunks.push(readRIFFChunk(chunk.chunkData));
|
|
24
|
+
}
|
|
14
25
|
|
|
15
|
-
const instrumentHeader = readRIFFChunk(chunk.chunkData);
|
|
16
|
-
this.verifyHeader(instrumentHeader, "insh");
|
|
17
26
|
|
|
27
|
+
const instrumentHeader = chunks.find(c => c.header === "insh");
|
|
28
|
+
if(!instrumentHeader)
|
|
29
|
+
{
|
|
30
|
+
SpessaSynthGroupEnd();
|
|
31
|
+
throw new Error("No instrument header!");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// read instrument header
|
|
18
35
|
const regions = readLittleEndian(instrumentHeader.chunkData, 4);
|
|
19
36
|
const ulBank = readLittleEndian(instrumentHeader.chunkData, 4);
|
|
20
37
|
const ulInstrument = readLittleEndian(instrumentHeader.chunkData, 4);
|
|
21
|
-
|
|
38
|
+
const preset = new DLSPreset(ulBank, ulInstrument);
|
|
39
|
+
|
|
40
|
+
// read preset name in INFO
|
|
41
|
+
let presetName = "unnamedPreset";
|
|
42
|
+
const infoChunk = findRIFFListType(chunks, "INFO");
|
|
43
|
+
if(infoChunk)
|
|
44
|
+
{
|
|
45
|
+
let info = readRIFFChunk(infoChunk.chunkData);
|
|
46
|
+
while(info.header !== "INAM")
|
|
47
|
+
{
|
|
48
|
+
info = readRIFFChunk(infoChunk.chunkData);
|
|
49
|
+
}
|
|
50
|
+
presetName = readBytesAsString(info.chunkData, info.chunkData.length).trim();
|
|
51
|
+
}
|
|
52
|
+
preset.presetName = presetName;
|
|
53
|
+
preset.DLSInstrument.instrumentName = presetName;
|
|
54
|
+
SpessaSynthGroup(`%cParsing %c"${presetName}"%c...`,
|
|
55
|
+
consoleColors.info,
|
|
56
|
+
consoleColors.recognized,
|
|
57
|
+
consoleColors.info);
|
|
58
|
+
|
|
59
|
+
// list of regions
|
|
60
|
+
const regionListChunk = findRIFFListType(chunks, "lrgn");
|
|
61
|
+
if(!regionListChunk)
|
|
62
|
+
{
|
|
63
|
+
SpessaSynthGroupEnd();
|
|
64
|
+
throw new Error("No region list!");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// global articulation: essentially global zone
|
|
68
|
+
const globalZone = new BasicInstrumentZone();
|
|
69
|
+
globalZone.isGlobal = true;
|
|
70
|
+
|
|
71
|
+
// read articulators
|
|
72
|
+
const globalLart = findRIFFListType(chunks, "lart");
|
|
73
|
+
const globalLar2 = findRIFFListType(chunks, "lar2");
|
|
74
|
+
this.readLart(globalLart, globalLar2, globalZone);
|
|
75
|
+
preset.DLSInstrument.instrumentZones.push(globalZone);
|
|
76
|
+
|
|
77
|
+
// read regions
|
|
78
|
+
for (let i = 0; i < regions; i++)
|
|
79
|
+
{
|
|
80
|
+
const chunk = readRIFFChunk(regionListChunk.chunkData);
|
|
81
|
+
this.verifyHeader(chunk, "LIST");
|
|
82
|
+
const type = readBytesAsString(chunk.chunkData, 4);
|
|
83
|
+
if(type !== "rgn " && type !== "rgn2")
|
|
84
|
+
{
|
|
85
|
+
SpessaSynthGroupEnd();
|
|
86
|
+
throw new SyntaxError(`Invalid DLS region! Expected "rgn " or "rgn2" got "${type}"`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
const zone = this.readRegion(chunk);
|
|
91
|
+
if(zone)
|
|
92
|
+
{
|
|
93
|
+
preset.DLSInstrument.instrumentZones.push(zone);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.presets.push(preset);
|
|
98
|
+
this.instruments.push(preset.DLSInstrument);
|
|
99
|
+
SpessaSynthGroupEnd();
|
|
22
100
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
2
|
-
import {
|
|
2
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd } from '../../utils/loggin.js'
|
|
3
|
+
import { consoleColors } from '../../utils/other.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @this {DLSSoundFont}
|
|
6
|
-
* @param
|
|
7
|
+
* @param instrumentListChunk {RiffChunk}
|
|
7
8
|
*/
|
|
8
|
-
export function readDLSInstrumentList(
|
|
9
|
+
export function readDLSInstrumentList(instrumentListChunk)
|
|
9
10
|
{
|
|
10
|
-
|
|
11
|
-
this.verifyHeader(instrumentListChunk, "LIST");
|
|
12
|
-
this.verifyText(readBytesAsString(instrumentListChunk.chunkData, 4), "lins");
|
|
11
|
+
SpessaSynthGroupCollapsed("%cLoading instruments...", consoleColors.info);
|
|
13
12
|
for(let i = 0; i < this.instrumentAmount; i++)
|
|
14
13
|
{
|
|
15
14
|
this.readDLSInstrument(readRIFFChunk(instrumentListChunk.chunkData));
|
|
16
15
|
}
|
|
16
|
+
SpessaSynthGroupEnd();
|
|
17
17
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
2
|
+
import { readArticulation } from './read_articulation.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param lartChunk {RiffChunk|undefined}
|
|
6
|
+
* @param lar2Chunk {RiffChunk|undefined}
|
|
7
|
+
* @param zone {BasicInstrumentZone}
|
|
8
|
+
* @this {DLSSoundFont}
|
|
9
|
+
*/
|
|
10
|
+
export function readLart(lartChunk, lar2Chunk, zone)
|
|
11
|
+
{
|
|
12
|
+
if(lartChunk)
|
|
13
|
+
{
|
|
14
|
+
while(lartChunk.chunkData.currentIndex < lartChunk.chunkData.length)
|
|
15
|
+
{
|
|
16
|
+
const art1 = readRIFFChunk(lartChunk.chunkData);
|
|
17
|
+
this.verifyHeader(art1, "art1");
|
|
18
|
+
const modsAndGens = readArticulation(art1, true);
|
|
19
|
+
zone.generators.push(...modsAndGens.generators);
|
|
20
|
+
zone.modulators.push(...modsAndGens.modulators);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if(lar2Chunk)
|
|
25
|
+
{
|
|
26
|
+
while(lar2Chunk.chunkData.currentIndex < lar2Chunk.chunkData.length)
|
|
27
|
+
{
|
|
28
|
+
const art2 = readRIFFChunk(lar2Chunk.chunkData);
|
|
29
|
+
this.verifyHeader(art2, "art2");
|
|
30
|
+
const modsAndGens = readArticulation(art2, false);
|
|
31
|
+
zone.generators.push(...modsAndGens.generators);
|
|
32
|
+
zone.modulators.push(...modsAndGens.modulators);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
2
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
3
|
+
import { Generator, generatorTypes } from '../read_sf2/generators.js'
|
|
4
|
+
import { DLSZone } from './dls_zone.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @this {DLSSoundFont}
|
|
8
|
+
* @param chunk {RiffChunk}
|
|
9
|
+
* @returns {DLSZone}
|
|
10
|
+
*/
|
|
11
|
+
export function readRegion(chunk)
|
|
12
|
+
{
|
|
13
|
+
// regions are essentially instrument zones
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* read chunks in the region
|
|
17
|
+
* @type {RiffChunk[]}
|
|
18
|
+
*/
|
|
19
|
+
const regionChunks = [];
|
|
20
|
+
while(chunk.chunkData.length > chunk.chunkData.currentIndex)
|
|
21
|
+
{
|
|
22
|
+
regionChunks.push(readRIFFChunk(chunk.chunkData));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// region header
|
|
26
|
+
const regionHeader = regionChunks.find(c => c.header === "rgnh");
|
|
27
|
+
// key range
|
|
28
|
+
const keyMin = readLittleEndian(regionHeader.chunkData, 2);
|
|
29
|
+
const keyMax = readLittleEndian(regionHeader.chunkData, 2);
|
|
30
|
+
// vel range
|
|
31
|
+
const velMin = readLittleEndian(regionHeader.chunkData, 2);
|
|
32
|
+
const velMax = readLittleEndian(regionHeader.chunkData, 2);
|
|
33
|
+
|
|
34
|
+
const zone = new DLSZone(
|
|
35
|
+
{min: keyMin, max: keyMax},
|
|
36
|
+
{min: velMin, max: velMax}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// fusOptions: no idea about that one???
|
|
40
|
+
readLittleEndian(regionHeader.chunkData, 2);
|
|
41
|
+
|
|
42
|
+
// keyGroup: essentially exclusive class
|
|
43
|
+
const exclusive = readLittleEndian(regionHeader.chunkData, 2);
|
|
44
|
+
if(exclusive !== 0)
|
|
45
|
+
{
|
|
46
|
+
zone.generators.push(new Generator(generatorTypes.exclusiveClass, exclusive));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// lart
|
|
50
|
+
const lart = findRIFFListType(regionChunks, "lart");
|
|
51
|
+
const lar2 = findRIFFListType(regionChunks, "lar2");
|
|
52
|
+
this.readLart(lart, lar2, zone);
|
|
53
|
+
|
|
54
|
+
// wsmpl: wave sample chunk
|
|
55
|
+
zone.isGlobal = false;
|
|
56
|
+
const waveSampleChunk = regionChunks.find(c => c.header === "wsmp");
|
|
57
|
+
// cbSize
|
|
58
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
59
|
+
const originalKey = readLittleEndian(waveSampleChunk.chunkData, 2);
|
|
60
|
+
// pitch correction is read from the wave wsmpl chunk
|
|
61
|
+
readLittleEndian(waveSampleChunk.chunkData, 2);
|
|
62
|
+
|
|
63
|
+
// gain correction: Each unit of gain represents 1/655360 dB
|
|
64
|
+
const gainCorrection = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
65
|
+
// convert to signed and turn into attenuation (invert)
|
|
66
|
+
const dbCorrection = (gainCorrection | 0) / -655360;
|
|
67
|
+
// convert to centibels
|
|
68
|
+
const attenuation = (dbCorrection * 10) / 0.4; // make sure to apply EMU correction
|
|
69
|
+
|
|
70
|
+
// skip options
|
|
71
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
72
|
+
|
|
73
|
+
// read loop count (always one or zero)
|
|
74
|
+
const loopsAmount = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
75
|
+
let loopingMode;
|
|
76
|
+
const loop = {start: 0, end: 0};
|
|
77
|
+
if(loopsAmount === 0)
|
|
78
|
+
{
|
|
79
|
+
// no loop
|
|
80
|
+
loopingMode = 0;
|
|
81
|
+
}
|
|
82
|
+
else
|
|
83
|
+
{
|
|
84
|
+
// ignore cbSize
|
|
85
|
+
readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
86
|
+
// loop type: loop normally or loop until release (like soundfont)
|
|
87
|
+
const loopType = readLittleEndian(waveSampleChunk.chunkData, 4); // why is it long???
|
|
88
|
+
if(loopType === 0)
|
|
89
|
+
{
|
|
90
|
+
loopingMode = 1;
|
|
91
|
+
}
|
|
92
|
+
else
|
|
93
|
+
{
|
|
94
|
+
loopingMode = 3;
|
|
95
|
+
}
|
|
96
|
+
loop.start = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
97
|
+
const loopLength = readLittleEndian(waveSampleChunk.chunkData, 4);
|
|
98
|
+
loop.end = loop.start + loopLength;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// wave link
|
|
102
|
+
const waveLinkChunk = regionChunks.find(c => c.header === "wlnk");
|
|
103
|
+
if(waveLinkChunk === undefined)
|
|
104
|
+
{
|
|
105
|
+
// no wave link = no sample. What? Why is it even here then????
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// flags
|
|
110
|
+
readLittleEndian(waveLinkChunk.chunkData, 2);
|
|
111
|
+
// phasse group
|
|
112
|
+
readLittleEndian(waveLinkChunk.chunkData, 2);
|
|
113
|
+
// channel
|
|
114
|
+
readLittleEndian(waveLinkChunk.chunkData, 4);
|
|
115
|
+
// sampleID
|
|
116
|
+
const sampleID = readLittleEndian(waveLinkChunk.chunkData, 4);
|
|
117
|
+
const sample = this.samples[sampleID];
|
|
118
|
+
if(sample === undefined)
|
|
119
|
+
{
|
|
120
|
+
throw new Error("Invalid sample ID!");
|
|
121
|
+
}
|
|
122
|
+
zone.setWavesample(
|
|
123
|
+
attenuation, loopingMode,
|
|
124
|
+
loop,
|
|
125
|
+
originalKey,
|
|
126
|
+
sample,
|
|
127
|
+
sampleID);
|
|
128
|
+
return zone;
|
|
129
|
+
}
|