spessasynth_core 3.27.7 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -116
- package/dist/index.d.ts +4057 -0
- package/dist/index.js +17188 -0
- package/dist/index.js.map +1 -0
- package/package.json +23 -6
- package/index.js +0 -132
- package/src/externals/README.md +0 -6
- package/src/externals/fflate/LICENSE +0 -21
- package/src/externals/fflate/fflate.min.js +0 -1
- package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
- package/src/externals/stbvorbis_sync/LICENSE +0 -202
- package/src/externals/stbvorbis_sync/NOTICE +0 -6
- package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
- package/src/midi/README.md +0 -32
- package/src/midi/basic_midi.js +0 -587
- package/src/midi/midi_builder.js +0 -203
- package/src/midi/midi_loader.js +0 -321
- package/src/midi/midi_message.js +0 -254
- package/src/midi/midi_sequence.js +0 -230
- package/src/midi/midi_tools/get_note_times.js +0 -154
- package/src/midi/midi_tools/midi_editor.js +0 -611
- package/src/midi/midi_tools/midi_writer.js +0 -105
- package/src/midi/midi_tools/rmidi_writer.js +0 -566
- package/src/midi/midi_tools/used_keys_loaded.js +0 -256
- package/src/midi/xmf_loader.js +0 -454
- package/src/sequencer/README.md +0 -9
- package/src/sequencer/events.js +0 -81
- package/src/sequencer/play.js +0 -362
- package/src/sequencer/process_event.js +0 -165
- package/src/sequencer/process_tick.js +0 -104
- package/src/sequencer/sequencer_engine.js +0 -372
- package/src/sequencer/song_control.js +0 -196
- package/src/soundfont/README.md +0 -11
- package/src/soundfont/basic_soundfont/basic_global_zone.js +0 -6
- package/src/soundfont/basic_soundfont/basic_instrument.js +0 -115
- package/src/soundfont/basic_soundfont/basic_instrument_zone.js +0 -45
- package/src/soundfont/basic_soundfont/basic_preset.js +0 -313
- package/src/soundfont/basic_soundfont/basic_preset_zone.js +0 -39
- package/src/soundfont/basic_soundfont/basic_sample.js +0 -477
- package/src/soundfont/basic_soundfont/basic_soundbank.js +0 -740
- package/src/soundfont/basic_soundfont/basic_zone.js +0 -145
- package/src/soundfont/basic_soundfont/generator.js +0 -76
- package/src/soundfont/basic_soundfont/generator_types.js +0 -151
- package/src/soundfont/basic_soundfont/modulator.js +0 -581
- package/src/soundfont/basic_soundfont/riff_chunk.js +0 -195
- package/src/soundfont/basic_soundfont/write_dls/art2.js +0 -174
- package/src/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
- package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -374
- package/src/soundfont/basic_soundfont/write_dls/ins.js +0 -85
- package/src/soundfont/basic_soundfont/write_dls/lins.js +0 -15
- package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
- package/src/soundfont/basic_soundfont/write_dls/rgn2.js +0 -120
- package/src/soundfont/basic_soundfont/write_dls/wave.js +0 -71
- package/src/soundfont/basic_soundfont/write_dls/write_dls.js +0 -124
- package/src/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
- package/src/soundfont/basic_soundfont/write_dls/wvpl.js +0 -35
- package/src/soundfont/basic_soundfont/write_sf2/ibag.js +0 -60
- package/src/soundfont/basic_soundfont/write_sf2/igen.js +0 -91
- package/src/soundfont/basic_soundfont/write_sf2/imod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/inst.js +0 -42
- package/src/soundfont/basic_soundfont/write_sf2/pbag.js +0 -57
- package/src/soundfont/basic_soundfont/write_sf2/pgen.js +0 -92
- package/src/soundfont/basic_soundfont/write_sf2/phdr.js +0 -61
- package/src/soundfont/basic_soundfont/write_sf2/pmod.js +0 -62
- package/src/soundfont/basic_soundfont/write_sf2/sdta.js +0 -131
- package/src/soundfont/basic_soundfont/write_sf2/shdr.js +0 -77
- package/src/soundfont/basic_soundfont/write_sf2/write.js +0 -287
- package/src/soundfont/dls/articulator_converter.js +0 -402
- package/src/soundfont/dls/dls_destinations.js +0 -38
- package/src/soundfont/dls/dls_instrument.js +0 -20
- package/src/soundfont/dls/dls_preset.js +0 -43
- package/src/soundfont/dls/dls_sample.js +0 -238
- package/src/soundfont/dls/dls_soundfont.js +0 -183
- package/src/soundfont/dls/dls_sources.js +0 -63
- package/src/soundfont/dls/dls_zone.js +0 -89
- package/src/soundfont/dls/read_articulation.js +0 -300
- package/src/soundfont/dls/read_instrument.js +0 -118
- package/src/soundfont/dls/read_instrument_list.js +0 -17
- package/src/soundfont/dls/read_lart.js +0 -35
- package/src/soundfont/dls/read_region.js +0 -157
- package/src/soundfont/dls/read_samples.js +0 -154
- package/src/soundfont/load_soundfont.js +0 -21
- package/src/soundfont/read_sf2/generators.js +0 -43
- package/src/soundfont/read_sf2/instrument_zones.js +0 -75
- package/src/soundfont/read_sf2/instruments.js +0 -71
- package/src/soundfont/read_sf2/modulators.js +0 -25
- package/src/soundfont/read_sf2/preset_zones.js +0 -79
- package/src/soundfont/read_sf2/presets.js +0 -80
- package/src/soundfont/read_sf2/samples.js +0 -317
- package/src/soundfont/read_sf2/soundfont.js +0 -452
- package/src/soundfont/read_sf2/zones.js +0 -28
- package/src/synthetizer/README.md +0 -7
- package/src/synthetizer/audio_engine/README.md +0 -9
- package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -289
- package/src/synthetizer/audio_engine/engine_components/controller_tables.js +0 -90
- package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +0 -95
- package/src/synthetizer/audio_engine/engine_components/enums.js +0 -18
- package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -151
- package/src/synthetizer/audio_engine/engine_components/lfo.js +0 -26
- package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
- package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -551
- package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
- package/src/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
- package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +0 -265
- package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -124
- package/src/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
- package/src/synthetizer/audio_engine/engine_components/voice.js +0 -525
- package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -402
- package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -274
- package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -159
- package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -48
- package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -254
- package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -20
- package/src/synthetizer/audio_engine/engine_methods/data_entry/awe32.js +0 -198
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -281
- package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -109
- package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
- package/src/synthetizer/audio_engine/engine_methods/note_on.js +0 -214
- package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
- package/src/synthetizer/audio_engine/engine_methods/program_change.js +0 -35
- package/src/synthetizer/audio_engine/engine_methods/render_voice.js +0 -214
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +0 -42
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -0
- package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -56
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
- package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
- package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -1058
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -23
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -31
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -29
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -27
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
- package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
- package/src/synthetizer/audio_engine/main_processor.js +0 -813
- package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -16
- package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
- package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -116
- package/src/synthetizer/audio_engine/synth_processor_options.js +0 -18
- package/src/synthetizer/synth_constants.js +0 -26
- package/src/utils/README.md +0 -8
- package/src/utils/buffer_to_wav.js +0 -197
- package/src/utils/byte_functions/big_endian.js +0 -32
- package/src/utils/byte_functions/little_endian.js +0 -77
- package/src/utils/byte_functions/string.js +0 -92
- package/src/utils/byte_functions/variable_length_quantity.js +0 -42
- package/src/utils/fill_with_defaults.js +0 -21
- package/src/utils/indexed_array.js +0 -34
- package/src/utils/loggin.js +0 -71
- package/src/utils/other.js +0 -92
- package/src/utils/sysex_detector.js +0 -58
- package/src/utils/xg_hacks.js +0 -193
|
@@ -1,740 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
SpessaSynthGroup,
|
|
3
|
-
SpessaSynthGroupCollapsed,
|
|
4
|
-
SpessaSynthGroupEnd,
|
|
5
|
-
SpessaSynthInfo,
|
|
6
|
-
SpessaSynthWarn
|
|
7
|
-
} from "../../utils/loggin.js";
|
|
8
|
-
import { consoleColors } from "../../utils/other.js";
|
|
9
|
-
import { write } from "./write_sf2/write.js";
|
|
10
|
-
import { defaultModulators, Modulator } from "./modulator.js";
|
|
11
|
-
import { writeDLS } from "./write_dls/write_dls.js";
|
|
12
|
-
import { BasicSample, sampleTypes } from "./basic_sample.js";
|
|
13
|
-
import { Generator } from "./generator.js";
|
|
14
|
-
import { BasicInstrument } from "./basic_instrument.js";
|
|
15
|
-
import { BasicPreset } from "./basic_preset.js";
|
|
16
|
-
import { isXGDrums } from "../../utils/xg_hacks.js";
|
|
17
|
-
import { generatorTypes } from "./generator_types.js";
|
|
18
|
-
import { BasicGlobalZone } from "./basic_global_zone.js";
|
|
19
|
-
import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Represents a single sound bank, be it DLS or SF2.
|
|
23
|
-
*/
|
|
24
|
-
class BasicSoundBank
|
|
25
|
-
{
|
|
26
|
-
/**
|
|
27
|
-
* Indicates if the SF3/SF2Pack decoder is ready.
|
|
28
|
-
* @type {Promise<boolean>}
|
|
29
|
-
* @static
|
|
30
|
-
*/
|
|
31
|
-
static isSF3DecoderReady = stbvorbis.isInitialized;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Soundfont's info stored as name: value. ifil and iver are stored as string representation of float (e.g., 2.1)
|
|
35
|
-
* @type {Record<string, string|IndexedByteArray>}
|
|
36
|
-
*/
|
|
37
|
-
soundFontInfo = {};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* The soundfont's presets
|
|
41
|
-
* @type {BasicPreset[]}
|
|
42
|
-
* @readonly
|
|
43
|
-
*/
|
|
44
|
-
presets = [];
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The soundfont's samples
|
|
48
|
-
* @type {BasicSample[]}
|
|
49
|
-
* @readonly
|
|
50
|
-
*/
|
|
51
|
-
samples = [];
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The soundfont's instruments
|
|
55
|
-
* @type {BasicInstrument[]}
|
|
56
|
-
* @readonly
|
|
57
|
-
*/
|
|
58
|
-
instruments = [];
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Soundfont's default modulatorss
|
|
62
|
-
* @type {Modulator[]}
|
|
63
|
-
*/
|
|
64
|
-
defaultModulators = defaultModulators.map(m => Modulator.copy(m));
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* If the bank has custom default modulators (DMOD).
|
|
68
|
-
* @type {boolean}
|
|
69
|
-
*/
|
|
70
|
-
customDefaultModulators = false;
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Checks for XG drumsets and considers if this soundfont is XG.
|
|
74
|
-
* @type {boolean}
|
|
75
|
-
*/
|
|
76
|
-
isXGBank = false;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Creates a new basic soundfont template (or copies)
|
|
80
|
-
* @param data {undefined|{presets: BasicPreset[], info: Record<string, string>}}
|
|
81
|
-
*/
|
|
82
|
-
constructor(data = undefined)
|
|
83
|
-
{
|
|
84
|
-
if (data?.presets)
|
|
85
|
-
{
|
|
86
|
-
this.soundFontInfo = data.info;
|
|
87
|
-
this.addPresets(...data.presets);
|
|
88
|
-
/**
|
|
89
|
-
* @type {BasicInstrument[]}
|
|
90
|
-
*/
|
|
91
|
-
const instrumentList = [];
|
|
92
|
-
for (const preset of data.presets)
|
|
93
|
-
{
|
|
94
|
-
for (const zone of preset.presetZones)
|
|
95
|
-
{
|
|
96
|
-
if (!instrumentList.includes(zone.instrument))
|
|
97
|
-
{
|
|
98
|
-
instrumentList.push(zone.instrument);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
this.addInstruments(...instrumentList);
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @type {BasicSample[]}
|
|
106
|
-
*/
|
|
107
|
-
const sampleList = [];
|
|
108
|
-
|
|
109
|
-
for (const instrument of instrumentList)
|
|
110
|
-
{
|
|
111
|
-
for (const zone of instrument.instrumentZones)
|
|
112
|
-
{
|
|
113
|
-
if (!sampleList.includes(zone.sample))
|
|
114
|
-
{
|
|
115
|
-
sampleList.push(zone.sample);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
this.addSamples(...sampleList);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Merges soundfonts with the given order. Keep in mind that the info read is copied from the first one
|
|
125
|
-
* @param soundfonts {...BasicSoundBank} the soundfonts to merge, the first overwrites the last
|
|
126
|
-
* @returns {BasicSoundBank}
|
|
127
|
-
*/
|
|
128
|
-
static mergeSoundBanks(...soundfonts)
|
|
129
|
-
{
|
|
130
|
-
const mainSf = soundfonts.shift();
|
|
131
|
-
const presets = mainSf.presets;
|
|
132
|
-
while (soundfonts.length)
|
|
133
|
-
{
|
|
134
|
-
const newPresets = soundfonts.shift().presets;
|
|
135
|
-
newPresets.forEach(newPreset =>
|
|
136
|
-
{
|
|
137
|
-
if (
|
|
138
|
-
presets.find(existingPreset => existingPreset.bank === newPreset.bank && existingPreset.program === newPreset.program) === undefined
|
|
139
|
-
)
|
|
140
|
-
{
|
|
141
|
-
presets.push(newPreset);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return new BasicSoundBank({ presets: presets, info: mainSf.soundFontInfo });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Creates a simple soundfont with one saw wave preset.
|
|
151
|
-
* @returns {Promise<ArrayBufferLike>}
|
|
152
|
-
*/
|
|
153
|
-
static async getDummySoundfontFile()
|
|
154
|
-
{
|
|
155
|
-
const font = new BasicSoundBank();
|
|
156
|
-
const sample = new BasicSample(
|
|
157
|
-
"Saw",
|
|
158
|
-
44100,
|
|
159
|
-
65,
|
|
160
|
-
20,
|
|
161
|
-
sampleTypes.monoSample,
|
|
162
|
-
0,
|
|
163
|
-
127
|
|
164
|
-
);
|
|
165
|
-
const sampleData = new Float32Array(128);
|
|
166
|
-
for (let i = 0; i < 128; i++)
|
|
167
|
-
{
|
|
168
|
-
sampleData[i] = (i / 128) * 2 - 1;
|
|
169
|
-
}
|
|
170
|
-
sample.setAudioData(sampleData);
|
|
171
|
-
font.addSamples(sample);
|
|
172
|
-
|
|
173
|
-
const gZone = new BasicGlobalZone();
|
|
174
|
-
gZone.addGenerators(
|
|
175
|
-
new Generator(generatorTypes.initialAttenuation, 375),
|
|
176
|
-
new Generator(generatorTypes.releaseVolEnv, -1000),
|
|
177
|
-
new Generator(generatorTypes.sampleModes, 1)
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
const inst = new BasicInstrument();
|
|
181
|
-
inst.instrumentName = "Saw Wave";
|
|
182
|
-
inst.globalZone = gZone;
|
|
183
|
-
|
|
184
|
-
const zone1 = inst.createZone();
|
|
185
|
-
zone1.setSample(sample);
|
|
186
|
-
|
|
187
|
-
const zone2 = inst.createZone();
|
|
188
|
-
zone2.setSample(sample);
|
|
189
|
-
zone2.addGenerators(new Generator(generatorTypes.fineTune, -9));
|
|
190
|
-
|
|
191
|
-
font.addInstruments(inst);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const preset = new BasicPreset(font);
|
|
195
|
-
preset.presetName = "Saw Wave";
|
|
196
|
-
const pZone = preset.createZone();
|
|
197
|
-
pZone.setInstrument(inst);
|
|
198
|
-
|
|
199
|
-
font.addPresets(preset);
|
|
200
|
-
|
|
201
|
-
font.soundFontInfo["ifil"] = "2.1";
|
|
202
|
-
font.soundFontInfo["isng"] = "E-mu 10K2";
|
|
203
|
-
font.soundFontInfo["INAM"] = "Dummy";
|
|
204
|
-
font.flush();
|
|
205
|
-
const f = await font.write();
|
|
206
|
-
return f.buffer;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* @param presets {BasicPreset}
|
|
211
|
-
*/
|
|
212
|
-
addPresets(...presets)
|
|
213
|
-
{
|
|
214
|
-
this.presets.push(...presets);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* @param instruments {BasicInstrument}
|
|
219
|
-
*/
|
|
220
|
-
addInstruments(...instruments)
|
|
221
|
-
{
|
|
222
|
-
this.instruments.push(...instruments);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* @param samples {BasicSample}
|
|
227
|
-
*/
|
|
228
|
-
addSamples(...samples)
|
|
229
|
-
{
|
|
230
|
-
this.samples.push(...samples);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Clones samples into this bank
|
|
235
|
-
* @param sample {BasicSample} samples to copy
|
|
236
|
-
* @returns {BasicSample} copied sample, if a sample exists with that name, it is returned instead
|
|
237
|
-
*/
|
|
238
|
-
cloneSample(sample)
|
|
239
|
-
{
|
|
240
|
-
const duplicate = this.samples.find(s => s.sampleName === sample.sampleName);
|
|
241
|
-
if (duplicate)
|
|
242
|
-
{
|
|
243
|
-
return duplicate;
|
|
244
|
-
}
|
|
245
|
-
const newSample = new BasicSample(
|
|
246
|
-
sample.sampleName,
|
|
247
|
-
sample.sampleRate,
|
|
248
|
-
sample.samplePitch,
|
|
249
|
-
sample.samplePitchCorrection,
|
|
250
|
-
sample.sampleType,
|
|
251
|
-
sample.sampleLoopStartIndex,
|
|
252
|
-
sample.sampleLoopEndIndex
|
|
253
|
-
);
|
|
254
|
-
if (sample.isCompressed)
|
|
255
|
-
{
|
|
256
|
-
newSample.setCompressedData(sample.compressedData.slice());
|
|
257
|
-
}
|
|
258
|
-
else
|
|
259
|
-
{
|
|
260
|
-
newSample.setAudioData(sample.getAudioData());
|
|
261
|
-
}
|
|
262
|
-
this.addSamples(newSample);
|
|
263
|
-
if (sample.linkedSample)
|
|
264
|
-
{
|
|
265
|
-
const clonedLinked = this.cloneSample(sample.linkedSample);
|
|
266
|
-
// sanity check
|
|
267
|
-
if (!clonedLinked.linkedSample)
|
|
268
|
-
{
|
|
269
|
-
newSample.setLinkedSample(clonedLinked, newSample.sampleType);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return newSample;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Clones an instruments into this bank
|
|
278
|
-
* @param instrument {BasicInstrument}
|
|
279
|
-
* @returns {BasicInstrument} the copied instrument, if an instrument exists with that name, it is returned instead
|
|
280
|
-
*/
|
|
281
|
-
cloneInstrument(instrument)
|
|
282
|
-
{
|
|
283
|
-
const duplicate = this.instruments.find(i => i.instrumentName === instrument.instrumentName);
|
|
284
|
-
if (duplicate)
|
|
285
|
-
{
|
|
286
|
-
return duplicate;
|
|
287
|
-
}
|
|
288
|
-
const newInstrument = new BasicInstrument();
|
|
289
|
-
newInstrument.instrumentName = instrument.instrumentName;
|
|
290
|
-
newInstrument.globalZone.copyFrom(instrument.globalZone);
|
|
291
|
-
for (const zone of instrument.instrumentZones)
|
|
292
|
-
{
|
|
293
|
-
const copiedZone = newInstrument.createZone();
|
|
294
|
-
copiedZone.copyFrom(zone);
|
|
295
|
-
copiedZone.setSample(this.cloneSample(zone.sample));
|
|
296
|
-
}
|
|
297
|
-
this.addInstruments(newInstrument);
|
|
298
|
-
return newInstrument;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// noinspection JSUnusedGlobalSymbols
|
|
302
|
-
/**
|
|
303
|
-
* Clones presets into this sound bank
|
|
304
|
-
* @param preset {BasicPreset}
|
|
305
|
-
* @returns {BasicPreset} the copied preset, if a preset exists with that name, it is returned instead
|
|
306
|
-
*/
|
|
307
|
-
clonePreset(preset)
|
|
308
|
-
{
|
|
309
|
-
const duplicate = this.presets.find(p => p.presetName === preset.presetName);
|
|
310
|
-
if (duplicate)
|
|
311
|
-
{
|
|
312
|
-
return duplicate;
|
|
313
|
-
}
|
|
314
|
-
const newPreset = new BasicPreset(this);
|
|
315
|
-
newPreset.presetName = preset.presetName;
|
|
316
|
-
newPreset.bank = preset.bank;
|
|
317
|
-
newPreset.program = preset.program;
|
|
318
|
-
newPreset.library = preset.library;
|
|
319
|
-
newPreset.genre = preset.genre;
|
|
320
|
-
newPreset.morphology = preset.morphology;
|
|
321
|
-
newPreset.globalZone.copyFrom(preset.globalZone);
|
|
322
|
-
for (const zone of preset.presetZones)
|
|
323
|
-
{
|
|
324
|
-
const copiedZone = newPreset.createZone();
|
|
325
|
-
copiedZone.copyFrom(zone);
|
|
326
|
-
copiedZone.setInstrument(this.cloneInstrument(zone.instrument));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
this.addPresets(newPreset);
|
|
330
|
-
return newPreset;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
flush()
|
|
334
|
-
{
|
|
335
|
-
this.presets.sort((a, b) =>
|
|
336
|
-
{
|
|
337
|
-
if (a.bank !== b.bank)
|
|
338
|
-
{
|
|
339
|
-
return a.bank - b.bank;
|
|
340
|
-
}
|
|
341
|
-
return a.program - b.program;
|
|
342
|
-
});
|
|
343
|
-
this._parseInternal();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* parses the bank after loading is done
|
|
348
|
-
* @protected
|
|
349
|
-
*/
|
|
350
|
-
_parseInternal()
|
|
351
|
-
{
|
|
352
|
-
this.isXGBank = false;
|
|
353
|
-
// definitions for XG:
|
|
354
|
-
// at least one preset with bank 127, 126 or 120
|
|
355
|
-
// MUST be a valid XG bank.
|
|
356
|
-
// allowed banks: (see XG specification)
|
|
357
|
-
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
|
|
358
|
-
// 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
|
|
359
|
-
// 64, 65, 66, 126, 127
|
|
360
|
-
const allowedPrograms = new Set([
|
|
361
|
-
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
|
|
362
|
-
25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
|
|
363
|
-
64, 65, 66, 126, 127
|
|
364
|
-
]);
|
|
365
|
-
for (const preset of this.presets)
|
|
366
|
-
{
|
|
367
|
-
if (isXGDrums(preset.bank))
|
|
368
|
-
{
|
|
369
|
-
this.isXGBank = true;
|
|
370
|
-
if (!allowedPrograms.has(preset.program))
|
|
371
|
-
{
|
|
372
|
-
// not valid!
|
|
373
|
-
this.isXGBank = false;
|
|
374
|
-
SpessaSynthInfo(
|
|
375
|
-
`%cThis bank is not valid XG. Preset %c${preset.bank}:${preset.program}%c is not a valid XG drum. XG mode will use presets on bank 128.`,
|
|
376
|
-
consoleColors.info,
|
|
377
|
-
consoleColors.value,
|
|
378
|
-
consoleColors.info
|
|
379
|
-
);
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Trims a sound bank to only contain samples in a given MIDI file
|
|
388
|
-
* @param mid {BasicMIDI} - the MIDI file
|
|
389
|
-
*/
|
|
390
|
-
trimSoundBank(mid)
|
|
391
|
-
{
|
|
392
|
-
const soundfont = this;
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* @param instrument {BasicInstrument}
|
|
396
|
-
* @param combos {{key: number, velocity: number}[]}
|
|
397
|
-
* @returns {number}
|
|
398
|
-
*/
|
|
399
|
-
function trimInstrumentZones(instrument, combos)
|
|
400
|
-
{
|
|
401
|
-
let trimmedIZones = 0;
|
|
402
|
-
for (let iZoneIndex = 0; iZoneIndex < instrument.instrumentZones.length; iZoneIndex++)
|
|
403
|
-
{
|
|
404
|
-
const iZone = instrument.instrumentZones[iZoneIndex];
|
|
405
|
-
const iKeyRange = iZone.keyRange;
|
|
406
|
-
const iVelRange = iZone.velRange;
|
|
407
|
-
let isIZoneUsed = false;
|
|
408
|
-
for (const iCombo of combos)
|
|
409
|
-
{
|
|
410
|
-
if (
|
|
411
|
-
(iCombo.key >= iKeyRange.min && iCombo.key <= iKeyRange.max) &&
|
|
412
|
-
(iCombo.velocity >= iVelRange.min && iCombo.velocity <= iVelRange.max)
|
|
413
|
-
)
|
|
414
|
-
{
|
|
415
|
-
isIZoneUsed = true;
|
|
416
|
-
break;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (!isIZoneUsed)
|
|
420
|
-
{
|
|
421
|
-
SpessaSynthInfo(
|
|
422
|
-
`%c${iZone.sample.sampleName} %cremoved from %c${instrument.instrumentName}%c.`,
|
|
423
|
-
consoleColors.recognized,
|
|
424
|
-
consoleColors.info,
|
|
425
|
-
consoleColors.recognized,
|
|
426
|
-
consoleColors.info
|
|
427
|
-
);
|
|
428
|
-
if (instrument.deleteZone(iZoneIndex))
|
|
429
|
-
{
|
|
430
|
-
trimmedIZones++;
|
|
431
|
-
iZoneIndex--;
|
|
432
|
-
SpessaSynthInfo(
|
|
433
|
-
`%c${iZone.sample.sampleName} %cdeleted`,
|
|
434
|
-
consoleColors.recognized,
|
|
435
|
-
consoleColors.info
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
if (iZone.sample.useCount < 1)
|
|
439
|
-
{
|
|
440
|
-
soundfont.deleteSample(iZone.sample);
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
return trimmedIZones;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
SpessaSynthGroup(
|
|
449
|
-
"%cTrimming soundfont...",
|
|
450
|
-
consoleColors.info
|
|
451
|
-
);
|
|
452
|
-
const usedProgramsAndKeys = mid.getUsedProgramsAndKeys(soundfont);
|
|
453
|
-
|
|
454
|
-
SpessaSynthGroupCollapsed(
|
|
455
|
-
"%cModifying soundfont...",
|
|
456
|
-
consoleColors.info
|
|
457
|
-
);
|
|
458
|
-
SpessaSynthInfo("Detected keys for midi:", usedProgramsAndKeys);
|
|
459
|
-
// modify the soundfont to only include programs and samples that are used
|
|
460
|
-
for (let presetIndex = 0; presetIndex < soundfont.presets.length; presetIndex++)
|
|
461
|
-
{
|
|
462
|
-
const p = soundfont.presets[presetIndex];
|
|
463
|
-
const string = p.bank + ":" + p.program;
|
|
464
|
-
const used = usedProgramsAndKeys[string];
|
|
465
|
-
if (used === undefined)
|
|
466
|
-
{
|
|
467
|
-
SpessaSynthInfo(
|
|
468
|
-
`%cDeleting preset %c${p.presetName}%c and its zones`,
|
|
469
|
-
consoleColors.info,
|
|
470
|
-
consoleColors.recognized,
|
|
471
|
-
consoleColors.info
|
|
472
|
-
);
|
|
473
|
-
soundfont.deletePreset(p);
|
|
474
|
-
presetIndex--;
|
|
475
|
-
}
|
|
476
|
-
else
|
|
477
|
-
{
|
|
478
|
-
const combos = [...used].map(s =>
|
|
479
|
-
{
|
|
480
|
-
const split = s.split("-");
|
|
481
|
-
return {
|
|
482
|
-
key: parseInt(split[0]),
|
|
483
|
-
velocity: parseInt(split[1])
|
|
484
|
-
};
|
|
485
|
-
});
|
|
486
|
-
SpessaSynthGroupCollapsed(
|
|
487
|
-
`%cTrimming %c${p.presetName}`,
|
|
488
|
-
consoleColors.info,
|
|
489
|
-
consoleColors.recognized
|
|
490
|
-
);
|
|
491
|
-
SpessaSynthInfo(`Keys for ${p.presetName}:`, combos);
|
|
492
|
-
let trimmedZones = 0;
|
|
493
|
-
// clean the preset to only use zones that are used
|
|
494
|
-
for (let zoneIndex = 0; zoneIndex < p.presetZones.length; zoneIndex++)
|
|
495
|
-
{
|
|
496
|
-
const zone = p.presetZones[zoneIndex];
|
|
497
|
-
const keyRange = zone.keyRange;
|
|
498
|
-
const velRange = zone.velRange;
|
|
499
|
-
// check if any of the combos matches the zone
|
|
500
|
-
let isZoneUsed = false;
|
|
501
|
-
for (const combo of combos)
|
|
502
|
-
{
|
|
503
|
-
if (
|
|
504
|
-
(combo.key >= keyRange.min && combo.key <= keyRange.max) &&
|
|
505
|
-
(combo.velocity >= velRange.min && combo.velocity <= velRange.max)
|
|
506
|
-
)
|
|
507
|
-
{
|
|
508
|
-
// zone is used, trim the instrument zones
|
|
509
|
-
isZoneUsed = true;
|
|
510
|
-
const trimmedIZones = trimInstrumentZones(zone.instrument, combos);
|
|
511
|
-
SpessaSynthInfo(
|
|
512
|
-
`%cTrimmed off %c${trimmedIZones}%c zones from %c${zone.instrument.instrumentName}`,
|
|
513
|
-
consoleColors.info,
|
|
514
|
-
consoleColors.recognized,
|
|
515
|
-
consoleColors.info,
|
|
516
|
-
consoleColors.recognized
|
|
517
|
-
);
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
if (!isZoneUsed)
|
|
522
|
-
{
|
|
523
|
-
trimmedZones++;
|
|
524
|
-
p.deleteZone(zoneIndex);
|
|
525
|
-
if (zone.instrument.useCount < 1)
|
|
526
|
-
{
|
|
527
|
-
soundfont.deleteInstrument(zone.instrument);
|
|
528
|
-
}
|
|
529
|
-
zoneIndex--;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
SpessaSynthInfo(
|
|
533
|
-
`%cTrimmed off %c${trimmedZones}%c zones from %c${p.presetName}`,
|
|
534
|
-
consoleColors.info,
|
|
535
|
-
consoleColors.recognized,
|
|
536
|
-
consoleColors.info,
|
|
537
|
-
consoleColors.recognized
|
|
538
|
-
);
|
|
539
|
-
SpessaSynthGroupEnd();
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
soundfont.removeUnusedElements();
|
|
543
|
-
|
|
544
|
-
soundfont.soundFontInfo["ICMT"] = `NOTE: This soundfont was trimmed by SpessaSynth to only contain presets used in "${mid.midiName}"\n\n`
|
|
545
|
-
+ soundfont.soundFontInfo["ICMT"];
|
|
546
|
-
|
|
547
|
-
SpessaSynthInfo(
|
|
548
|
-
"%cSoundfont modified!",
|
|
549
|
-
consoleColors.recognized
|
|
550
|
-
);
|
|
551
|
-
SpessaSynthGroupEnd();
|
|
552
|
-
SpessaSynthGroupEnd();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
removeUnusedElements()
|
|
556
|
-
{
|
|
557
|
-
this.instruments = this.instruments.filter(i =>
|
|
558
|
-
{
|
|
559
|
-
i.deleteUnusedZones();
|
|
560
|
-
const deletable = i.useCount < 1;
|
|
561
|
-
if (deletable)
|
|
562
|
-
{
|
|
563
|
-
i.deleteInstrument();
|
|
564
|
-
}
|
|
565
|
-
return !deletable;
|
|
566
|
-
});
|
|
567
|
-
this.samples = this.samples.filter(s =>
|
|
568
|
-
{
|
|
569
|
-
const deletable = s.useCount < 1;
|
|
570
|
-
if (deletable)
|
|
571
|
-
{
|
|
572
|
-
s.deleteSample();
|
|
573
|
-
}
|
|
574
|
-
return !deletable;
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
/**
|
|
579
|
-
* @param instrument {BasicInstrument}
|
|
580
|
-
*/
|
|
581
|
-
deleteInstrument(instrument)
|
|
582
|
-
{
|
|
583
|
-
instrument.deleteInstrument();
|
|
584
|
-
this.instruments.splice(this.instruments.indexOf(instrument), 1);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* @param preset {BasicPreset}
|
|
589
|
-
*/
|
|
590
|
-
deletePreset(preset)
|
|
591
|
-
{
|
|
592
|
-
preset.deletePreset();
|
|
593
|
-
this.presets.splice(this.presets.indexOf(preset), 1);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* @param sample {BasicSample}
|
|
598
|
-
*/
|
|
599
|
-
deleteSample(sample)
|
|
600
|
-
{
|
|
601
|
-
sample.deleteSample();
|
|
602
|
-
this.samples.splice(this.samples.indexOf(sample), 1);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
/**
|
|
606
|
-
* Get the appropriate preset, undefined if not found
|
|
607
|
-
* @param bankNr {number}
|
|
608
|
-
* @param programNr {number}
|
|
609
|
-
* @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
|
|
610
|
-
* @return {BasicPreset}
|
|
611
|
-
*/
|
|
612
|
-
getPresetNoFallback(bankNr, programNr, allowXGDrums = false)
|
|
613
|
-
{
|
|
614
|
-
const isDrum = bankNr === 128 || (allowXGDrums && isXGDrums(bankNr));
|
|
615
|
-
// check for exact match
|
|
616
|
-
let p;
|
|
617
|
-
if (isDrum)
|
|
618
|
-
{
|
|
619
|
-
p = this.presets.find(p => p.bank === bankNr && p.isDrumPreset(allowXGDrums) && p.program === programNr);
|
|
620
|
-
}
|
|
621
|
-
else
|
|
622
|
-
{
|
|
623
|
-
p = this.presets.find(p => p.bank === bankNr && p.program === programNr);
|
|
624
|
-
}
|
|
625
|
-
if (p)
|
|
626
|
-
{
|
|
627
|
-
return p;
|
|
628
|
-
}
|
|
629
|
-
// no match...
|
|
630
|
-
if (isDrum)
|
|
631
|
-
{
|
|
632
|
-
if (allowXGDrums)
|
|
633
|
-
{
|
|
634
|
-
// try any drum preset with matching program?
|
|
635
|
-
const p = this.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNr);
|
|
636
|
-
if (p)
|
|
637
|
-
{
|
|
638
|
-
return p;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return undefined;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
/**
|
|
646
|
-
* Get the appropriate preset
|
|
647
|
-
* @param bankNr {number}
|
|
648
|
-
* @param programNr {number}
|
|
649
|
-
* @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
|
|
650
|
-
* @returns {BasicPreset}
|
|
651
|
-
*/
|
|
652
|
-
getPreset(bankNr, programNr, allowXGDrums = false)
|
|
653
|
-
{
|
|
654
|
-
const isDrums = bankNr === 128 || (allowXGDrums && isXGDrums(bankNr));
|
|
655
|
-
// check for exact match
|
|
656
|
-
let preset;
|
|
657
|
-
// only allow drums if the preset is considered to be a drum preset
|
|
658
|
-
if (isDrums)
|
|
659
|
-
{
|
|
660
|
-
preset = this.presets.find(p => p.bank === bankNr && p.isDrumPreset(allowXGDrums) && p.program === programNr);
|
|
661
|
-
}
|
|
662
|
-
else
|
|
663
|
-
{
|
|
664
|
-
preset = this.presets.find(p => p.bank === bankNr && p.program === programNr);
|
|
665
|
-
}
|
|
666
|
-
if (preset)
|
|
667
|
-
{
|
|
668
|
-
return preset;
|
|
669
|
-
}
|
|
670
|
-
// no match...
|
|
671
|
-
if (isDrums)
|
|
672
|
-
{
|
|
673
|
-
// drum preset: find any preset with bank 128
|
|
674
|
-
preset = this.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNr);
|
|
675
|
-
if (!preset)
|
|
676
|
-
{
|
|
677
|
-
// only allow 128, otherwise it would default to XG SFX
|
|
678
|
-
preset = this.presets.find(p => p.isDrumPreset(allowXGDrums));
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
else
|
|
682
|
-
{
|
|
683
|
-
// non-drum preset: find any preset with the given program that is not a drum preset
|
|
684
|
-
preset = this.presets.find(p => p.program === programNr && !p.isDrumPreset(allowXGDrums));
|
|
685
|
-
}
|
|
686
|
-
if (preset)
|
|
687
|
-
{
|
|
688
|
-
SpessaSynthWarn(
|
|
689
|
-
`%cPreset ${bankNr}.${programNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
|
|
690
|
-
consoleColors.warn,
|
|
691
|
-
consoleColors.recognized
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
// no preset, use the first one available
|
|
696
|
-
if (!preset)
|
|
697
|
-
{
|
|
698
|
-
SpessaSynthWarn(`Preset ${programNr} not found. Defaulting to`, this.presets[0].presetName);
|
|
699
|
-
preset = this.presets[0];
|
|
700
|
-
}
|
|
701
|
-
return preset;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* gets preset by name
|
|
706
|
-
* @param presetName {string}
|
|
707
|
-
* @returns {BasicPreset}
|
|
708
|
-
*/
|
|
709
|
-
getPresetByName(presetName)
|
|
710
|
-
{
|
|
711
|
-
let preset = this.presets.find(p => p.presetName === presetName);
|
|
712
|
-
if (!preset)
|
|
713
|
-
{
|
|
714
|
-
SpessaSynthWarn("Preset not found. Defaulting to:", this.presets[0].presetName);
|
|
715
|
-
preset = this.presets[0];
|
|
716
|
-
}
|
|
717
|
-
return preset;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* @param error {string}
|
|
722
|
-
*/
|
|
723
|
-
parsingError(error)
|
|
724
|
-
{
|
|
725
|
-
throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
destroySoundBank()
|
|
729
|
-
{
|
|
730
|
-
delete this.presets;
|
|
731
|
-
delete this.instruments;
|
|
732
|
-
delete this.samples;
|
|
733
|
-
delete this.soundFontInfo;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
BasicSoundBank.prototype.write = write;
|
|
738
|
-
BasicSoundBank.prototype.writeDLS = writeDLS;
|
|
739
|
-
|
|
740
|
-
export { BasicSoundBank };
|