spessasynth_lib 3.22.12 → 3.23.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_soundfont.d.ts +2 -0
- package/@types/soundfont/basic_soundfont/basic_zone.d.ts +16 -0
- package/@types/soundfont/basic_soundfont/generator.d.ts +2 -1
- package/@types/soundfont/basic_soundfont/riff_chunk.d.ts +2 -1
- package/@types/soundfont/basic_soundfont/write_dls/art2.d.ts +6 -0
- package/@types/soundfont/basic_soundfont/write_dls/articulator.d.ts +28 -0
- package/@types/soundfont/basic_soundfont/write_dls/combine_zones.d.ts +8 -0
- package/@types/soundfont/basic_soundfont/write_dls/ins.d.ts +7 -0
- package/@types/soundfont/basic_soundfont/write_dls/lins.d.ts +5 -0
- package/@types/soundfont/basic_soundfont/write_dls/modulator_converter.d.ts +11 -0
- package/@types/soundfont/basic_soundfont/write_dls/rgn2.d.ts +7 -0
- package/@types/soundfont/basic_soundfont/write_dls/wave.d.ts +6 -0
- package/@types/soundfont/basic_soundfont/write_dls/write_dls.d.ts +6 -0
- package/@types/soundfont/basic_soundfont/write_dls/wsmp.d.ts +12 -0
- package/@types/soundfont/basic_soundfont/write_dls/wvpl.d.ts +8 -0
- package/midi_parser/midi_editor.js +0 -0
- package/package.json +1 -1
- package/soundfont/basic_soundfont/basic_preset.js +23 -12
- package/soundfont/basic_soundfont/basic_soundfont.js +2 -0
- package/soundfont/basic_soundfont/basic_zone.js +30 -5
- package/soundfont/basic_soundfont/generator.js +10 -4
- package/soundfont/basic_soundfont/riff_chunk.js +20 -5
- package/soundfont/basic_soundfont/write_dls/art2.js +136 -0
- package/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
- package/soundfont/basic_soundfont/write_dls/combine_zones.js +398 -0
- package/soundfont/basic_soundfont/write_dls/ins.js +103 -0
- package/soundfont/basic_soundfont/write_dls/lins.js +18 -0
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +324 -0
- package/soundfont/basic_soundfont/write_dls/rgn2.js +101 -0
- package/soundfont/basic_soundfont/write_dls/wave.js +74 -0
- package/soundfont/basic_soundfont/write_dls/write_dls.js +118 -0
- package/soundfont/basic_soundfont/write_dls/wsmp.js +74 -0
- package/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
- package/soundfont/basic_soundfont/write_sf2/igen.js +3 -3
- package/soundfont/basic_soundfont/write_sf2/pgen.js +2 -2
- package/soundfont/dls/articulator_converter.js +10 -0
- package/soundfont/dls/dls_sample.js +2 -2
- package/soundfont/dls/dls_soundfont.js +2 -1
- package/soundfont/dls/dls_zone.js +21 -18
- package/soundfont/dls/read_articulation.js +18 -30
- package/soundfont/dls/read_instrument.js +31 -1
- package/soundfont/dls/read_region.js +0 -4
- package/soundfont/dls/read_samples.js +17 -17
- package/synthetizer/key_modifier_manager.js +1 -1
- package/synthetizer/worklet_processor.min.js +13 -12
- package/synthetizer/worklet_system/main_processor.js +2 -0
- package/synthetizer/worklet_system/worklet_methods/note_on.js +2 -1
- package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +4 -12
- package/synthetizer/worklet_system/worklet_methods/voice_control.js +4 -3
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +0 -0
- package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +1 -1
- package/utils/byte_functions/string.js +1 -1
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { midiControllers } from "../../../midi_parser/midi_message.js";
|
|
2
|
+
import { DLSSources } from "../../dls/dls_sources.js";
|
|
3
|
+
import { modulatorCurveTypes, modulatorSources } from "../modulator.js";
|
|
4
|
+
import { generatorTypes } from "../generator.js";
|
|
5
|
+
import { DLSDestinations } from "../../dls/dls_destinations.js";
|
|
6
|
+
import { Articulator } from "./articulator.js";
|
|
7
|
+
import { SpessaSynthWarn } from "../../../utils/loggin.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param cc {boolean}
|
|
12
|
+
* @param index {number}
|
|
13
|
+
* @returns {number|undefined}
|
|
14
|
+
*/
|
|
15
|
+
function getDLSSourceFromSf2Source(cc, index)
|
|
16
|
+
{
|
|
17
|
+
if (cc)
|
|
18
|
+
{
|
|
19
|
+
switch (index)
|
|
20
|
+
{
|
|
21
|
+
default:
|
|
22
|
+
// DLS supports limited controllers
|
|
23
|
+
return undefined;
|
|
24
|
+
|
|
25
|
+
case midiControllers.modulationWheel:
|
|
26
|
+
return DLSSources.modulationWheel;
|
|
27
|
+
case midiControllers.mainVolume:
|
|
28
|
+
return DLSSources.volume;
|
|
29
|
+
case midiControllers.pan:
|
|
30
|
+
return DLSSources.pan;
|
|
31
|
+
case midiControllers.expressionController:
|
|
32
|
+
return DLSSources.expression;
|
|
33
|
+
case midiControllers.chorusDepth:
|
|
34
|
+
return DLSSources.chorus;
|
|
35
|
+
case midiControllers.reverbDepth:
|
|
36
|
+
return DLSSources.reverb;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
{
|
|
41
|
+
switch (index)
|
|
42
|
+
{
|
|
43
|
+
default:
|
|
44
|
+
// cannot be a DLS articulator
|
|
45
|
+
return undefined;
|
|
46
|
+
|
|
47
|
+
case modulatorSources.noteOnKeyNum:
|
|
48
|
+
return DLSSources.keyNum;
|
|
49
|
+
case modulatorSources.noteOnVelocity:
|
|
50
|
+
return DLSSources.velocity;
|
|
51
|
+
case modulatorSources.noController:
|
|
52
|
+
return DLSSources.none;
|
|
53
|
+
case modulatorSources.polyPressure:
|
|
54
|
+
return DLSSources.polyPressure;
|
|
55
|
+
case modulatorSources.channelPressure:
|
|
56
|
+
return DLSSources.channelPressure;
|
|
57
|
+
case modulatorSources.pitchWheel:
|
|
58
|
+
return DLSSources.pitchWheel;
|
|
59
|
+
case modulatorSources.pitchWheelRange:
|
|
60
|
+
return DLSSources.pitchWheelRange;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param dest {number}
|
|
67
|
+
* @param amount {number}
|
|
68
|
+
* @returns {number|undefined|{dest: number, amount: number}}
|
|
69
|
+
*/
|
|
70
|
+
function getDLSDestinationFromSf2(dest, amount)
|
|
71
|
+
{
|
|
72
|
+
switch (dest)
|
|
73
|
+
{
|
|
74
|
+
default:
|
|
75
|
+
return undefined;
|
|
76
|
+
|
|
77
|
+
case generatorTypes.initialAttenuation:
|
|
78
|
+
// amount does not get EMU corrected here, as this only applies to modulator attenuation
|
|
79
|
+
// the generator (affected) attenuation is handled in wsmp.
|
|
80
|
+
return { dest: DLSDestinations.gain, amount: -amount };
|
|
81
|
+
case generatorTypes.fineTune:
|
|
82
|
+
return DLSDestinations.pitch;
|
|
83
|
+
case generatorTypes.pan:
|
|
84
|
+
return DLSDestinations.pan;
|
|
85
|
+
case generatorTypes.keyNum:
|
|
86
|
+
return DLSDestinations.keyNum;
|
|
87
|
+
|
|
88
|
+
case generatorTypes.reverbEffectsSend:
|
|
89
|
+
return DLSDestinations.reverbSend;
|
|
90
|
+
case generatorTypes.chorusEffectsSend:
|
|
91
|
+
return DLSDestinations.chorusSend;
|
|
92
|
+
|
|
93
|
+
case generatorTypes.freqModLFO:
|
|
94
|
+
return DLSDestinations.modLfoFreq;
|
|
95
|
+
case generatorTypes.delayModLFO:
|
|
96
|
+
return DLSDestinations.modLfoDelay;
|
|
97
|
+
|
|
98
|
+
case generatorTypes.delayVibLFO:
|
|
99
|
+
return DLSDestinations.vibLfoDelay;
|
|
100
|
+
case generatorTypes.freqVibLFO:
|
|
101
|
+
return DLSDestinations.vibLfoFreq;
|
|
102
|
+
|
|
103
|
+
case generatorTypes.delayVolEnv:
|
|
104
|
+
return DLSDestinations.volEnvDelay;
|
|
105
|
+
case generatorTypes.attackVolEnv:
|
|
106
|
+
return DLSDestinations.volEnvAttack;
|
|
107
|
+
case generatorTypes.holdVolEnv:
|
|
108
|
+
return DLSDestinations.volEnvHold;
|
|
109
|
+
case generatorTypes.decayVolEnv:
|
|
110
|
+
return DLSDestinations.volEnvDecay;
|
|
111
|
+
case generatorTypes.sustainVolEnv:
|
|
112
|
+
return { dest: DLSDestinations.volEnvSustain, amount: 1000 - amount };
|
|
113
|
+
case generatorTypes.releaseVolEnv:
|
|
114
|
+
return DLSDestinations.volEnvRelease;
|
|
115
|
+
|
|
116
|
+
case generatorTypes.delayModEnv:
|
|
117
|
+
return DLSDestinations.modEnvDelay;
|
|
118
|
+
case generatorTypes.attackModEnv:
|
|
119
|
+
return DLSDestinations.modEnvAttack;
|
|
120
|
+
case generatorTypes.holdModEnv:
|
|
121
|
+
return DLSDestinations.modEnvHold;
|
|
122
|
+
case generatorTypes.decayModEnv:
|
|
123
|
+
return DLSDestinations.modEnvDecay;
|
|
124
|
+
case generatorTypes.sustainModEnv:
|
|
125
|
+
return { dest: DLSDestinations.modEnvSustain, amount: 1000 - amount };
|
|
126
|
+
case generatorTypes.releaseModEnv:
|
|
127
|
+
return DLSDestinations.modEnvRelease;
|
|
128
|
+
|
|
129
|
+
case generatorTypes.initialFilterFc:
|
|
130
|
+
return DLSDestinations.filterCutoff;
|
|
131
|
+
case generatorTypes.initialFilterQ:
|
|
132
|
+
return DLSDestinations.filterQ;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @param dest {number}
|
|
138
|
+
* @param amt {number}
|
|
139
|
+
* @returns {{source: DLSSources, dest: DLSDestinations, amt: number, isBipolar: boolean}|undefined}
|
|
140
|
+
*/
|
|
141
|
+
function checkSF2SpecialCombos(dest, amt)
|
|
142
|
+
{
|
|
143
|
+
|
|
144
|
+
switch (dest)
|
|
145
|
+
{
|
|
146
|
+
default:
|
|
147
|
+
return undefined;
|
|
148
|
+
// mod env
|
|
149
|
+
case generatorTypes.modEnvToFilterFc:
|
|
150
|
+
return { source: DLSSources.modEnv, dest: DLSDestinations.filterCutoff, amt: amt, isBipolar: false };
|
|
151
|
+
case generatorTypes.modEnvToPitch:
|
|
152
|
+
return { source: DLSSources.modEnv, dest: DLSDestinations.pitch, amt: amt, isBipolar: false };
|
|
153
|
+
|
|
154
|
+
// mod lfo
|
|
155
|
+
case generatorTypes.modLfoToFilterFc:
|
|
156
|
+
return { source: DLSSources.modLfo, dest: DLSDestinations.filterCutoff, amt: amt, isBipolar: true };
|
|
157
|
+
case generatorTypes.modLfoToVolume:
|
|
158
|
+
return { source: DLSSources.modLfo, dest: DLSDestinations.gain, amt: amt, isBipolar: true };
|
|
159
|
+
case generatorTypes.modLfoToPitch:
|
|
160
|
+
return { source: DLSSources.modLfo, dest: DLSDestinations.pitch, amt: amt, isBipolar: true };
|
|
161
|
+
|
|
162
|
+
// vib lfo
|
|
163
|
+
case generatorTypes.vibLfoToPitch:
|
|
164
|
+
return { source: DLSSources.vibratoLfo, dest: DLSDestinations.pitch, amt: amt, isBipolar: true };
|
|
165
|
+
|
|
166
|
+
// key to something
|
|
167
|
+
case generatorTypes.keyNumToVolEnvHold:
|
|
168
|
+
return {
|
|
169
|
+
source: DLSSources.keyNum,
|
|
170
|
+
dest: DLSDestinations.volEnvHold,
|
|
171
|
+
amt: amt,
|
|
172
|
+
isBipolar: true
|
|
173
|
+
};
|
|
174
|
+
case generatorTypes.keyNumToVolEnvDecay:
|
|
175
|
+
return {
|
|
176
|
+
source: DLSSources.keyNum,
|
|
177
|
+
dest: DLSDestinations.volEnvDecay,
|
|
178
|
+
amt: amt,
|
|
179
|
+
isBipolar: true
|
|
180
|
+
};
|
|
181
|
+
case generatorTypes.keyNumToModEnvHold:
|
|
182
|
+
return {
|
|
183
|
+
source: DLSSources.keyNum,
|
|
184
|
+
dest: DLSDestinations.modEnvHold,
|
|
185
|
+
amt: amt,
|
|
186
|
+
isBipolar: true
|
|
187
|
+
};
|
|
188
|
+
case generatorTypes.keyNumToModEnvDecay:
|
|
189
|
+
return {
|
|
190
|
+
source: DLSSources.keyNum,
|
|
191
|
+
dest: DLSDestinations.modEnvDecay,
|
|
192
|
+
amt: amt,
|
|
193
|
+
isBipolar: true
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// scale tuning is implemented in DLS via an articulator:
|
|
197
|
+
// keyNum to relative pitch at 12800 cents.
|
|
198
|
+
// Change that to scale tuning * 128.
|
|
199
|
+
// therefore regular scale is still 12800, half is 6400 etc.
|
|
200
|
+
case generatorTypes.scaleTuning:
|
|
201
|
+
return {
|
|
202
|
+
source: DLSSources.keyNum,
|
|
203
|
+
dest: DLSDestinations.pitch,
|
|
204
|
+
amt: amt * 128,
|
|
205
|
+
isBipolar: false // according to table 4, this should be false.
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param gen {Generator}
|
|
212
|
+
* @returns {Articulator|undefined}
|
|
213
|
+
*/
|
|
214
|
+
export function getDLSArticulatorFromSf2Generator(gen)
|
|
215
|
+
{
|
|
216
|
+
const dest = getDLSDestinationFromSf2(gen.generatorType, gen.generatorValue);
|
|
217
|
+
let destination = dest;
|
|
218
|
+
let source = 0;
|
|
219
|
+
let amount = gen.generatorValue;
|
|
220
|
+
if (dest?.amount !== undefined)
|
|
221
|
+
{
|
|
222
|
+
amount = dest.amount;
|
|
223
|
+
destination = dest.dest;
|
|
224
|
+
}
|
|
225
|
+
// check for special combo
|
|
226
|
+
const combo = checkSF2SpecialCombos(gen.generatorType, gen.generatorValue);
|
|
227
|
+
if (combo !== undefined)
|
|
228
|
+
{
|
|
229
|
+
amount = combo.amt;
|
|
230
|
+
destination = combo.dest;
|
|
231
|
+
source = combo.source;
|
|
232
|
+
}
|
|
233
|
+
else if (destination === undefined)
|
|
234
|
+
{
|
|
235
|
+
SpessaSynthWarn(`Invalid generator type: ${gen.generatorType}`);
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
return new Articulator(
|
|
239
|
+
source,
|
|
240
|
+
0,
|
|
241
|
+
destination,
|
|
242
|
+
amount,
|
|
243
|
+
0
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param mod {Modulator}
|
|
250
|
+
* @returns {Articulator|undefined}
|
|
251
|
+
*/
|
|
252
|
+
export function getDLSArticulatorFromSf2Modulator(mod)
|
|
253
|
+
{
|
|
254
|
+
if (mod.transformType !== 0)
|
|
255
|
+
{
|
|
256
|
+
SpessaSynthWarn("Other transform types are not supported.");
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
let source = getDLSSourceFromSf2Source(mod.sourceUsesCC, mod.sourceIndex);
|
|
260
|
+
let sourceCurve = mod.sourceCurveType;
|
|
261
|
+
let sourceBipolar = mod.sourcePolarity;
|
|
262
|
+
let sourceDirection = mod.sourceDirection;
|
|
263
|
+
if (source === undefined)
|
|
264
|
+
{
|
|
265
|
+
SpessaSynthWarn(`Invalid source: ${mod.sourceIndex}, CC: ${mod.sourceUsesCC}`);
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
let control = getDLSSourceFromSf2Source(mod.secSrcUsesCC, mod.secSrcIndex);
|
|
269
|
+
let controlCurve = mod.secSrcCurveType;
|
|
270
|
+
let controlBipolar = mod.secSrcPolarity;
|
|
271
|
+
let controlDirection = mod.secSrcDirection;
|
|
272
|
+
if (control === undefined)
|
|
273
|
+
{
|
|
274
|
+
SpessaSynthWarn(`Invalid secondary source: ${mod.secSrcIndex}, CC: ${mod.secSrcUsesCC}`);
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
let dlsDestinationFromSf2 = getDLSDestinationFromSf2(mod.modulatorDestination, mod.transformAmount);
|
|
278
|
+
let destination = dlsDestinationFromSf2;
|
|
279
|
+
let amt = mod.transformAmount;
|
|
280
|
+
if (dlsDestinationFromSf2?.dest !== undefined)
|
|
281
|
+
{
|
|
282
|
+
destination = dlsDestinationFromSf2.dest;
|
|
283
|
+
amt = dlsDestinationFromSf2.amount;
|
|
284
|
+
}
|
|
285
|
+
const specialCombo = checkSF2SpecialCombos(mod.modulatorDestination, mod.transformAmount);
|
|
286
|
+
if (specialCombo !== undefined)
|
|
287
|
+
{
|
|
288
|
+
amt = specialCombo.amt;
|
|
289
|
+
// move source to control
|
|
290
|
+
control = source;
|
|
291
|
+
controlCurve = sourceCurve;
|
|
292
|
+
controlBipolar = sourceBipolar;
|
|
293
|
+
controlDirection = sourceDirection;
|
|
294
|
+
|
|
295
|
+
// set source as static as it's either: env, lfo or keynum
|
|
296
|
+
sourceCurve = modulatorCurveTypes.linear;
|
|
297
|
+
sourceBipolar = specialCombo.isBipolar ? 1 : 0;
|
|
298
|
+
sourceDirection = 0;
|
|
299
|
+
source = specialCombo.source;
|
|
300
|
+
destination = specialCombo.dest;
|
|
301
|
+
}
|
|
302
|
+
else if (destination === undefined)
|
|
303
|
+
{
|
|
304
|
+
SpessaSynthWarn(`Invalid destination: ${mod.modulatorDestination}`);
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// source curve type maps to desfont curve type in section 2.10, table 9
|
|
309
|
+
let transform = 0;
|
|
310
|
+
transform |= controlCurve << 4;
|
|
311
|
+
transform |= controlBipolar << 8;
|
|
312
|
+
transform |= controlDirection << 9;
|
|
313
|
+
|
|
314
|
+
transform |= sourceCurve << 10;
|
|
315
|
+
transform |= sourceBipolar << 14;
|
|
316
|
+
transform |= sourceDirection << 15;
|
|
317
|
+
return new Articulator(
|
|
318
|
+
source,
|
|
319
|
+
control,
|
|
320
|
+
destination,
|
|
321
|
+
amt,
|
|
322
|
+
transform
|
|
323
|
+
);
|
|
324
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
2
|
+
import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
|
|
3
|
+
import { generatorTypes } from "../generator.js";
|
|
4
|
+
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
5
|
+
import { writeWavesample } from "./wsmp.js";
|
|
6
|
+
import { writeArticulator } from "./art2.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param zone {BasicInstrumentZone}
|
|
10
|
+
* @this {BasicSoundFont}
|
|
11
|
+
* @returns {IndexedByteArray}
|
|
12
|
+
*/
|
|
13
|
+
export function writeDLSRegion(zone)
|
|
14
|
+
{
|
|
15
|
+
// region header
|
|
16
|
+
const rgnhData = new IndexedByteArray(14);
|
|
17
|
+
// keyRange
|
|
18
|
+
writeWord(rgnhData, Math.max(zone.keyRange.min, 0));
|
|
19
|
+
writeWord(rgnhData, zone.keyRange.max);
|
|
20
|
+
// velRange
|
|
21
|
+
writeWord(rgnhData, Math.max(zone.velRange.min, 0));
|
|
22
|
+
writeWord(rgnhData, zone.velRange.max);
|
|
23
|
+
// fusOptions
|
|
24
|
+
writeWord(rgnhData, 0);
|
|
25
|
+
// keyGroup (exclusive class)
|
|
26
|
+
const exclusive = zone.getGeneratorValue(generatorTypes.exclusiveClass, 0);
|
|
27
|
+
writeWord(rgnhData, exclusive);
|
|
28
|
+
// usLayer
|
|
29
|
+
writeWord(rgnhData, 0);
|
|
30
|
+
const rgnh = writeRIFFOddSize(
|
|
31
|
+
"rgnh",
|
|
32
|
+
rgnhData
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// wavesample (Wsmp)
|
|
36
|
+
const wsmp = writeWavesample(
|
|
37
|
+
zone.sample,
|
|
38
|
+
zone.getGeneratorValue(generatorTypes.overridingRootKey, zone.sample.samplePitch),
|
|
39
|
+
zone.getGeneratorValue(
|
|
40
|
+
generatorTypes.fineTune,
|
|
41
|
+
0
|
|
42
|
+
) + zone.getGeneratorValue(generatorTypes.coarseTune, 0) * 100
|
|
43
|
+
+ zone.sample.samplePitchCorrection,
|
|
44
|
+
zone.getGeneratorValue(generatorTypes.initialAttenuation, 0),
|
|
45
|
+
// calculate loop with offsets
|
|
46
|
+
zone.sample.sampleLoopStartIndex
|
|
47
|
+
+ zone.getGeneratorValue(generatorTypes.startloopAddrsOffset, 0)
|
|
48
|
+
+ zone.getGeneratorValue(generatorTypes.startloopAddrsCoarseOffset, 0) * 32768,
|
|
49
|
+
zone.sample.sampleLoopEndIndex
|
|
50
|
+
+ zone.getGeneratorValue(generatorTypes.endloopAddrsOffset, 0)
|
|
51
|
+
+ zone.getGeneratorValue(generatorTypes.endloopAddrsCoarseOffset, 0) * 32768,
|
|
52
|
+
zone.getGeneratorValue(generatorTypes.sampleModes, 0)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// wavelink (wlnk)
|
|
56
|
+
const wlnkData = new IndexedByteArray(12);
|
|
57
|
+
writeWord(wlnkData, 0); // fusOptions
|
|
58
|
+
writeWord(wlnkData, 0); // usPhaseGroup
|
|
59
|
+
let sampleType = 0;
|
|
60
|
+
switch (zone.sample.sampleType)
|
|
61
|
+
{
|
|
62
|
+
default:
|
|
63
|
+
case 1:
|
|
64
|
+
case 4:
|
|
65
|
+
// mono/left
|
|
66
|
+
sampleType = 0;
|
|
67
|
+
break;
|
|
68
|
+
|
|
69
|
+
case 2:
|
|
70
|
+
// right
|
|
71
|
+
sampleType = 1;
|
|
72
|
+
}
|
|
73
|
+
writeDword(wlnkData, sampleType); // ulChannel
|
|
74
|
+
writeDword(wlnkData, this.samples.indexOf(zone.sample)); // ulTableIndex
|
|
75
|
+
const wlnk = writeRIFFOddSize(
|
|
76
|
+
"wlnk",
|
|
77
|
+
wlnkData
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// art
|
|
81
|
+
const art2 = writeArticulator(zone);
|
|
82
|
+
|
|
83
|
+
const lar2 = writeRIFFOddSize(
|
|
84
|
+
"lar2",
|
|
85
|
+
art2,
|
|
86
|
+
false,
|
|
87
|
+
true
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return writeRIFFOddSize(
|
|
91
|
+
"rgn2",
|
|
92
|
+
combineArrays([
|
|
93
|
+
rgnh,
|
|
94
|
+
wsmp,
|
|
95
|
+
wlnk,
|
|
96
|
+
lar2
|
|
97
|
+
]),
|
|
98
|
+
false,
|
|
99
|
+
true
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
2
|
+
import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
|
|
3
|
+
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
4
|
+
import { writeWavesample } from "./wsmp.js";
|
|
5
|
+
import { getStringBytes } from "../../../utils/byte_functions/string.js";
|
|
6
|
+
import { SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
7
|
+
import { consoleColors } from "../../../utils/other.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param sample {BasicSample}
|
|
11
|
+
* @returns {IndexedByteArray}
|
|
12
|
+
*/
|
|
13
|
+
export function writeDLSSample(sample)
|
|
14
|
+
{
|
|
15
|
+
const fmtData = new IndexedByteArray(18);
|
|
16
|
+
writeWord(fmtData, 1); // wFormatTag
|
|
17
|
+
writeWord(fmtData, 1); // wChannels
|
|
18
|
+
writeDword(fmtData, sample.sampleRate);
|
|
19
|
+
writeDword(fmtData, sample.sampleRate * 2); // 16-bit samples
|
|
20
|
+
writeWord(fmtData, 2); // wBlockAlign
|
|
21
|
+
writeWord(fmtData, 16); // wBitsPerSample
|
|
22
|
+
const fmt = writeRIFFOddSize(
|
|
23
|
+
"fmt ",
|
|
24
|
+
fmtData
|
|
25
|
+
);
|
|
26
|
+
const wsmp = writeWavesample(
|
|
27
|
+
sample,
|
|
28
|
+
sample.samplePitch,
|
|
29
|
+
sample.samplePitchCorrection,
|
|
30
|
+
0,
|
|
31
|
+
sample.sampleLoopStartIndex,
|
|
32
|
+
sample.sampleLoopEndIndex,
|
|
33
|
+
1
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const audio = sample.getAudioData();
|
|
37
|
+
const data16 = new Int16Array(audio.length);
|
|
38
|
+
for (let i = 0; i < audio.length; i++)
|
|
39
|
+
{
|
|
40
|
+
data16[i] = audio[i] * 32768;
|
|
41
|
+
}
|
|
42
|
+
const data = writeRIFFOddSize(
|
|
43
|
+
"data",
|
|
44
|
+
new IndexedByteArray(data16.buffer)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const inam = writeRIFFOddSize(
|
|
48
|
+
"INAM",
|
|
49
|
+
getStringBytes(sample.sampleName)
|
|
50
|
+
);
|
|
51
|
+
const info = writeRIFFOddSize(
|
|
52
|
+
"INFO",
|
|
53
|
+
inam,
|
|
54
|
+
false,
|
|
55
|
+
true
|
|
56
|
+
);
|
|
57
|
+
SpessaSynthInfo(
|
|
58
|
+
`%cSaved %c${sample.sampleName}%c succesfully!`,
|
|
59
|
+
consoleColors.recognized,
|
|
60
|
+
consoleColors.value,
|
|
61
|
+
consoleColors.recognized
|
|
62
|
+
);
|
|
63
|
+
return writeRIFFOddSize(
|
|
64
|
+
"wave",
|
|
65
|
+
combineArrays([
|
|
66
|
+
fmt,
|
|
67
|
+
wsmp,
|
|
68
|
+
data,
|
|
69
|
+
info
|
|
70
|
+
]),
|
|
71
|
+
false,
|
|
72
|
+
true
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
2
|
+
import { writeDword } from "../../../utils/byte_functions/little_endian.js";
|
|
3
|
+
import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
4
|
+
import { writeLins } from "./lins.js";
|
|
5
|
+
import { getStringBytes, writeStringAsBytes } from "../../../utils/byte_functions/string.js";
|
|
6
|
+
import { writeWavePool } from "./wvpl.js";
|
|
7
|
+
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
|
|
8
|
+
import { consoleColors } from "../../../utils/other.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Write the soundfont as a .dls file. Experimental
|
|
12
|
+
* @this {BasicSoundFont}
|
|
13
|
+
* @returns {Uint8Array}
|
|
14
|
+
*/
|
|
15
|
+
export function writeDLS()
|
|
16
|
+
{
|
|
17
|
+
SpessaSynthGroupCollapsed(
|
|
18
|
+
"%cSaving DLS...",
|
|
19
|
+
consoleColors.info
|
|
20
|
+
);
|
|
21
|
+
// write colh
|
|
22
|
+
const colhNum = new IndexedByteArray(4);
|
|
23
|
+
writeDword(colhNum, this.presets.length);
|
|
24
|
+
const colh = writeRIFFOddSize(
|
|
25
|
+
"colh",
|
|
26
|
+
colhNum
|
|
27
|
+
);
|
|
28
|
+
SpessaSynthGroupCollapsed(
|
|
29
|
+
"%cWriting instruments...",
|
|
30
|
+
consoleColors.info
|
|
31
|
+
);
|
|
32
|
+
const lins = writeLins.apply(this);
|
|
33
|
+
SpessaSynthInfo(
|
|
34
|
+
"%cSuccess!",
|
|
35
|
+
consoleColors.recognized
|
|
36
|
+
);
|
|
37
|
+
SpessaSynthGroupEnd();
|
|
38
|
+
|
|
39
|
+
SpessaSynthGroupCollapsed(
|
|
40
|
+
"%cWriting WAVE samples...",
|
|
41
|
+
consoleColors.info
|
|
42
|
+
);
|
|
43
|
+
const wavepool = writeWavePool.apply(this);
|
|
44
|
+
const wvpl = wavepool.data;
|
|
45
|
+
const ptblOffsets = wavepool.indexes;
|
|
46
|
+
SpessaSynthInfo("%cSucceeded!", consoleColors.recognized);
|
|
47
|
+
SpessaSynthGroupEnd();
|
|
48
|
+
|
|
49
|
+
// write ptbl
|
|
50
|
+
const ptblData = new IndexedByteArray(8 + 8 * ptblOffsets.length);
|
|
51
|
+
writeDword(ptblData, 8);
|
|
52
|
+
writeDword(ptblData, ptblOffsets.length);
|
|
53
|
+
for (const offset of ptblOffsets)
|
|
54
|
+
{
|
|
55
|
+
writeDword(ptblData, offset);
|
|
56
|
+
}
|
|
57
|
+
const ptbl = writeRIFFOddSize(
|
|
58
|
+
"ptbl",
|
|
59
|
+
ptblData
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
this.soundFontInfo["ICMT"] = (this.soundFontInfo["ICMT"] || "Soundfont") + "\nConverted from SF2 to DLS using SpessaSynth";
|
|
63
|
+
this.soundFontInfo["ISFT"] = "SpessaSynth";
|
|
64
|
+
// write INFO
|
|
65
|
+
const infos = [];
|
|
66
|
+
for (const [info, data] of Object.entries(this.soundFontInfo))
|
|
67
|
+
{
|
|
68
|
+
if (
|
|
69
|
+
info !== "ICMT" &&
|
|
70
|
+
info !== "INAM" &&
|
|
71
|
+
info !== "ICRD" &&
|
|
72
|
+
info !== "IENG" &&
|
|
73
|
+
info !== "ICOP" &&
|
|
74
|
+
info !== "ISFT" &&
|
|
75
|
+
info !== "ISBJ"
|
|
76
|
+
)
|
|
77
|
+
{
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
infos.push(
|
|
81
|
+
writeRIFFOddSize(
|
|
82
|
+
info,
|
|
83
|
+
getStringBytes(data)
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const info = writeRIFFOddSize(
|
|
88
|
+
"INFO",
|
|
89
|
+
combineArrays(infos),
|
|
90
|
+
false,
|
|
91
|
+
true
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const out = new IndexedByteArray(
|
|
95
|
+
colh.length
|
|
96
|
+
+ lins.length
|
|
97
|
+
+ ptbl.length
|
|
98
|
+
+ wvpl.length
|
|
99
|
+
+ info.length
|
|
100
|
+
+ 4);
|
|
101
|
+
writeStringAsBytes(out, "DLS ");
|
|
102
|
+
out.set(combineArrays([
|
|
103
|
+
colh,
|
|
104
|
+
lins,
|
|
105
|
+
ptbl,
|
|
106
|
+
wvpl,
|
|
107
|
+
info
|
|
108
|
+
]), 4);
|
|
109
|
+
SpessaSynthInfo(
|
|
110
|
+
"%cSaved succesfully!",
|
|
111
|
+
consoleColors.recognized
|
|
112
|
+
);
|
|
113
|
+
SpessaSynthGroupEnd();
|
|
114
|
+
return writeRIFFOddSize(
|
|
115
|
+
"RIFF",
|
|
116
|
+
out
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { writeDword, writeWord } from "../../../utils/byte_functions/little_endian.js";
|
|
2
|
+
import { IndexedByteArray } from "../../../utils/indexed_array.js";
|
|
3
|
+
import { writeRIFFOddSize } from "../riff_chunk.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param sample {BasicSample}
|
|
7
|
+
* @param rootKey {number}
|
|
8
|
+
* @param tuning {number}
|
|
9
|
+
* @param attenuationCentibels {number} CENTIBELS, NO CORRECTION
|
|
10
|
+
* @param loopStart {number}
|
|
11
|
+
* @param loopEnd {number}
|
|
12
|
+
* @param loopingMode {number}
|
|
13
|
+
* @returns {IndexedByteArray}
|
|
14
|
+
*/
|
|
15
|
+
export function writeWavesample(
|
|
16
|
+
sample,
|
|
17
|
+
rootKey,
|
|
18
|
+
tuning,
|
|
19
|
+
attenuationCentibels,
|
|
20
|
+
loopStart,
|
|
21
|
+
loopEnd,
|
|
22
|
+
loopingMode)
|
|
23
|
+
{
|
|
24
|
+
// fixed size because always one loop
|
|
25
|
+
const wsmpData = new IndexedByteArray(36);
|
|
26
|
+
writeDword(wsmpData, 20); // cbSize
|
|
27
|
+
// usUnityNote (apply root pitch here)
|
|
28
|
+
writeWord(wsmpData, rootKey);
|
|
29
|
+
// sFineTune
|
|
30
|
+
writeWord(wsmpData, tuning);
|
|
31
|
+
|
|
32
|
+
// gain correction, use InitialAttenuation, apply attenuation correction
|
|
33
|
+
const attenuationCb = attenuationCentibels * 0.4;
|
|
34
|
+
|
|
35
|
+
// gain correction: Each unit of gain represents 1/655360 dB
|
|
36
|
+
const lGain = Math.floor(attenuationCb * -65536);
|
|
37
|
+
writeDword(wsmpData, lGain);
|
|
38
|
+
// fulOptions
|
|
39
|
+
writeDword(wsmpData, 0);
|
|
40
|
+
|
|
41
|
+
const loopSize = loopEnd - loopStart;
|
|
42
|
+
let loopCount = 1;
|
|
43
|
+
let ulLoopType = 0;
|
|
44
|
+
switch (loopingMode)
|
|
45
|
+
{
|
|
46
|
+
default:
|
|
47
|
+
case 0:
|
|
48
|
+
// no loop
|
|
49
|
+
loopCount = 0;
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 1:
|
|
53
|
+
// loop
|
|
54
|
+
ulLoopType = 0;
|
|
55
|
+
loopCount = 1;
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case 3:
|
|
59
|
+
// loop and release
|
|
60
|
+
ulLoopType = 1;
|
|
61
|
+
loopCount = 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// cSampleLoops
|
|
65
|
+
writeDword(wsmpData, loopCount);
|
|
66
|
+
writeDword(wsmpData, 16); // cbSize
|
|
67
|
+
writeDword(wsmpData, ulLoopType);
|
|
68
|
+
writeDword(wsmpData, loopStart);
|
|
69
|
+
writeDword(wsmpData, loopSize);
|
|
70
|
+
return writeRIFFOddSize(
|
|
71
|
+
"wsmp",
|
|
72
|
+
wsmpData
|
|
73
|
+
);
|
|
74
|
+
}
|