spessasynth_lib 3.16.5 → 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/index.d.ts +5 -4
- package/@types/midi_parser/basic_midi.d.ts +125 -0
- package/@types/midi_parser/midi_builder.d.ts +69 -0
- package/@types/midi_parser/midi_data.d.ts +2 -2
- package/@types/midi_parser/midi_editor.d.ts +4 -4
- package/@types/midi_parser/midi_loader.d.ts +3 -100
- package/@types/midi_parser/midi_writer.d.ts +2 -2
- package/@types/midi_parser/rmidi_writer.d.ts +3 -3
- package/@types/midi_parser/used_keys_loaded.d.ts +2 -2
- package/@types/sequencer/sequencer.d.ts +1 -1
- 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/basic_soundfont/write_sf2/soundfont_trimmer.d.ts +2 -2
- 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 +13 -0
- package/@types/soundfont/dls/dls_sample.d.ts +18 -0
- package/@types/soundfont/dls/dls_soundfont.d.ts +31 -0
- 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.d.ts +5 -0
- package/@types/soundfont/dls/read_instrument_list.d.ts +5 -0
- 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/load_soundfont.d.ts +6 -0
- package/@types/soundfont/read_sf2/generators.d.ts +18 -5
- package/@types/soundfont/read_sf2/modulators.d.ts +1 -0
- package/@types/soundfont/soundfont.d.ts +2 -1
- package/@types/synthetizer/synthetizer.d.ts +2 -2
- package/@types/utils/byte_functions/little_endian.d.ts +1 -1
- package/README.md +27 -15
- package/index.js +6 -4
- package/midi_parser/basic_midi.js +146 -0
- package/midi_parser/midi_builder.js +281 -0
- package/midi_parser/midi_data.js +1 -1
- package/midi_parser/midi_editor.js +2 -2
- package/midi_parser/midi_loader.js +38 -56
- package/midi_parser/midi_writer.js +1 -1
- package/midi_parser/rmidi_writer.js +2 -2
- package/midi_parser/used_keys_loaded.js +1 -1
- package/package.json +1 -1
- package/sequencer/sequencer.js +1 -1
- package/sequencer/worklet_sequencer/song_control.js +3 -3
- package/sequencer/worklet_sequencer/worklet_sequencer.js +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 +20 -4
- package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +1 -1
- package/soundfont/dls/articulator_converter.js +311 -0
- package/soundfont/dls/dls_destinations.js +38 -0
- package/soundfont/dls/dls_preset.js +32 -0
- package/soundfont/dls/dls_sample.js +58 -0
- package/soundfont/dls/dls_soundfont.js +150 -0
- 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 +100 -0
- package/soundfont/dls/read_instrument_list.js +17 -0
- 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/load_soundfont.js +21 -0
- package/soundfont/read_sf2/generators.js +41 -6
- package/soundfont/read_sf2/instruments.js +2 -2
- package/soundfont/read_sf2/modulators.js +8 -8
- package/soundfont/read_sf2/presets.js +7 -7
- package/soundfont/read_sf2/samples.js +8 -8
- package/soundfont/read_sf2/zones.js +5 -5
- package/soundfont/soundfont.js +8 -3
- package/synthetizer/synthetizer.js +1 -1
- package/synthetizer/worklet_processor.min.js +10 -7
- package/synthetizer/worklet_system/main_processor.js +1 -2
- package/synthetizer/worklet_system/worklet_methods/program_control.js +6 -3
- package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/worklet_soundfont_manager.js +5 -5
- package/utils/buffer_to_wav.js +5 -26
- package/utils/byte_functions/little_endian.js +1 -1
- /package/@types/{midi_handler → external_midi}/midi_handler.d.ts +0 -0
- /package/@types/{midi_handler → external_midi}/web_midi_link.d.ts +0 -0
- /package/{midi_handler → external_midi}/README.md +0 -0
- /package/{midi_handler → external_midi}/midi_handler.js +0 -0
- /package/{midi_handler → external_midi}/web_midi_link.js +0 -0
|
@@ -2,16 +2,16 @@ import { BasicZone } from './basic_zone.js'
|
|
|
2
2
|
|
|
3
3
|
export class BasicInstrumentZone extends BasicZone
|
|
4
4
|
{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Zone's sample. Undefined if global
|
|
7
|
+
* @type {BasicSample|undefined}
|
|
8
|
+
*/
|
|
9
|
+
sample = undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The zone's use count
|
|
12
|
+
* @type {number}
|
|
13
|
+
*/
|
|
14
|
+
useCount = 0;
|
|
15
15
|
|
|
16
16
|
deleteZone()
|
|
17
17
|
{
|
|
@@ -26,15 +26,11 @@ export class BasicInstrumentZone extends BasicZone
|
|
|
26
26
|
|
|
27
27
|
export class BasicPresetZone extends BasicZone
|
|
28
28
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* @type {BasicInstrument|undefined}
|
|
35
|
-
*/
|
|
36
|
-
this.instrument = undefined
|
|
37
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* Zone's instrument. Undefined if global
|
|
31
|
+
* @type {BasicInstrument|undefined}
|
|
32
|
+
*/
|
|
33
|
+
instrument = undefined
|
|
38
34
|
|
|
39
35
|
deleteZone()
|
|
40
36
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IndexedByteArray } from '../../utils/indexed_array.js'
|
|
2
|
-
import {
|
|
2
|
+
import { readLittleEndian, writeDword } from '../../utils/byte_functions/little_endian.js'
|
|
3
3
|
import { readBytesAsString, writeStringAsBytes } from '../../utils/byte_functions/string.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -33,12 +33,11 @@ export class RiffChunk
|
|
|
33
33
|
export function readRIFFChunk(dataArray, readData = true, forceShift = false) {
|
|
34
34
|
let header = readBytesAsString(dataArray, 4)
|
|
35
35
|
|
|
36
|
-
let size =
|
|
36
|
+
let size = readLittleEndian(dataArray, 4)
|
|
37
37
|
let chunkData = undefined
|
|
38
38
|
if (readData)
|
|
39
39
|
{
|
|
40
|
-
chunkData = new IndexedByteArray(size)
|
|
41
|
-
chunkData.set(dataArray.slice(dataArray.currentIndex, dataArray.currentIndex + size))
|
|
40
|
+
chunkData = new IndexedByteArray(dataArray.buffer.slice(dataArray.currentIndex, dataArray.currentIndex + size))
|
|
42
41
|
}
|
|
43
42
|
if(readData || forceShift)
|
|
44
43
|
{
|
|
@@ -112,4 +111,21 @@ export function writeRIFFOddSize(header, data, addZeroByte = false)
|
|
|
112
111
|
writeDword(outArray, data.length);
|
|
113
112
|
outArray.set(data, 8);
|
|
114
113
|
return outArray;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @param collection {RiffChunk[]}
|
|
118
|
+
* @param type {string}
|
|
119
|
+
* @returns {RiffChunk|undefined}
|
|
120
|
+
*/
|
|
121
|
+
export function findRIFFListType(collection, type)
|
|
122
|
+
{
|
|
123
|
+
return collection.find(c => {
|
|
124
|
+
if(c.header !== "LIST")
|
|
125
|
+
{
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
c.chunkData.currentIndex = 0;
|
|
129
|
+
return readBytesAsString(c.chunkData, 4) === type;
|
|
130
|
+
});
|
|
115
131
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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'
|
|
4
|
+
|
|
5
|
+
export class DLSPreset extends BasicPreset
|
|
6
|
+
{
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new DLS preset
|
|
9
|
+
* @param ulBank {number}
|
|
10
|
+
* @param ulInstrument {number}
|
|
11
|
+
*/
|
|
12
|
+
constructor(ulBank, ulInstrument)
|
|
13
|
+
{
|
|
14
|
+
super();
|
|
15
|
+
this.program = ulInstrument & 127;
|
|
16
|
+
this.bank = (ulBank >> 8) & 127;
|
|
17
|
+
const isDrums = ulBank >> 31;
|
|
18
|
+
if(isDrums)
|
|
19
|
+
{
|
|
20
|
+
// soundfont bank is 128 so we change it here
|
|
21
|
+
this.bank = 128;
|
|
22
|
+
}
|
|
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];
|
|
31
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { BasicSoundFont } from '../basic_soundfont/basic_soundfont.js'
|
|
2
|
+
import { IndexedByteArray } from '../../utils/indexed_array.js'
|
|
3
|
+
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from '../../utils/loggin.js'
|
|
4
|
+
import { consoleColors } from '../../utils/other.js'
|
|
5
|
+
import { findRIFFListType, readRIFFChunk } from '../basic_soundfont/riff_chunk.js'
|
|
6
|
+
import { readBytesAsString } from '../../utils/byte_functions/string.js'
|
|
7
|
+
import { readLittleEndian } from '../../utils/byte_functions/little_endian.js'
|
|
8
|
+
import { readDLSInstrumentList } from './read_instrument_list.js'
|
|
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'
|
|
13
|
+
|
|
14
|
+
class DLSSoundFont extends BasicSoundFont
|
|
15
|
+
{
|
|
16
|
+
/**
|
|
17
|
+
* Loads a new DLS (Downloadable sounds) soundfont
|
|
18
|
+
* @param buffer {ArrayBuffer}
|
|
19
|
+
*/
|
|
20
|
+
constructor(buffer)
|
|
21
|
+
{
|
|
22
|
+
super();
|
|
23
|
+
this.dataArray = new IndexedByteArray(buffer);
|
|
24
|
+
SpessaSynthGroup("%cParsing DLS...", consoleColors.info);
|
|
25
|
+
if(!this.dataArray)
|
|
26
|
+
{
|
|
27
|
+
SpessaSynthGroupEnd();
|
|
28
|
+
throw new TypeError("No data!");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// read the main chunk
|
|
32
|
+
let firstChunk = readRIFFChunk(this.dataArray, false);
|
|
33
|
+
this.verifyHeader(firstChunk, "riff");
|
|
34
|
+
this.verifyText(readBytesAsString(this.dataArray,4).toLowerCase(), "dls ");
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Read list
|
|
38
|
+
* @type {RiffChunk[]}
|
|
39
|
+
*/
|
|
40
|
+
const chunks = [];
|
|
41
|
+
while(this.dataArray.currentIndex < this.dataArray.length)
|
|
42
|
+
{
|
|
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!");
|
|
85
|
+
}
|
|
86
|
+
this.instrumentAmount = readLittleEndian(colhChunk.chunkData, 4);
|
|
87
|
+
SpessaSynthInfo(`%cInstruments amount: %c${this.instrumentAmount}`,
|
|
88
|
+
consoleColors.info,
|
|
89
|
+
consoleColors.recognized);
|
|
90
|
+
|
|
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);
|
|
103
|
+
|
|
104
|
+
SpessaSynthInfo(`%cParsing finished! %c"${this.soundFontInfo["INAM"] || "UNNAMED"}"%c has %c${this.presets.length} %cpresets,
|
|
105
|
+
%c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
|
|
106
|
+
consoleColors.info,
|
|
107
|
+
consoleColors.recognized,
|
|
108
|
+
consoleColors.info,
|
|
109
|
+
consoleColors.recognized,
|
|
110
|
+
consoleColors.info,
|
|
111
|
+
consoleColors.recognized,
|
|
112
|
+
consoleColors.info,
|
|
113
|
+
consoleColors.recognized,
|
|
114
|
+
consoleColors.info);
|
|
115
|
+
SpessaSynthGroupEnd();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param chunk {RiffChunk}
|
|
120
|
+
* @param expected {string}
|
|
121
|
+
*/
|
|
122
|
+
verifyHeader(chunk, expected)
|
|
123
|
+
{
|
|
124
|
+
if(chunk.header.toLowerCase() !== expected.toLowerCase())
|
|
125
|
+
{
|
|
126
|
+
SpessaSynthGroupEnd();
|
|
127
|
+
throw new SyntaxError(`Invalid DLS chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param text {string}
|
|
133
|
+
* @param expected {string}
|
|
134
|
+
*/
|
|
135
|
+
verifyText(text, expected)
|
|
136
|
+
{
|
|
137
|
+
if(text.toLowerCase() !== expected.toLowerCase())
|
|
138
|
+
{
|
|
139
|
+
SpessaSynthGroupEnd();
|
|
140
|
+
throw new SyntaxError(`Invalid DLS soundfont! Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
DLSSoundFont.prototype.readDLSInstrumentList = readDLSInstrumentList;
|
|
145
|
+
DLSSoundFont.prototype.readDLSInstrument = readDLSInstrument;
|
|
146
|
+
DLSSoundFont.prototype.readRegion = readRegion;
|
|
147
|
+
DLSSoundFont.prototype.readLart = readLart;
|
|
148
|
+
DLSSoundFont.prototype.readDLSSamples = readDLSSamples;
|
|
149
|
+
|
|
150
|
+
export {DLSSoundFont}
|