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,311 @@
|
|
|
1
|
+
import { DLSSources } from './dls_sources.js'
|
|
2
|
+
import { getModSourceEnum, Modulator, modulatorCurveTypes, modulatorSources } from '../read_sf2/modulators.js'
|
|
3
|
+
import { midiControllers } from '../../midi_parser/midi_message.js'
|
|
4
|
+
import { DLSDestinations } from './dls_destinations.js'
|
|
5
|
+
import { generatorTypes } from '../read_sf2/generators.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param source {number}
|
|
9
|
+
* @returns {{enum: number, isCC: boolean}|undefined}
|
|
10
|
+
*/
|
|
11
|
+
function getSF2SourceFromDLS(source)
|
|
12
|
+
{
|
|
13
|
+
let sourceEnum = undefined;
|
|
14
|
+
let isCC = false;
|
|
15
|
+
switch(source)
|
|
16
|
+
{
|
|
17
|
+
default:
|
|
18
|
+
case DLSSources.modLfo:
|
|
19
|
+
case DLSSources.vibratoLfo:
|
|
20
|
+
case DLSSources.coarseTune:
|
|
21
|
+
case DLSSources.fineTune:
|
|
22
|
+
case DLSSources.modEnv:
|
|
23
|
+
return undefined; // cannot be this in sf2
|
|
24
|
+
|
|
25
|
+
case DLSSources.keyNum:
|
|
26
|
+
sourceEnum = modulatorSources.noteOnKeyNum;
|
|
27
|
+
break;
|
|
28
|
+
case DLSSources.none:
|
|
29
|
+
sourceEnum = modulatorSources.noController;
|
|
30
|
+
break;
|
|
31
|
+
case DLSSources.modulationWheel:
|
|
32
|
+
sourceEnum = midiControllers.modulationWheel;
|
|
33
|
+
isCC = true;
|
|
34
|
+
break;
|
|
35
|
+
case DLSSources.pan:
|
|
36
|
+
sourceEnum = midiControllers.pan;
|
|
37
|
+
isCC = true;
|
|
38
|
+
break;
|
|
39
|
+
case DLSSources.reverb:
|
|
40
|
+
sourceEnum = midiControllers.effects1Depth;
|
|
41
|
+
isCC = true;
|
|
42
|
+
break;
|
|
43
|
+
case DLSSources.chorus:
|
|
44
|
+
sourceEnum = midiControllers.effects3Depth;
|
|
45
|
+
isCC = true;
|
|
46
|
+
break;
|
|
47
|
+
case DLSSources.expression:
|
|
48
|
+
sourceEnum = midiControllers.expressionController;
|
|
49
|
+
isCC = true;
|
|
50
|
+
break;
|
|
51
|
+
case DLSSources.volume:
|
|
52
|
+
sourceEnum = midiControllers.mainVolume;
|
|
53
|
+
isCC = true;
|
|
54
|
+
break;
|
|
55
|
+
case DLSSources.velocity:
|
|
56
|
+
sourceEnum = modulatorSources.noteOnVelocity;
|
|
57
|
+
break;
|
|
58
|
+
case DLSSources.polyPressure:
|
|
59
|
+
sourceEnum = modulatorSources.polyPressure;
|
|
60
|
+
break;
|
|
61
|
+
case DLSSources.channelPressure:
|
|
62
|
+
sourceEnum = modulatorSources.channelPressure;
|
|
63
|
+
break;
|
|
64
|
+
case DLSSources.pitchWheel:
|
|
65
|
+
sourceEnum = modulatorSources.pitchWheel;
|
|
66
|
+
break;
|
|
67
|
+
case DLSSources.pitchWheelRange:
|
|
68
|
+
sourceEnum = modulatorSources.pitchWheelRange;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if(sourceEnum === undefined)
|
|
72
|
+
{
|
|
73
|
+
throw `not known?? ${source}`
|
|
74
|
+
}
|
|
75
|
+
return {enum: sourceEnum, isCC: isCC};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param destination {number}
|
|
80
|
+
* @param amount {number}
|
|
81
|
+
* @returns {generatorTypes|{gen: generatorTypes, newAmount: number}} // transform amount to sf2 units
|
|
82
|
+
*/
|
|
83
|
+
function getSF2GeneratorFromDLS(destination, amount)
|
|
84
|
+
{
|
|
85
|
+
switch (destination)
|
|
86
|
+
{
|
|
87
|
+
default:
|
|
88
|
+
case DLSDestinations.none:
|
|
89
|
+
return undefined;
|
|
90
|
+
case DLSDestinations.pan:
|
|
91
|
+
return generatorTypes.pan;
|
|
92
|
+
case DLSDestinations.gain:
|
|
93
|
+
return {gen: generatorTypes.initialAttenuation, newAmount: amount * -1};
|
|
94
|
+
case DLSDestinations.pitch:
|
|
95
|
+
return generatorTypes.fineTune;
|
|
96
|
+
case DLSDestinations.keyNum:
|
|
97
|
+
return generatorTypes.overridingRootKey;
|
|
98
|
+
|
|
99
|
+
// vol env
|
|
100
|
+
case DLSDestinations.volEnvDelay:
|
|
101
|
+
return generatorTypes.delayVolEnv;
|
|
102
|
+
case DLSDestinations.volEnvAttack:
|
|
103
|
+
return generatorTypes.attackVolEnv;
|
|
104
|
+
case DLSDestinations.volEnvHold:
|
|
105
|
+
return generatorTypes.holdVolEnv;
|
|
106
|
+
case DLSDestinations.volEnvDecay:
|
|
107
|
+
return generatorTypes.decayVolEnv;
|
|
108
|
+
case DLSDestinations.volEnvSustain:
|
|
109
|
+
return {gen: generatorTypes.sustainVolEnv, newAmount: 1000 - amount};
|
|
110
|
+
case DLSDestinations.volEnvRelease:
|
|
111
|
+
return generatorTypes.releaseVolEnv;
|
|
112
|
+
|
|
113
|
+
// mod env
|
|
114
|
+
case DLSDestinations.modEnvDelay:
|
|
115
|
+
return generatorTypes.delayModEnv;
|
|
116
|
+
case DLSDestinations.modEnvAttack:
|
|
117
|
+
return generatorTypes.attackModEnv;
|
|
118
|
+
case DLSDestinations.modEnvHold:
|
|
119
|
+
return generatorTypes.holdModEnv;
|
|
120
|
+
case DLSDestinations.modEnvDecay:
|
|
121
|
+
return generatorTypes.decayModEnv;
|
|
122
|
+
case DLSDestinations.modEnvSustain:
|
|
123
|
+
return {gen: generatorTypes.sustainModEnv, newAmount: (1000 - amount) / 10};
|
|
124
|
+
case DLSDestinations.modEnvRelease:
|
|
125
|
+
return generatorTypes.releaseModEnv;
|
|
126
|
+
|
|
127
|
+
case DLSDestinations.filterCutoff:
|
|
128
|
+
return generatorTypes.initialFilterFc;
|
|
129
|
+
case DLSDestinations.filterQ:
|
|
130
|
+
return generatorTypes.initialFilterQ;
|
|
131
|
+
case DLSDestinations.chorusSend:
|
|
132
|
+
return generatorTypes.chorusEffectsSend;
|
|
133
|
+
case DLSDestinations.reverbSend:
|
|
134
|
+
return generatorTypes.reverbEffectsSend;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* checks for combos such as mod lfo as source and pitch as destination which results in modLfoToPitch
|
|
140
|
+
* @param source {number}
|
|
141
|
+
* @param destination {number}
|
|
142
|
+
* @returns {generatorTypes} real destination
|
|
143
|
+
*/
|
|
144
|
+
function checkForSpecialDLSCombo(source, destination)
|
|
145
|
+
{
|
|
146
|
+
if(source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
|
|
147
|
+
{
|
|
148
|
+
return generatorTypes.vibLfoToPitch;
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.pitch)
|
|
152
|
+
{
|
|
153
|
+
return generatorTypes.modLfoToPitch;
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
|
|
157
|
+
{
|
|
158
|
+
return generatorTypes.modLfoToFilterFc;
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
if(source === DLSSources.modLfo && destination === DLSDestinations.gain)
|
|
162
|
+
{
|
|
163
|
+
return generatorTypes.modLfoToVolume;
|
|
164
|
+
}
|
|
165
|
+
else
|
|
166
|
+
if(source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
|
|
167
|
+
{
|
|
168
|
+
return generatorTypes.modEnvToFilterFc;
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
if(source === DLSSources.modEnv && destination === DLSDestinations.pitch)
|
|
172
|
+
{
|
|
173
|
+
return generatorTypes.modEnvToPitch;
|
|
174
|
+
}
|
|
175
|
+
else
|
|
176
|
+
{
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param source {number}
|
|
183
|
+
* @param control {number}
|
|
184
|
+
* @param destination {number}
|
|
185
|
+
* @param transform {number}
|
|
186
|
+
* @param value {number}
|
|
187
|
+
* @returns {Modulator|undefined}
|
|
188
|
+
*/
|
|
189
|
+
export function getSF2ModulatorFromArticulator(
|
|
190
|
+
source,
|
|
191
|
+
control,
|
|
192
|
+
destination,
|
|
193
|
+
transform,
|
|
194
|
+
value
|
|
195
|
+
)
|
|
196
|
+
{
|
|
197
|
+
// check for special combinations
|
|
198
|
+
const specialDestination = checkForSpecialDLSCombo(source, destination);
|
|
199
|
+
/**
|
|
200
|
+
* @type {generatorTypes}
|
|
201
|
+
*/
|
|
202
|
+
let destinationGenerator;
|
|
203
|
+
/**
|
|
204
|
+
* @type {{enum: number, isCC: boolean}}
|
|
205
|
+
*/
|
|
206
|
+
let sf2Source
|
|
207
|
+
let swapSources = false;
|
|
208
|
+
let isSourceNoController = false;
|
|
209
|
+
if(specialDestination === undefined)
|
|
210
|
+
{
|
|
211
|
+
// determine destination
|
|
212
|
+
const sf2GenDestination = getSF2GeneratorFromDLS(destination, value);
|
|
213
|
+
if(sf2GenDestination === undefined)
|
|
214
|
+
{
|
|
215
|
+
// cannot be a valid modulator
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* @type {generatorTypes}
|
|
220
|
+
*/
|
|
221
|
+
destinationGenerator = sf2GenDestination;
|
|
222
|
+
if(sf2GenDestination.newAmount !== undefined)
|
|
223
|
+
{
|
|
224
|
+
value = sf2GenDestination.newAmount;
|
|
225
|
+
destinationGenerator = sf2GenDestination.gen;
|
|
226
|
+
}
|
|
227
|
+
sf2Source = getSF2SourceFromDLS(source);
|
|
228
|
+
if(sf2Source === undefined)
|
|
229
|
+
{
|
|
230
|
+
// cannot be a valid modulator
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else
|
|
235
|
+
{
|
|
236
|
+
destinationGenerator = specialDestination;
|
|
237
|
+
swapSources = true;
|
|
238
|
+
sf2Source = {enum: modulatorSources.noController, isCC: false};
|
|
239
|
+
isSourceNoController = true;
|
|
240
|
+
}
|
|
241
|
+
let sf2SecondSource = getSF2SourceFromDLS(control);
|
|
242
|
+
if(sf2SecondSource === undefined)
|
|
243
|
+
{
|
|
244
|
+
// cannot be a valid modulator
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// get transforms and final enums
|
|
249
|
+
let sourceEnumFinal;
|
|
250
|
+
if(isSourceNoController)
|
|
251
|
+
{
|
|
252
|
+
// we force it into this state because before it was some strange value,
|
|
253
|
+
// like vibrato lfo bipolar for example
|
|
254
|
+
// since we turn it into NoController -> vibLfoToPitch the result is the same and bipolar concontroller is technically 0
|
|
255
|
+
sourceEnumFinal = 0x0;
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
{
|
|
259
|
+
// output transform is ignored as it's not a thing in sfont format
|
|
260
|
+
// unless the curve type of source is linear, then output is copied
|
|
261
|
+
const outputTransform = transform & 0b1111;
|
|
262
|
+
// source curve type maps to desfont curve type in section 2.10, table 9
|
|
263
|
+
let sourceTransform = (transform >> 10) & 0b1111;
|
|
264
|
+
if(sourceTransform === modulatorCurveTypes.linear && outputTransform !== modulatorCurveTypes.linear)
|
|
265
|
+
{
|
|
266
|
+
sourceTransform = outputTransform;
|
|
267
|
+
}
|
|
268
|
+
const sourceIsBipolar = (transform >> 14) & 1;
|
|
269
|
+
let sourceIsNegative = (transform >> 15) & 1;
|
|
270
|
+
// special case: for attenuation, invert source
|
|
271
|
+
if(destinationGenerator === generatorTypes.initialAttenuation)
|
|
272
|
+
{
|
|
273
|
+
sourceIsNegative = !sourceIsNegative;
|
|
274
|
+
}
|
|
275
|
+
sourceEnumFinal = getModSourceEnum(
|
|
276
|
+
sourceTransform,
|
|
277
|
+
sourceIsBipolar,
|
|
278
|
+
sourceIsNegative,
|
|
279
|
+
sf2Source.isCC,
|
|
280
|
+
sf2Source.enum
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const secSourceTransform = (transform >> 4) & 0b1111;
|
|
285
|
+
const secSourceIsBipolar = (transform >> 8) & 1;
|
|
286
|
+
const secSourceIsNegative = transform >> 9 & 1;
|
|
287
|
+
let secSourceEnumFinal = getModSourceEnum(
|
|
288
|
+
secSourceTransform,
|
|
289
|
+
secSourceIsBipolar,
|
|
290
|
+
secSourceIsNegative,
|
|
291
|
+
sf2SecondSource.isCC,
|
|
292
|
+
sf2SecondSource.enum
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if(swapSources)
|
|
296
|
+
{
|
|
297
|
+
const temp = secSourceEnumFinal;
|
|
298
|
+
secSourceEnumFinal = sourceEnumFinal;
|
|
299
|
+
sourceEnumFinal = temp;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// return the modulator!
|
|
303
|
+
return new Modulator({
|
|
304
|
+
srcEnum: sourceEnumFinal,
|
|
305
|
+
secSrcEnum: secSourceEnumFinal,
|
|
306
|
+
dest: destinationGenerator,
|
|
307
|
+
transform: 0x0,
|
|
308
|
+
amt: value
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @enum {number}
|
|
4
|
+
*/
|
|
5
|
+
export const DLSDestinations = {
|
|
6
|
+
none: 0x0,
|
|
7
|
+
gain: 0x1,
|
|
8
|
+
reserved: 0x2,
|
|
9
|
+
pitch: 0x3,
|
|
10
|
+
pan: 0x4,
|
|
11
|
+
keyNum: 0x5,
|
|
12
|
+
// nuh uh, the channel controllers are not supported!!!!
|
|
13
|
+
chorusSend: 0x80,
|
|
14
|
+
reverbSend: 0x81,
|
|
15
|
+
|
|
16
|
+
modLfoFreq: 0x104,
|
|
17
|
+
modLfoDelay: 0x105,
|
|
18
|
+
|
|
19
|
+
vibLfoFreq: 0x114,
|
|
20
|
+
vibLfoDelay: 0x115,
|
|
21
|
+
|
|
22
|
+
volEnvAttack: 0x206,
|
|
23
|
+
volEnvDecay: 0x207,
|
|
24
|
+
volEnvRelease: 0x209,
|
|
25
|
+
volEnvSustain: 0x20a,
|
|
26
|
+
volEnvDelay: 0x20b,
|
|
27
|
+
volEnvHold: 0x20c,
|
|
28
|
+
|
|
29
|
+
modEnvAttack: 0x30a,
|
|
30
|
+
modEnvDecay: 0x30b,
|
|
31
|
+
modEnvRelease: 0x30d,
|
|
32
|
+
modEnvSustain: 0x30e,
|
|
33
|
+
modEnvDelay: 0x30f,
|
|
34
|
+
modEnvHold: 0x310,
|
|
35
|
+
|
|
36
|
+
filterCutoff: 0x500,
|
|
37
|
+
filterQ: 0x501
|
|
38
|
+
}
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import { BasicPreset } from '../basic_soundfont/basic_preset.js'
|
|
2
|
+
import { BasicPresetZone } from '../basic_soundfont/basic_zones.js'
|
|
3
|
+
import { BasicInstrument } from '../basic_soundfont/basic_instrument.js'
|
|
2
4
|
|
|
3
5
|
export class DLSPreset extends BasicPreset
|
|
4
6
|
{
|
|
5
7
|
/**
|
|
6
8
|
* Creates a new DLS preset
|
|
7
|
-
* @param ulBank {number}
|
|
8
|
-
* @param ulInstrument {number}
|
|
9
|
-
* @param regionsAmount {number}
|
|
9
|
+
* @param ulBank {number}
|
|
10
|
+
* @param ulInstrument {number}
|
|
10
11
|
*/
|
|
11
|
-
constructor(ulBank, ulInstrument
|
|
12
|
+
constructor(ulBank, ulInstrument)
|
|
12
13
|
{
|
|
13
14
|
super();
|
|
14
|
-
this.regionsAmount = regionsAmount;
|
|
15
15
|
this.program = ulInstrument & 127;
|
|
16
16
|
this.bank = (ulBank >> 8) & 127;
|
|
17
|
-
const isDrums =
|
|
17
|
+
const isDrums = ulBank >> 31;
|
|
18
18
|
if(isDrums)
|
|
19
19
|
{
|
|
20
|
-
|
|
20
|
+
// soundfont bank is 128 so we change it here
|
|
21
|
+
this.bank = 128;
|
|
21
22
|
}
|
|
22
|
-
console.log(this.bank, this.program)
|
|
23
23
|
|
|
24
|
+
this.DLSInstrument = new BasicInstrument();
|
|
25
|
+
this.DLSInstrument.addUseCount();
|
|
26
|
+
|
|
27
|
+
const zone = new BasicPresetZone();
|
|
28
|
+
zone.instrument = this.DLSInstrument;
|
|
29
|
+
|
|
30
|
+
this.presetZones = [zone];
|
|
24
31
|
}
|
|
25
32
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BasicSample } from '../basic_soundfont/basic_sample.js'
|
|
2
|
+
|
|
3
|
+
export class DLSSample extends BasicSample
|
|
4
|
+
{
|
|
5
|
+
/**
|
|
6
|
+
* @param name {string}
|
|
7
|
+
* @param rate {number}
|
|
8
|
+
* @param pitch {number}
|
|
9
|
+
* @param pitchCorrection {number}
|
|
10
|
+
* @param loopStart {number} sample data points
|
|
11
|
+
* @param loopEnd {number} sample data points
|
|
12
|
+
* @param data {Float32Array}
|
|
13
|
+
*/
|
|
14
|
+
constructor(
|
|
15
|
+
name,
|
|
16
|
+
rate,
|
|
17
|
+
pitch,
|
|
18
|
+
pitchCorrection,
|
|
19
|
+
loopStart,
|
|
20
|
+
loopEnd,
|
|
21
|
+
data
|
|
22
|
+
)
|
|
23
|
+
{
|
|
24
|
+
super(
|
|
25
|
+
name,
|
|
26
|
+
rate,
|
|
27
|
+
pitch,
|
|
28
|
+
pitchCorrection,
|
|
29
|
+
0,
|
|
30
|
+
1,
|
|
31
|
+
loopStart * 2,
|
|
32
|
+
(loopEnd - 1) * 2 // -1 sample because soundfont end is last sample and dls end is next sample
|
|
33
|
+
);
|
|
34
|
+
this.sampleData = data;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getAudioData()
|
|
38
|
+
{
|
|
39
|
+
return this.sampleData;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @type {Float32Array}
|
|
44
|
+
*/
|
|
45
|
+
sampleData;
|
|
46
|
+
|
|
47
|
+
getRawData()
|
|
48
|
+
{
|
|
49
|
+
const uint8 = new Uint8Array(this.sampleData.length * 2);
|
|
50
|
+
for (let i = 0; i < this.sampleData.length; i++)
|
|
51
|
+
{
|
|
52
|
+
const sample = Math.floor(this.sampleData[i] * 32767);
|
|
53
|
+
uint8[i * 2] = sample & 0xFF; // lower byte
|
|
54
|
+
uint8[i * 2 + 1] = (sample >> 8) & 0xFF; // upper byte
|
|
55
|
+
}
|
|
56
|
+
return uint8;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -2,11 +2,14 @@ import { BasicSoundFont } from '../basic_soundfont/basic_soundfont.js'
|
|
|
2
2
|
import { IndexedByteArray } from '../../utils/indexed_array.js'
|
|
3
3
|
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from '../../utils/loggin.js'
|
|
4
4
|
import { consoleColors } from '../../utils/other.js'
|
|
5
|
-
import { readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
5
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
6
6
|
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
7
7
|
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
8
8
|
import { readDLSInstrumentList } from './read_instrument_list.js'
|
|
9
9
|
import { readDLSInstrument } from './read_instrument.js'
|
|
10
|
+
import { readLart } from './read_lart.js'
|
|
11
|
+
import { readRegion } from './read_region.js'
|
|
12
|
+
import { readDLSSamples } from './read_samples.js'
|
|
10
13
|
|
|
11
14
|
class DLSSoundFont extends BasicSoundFont
|
|
12
15
|
{
|
|
@@ -25,28 +28,80 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
25
28
|
throw new TypeError("No data!");
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
this.soundFontInfo["ifil"] = "2.1"; // always for dls
|
|
29
|
-
|
|
30
31
|
// read the main chunk
|
|
31
32
|
let firstChunk = readRIFFChunk(this.dataArray, false);
|
|
32
33
|
this.verifyHeader(firstChunk, "riff");
|
|
33
34
|
this.verifyText(readBytesAsString(this.dataArray,4).toLowerCase(), "dls ");
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Read list
|
|
38
|
+
* @type {RiffChunk[]}
|
|
39
|
+
*/
|
|
40
|
+
const chunks = [];
|
|
41
|
+
while(this.dataArray.currentIndex < this.dataArray.length)
|
|
38
42
|
{
|
|
39
|
-
|
|
43
|
+
chunks.push(readRIFFChunk(this.dataArray));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// mandatory
|
|
47
|
+
this.soundFontInfo["ifil"] = "2.1"; // always for dls
|
|
48
|
+
this.soundFontInfo["isng"] = "EMU8000";
|
|
49
|
+
|
|
50
|
+
// set some defaults
|
|
51
|
+
this.soundFontInfo["IPRD"] = "SpessaSynth DLS";
|
|
52
|
+
this.soundFontInfo["ICRD"] = new Date().toDateString();
|
|
53
|
+
|
|
54
|
+
// read info
|
|
55
|
+
const infoChunk = findRIFFListType(chunks, "INFO");
|
|
56
|
+
if(infoChunk)
|
|
57
|
+
{
|
|
58
|
+
while(infoChunk.chunkData.currentIndex < infoChunk.chunkData.length)
|
|
59
|
+
{
|
|
60
|
+
const infoPart = readRIFFChunk(infoChunk.chunkData);
|
|
61
|
+
this.soundFontInfo[infoPart.header] = readBytesAsString(infoPart.chunkData, infoPart.size);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.soundFontInfo["ICMT"] = (this.soundFontInfo["ICMT"] || "") + "\nConverted from DLS to SF2 with SpessaSynth";
|
|
65
|
+
if(this.soundFontInfo["ISBJ"])
|
|
66
|
+
{
|
|
67
|
+
// merge it
|
|
68
|
+
this.soundFontInfo["ICMT"] += "\n" + this.soundFontInfo["ISBJ"];
|
|
69
|
+
delete this.soundFontInfo["ISBJ"];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for(const [info, value] of Object.entries(this.soundFontInfo))
|
|
73
|
+
{
|
|
74
|
+
SpessaSynthInfo(`%c"${info}": %c"${value}"`,
|
|
75
|
+
consoleColors.info,
|
|
76
|
+
consoleColors.recognized);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// read "colh"
|
|
80
|
+
let colhChunk = chunks.find(c => c.header === "colh");
|
|
81
|
+
if(!colhChunk)
|
|
82
|
+
{
|
|
83
|
+
SpessaSynthGroupEnd();
|
|
84
|
+
throw new Error("No colh chunk!");
|
|
40
85
|
}
|
|
41
86
|
this.instrumentAmount = readLittleEndian(colhChunk.chunkData, 4);
|
|
42
87
|
SpessaSynthInfo(`%cInstruments amount: %c${this.instrumentAmount}`,
|
|
43
88
|
consoleColors.info,
|
|
44
89
|
consoleColors.recognized);
|
|
45
90
|
|
|
46
|
-
//
|
|
47
|
-
|
|
91
|
+
// read wave list
|
|
92
|
+
let waveListChunk = findRIFFListType(chunks , "wvpl");
|
|
93
|
+
this.readDLSSamples(waveListChunk);
|
|
94
|
+
|
|
95
|
+
// read instrument list
|
|
96
|
+
let instrumentListChunk = findRIFFListType(chunks, "lins");
|
|
97
|
+
if(!instrumentListChunk)
|
|
98
|
+
{
|
|
99
|
+
SpessaSynthGroupEnd();
|
|
100
|
+
throw new Error("No lins chunk!");
|
|
101
|
+
}
|
|
102
|
+
this.readDLSInstrumentList(instrumentListChunk);
|
|
48
103
|
|
|
49
|
-
SpessaSynthInfo(`%cParsing finished! %c"
|
|
104
|
+
SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"] || "UNNAMED"}"%c has %c${this.presets.length} %cpresets,
|
|
50
105
|
%c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
|
|
51
106
|
consoleColors.info,
|
|
52
107
|
consoleColors.recognized,
|
|
@@ -58,7 +113,6 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
58
113
|
consoleColors.recognized,
|
|
59
114
|
consoleColors.info);
|
|
60
115
|
SpessaSynthGroupEnd();
|
|
61
|
-
throw new Error("Not implemented yet...")
|
|
62
116
|
}
|
|
63
117
|
|
|
64
118
|
/**
|
|
@@ -89,5 +143,8 @@ class DLSSoundFont extends BasicSoundFont
|
|
|
89
143
|
}
|
|
90
144
|
DLSSoundFont.prototype.readDLSInstrumentList = readDLSInstrumentList;
|
|
91
145
|
DLSSoundFont.prototype.readDLSInstrument = readDLSInstrument;
|
|
146
|
+
DLSSoundFont.prototype.readRegion = readRegion;
|
|
147
|
+
DLSSoundFont.prototype.readLart = readLart;
|
|
148
|
+
DLSSoundFont.prototype.readDLSSamples = readDLSSamples;
|
|
92
149
|
|
|
93
150
|
export {DLSSoundFont}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @enum {number}
|
|
3
|
+
*/
|
|
4
|
+
export const DLSSources = {
|
|
5
|
+
none: 0x0,
|
|
6
|
+
modLfo: 0x1,
|
|
7
|
+
velocity: 0x2,
|
|
8
|
+
keyNum: 0x3,
|
|
9
|
+
volEnv: 0x4,
|
|
10
|
+
modEnv: 0x5,
|
|
11
|
+
pitchWheel: 0x6,
|
|
12
|
+
polyPressure: 0x7,
|
|
13
|
+
channelPressure: 0x8,
|
|
14
|
+
vibratoLfo: 0x9,
|
|
15
|
+
|
|
16
|
+
modulationWheel: 0x81,
|
|
17
|
+
volume: 0x87,
|
|
18
|
+
pan: 0x8a,
|
|
19
|
+
expression: 0x8b,
|
|
20
|
+
chorus: 0xdb,
|
|
21
|
+
reverb: 0xdd,
|
|
22
|
+
|
|
23
|
+
pitchWheelRange: 0x100,
|
|
24
|
+
fineTune: 0x101,
|
|
25
|
+
coarseTune: 0x102
|
|
26
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { BasicInstrumentZone } from '../basic_soundfont/basic_zones.js'
|
|
2
|
+
import { Generator, generatorTypes } from '../read_sf2/generators.js'
|
|
3
|
+
|
|
4
|
+
export class DLSZone extends BasicInstrumentZone
|
|
5
|
+
{
|
|
6
|
+
/**
|
|
7
|
+
* @param keyRange {SoundFontRange}
|
|
8
|
+
* @param velRange {SoundFontRange}
|
|
9
|
+
*/
|
|
10
|
+
constructor(keyRange, velRange)
|
|
11
|
+
{
|
|
12
|
+
super();
|
|
13
|
+
this.keyRange = keyRange;
|
|
14
|
+
this.velRange = velRange;
|
|
15
|
+
this.isGlobal = true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param attenuationCb {number} with EMU correction
|
|
20
|
+
* @param loopingMode {number} the sfont one
|
|
21
|
+
* @param loop {{start: number, end: number}}
|
|
22
|
+
* @param sampleKey {number}
|
|
23
|
+
* @param sample {BasicSample}
|
|
24
|
+
* @param sampleID {number}
|
|
25
|
+
*/
|
|
26
|
+
setWavesample(
|
|
27
|
+
attenuationCb,
|
|
28
|
+
loopingMode,
|
|
29
|
+
loop,
|
|
30
|
+
sampleKey,
|
|
31
|
+
sample,
|
|
32
|
+
sampleID,
|
|
33
|
+
)
|
|
34
|
+
{
|
|
35
|
+
if(loopingMode !== 0)
|
|
36
|
+
{
|
|
37
|
+
this.generators.push(new Generator(generatorTypes.sampleModes, loopingMode));
|
|
38
|
+
}
|
|
39
|
+
this.generators.push(new Generator(generatorTypes.initialAttenuation, attenuationCb));
|
|
40
|
+
this.isGlobal = false;
|
|
41
|
+
|
|
42
|
+
// correct loop if needed
|
|
43
|
+
const diffStart = loop.start - (sample.sampleLoopStartIndex / 2);
|
|
44
|
+
const diffEnd = loop.end - (sample.sampleLoopEndIndex / 2);
|
|
45
|
+
if(diffStart !== 0)
|
|
46
|
+
{
|
|
47
|
+
const fine = diffStart % 32768;
|
|
48
|
+
this.generators.push(new Generator(generatorTypes.startloopAddrsOffset, fine));
|
|
49
|
+
const coarse = Math.round(diffStart / 32768);
|
|
50
|
+
if(coarse !== 0)
|
|
51
|
+
{
|
|
52
|
+
this.generators.push(new Generator(generatorTypes.startloopAddrsCoarseOffset, fine));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if(diffEnd !== 0)
|
|
56
|
+
{
|
|
57
|
+
const fine = diffEnd % 32768;
|
|
58
|
+
this.generators.push(new Generator(generatorTypes.endloopAddrsOffset, fine));
|
|
59
|
+
const coarse = Math.round(diffEnd / 32768);
|
|
60
|
+
if(coarse !== 0)
|
|
61
|
+
{
|
|
62
|
+
this.generators.push(new Generator(generatorTypes.endloopAddrsCoarseOffset, fine));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// correct key if needed
|
|
66
|
+
if(sampleKey !== sample.samplePitch)
|
|
67
|
+
{
|
|
68
|
+
this.generators.push(new Generator(generatorTypes.overridingRootKey, sampleKey));
|
|
69
|
+
}
|
|
70
|
+
// add sample ID
|
|
71
|
+
this.generators.push(new Generator(generatorTypes.sampleID, sampleID));
|
|
72
|
+
this.sample = sample;
|
|
73
|
+
sample.useCount++;
|
|
74
|
+
}
|
|
75
|
+
}
|