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.
Files changed (157) hide show
  1. package/README.md +85 -51
  2. package/dist/index.d.ts +4057 -0
  3. package/dist/index.js +17178 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +24 -6
  6. package/index.js +0 -132
  7. package/src/externals/README.md +0 -6
  8. package/src/externals/fflate/LICENSE +0 -21
  9. package/src/externals/fflate/fflate.min.js +0 -1
  10. package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
  11. package/src/externals/stbvorbis_sync/LICENSE +0 -202
  12. package/src/externals/stbvorbis_sync/NOTICE +0 -6
  13. package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
  14. package/src/midi/README.md +0 -32
  15. package/src/midi/basic_midi.js +0 -587
  16. package/src/midi/midi_builder.js +0 -203
  17. package/src/midi/midi_loader.js +0 -321
  18. package/src/midi/midi_message.js +0 -254
  19. package/src/midi/midi_sequence.js +0 -230
  20. package/src/midi/midi_tools/get_note_times.js +0 -154
  21. package/src/midi/midi_tools/midi_editor.js +0 -611
  22. package/src/midi/midi_tools/midi_writer.js +0 -105
  23. package/src/midi/midi_tools/rmidi_writer.js +0 -566
  24. package/src/midi/midi_tools/used_keys_loaded.js +0 -256
  25. package/src/midi/xmf_loader.js +0 -454
  26. package/src/sequencer/README.md +0 -9
  27. package/src/sequencer/events.js +0 -81
  28. package/src/sequencer/play.js +0 -362
  29. package/src/sequencer/process_event.js +0 -165
  30. package/src/sequencer/process_tick.js +0 -104
  31. package/src/sequencer/sequencer_engine.js +0 -372
  32. package/src/sequencer/song_control.js +0 -196
  33. package/src/soundfont/README.md +0 -11
  34. package/src/soundfont/basic_soundfont/basic_global_zone.js +0 -6
  35. package/src/soundfont/basic_soundfont/basic_instrument.js +0 -115
  36. package/src/soundfont/basic_soundfont/basic_instrument_zone.js +0 -45
  37. package/src/soundfont/basic_soundfont/basic_preset.js +0 -313
  38. package/src/soundfont/basic_soundfont/basic_preset_zone.js +0 -39
  39. package/src/soundfont/basic_soundfont/basic_sample.js +0 -477
  40. package/src/soundfont/basic_soundfont/basic_soundbank.js +0 -740
  41. package/src/soundfont/basic_soundfont/basic_zone.js +0 -145
  42. package/src/soundfont/basic_soundfont/generator.js +0 -76
  43. package/src/soundfont/basic_soundfont/generator_types.js +0 -151
  44. package/src/soundfont/basic_soundfont/modulator.js +0 -581
  45. package/src/soundfont/basic_soundfont/riff_chunk.js +0 -195
  46. package/src/soundfont/basic_soundfont/write_dls/art2.js +0 -174
  47. package/src/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
  48. package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -374
  49. package/src/soundfont/basic_soundfont/write_dls/ins.js +0 -85
  50. package/src/soundfont/basic_soundfont/write_dls/lins.js +0 -15
  51. package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
  52. package/src/soundfont/basic_soundfont/write_dls/rgn2.js +0 -120
  53. package/src/soundfont/basic_soundfont/write_dls/wave.js +0 -71
  54. package/src/soundfont/basic_soundfont/write_dls/write_dls.js +0 -124
  55. package/src/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
  56. package/src/soundfont/basic_soundfont/write_dls/wvpl.js +0 -35
  57. package/src/soundfont/basic_soundfont/write_sf2/ibag.js +0 -60
  58. package/src/soundfont/basic_soundfont/write_sf2/igen.js +0 -91
  59. package/src/soundfont/basic_soundfont/write_sf2/imod.js +0 -62
  60. package/src/soundfont/basic_soundfont/write_sf2/inst.js +0 -42
  61. package/src/soundfont/basic_soundfont/write_sf2/pbag.js +0 -57
  62. package/src/soundfont/basic_soundfont/write_sf2/pgen.js +0 -92
  63. package/src/soundfont/basic_soundfont/write_sf2/phdr.js +0 -61
  64. package/src/soundfont/basic_soundfont/write_sf2/pmod.js +0 -62
  65. package/src/soundfont/basic_soundfont/write_sf2/sdta.js +0 -131
  66. package/src/soundfont/basic_soundfont/write_sf2/shdr.js +0 -77
  67. package/src/soundfont/basic_soundfont/write_sf2/write.js +0 -287
  68. package/src/soundfont/dls/articulator_converter.js +0 -402
  69. package/src/soundfont/dls/dls_destinations.js +0 -38
  70. package/src/soundfont/dls/dls_instrument.js +0 -20
  71. package/src/soundfont/dls/dls_preset.js +0 -43
  72. package/src/soundfont/dls/dls_sample.js +0 -238
  73. package/src/soundfont/dls/dls_soundfont.js +0 -183
  74. package/src/soundfont/dls/dls_sources.js +0 -63
  75. package/src/soundfont/dls/dls_zone.js +0 -89
  76. package/src/soundfont/dls/read_articulation.js +0 -300
  77. package/src/soundfont/dls/read_instrument.js +0 -118
  78. package/src/soundfont/dls/read_instrument_list.js +0 -17
  79. package/src/soundfont/dls/read_lart.js +0 -35
  80. package/src/soundfont/dls/read_region.js +0 -157
  81. package/src/soundfont/dls/read_samples.js +0 -154
  82. package/src/soundfont/load_soundfont.js +0 -21
  83. package/src/soundfont/read_sf2/generators.js +0 -43
  84. package/src/soundfont/read_sf2/instrument_zones.js +0 -75
  85. package/src/soundfont/read_sf2/instruments.js +0 -71
  86. package/src/soundfont/read_sf2/modulators.js +0 -25
  87. package/src/soundfont/read_sf2/preset_zones.js +0 -79
  88. package/src/soundfont/read_sf2/presets.js +0 -80
  89. package/src/soundfont/read_sf2/samples.js +0 -317
  90. package/src/soundfont/read_sf2/soundfont.js +0 -452
  91. package/src/soundfont/read_sf2/zones.js +0 -28
  92. package/src/synthetizer/README.md +0 -7
  93. package/src/synthetizer/audio_engine/README.md +0 -9
  94. package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -289
  95. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +0 -90
  96. package/src/synthetizer/audio_engine/engine_components/dynamic_modulator_system.js +0 -95
  97. package/src/synthetizer/audio_engine/engine_components/enums.js +0 -18
  98. package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -151
  99. package/src/synthetizer/audio_engine/engine_components/lfo.js +0 -26
  100. package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
  101. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -551
  102. package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
  103. package/src/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
  104. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +0 -265
  105. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -124
  106. package/src/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
  107. package/src/synthetizer/audio_engine/engine_components/voice.js +0 -525
  108. package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -402
  109. package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -274
  110. package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -159
  111. package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -53
  112. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -254
  113. package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -20
  114. package/src/synthetizer/audio_engine/engine_methods/data_entry/awe32.js +0 -198
  115. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -281
  116. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -109
  117. package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
  118. package/src/synthetizer/audio_engine/engine_methods/note_on.js +0 -220
  119. package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
  120. package/src/synthetizer/audio_engine/engine_methods/program_change.js +0 -35
  121. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +0 -214
  122. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/embedded_sound_bank.js +0 -42
  123. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -0
  124. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/update_preset_list.js +0 -19
  125. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -23
  126. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -56
  127. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
  128. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
  129. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
  130. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -1058
  131. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -23
  132. package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -31
  133. package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -29
  134. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
  135. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -26
  136. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
  137. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
  138. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
  139. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
  140. package/src/synthetizer/audio_engine/main_processor.js +0 -817
  141. package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -16
  142. package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
  143. package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -116
  144. package/src/synthetizer/audio_engine/synth_processor_options.js +0 -18
  145. package/src/synthetizer/synth_constants.js +0 -26
  146. package/src/utils/README.md +0 -8
  147. package/src/utils/buffer_to_wav.js +0 -197
  148. package/src/utils/byte_functions/big_endian.js +0 -32
  149. package/src/utils/byte_functions/little_endian.js +0 -77
  150. package/src/utils/byte_functions/string.js +0 -92
  151. package/src/utils/byte_functions/variable_length_quantity.js +0 -42
  152. package/src/utils/fill_with_defaults.js +0 -21
  153. package/src/utils/indexed_array.js +0 -34
  154. package/src/utils/loggin.js +0 -71
  155. package/src/utils/other.js +0 -92
  156. package/src/utils/sysex_detector.js +0 -58
  157. 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
- }
@@ -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
- }