spessasynth_core 3.27.8 → 4.0.1
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 +85 -51
- package/dist/index.d.ts +4057 -0
- package/dist/index.js +17178 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -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 -53
- 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 -220
- 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 -26
- 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 -817
- 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,256 +0,0 @@
|
|
|
1
|
-
import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
|
|
2
|
-
import { consoleColors } from "../../utils/other.js";
|
|
3
|
-
import { messageTypes, midiControllers } from "../midi_message.js";
|
|
4
|
-
import { DEFAULT_PERCUSSION } from "../../synthetizer/synth_constants.js";
|
|
5
|
-
import { chooseBank, isSystemXG, parseBankSelect } from "../../utils/xg_hacks.js";
|
|
6
|
-
import { isGSDrumsOn, isXGOn } from "../../utils/sysex_detector.js";
|
|
7
|
-
import { SoundFontManager } from "../../synthetizer/audio_engine/engine_components/soundfont_manager.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Gets the used programs and keys for this MIDI file with a given sound bank
|
|
11
|
-
* @this {BasicMIDI}
|
|
12
|
-
* @param soundfont {SoundFontManager|BasicSoundBank} - the sound bank
|
|
13
|
-
* @returns {Record<string, Set<string>>} Record<bank:program, Set<key-velocity>>
|
|
14
|
-
*/
|
|
15
|
-
export function getUsedProgramsAndKeys(soundfont)
|
|
16
|
-
{
|
|
17
|
-
const mid = this;
|
|
18
|
-
SpessaSynthGroupCollapsed(
|
|
19
|
-
"%cSearching for all used programs and keys...",
|
|
20
|
-
consoleColors.info
|
|
21
|
-
);
|
|
22
|
-
// Find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
|
|
23
|
-
const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
|
|
24
|
-
/**
|
|
25
|
-
* @type {{program: number, bank: number, bankLSB: number, drums: boolean, string: string, actualBank: number}[]}
|
|
26
|
-
*/
|
|
27
|
-
const channelPresets = [];
|
|
28
|
-
for (let i = 0; i < channelsAmount; i++)
|
|
29
|
-
{
|
|
30
|
-
const bank = i % 16 === DEFAULT_PERCUSSION ? 128 : 0;
|
|
31
|
-
channelPresets.push({
|
|
32
|
-
program: 0,
|
|
33
|
-
bank: bank,
|
|
34
|
-
bankLSB: 0,
|
|
35
|
-
actualBank: bank,
|
|
36
|
-
drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
|
|
37
|
-
string: `${bank}:0`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// check for xg
|
|
42
|
-
let system = "gs";
|
|
43
|
-
|
|
44
|
-
function updateString(ch)
|
|
45
|
-
{
|
|
46
|
-
const bank = chooseBank(ch.bank, ch.bankLSB, ch.drums, isSystemXG(system));
|
|
47
|
-
// check if this exists in the soundfont
|
|
48
|
-
let existsBank, existsProgram;
|
|
49
|
-
if (soundfont instanceof SoundFontManager)
|
|
50
|
-
{
|
|
51
|
-
/**
|
|
52
|
-
* @type {{preset: BasicPreset, bankOffset: number}}
|
|
53
|
-
*/
|
|
54
|
-
let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
|
|
55
|
-
existsBank = exists.preset.bank + exists.bankOffset;
|
|
56
|
-
existsProgram = exists.preset.program;
|
|
57
|
-
}
|
|
58
|
-
else
|
|
59
|
-
{
|
|
60
|
-
/**
|
|
61
|
-
* @type {BasicPreset}
|
|
62
|
-
*/
|
|
63
|
-
let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
|
|
64
|
-
existsBank = exists.bank;
|
|
65
|
-
existsProgram = exists.program;
|
|
66
|
-
}
|
|
67
|
-
ch.actualBank = existsBank;
|
|
68
|
-
ch.program = existsProgram;
|
|
69
|
-
ch.string = ch.actualBank + ":" + ch.program;
|
|
70
|
-
if (!usedProgramsAndKeys[ch.string])
|
|
71
|
-
{
|
|
72
|
-
SpessaSynthInfo(
|
|
73
|
-
`%cDetected a new preset: %c${ch.string}`,
|
|
74
|
-
consoleColors.info,
|
|
75
|
-
consoleColors.recognized
|
|
76
|
-
);
|
|
77
|
-
usedProgramsAndKeys[ch.string] = new Set();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* find all programs used and key-velocity combos in them
|
|
83
|
-
* bank:program each has a set of midiNote-velocity
|
|
84
|
-
* @type {Record<string, Set<string>>}
|
|
85
|
-
*/
|
|
86
|
-
const usedProgramsAndKeys = {};
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* indexes for tracks
|
|
90
|
-
* @type {number[]}
|
|
91
|
-
*/
|
|
92
|
-
const eventIndexes = Array(mid.tracks.length).fill(0);
|
|
93
|
-
let remainingTracks = mid.tracks.length;
|
|
94
|
-
|
|
95
|
-
function findFirstEventIndex()
|
|
96
|
-
{
|
|
97
|
-
let index = 0;
|
|
98
|
-
let ticks = Infinity;
|
|
99
|
-
mid.tracks.forEach((track, i) =>
|
|
100
|
-
{
|
|
101
|
-
if (eventIndexes[i] >= track.length)
|
|
102
|
-
{
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (track[eventIndexes[i]].ticks < ticks)
|
|
106
|
-
{
|
|
107
|
-
index = i;
|
|
108
|
-
ticks = track[eventIndexes[i]].ticks;
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
return index;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const ports = mid.midiPorts.slice();
|
|
115
|
-
// initialize
|
|
116
|
-
channelPresets.forEach(c =>
|
|
117
|
-
{
|
|
118
|
-
updateString(c);
|
|
119
|
-
});
|
|
120
|
-
while (remainingTracks > 0)
|
|
121
|
-
{
|
|
122
|
-
let trackNum = findFirstEventIndex();
|
|
123
|
-
const track = mid.tracks[trackNum];
|
|
124
|
-
if (eventIndexes[trackNum] >= track.length)
|
|
125
|
-
{
|
|
126
|
-
remainingTracks--;
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
const event = track[eventIndexes[trackNum]];
|
|
130
|
-
eventIndexes[trackNum]++;
|
|
131
|
-
|
|
132
|
-
if (event.messageStatusByte === messageTypes.midiPort)
|
|
133
|
-
{
|
|
134
|
-
ports[trackNum] = event.messageData[0];
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const status = event.messageStatusByte & 0xF0;
|
|
138
|
-
if (
|
|
139
|
-
status !== messageTypes.noteOn &&
|
|
140
|
-
status !== messageTypes.controllerChange &&
|
|
141
|
-
status !== messageTypes.programChange &&
|
|
142
|
-
status !== messageTypes.systemExclusive
|
|
143
|
-
)
|
|
144
|
-
{
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const channel = (event.messageStatusByte & 0xF) + mid.midiPortChannelOffsets[ports[trackNum]] || 0;
|
|
148
|
-
let ch = channelPresets[channel];
|
|
149
|
-
switch (status)
|
|
150
|
-
{
|
|
151
|
-
case messageTypes.programChange:
|
|
152
|
-
ch.program = event.messageData[0];
|
|
153
|
-
updateString(ch);
|
|
154
|
-
break;
|
|
155
|
-
|
|
156
|
-
case messageTypes.controllerChange:
|
|
157
|
-
const isLSB = event.messageData[0] === midiControllers.lsbForControl0BankSelect;
|
|
158
|
-
if (event.messageData[0] !== midiControllers.bankSelect && !isLSB)
|
|
159
|
-
{
|
|
160
|
-
// we only care about bank select
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
if (system === "gs" && ch.drums)
|
|
164
|
-
{
|
|
165
|
-
// gs drums get changed via sysex, ignore here
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const bank = event.messageData[1];
|
|
169
|
-
if (isLSB)
|
|
170
|
-
{
|
|
171
|
-
ch.bankLSB = bank;
|
|
172
|
-
}
|
|
173
|
-
else
|
|
174
|
-
{
|
|
175
|
-
ch.bank = bank;
|
|
176
|
-
}
|
|
177
|
-
// interpret the bank
|
|
178
|
-
const intepretation = parseBankSelect(
|
|
179
|
-
ch.bank,
|
|
180
|
-
bank,
|
|
181
|
-
system,
|
|
182
|
-
isLSB,
|
|
183
|
-
ch.drums,
|
|
184
|
-
channel
|
|
185
|
-
);
|
|
186
|
-
switch (intepretation.drumsStatus)
|
|
187
|
-
{
|
|
188
|
-
case 0:
|
|
189
|
-
// no change
|
|
190
|
-
break;
|
|
191
|
-
|
|
192
|
-
case 1:
|
|
193
|
-
// drums changed to off
|
|
194
|
-
// drum change is a program change
|
|
195
|
-
ch.drums = false;
|
|
196
|
-
updateString(ch);
|
|
197
|
-
break;
|
|
198
|
-
|
|
199
|
-
case 2:
|
|
200
|
-
// drums changed to on
|
|
201
|
-
// drum change is a program change
|
|
202
|
-
ch.drums = true;
|
|
203
|
-
updateString(ch);
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
// do not update the data, bank change doesn't change the preset
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case messageTypes.noteOn:
|
|
210
|
-
if (event.messageData[1] === 0)
|
|
211
|
-
{
|
|
212
|
-
// that's a note off
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
usedProgramsAndKeys[ch.string].add(`${event.messageData[0]}-${event.messageData[1]}`);
|
|
216
|
-
break;
|
|
217
|
-
|
|
218
|
-
case messageTypes.systemExclusive:
|
|
219
|
-
// check for drum sysex
|
|
220
|
-
if (!isGSDrumsOn(event))
|
|
221
|
-
{
|
|
222
|
-
// check for XG
|
|
223
|
-
if (isXGOn(event))
|
|
224
|
-
{
|
|
225
|
-
system = "xg";
|
|
226
|
-
SpessaSynthInfo(
|
|
227
|
-
"%cXG on detected!",
|
|
228
|
-
consoleColors.recognized
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const sysexChannel = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15][event.messageData[5] & 0x0F] + mid.midiPortChannelOffsets[ports[trackNum]];
|
|
234
|
-
const isDrum = !!(event.messageData[7] > 0 && event.messageData[5] >> 4);
|
|
235
|
-
ch = channelPresets[sysexChannel];
|
|
236
|
-
ch.drums = isDrum;
|
|
237
|
-
updateString(ch);
|
|
238
|
-
break;
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
for (const key of Object.keys(usedProgramsAndKeys))
|
|
243
|
-
{
|
|
244
|
-
if (usedProgramsAndKeys[key].size === 0)
|
|
245
|
-
{
|
|
246
|
-
SpessaSynthInfo(
|
|
247
|
-
`%cDetected change but no keys for %c${key}`,
|
|
248
|
-
consoleColors.info,
|
|
249
|
-
consoleColors.value
|
|
250
|
-
);
|
|
251
|
-
delete usedProgramsAndKeys[key];
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
SpessaSynthGroupEnd();
|
|
255
|
-
return usedProgramsAndKeys;
|
|
256
|
-
}
|
package/src/midi/xmf_loader.js
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
import { readBytesAsString } from "../utils/byte_functions/string.js";
|
|
2
|
-
import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
|
|
3
|
-
import { consoleColors } from "../utils/other.js";
|
|
4
|
-
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
5
|
-
import { readVariableLengthQuantity } from "../utils/byte_functions/variable_length_quantity.js";
|
|
6
|
-
import { RMIDINFOChunks } from "./midi_tools/rmidi_writer.js";
|
|
7
|
-
import { inflateSync } from "../externals/fflate/fflate.min.js";
|
|
8
|
-
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @enum {number}
|
|
12
|
-
*/
|
|
13
|
-
const metadataTypes = {
|
|
14
|
-
XMFFileType: 0,
|
|
15
|
-
nodeName: 1,
|
|
16
|
-
nodeIDNumber: 2,
|
|
17
|
-
resourceFormat: 3,
|
|
18
|
-
filenameOnDisk: 4,
|
|
19
|
-
filenameExtensionOnDisk: 5,
|
|
20
|
-
macOSFileTypeAndCreator: 6,
|
|
21
|
-
mimeType: 7,
|
|
22
|
-
title: 8,
|
|
23
|
-
copyrightNotice: 9,
|
|
24
|
-
comment: 10,
|
|
25
|
-
autoStart: 11, // Node Name of the FileNode containing the SMF image to autostart when the XMF file loads
|
|
26
|
-
preload: 12, // Used to preload specific SMF and DLS file images.
|
|
27
|
-
contentDescription: 13, // RP-42a (https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp42.pdf)
|
|
28
|
-
ID3Metadata: 14 // RP-47 (https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp47.pdf)
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @enum {number}
|
|
33
|
-
*/
|
|
34
|
-
const referenceTypeIds = {
|
|
35
|
-
inLineResource: 1,
|
|
36
|
-
inFileResource: 2,
|
|
37
|
-
inFileNode: 3,
|
|
38
|
-
externalFile: 4,
|
|
39
|
-
externalXMF: 5,
|
|
40
|
-
XMFFileURIandNodeID: 6
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @enum {number}
|
|
45
|
-
*/
|
|
46
|
-
const resourceFormatIDs = {
|
|
47
|
-
StandardMIDIFile: 0,
|
|
48
|
-
StandardMIDIFileType1: 1,
|
|
49
|
-
DLS1: 2,
|
|
50
|
-
DLS2: 3,
|
|
51
|
-
DLS22: 4,
|
|
52
|
-
mobileDLS: 5
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @enum {number}
|
|
57
|
-
*/
|
|
58
|
-
const formatTypeIDs = {
|
|
59
|
-
standard: 0,
|
|
60
|
-
MMA: 1,
|
|
61
|
-
registered: 2,
|
|
62
|
-
nonRegistered: 3
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @enum {number}
|
|
68
|
-
*/
|
|
69
|
-
const unpackerIDs = {
|
|
70
|
-
none: 0,
|
|
71
|
-
MMAUnpacker: 1,
|
|
72
|
-
registered: 2,
|
|
73
|
-
nonRegistered: 3
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
class XMFNode
|
|
77
|
-
{
|
|
78
|
-
/**
|
|
79
|
-
* @type {number}
|
|
80
|
-
*/
|
|
81
|
-
length;
|
|
82
|
-
/**
|
|
83
|
-
* 0 means it's a file node
|
|
84
|
-
* @type {number}
|
|
85
|
-
*/
|
|
86
|
-
itemCount;
|
|
87
|
-
/**
|
|
88
|
-
* @type {number}
|
|
89
|
-
*/
|
|
90
|
-
metadataLength;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* @type {Record<string, any>}
|
|
94
|
-
*/
|
|
95
|
-
metadata = {};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @type {IndexedByteArray}
|
|
99
|
-
*/
|
|
100
|
-
nodeData;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* @type {XMFNode[]}
|
|
104
|
-
*/
|
|
105
|
-
innerNodes = [];
|
|
106
|
-
|
|
107
|
-
packedContent = false;
|
|
108
|
-
|
|
109
|
-
nodeUnpackers = [];
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* @type {"StandardMIDIFile"|
|
|
114
|
-
* "StandardMIDIFileType1"|
|
|
115
|
-
* "DLS1"|
|
|
116
|
-
* "DLS2"|
|
|
117
|
-
* "DLS22"|
|
|
118
|
-
* "mobileDLS"|
|
|
119
|
-
* "unknown"|"folder"}
|
|
120
|
-
*/
|
|
121
|
-
resourceFormat = "unknown";
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* @param binaryData {IndexedByteArray}
|
|
125
|
-
*/
|
|
126
|
-
constructor(binaryData)
|
|
127
|
-
{
|
|
128
|
-
let nodeStartIndex = binaryData.currentIndex;
|
|
129
|
-
this.length = readVariableLengthQuantity(binaryData);
|
|
130
|
-
this.itemCount = readVariableLengthQuantity(binaryData);
|
|
131
|
-
// header length
|
|
132
|
-
const headerLength = readVariableLengthQuantity(binaryData);
|
|
133
|
-
const readBytes = binaryData.currentIndex - nodeStartIndex;
|
|
134
|
-
|
|
135
|
-
const remainingHeader = headerLength - readBytes;
|
|
136
|
-
const headerData = binaryData.slice(
|
|
137
|
-
binaryData.currentIndex,
|
|
138
|
-
binaryData.currentIndex + remainingHeader
|
|
139
|
-
);
|
|
140
|
-
binaryData.currentIndex += remainingHeader;
|
|
141
|
-
|
|
142
|
-
this.metadataLength = readVariableLengthQuantity(headerData);
|
|
143
|
-
|
|
144
|
-
const metadataChunk = headerData.slice(
|
|
145
|
-
headerData.currentIndex,
|
|
146
|
-
headerData.currentIndex + this.metadataLength
|
|
147
|
-
);
|
|
148
|
-
headerData.currentIndex += this.metadataLength;
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* @type {metadataTypes|string|number}
|
|
152
|
-
*/
|
|
153
|
-
let fieldSpecifier;
|
|
154
|
-
let key;
|
|
155
|
-
while (metadataChunk.currentIndex < metadataChunk.length)
|
|
156
|
-
{
|
|
157
|
-
const firstSpecifierByte = metadataChunk[metadataChunk.currentIndex];
|
|
158
|
-
if (firstSpecifierByte === 0)
|
|
159
|
-
{
|
|
160
|
-
metadataChunk.currentIndex++;
|
|
161
|
-
fieldSpecifier = readVariableLengthQuantity(metadataChunk);
|
|
162
|
-
if (Object.values(metadataTypes).indexOf(fieldSpecifier) === -1)
|
|
163
|
-
{
|
|
164
|
-
SpessaSynthWarn(`Unknown field specifier: ${fieldSpecifier}`);
|
|
165
|
-
key = `unknown_${fieldSpecifier}`;
|
|
166
|
-
}
|
|
167
|
-
else
|
|
168
|
-
{
|
|
169
|
-
key = Object.keys(metadataTypes).find(k => metadataTypes[k] === fieldSpecifier);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
else
|
|
173
|
-
{
|
|
174
|
-
// this is the length of string
|
|
175
|
-
const stringLength = readVariableLengthQuantity(metadataChunk);
|
|
176
|
-
fieldSpecifier = readBytesAsString(metadataChunk, stringLength);
|
|
177
|
-
key = fieldSpecifier;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const numberOfVersions = readVariableLengthQuantity(metadataChunk);
|
|
181
|
-
if (numberOfVersions === 0)
|
|
182
|
-
{
|
|
183
|
-
const dataLength = readVariableLengthQuantity(metadataChunk);
|
|
184
|
-
const contentsChunk = metadataChunk.slice(
|
|
185
|
-
metadataChunk.currentIndex,
|
|
186
|
-
metadataChunk.currentIndex + dataLength
|
|
187
|
-
);
|
|
188
|
-
metadataChunk.currentIndex += dataLength;
|
|
189
|
-
const formatID = readVariableLengthQuantity(contentsChunk);
|
|
190
|
-
// text only
|
|
191
|
-
if (formatID < 4)
|
|
192
|
-
{
|
|
193
|
-
this.metadata[key] = readBytesAsString(contentsChunk, dataLength - 1);
|
|
194
|
-
}
|
|
195
|
-
else
|
|
196
|
-
{
|
|
197
|
-
this.metadata[key] = contentsChunk.slice(contentsChunk.currentIndex);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else
|
|
201
|
-
{
|
|
202
|
-
// throw new Error ("International content is not supported.");
|
|
203
|
-
// Skip the number of versions
|
|
204
|
-
SpessaSynthWarn(`International content: ${numberOfVersions}`);
|
|
205
|
-
// Length in bytes
|
|
206
|
-
// Skip the whole thing!
|
|
207
|
-
metadataChunk.currentIndex += readVariableLengthQuantity(metadataChunk);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const unpackersStart = headerData.currentIndex;
|
|
212
|
-
const unpackersLength = readVariableLengthQuantity(headerData);
|
|
213
|
-
const unpackersData = headerData.slice(headerData.currentIndex, unpackersStart + unpackersLength);
|
|
214
|
-
headerData.currentIndex = unpackersStart + unpackersLength;
|
|
215
|
-
if (unpackersLength > 0)
|
|
216
|
-
{
|
|
217
|
-
this.packedContent = true;
|
|
218
|
-
while (unpackersData.currentIndex < unpackersLength)
|
|
219
|
-
{
|
|
220
|
-
const unpacker = {};
|
|
221
|
-
unpacker.id = readVariableLengthQuantity(unpackersData);
|
|
222
|
-
switch (unpacker.id)
|
|
223
|
-
{
|
|
224
|
-
case unpackerIDs.nonRegistered:
|
|
225
|
-
case unpackerIDs.registered:
|
|
226
|
-
SpessaSynthGroupEnd();
|
|
227
|
-
throw new Error(`Unsupported unpacker ID: ${unpacker.id}`);
|
|
228
|
-
|
|
229
|
-
default:
|
|
230
|
-
SpessaSynthGroupEnd();
|
|
231
|
-
throw new Error(`Unknown unpacker ID: ${unpacker.id}`);
|
|
232
|
-
|
|
233
|
-
case unpackerIDs.none:
|
|
234
|
-
unpacker.standardID = readVariableLengthQuantity(unpackersData);
|
|
235
|
-
break;
|
|
236
|
-
|
|
237
|
-
case unpackerIDs.MMAUnpacker:
|
|
238
|
-
let manufacturerID = unpackersData[unpackersData.currentIndex++];
|
|
239
|
-
// one or three byte form, depending on if the first byte is zero
|
|
240
|
-
if (manufacturerID === 0)
|
|
241
|
-
{
|
|
242
|
-
manufacturerID <<= 8;
|
|
243
|
-
manufacturerID |= unpackersData[unpackersData.currentIndex++];
|
|
244
|
-
manufacturerID <<= 8;
|
|
245
|
-
manufacturerID |= unpackersData[unpackersData.currentIndex++];
|
|
246
|
-
}
|
|
247
|
-
const manufacturerInternalID = readVariableLengthQuantity(unpackersData);
|
|
248
|
-
unpacker.manufacturerID = manufacturerID;
|
|
249
|
-
unpacker.manufacturerInternalID = manufacturerInternalID;
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
unpacker.decodedSize = readVariableLengthQuantity(unpackersData);
|
|
253
|
-
this.nodeUnpackers.push(unpacker);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
binaryData.currentIndex = nodeStartIndex + headerLength;
|
|
257
|
-
/**
|
|
258
|
-
* @type {referenceTypeIds|number}
|
|
259
|
-
*/
|
|
260
|
-
this.referenceTypeID = readVariableLengthQuantity(binaryData);
|
|
261
|
-
this.nodeData = binaryData.slice(binaryData.currentIndex, nodeStartIndex + this.length);
|
|
262
|
-
binaryData.currentIndex = nodeStartIndex + this.length;
|
|
263
|
-
switch (this.referenceTypeID)
|
|
264
|
-
{
|
|
265
|
-
case referenceTypeIds.inLineResource:
|
|
266
|
-
break;
|
|
267
|
-
|
|
268
|
-
case referenceTypeIds.externalXMF:
|
|
269
|
-
case referenceTypeIds.inFileNode:
|
|
270
|
-
case referenceTypeIds.XMFFileURIandNodeID:
|
|
271
|
-
case referenceTypeIds.externalFile:
|
|
272
|
-
case referenceTypeIds.inFileResource:
|
|
273
|
-
SpessaSynthGroupEnd();
|
|
274
|
-
throw new Error(`Unsupported reference type: ${this.referenceTypeID}`);
|
|
275
|
-
|
|
276
|
-
default:
|
|
277
|
-
SpessaSynthGroupEnd();
|
|
278
|
-
throw new Error(`Unknown reference type: ${this.referenceTypeID}`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// read the data
|
|
282
|
-
if (this.isFile)
|
|
283
|
-
{
|
|
284
|
-
if (this.packedContent)
|
|
285
|
-
{
|
|
286
|
-
const compressed = this.nodeData.slice(2, this.nodeData.length);
|
|
287
|
-
SpessaSynthInfo(
|
|
288
|
-
`%cPacked content. Attemting to deflate. Target size: %c${this.nodeUnpackers[0].decodedSize}`,
|
|
289
|
-
consoleColors.warn,
|
|
290
|
-
consoleColors.value
|
|
291
|
-
);
|
|
292
|
-
try
|
|
293
|
-
{
|
|
294
|
-
this.nodeData = new IndexedByteArray(inflateSync(compressed).buffer);
|
|
295
|
-
}
|
|
296
|
-
catch (e)
|
|
297
|
-
{
|
|
298
|
-
SpessaSynthGroupEnd();
|
|
299
|
-
throw new Error(`Error unpacking XMF file contents: ${e.message}.`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
/**
|
|
303
|
-
* interpret the content
|
|
304
|
-
* @type {number[]}
|
|
305
|
-
*/
|
|
306
|
-
const resourceFormat = this.metadata["resourceFormat"];
|
|
307
|
-
if (resourceFormat === undefined)
|
|
308
|
-
{
|
|
309
|
-
SpessaSynthWarn("No resource format for this file node!");
|
|
310
|
-
}
|
|
311
|
-
else
|
|
312
|
-
{
|
|
313
|
-
const formatTypeID = resourceFormat[0];
|
|
314
|
-
if (formatTypeID !== formatTypeIDs.standard)
|
|
315
|
-
{
|
|
316
|
-
SpessaSynthWarn(`Non-standard formatTypeID: ${resourceFormat}`);
|
|
317
|
-
this.resourceFormat = resourceFormat.toString();
|
|
318
|
-
}
|
|
319
|
-
const resourceFormatID = resourceFormat[1];
|
|
320
|
-
if (Object.values(resourceFormatIDs).indexOf(resourceFormatID) === -1)
|
|
321
|
-
{
|
|
322
|
-
SpessaSynthWarn(`Unrecognized resource format: ${resourceFormatID}`);
|
|
323
|
-
}
|
|
324
|
-
else
|
|
325
|
-
{
|
|
326
|
-
this.resourceFormat = Object.keys(resourceFormatIDs)
|
|
327
|
-
.find(k => resourceFormatIDs[k] === resourceFormatID);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else
|
|
332
|
-
{
|
|
333
|
-
// folder node
|
|
334
|
-
this.resourceFormat = "folder";
|
|
335
|
-
while (this.nodeData.currentIndex < this.nodeData.length)
|
|
336
|
-
{
|
|
337
|
-
const nodeStartIndex = this.nodeData.currentIndex;
|
|
338
|
-
const nodeLength = readVariableLengthQuantity(this.nodeData);
|
|
339
|
-
const nodeData = this.nodeData.slice(nodeStartIndex, nodeStartIndex + nodeLength);
|
|
340
|
-
this.nodeData.currentIndex = nodeStartIndex + nodeLength;
|
|
341
|
-
this.innerNodes.push(new XMFNode(nodeData));
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
get isFile()
|
|
347
|
-
{
|
|
348
|
-
return this.itemCount === 0;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* @param midi {BasicMIDI}
|
|
354
|
-
* @param binaryData {IndexedByteArray}
|
|
355
|
-
* @returns {IndexedByteArray} the file byte array
|
|
356
|
-
*/
|
|
357
|
-
export function loadXMF(midi, binaryData)
|
|
358
|
-
{
|
|
359
|
-
midi.bankOffset = 0;
|
|
360
|
-
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/xmf-v1a.pdf
|
|
361
|
-
// https://wiki.multimedia.cx/index.php?title=Extensible_Music_Format_(XMF)
|
|
362
|
-
const sanityCheck = readBytesAsString(binaryData, 4);
|
|
363
|
-
if (sanityCheck !== "XMF_")
|
|
364
|
-
{
|
|
365
|
-
SpessaSynthGroupEnd();
|
|
366
|
-
throw new SyntaxError(`Invalid XMF Header! Expected "_XMF", got "${sanityCheck}"`);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
SpessaSynthGroup("%cParsing XMF file...", consoleColors.info);
|
|
370
|
-
const version = readBytesAsString(binaryData, 4);
|
|
371
|
-
SpessaSynthInfo(
|
|
372
|
-
`%cXMF version: %c${version}`,
|
|
373
|
-
consoleColors.info, consoleColors.recognized
|
|
374
|
-
);
|
|
375
|
-
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp43.pdf
|
|
376
|
-
// version 2.00 has additional bytes
|
|
377
|
-
if (version === "2.00")
|
|
378
|
-
{
|
|
379
|
-
const fileTypeId = readBytesAsUintBigEndian(binaryData, 4);
|
|
380
|
-
const fileTypeRevisionId = readBytesAsUintBigEndian(binaryData, 4);
|
|
381
|
-
SpessaSynthInfo(
|
|
382
|
-
`%cFile Type ID: %c${fileTypeId}%c, File Type Revision ID: %c${fileTypeRevisionId}`,
|
|
383
|
-
consoleColors.info,
|
|
384
|
-
consoleColors.recognized,
|
|
385
|
-
consoleColors.info,
|
|
386
|
-
consoleColors.recognized
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// file length
|
|
391
|
-
readVariableLengthQuantity(binaryData);
|
|
392
|
-
|
|
393
|
-
const metadataTableLength = readVariableLengthQuantity(binaryData);
|
|
394
|
-
// skip metadata
|
|
395
|
-
binaryData.currentIndex += metadataTableLength;
|
|
396
|
-
|
|
397
|
-
// skip to tree root
|
|
398
|
-
binaryData.currentIndex = readVariableLengthQuantity(binaryData);
|
|
399
|
-
const rootNode = new XMFNode(binaryData);
|
|
400
|
-
/**
|
|
401
|
-
* @type {IndexedByteArray}
|
|
402
|
-
*/
|
|
403
|
-
let midiArray;
|
|
404
|
-
/**
|
|
405
|
-
* find the stuff we care about
|
|
406
|
-
* @param node {XMFNode}
|
|
407
|
-
*/
|
|
408
|
-
const searchNode = node =>
|
|
409
|
-
{
|
|
410
|
-
const checkMeta = (xmf, rmid) =>
|
|
411
|
-
{
|
|
412
|
-
if (node.metadata[xmf] !== undefined && typeof node.metadata[xmf] === "string")
|
|
413
|
-
{
|
|
414
|
-
midi.RMIDInfo[rmid] = node.metadata[xmf];
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
// meta
|
|
418
|
-
checkMeta("nodeName", RMIDINFOChunks.name);
|
|
419
|
-
checkMeta("title", RMIDINFOChunks.name);
|
|
420
|
-
checkMeta("copyrightNotice", RMIDINFOChunks.copyright);
|
|
421
|
-
checkMeta("comment", RMIDINFOChunks.comment);
|
|
422
|
-
if (node.isFile)
|
|
423
|
-
{
|
|
424
|
-
switch (node.resourceFormat)
|
|
425
|
-
{
|
|
426
|
-
default:
|
|
427
|
-
return;
|
|
428
|
-
case "DLS1":
|
|
429
|
-
case "DLS2":
|
|
430
|
-
case "DLS22":
|
|
431
|
-
case "mobileDLS":
|
|
432
|
-
SpessaSynthInfo("%cFound embedded DLS!", consoleColors.recognized);
|
|
433
|
-
midi.embeddedSoundFont = node.nodeData.buffer;
|
|
434
|
-
break;
|
|
435
|
-
|
|
436
|
-
case "StandardMIDIFile":
|
|
437
|
-
case "StandardMIDIFileType1":
|
|
438
|
-
SpessaSynthInfo("%cFound embedded MIDI!", consoleColors.recognized);
|
|
439
|
-
midiArray = node.nodeData;
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
else
|
|
444
|
-
{
|
|
445
|
-
for (const n of node.innerNodes)
|
|
446
|
-
{
|
|
447
|
-
searchNode(n);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
searchNode(rootNode);
|
|
452
|
-
SpessaSynthGroupEnd();
|
|
453
|
-
return midiArray;
|
|
454
|
-
}
|