spessasynth_core 1.1.3 → 1.1.4

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 (189) hide show
  1. package/LICENSE +3 -26
  2. package/README.md +156 -474
  3. package/index.js +73 -8
  4. package/package.json +21 -8
  5. package/src/externals/fflate/LICENSE +21 -0
  6. package/src/externals/fflate/fflate.min.js +1 -0
  7. package/src/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  8. package/src/externals/stbvorbis_sync/LICENSE +202 -0
  9. package/src/externals/stbvorbis_sync/NOTICE +6 -0
  10. package/src/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  11. package/src/midi/README.md +32 -0
  12. package/src/midi/basic_midi.js +567 -0
  13. package/src/midi/midi_builder.js +202 -0
  14. package/src/midi/midi_loader.js +324 -0
  15. package/{spessasynth_core/midi_parser → src/midi}/midi_message.js +58 -35
  16. package/src/midi/midi_sequence.js +224 -0
  17. package/src/midi/midi_tools/get_note_times.js +154 -0
  18. package/src/midi/midi_tools/midi_editor.js +611 -0
  19. package/src/midi/midi_tools/midi_writer.js +99 -0
  20. package/src/midi/midi_tools/rmidi_writer.js +567 -0
  21. package/src/midi/midi_tools/used_keys_loaded.js +238 -0
  22. package/src/midi/xmf_loader.js +454 -0
  23. package/src/sequencer/README.md +5 -0
  24. package/src/sequencer/events.js +81 -0
  25. package/src/sequencer/play.js +349 -0
  26. package/src/sequencer/process_event.js +165 -0
  27. package/{spessasynth_core/sequencer/worklet_sequencer → src/sequencer}/process_tick.js +103 -84
  28. package/src/sequencer/sequencer_engine.js +367 -0
  29. package/src/sequencer/song_control.js +201 -0
  30. package/src/soundfont/README.md +13 -0
  31. package/src/soundfont/basic_soundfont/basic_instrument.js +77 -0
  32. package/src/soundfont/basic_soundfont/basic_preset.js +336 -0
  33. package/src/soundfont/basic_soundfont/basic_sample.js +206 -0
  34. package/src/soundfont/basic_soundfont/basic_soundfont.js +565 -0
  35. package/src/soundfont/basic_soundfont/basic_zone.js +64 -0
  36. package/src/soundfont/basic_soundfont/basic_zones.js +43 -0
  37. package/src/soundfont/basic_soundfont/generator.js +220 -0
  38. package/src/soundfont/basic_soundfont/modulator.js +378 -0
  39. package/src/soundfont/basic_soundfont/riff_chunk.js +149 -0
  40. package/src/soundfont/basic_soundfont/write_dls/art2.js +173 -0
  41. package/src/soundfont/basic_soundfont/write_dls/articulator.js +49 -0
  42. package/src/soundfont/basic_soundfont/write_dls/combine_zones.js +400 -0
  43. package/src/soundfont/basic_soundfont/write_dls/ins.js +103 -0
  44. package/src/soundfont/basic_soundfont/write_dls/lins.js +18 -0
  45. package/src/soundfont/basic_soundfont/write_dls/modulator_converter.js +330 -0
  46. package/src/soundfont/basic_soundfont/write_dls/rgn2.js +121 -0
  47. package/src/soundfont/basic_soundfont/write_dls/wave.js +94 -0
  48. package/src/soundfont/basic_soundfont/write_dls/write_dls.js +119 -0
  49. package/src/soundfont/basic_soundfont/write_dls/wsmp.js +78 -0
  50. package/src/soundfont/basic_soundfont/write_dls/wvpl.js +32 -0
  51. package/src/soundfont/basic_soundfont/write_sf2/ibag.js +39 -0
  52. package/src/soundfont/basic_soundfont/write_sf2/igen.js +80 -0
  53. package/src/soundfont/basic_soundfont/write_sf2/imod.js +46 -0
  54. package/src/soundfont/basic_soundfont/write_sf2/inst.js +34 -0
  55. package/src/soundfont/basic_soundfont/write_sf2/pbag.js +39 -0
  56. package/src/soundfont/basic_soundfont/write_sf2/pgen.js +82 -0
  57. package/src/soundfont/basic_soundfont/write_sf2/phdr.js +42 -0
  58. package/src/soundfont/basic_soundfont/write_sf2/pmod.js +46 -0
  59. package/src/soundfont/basic_soundfont/write_sf2/sdta.js +80 -0
  60. package/src/soundfont/basic_soundfont/write_sf2/shdr.js +55 -0
  61. package/src/soundfont/basic_soundfont/write_sf2/write.js +222 -0
  62. package/src/soundfont/dls/articulator_converter.js +396 -0
  63. package/src/soundfont/dls/dls_destinations.js +38 -0
  64. package/src/soundfont/dls/dls_preset.js +44 -0
  65. package/src/soundfont/dls/dls_sample.js +75 -0
  66. package/src/soundfont/dls/dls_soundfont.js +186 -0
  67. package/src/soundfont/dls/dls_sources.js +62 -0
  68. package/src/soundfont/dls/dls_zone.js +95 -0
  69. package/src/soundfont/dls/read_articulation.js +299 -0
  70. package/src/soundfont/dls/read_instrument.js +121 -0
  71. package/src/soundfont/dls/read_instrument_list.js +17 -0
  72. package/src/soundfont/dls/read_lart.js +35 -0
  73. package/src/soundfont/dls/read_region.js +152 -0
  74. package/src/soundfont/dls/read_samples.js +270 -0
  75. package/src/soundfont/load_soundfont.js +21 -0
  76. package/src/soundfont/read_sf2/generators.js +46 -0
  77. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/instruments.js +20 -14
  78. package/src/soundfont/read_sf2/modulators.js +36 -0
  79. package/src/soundfont/read_sf2/presets.js +80 -0
  80. package/src/soundfont/read_sf2/samples.js +304 -0
  81. package/src/soundfont/read_sf2/soundfont.js +305 -0
  82. package/{spessasynth_core/soundfont/chunk → src/soundfont/read_sf2}/zones.js +68 -69
  83. package/src/synthetizer/README.md +7 -0
  84. package/src/synthetizer/audio_engine/README.md +9 -0
  85. package/src/synthetizer/audio_engine/engine_components/compute_modulator.js +266 -0
  86. package/src/synthetizer/audio_engine/engine_components/controller_tables.js +88 -0
  87. package/src/synthetizer/audio_engine/engine_components/key_modifier_manager.js +150 -0
  88. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/lfo.js +9 -6
  89. package/src/synthetizer/audio_engine/engine_components/lowpass_filter.js +282 -0
  90. package/src/synthetizer/audio_engine/engine_components/midi_audio_channel.js +467 -0
  91. package/src/synthetizer/audio_engine/engine_components/modulation_envelope.js +181 -0
  92. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/modulator_curves.js +33 -30
  93. package/src/synthetizer/audio_engine/engine_components/soundfont_manager.js +221 -0
  94. package/src/synthetizer/audio_engine/engine_components/stereo_panner.js +120 -0
  95. package/{spessasynth_core/synthetizer/worklet_system/worklet_utilities → src/synthetizer/audio_engine/engine_components}/unit_converter.js +11 -4
  96. package/src/synthetizer/audio_engine/engine_components/voice.js +519 -0
  97. package/src/synthetizer/audio_engine/engine_components/volume_envelope.js +401 -0
  98. package/src/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +263 -0
  99. package/src/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +132 -0
  100. package/src/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +48 -0
  101. package/src/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +241 -0
  102. package/src/synthetizer/audio_engine/engine_methods/create_midi_channel.js +27 -0
  103. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +253 -0
  104. package/src/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +66 -0
  105. package/src/synthetizer/audio_engine/engine_methods/mute_channel.js +17 -0
  106. package/src/synthetizer/audio_engine/engine_methods/note_on.js +175 -0
  107. package/src/synthetizer/audio_engine/engine_methods/portamento_time.js +92 -0
  108. package/src/synthetizer/audio_engine/engine_methods/program_change.js +61 -0
  109. package/src/synthetizer/audio_engine/engine_methods/render_voice.js +196 -0
  110. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +30 -0
  111. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +22 -0
  112. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +28 -0
  113. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +31 -0
  114. package/src/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  115. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +20 -0
  116. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +55 -0
  117. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +16 -0
  118. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +30 -0
  119. package/src/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +63 -0
  120. package/src/synthetizer/audio_engine/engine_methods/system_exclusive.js +776 -0
  121. package/src/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +24 -0
  122. package/src/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +33 -0
  123. package/src/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +31 -0
  124. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +15 -0
  125. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +27 -0
  126. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +19 -0
  127. package/src/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +27 -0
  128. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +15 -0
  129. package/src/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +34 -0
  130. package/src/synthetizer/audio_engine/main_processor.js +804 -0
  131. package/src/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +15 -0
  132. package/src/synthetizer/audio_engine/snapshot/channel_snapshot.js +175 -0
  133. package/src/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +116 -0
  134. package/src/synthetizer/synth_constants.js +22 -0
  135. package/{spessasynth_core → src}/utils/README.md +1 -0
  136. package/src/utils/buffer_to_wav.js +185 -0
  137. package/src/utils/byte_functions/big_endian.js +32 -0
  138. package/src/utils/byte_functions/little_endian.js +77 -0
  139. package/src/utils/byte_functions/string.js +107 -0
  140. package/src/utils/byte_functions/variable_length_quantity.js +42 -0
  141. package/src/utils/fill_with_defaults.js +21 -0
  142. package/src/utils/indexed_array.js +52 -0
  143. package/{spessasynth_core → src}/utils/loggin.js +70 -78
  144. package/src/utils/other.js +92 -0
  145. package/src/utils/sysex_detector.js +58 -0
  146. package/src/utils/xg_hacks.js +193 -0
  147. package/.idea/inspectionProfiles/Project_Default.xml +0 -10
  148. package/.idea/jsLibraryMappings.xml +0 -6
  149. package/.idea/modules.xml +0 -8
  150. package/.idea/spessasynth_core.iml +0 -12
  151. package/.idea/vcs.xml +0 -6
  152. package/spessasynth_core/midi_parser/README.md +0 -3
  153. package/spessasynth_core/midi_parser/midi_loader.js +0 -386
  154. package/spessasynth_core/sequencer/sequencer.js +0 -202
  155. package/spessasynth_core/sequencer/worklet_sequencer/play.js +0 -209
  156. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +0 -120
  157. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +0 -112
  158. package/spessasynth_core/soundfont/README.md +0 -4
  159. package/spessasynth_core/soundfont/chunk/generators.js +0 -205
  160. package/spessasynth_core/soundfont/chunk/modulators.js +0 -232
  161. package/spessasynth_core/soundfont/chunk/presets.js +0 -264
  162. package/spessasynth_core/soundfont/chunk/riff_chunk.js +0 -46
  163. package/spessasynth_core/soundfont/chunk/samples.js +0 -250
  164. package/spessasynth_core/soundfont/soundfont_parser.js +0 -301
  165. package/spessasynth_core/synthetizer/README.md +0 -6
  166. package/spessasynth_core/synthetizer/synthesizer.js +0 -313
  167. package/spessasynth_core/synthetizer/worklet_system/README.md +0 -3
  168. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -290
  169. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -280
  170. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +0 -102
  171. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +0 -77
  172. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +0 -140
  173. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -266
  174. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -104
  175. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
  176. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +0 -223
  177. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +0 -133
  178. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +0 -73
  179. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +0 -76
  180. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -272
  181. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -83
  182. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +0 -175
  183. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +0 -106
  184. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +0 -285
  185. package/spessasynth_core/utils/buffer_to_wav.js +0 -70
  186. package/spessasynth_core/utils/byte_functions.js +0 -141
  187. package/spessasynth_core/utils/other.js +0 -49
  188. package/spessasynth_core/utils/shiftable_array.js +0 -26
  189. package/spessasynth_core/utils/stbvorbis_sync.js +0 -1877
@@ -0,0 +1,238 @@
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
+
8
+ /**
9
+ * Gets the used programs and keys for this MIDI file with a given sound bank
10
+ * @this {BasicMIDI}
11
+ * @param soundfont {BasicSoundBank|WorkletSoundfontManager} - the sound bank
12
+ * @returns {Object<string, Set<string>>} Object<bank:program, Set<key-velocity>>
13
+ */
14
+ export function getUsedProgramsAndKeys(soundfont)
15
+ {
16
+ const mid = this;
17
+ SpessaSynthGroupCollapsed(
18
+ "%cSearching for all used programs and keys...",
19
+ consoleColors.info
20
+ );
21
+ // Find every bank:program combo and every key:velocity for each. Make sure to care about ports and drums
22
+ const channelsAmount = 16 + mid.midiPortChannelOffsets.reduce((max, cur) => cur > max ? cur : max);
23
+ /**
24
+ * @type {{program: number, bank: number, bankLSB: number, drums: boolean, string: string, actualBank: number}[]}
25
+ */
26
+ const channelPresets = [];
27
+ for (let i = 0; i < channelsAmount; i++)
28
+ {
29
+ const bank = i % 16 === DEFAULT_PERCUSSION ? 128 : 0;
30
+ channelPresets.push({
31
+ program: 0,
32
+ bank: bank,
33
+ bankLSB: 0,
34
+ actualBank: bank,
35
+ drums: i % 16 === DEFAULT_PERCUSSION, // drums appear on 9 every 16 channels,
36
+ string: `${bank}:0`
37
+ });
38
+ }
39
+
40
+ // check for xg
41
+ let system = "gs";
42
+
43
+ function updateString(ch)
44
+ {
45
+ const bank = chooseBank(ch.bank, ch.bankLSB, ch.drums, isSystemXG(system));
46
+ // check if this exists in the soundfont
47
+ let exists = soundfont.getPreset(bank, ch.program, isSystemXG(system));
48
+ ch.actualBank = exists.bank;
49
+ ch.program = exists.program;
50
+ ch.string = ch.actualBank + ":" + ch.program;
51
+ if (!usedProgramsAndKeys[ch.string])
52
+ {
53
+ SpessaSynthInfo(
54
+ `%cDetected a new preset: %c${ch.string}`,
55
+ consoleColors.info,
56
+ consoleColors.recognized
57
+ );
58
+ usedProgramsAndKeys[ch.string] = new Set();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * find all programs used and key-velocity combos in them
64
+ * bank:program each has a set of midiNote-velocity
65
+ * @type {Object<string, Set<string>>}
66
+ */
67
+ const usedProgramsAndKeys = {};
68
+
69
+ /**
70
+ * indexes for tracks
71
+ * @type {number[]}
72
+ */
73
+ const eventIndexes = Array(mid.tracks.length).fill(0);
74
+ let remainingTracks = mid.tracks.length;
75
+
76
+ function findFirstEventIndex()
77
+ {
78
+ let index = 0;
79
+ let ticks = Infinity;
80
+ mid.tracks.forEach((track, i) =>
81
+ {
82
+ if (eventIndexes[i] >= track.length)
83
+ {
84
+ return;
85
+ }
86
+ if (track[eventIndexes[i]].ticks < ticks)
87
+ {
88
+ index = i;
89
+ ticks = track[eventIndexes[i]].ticks;
90
+ }
91
+ });
92
+ return index;
93
+ }
94
+
95
+ const ports = mid.midiPorts.slice();
96
+ // initialize
97
+ channelPresets.forEach(c =>
98
+ {
99
+ updateString(c);
100
+ });
101
+ while (remainingTracks > 0)
102
+ {
103
+ let trackNum = findFirstEventIndex();
104
+ const track = mid.tracks[trackNum];
105
+ if (eventIndexes[trackNum] >= track.length)
106
+ {
107
+ remainingTracks--;
108
+ continue;
109
+ }
110
+ const event = track[eventIndexes[trackNum]];
111
+ eventIndexes[trackNum]++;
112
+
113
+ if (event.messageStatusByte === messageTypes.midiPort)
114
+ {
115
+ ports[trackNum] = event.messageData[0];
116
+ continue;
117
+ }
118
+ const status = event.messageStatusByte & 0xF0;
119
+ if (
120
+ status !== messageTypes.noteOn &&
121
+ status !== messageTypes.controllerChange &&
122
+ status !== messageTypes.programChange &&
123
+ status !== messageTypes.systemExclusive
124
+ )
125
+ {
126
+ continue;
127
+ }
128
+ const channel = (event.messageStatusByte & 0xF) + mid.midiPortChannelOffsets[ports[trackNum]] || 0;
129
+ let ch = channelPresets[channel];
130
+ switch (status)
131
+ {
132
+ case messageTypes.programChange:
133
+ ch.program = event.messageData[0];
134
+ updateString(ch);
135
+ break;
136
+
137
+ case messageTypes.controllerChange:
138
+ const isLSB = event.messageData[0] === midiControllers.lsbForControl0BankSelect;
139
+ if (event.messageData[0] !== midiControllers.bankSelect && !isLSB)
140
+ {
141
+ // we only care about bank select
142
+ continue;
143
+ }
144
+ if (system === "gs" && ch.drums)
145
+ {
146
+ // gs drums get changed via sysex, ignore here
147
+ continue;
148
+ }
149
+ const bank = event.messageData[1];
150
+ const realBank = Math.max(0, bank - mid.bankOffset);
151
+ if (isLSB)
152
+ {
153
+ ch.bankLSB = realBank;
154
+ }
155
+ else
156
+ {
157
+ ch.bank = realBank;
158
+ }
159
+ // interpret the bank
160
+ const intepretation = parseBankSelect(
161
+ ch.bank,
162
+ realBank,
163
+ system,
164
+ isLSB,
165
+ ch.drums,
166
+ channel
167
+ );
168
+ switch (intepretation.drumsStatus)
169
+ {
170
+ case 0:
171
+ // no change
172
+ break;
173
+
174
+ case 1:
175
+ // drums changed to off
176
+ // drum change is a program change
177
+ ch.drums = false;
178
+ updateString(ch);
179
+ break;
180
+
181
+ case 2:
182
+ // drums changed to on
183
+ // drum change is a program change
184
+ ch.drums = true;
185
+ updateString(ch);
186
+ break;
187
+ }
188
+ // do not update the data, bank change doesn't change the preset
189
+ break;
190
+
191
+ case messageTypes.noteOn:
192
+ if (event.messageData[1] === 0)
193
+ {
194
+ // that's a note off
195
+ continue;
196
+ }
197
+ usedProgramsAndKeys[ch.string].add(`${event.messageData[0]}-${event.messageData[1]}`);
198
+ break;
199
+
200
+ case messageTypes.systemExclusive:
201
+ // check for drum sysex
202
+ if (!isGSDrumsOn(event))
203
+ {
204
+ // check for XG
205
+ if (isXGOn(event))
206
+ {
207
+ system = "xg";
208
+ SpessaSynthInfo(
209
+ "%cXG on detected!",
210
+ consoleColors.recognized
211
+ );
212
+ }
213
+ continue;
214
+ }
215
+ 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]];
216
+ const isDrum = !!(event.messageData[7] > 0 && event.messageData[5] >> 4);
217
+ ch = channelPresets[sysexChannel];
218
+ ch.drums = isDrum;
219
+ updateString(ch);
220
+ break;
221
+
222
+ }
223
+ }
224
+ for (const key of Object.keys(usedProgramsAndKeys))
225
+ {
226
+ if (usedProgramsAndKeys[key].size === 0)
227
+ {
228
+ SpessaSynthInfo(
229
+ `%cDetected change but no keys for %c${key}`,
230
+ consoleColors.info,
231
+ consoleColors.value
232
+ );
233
+ delete usedProgramsAndKeys[key];
234
+ }
235
+ }
236
+ SpessaSynthGroupEnd();
237
+ return usedProgramsAndKeys;
238
+ }
@@ -0,0 +1,454 @@
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 {Object<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 {MIDI}
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
+ }
@@ -0,0 +1,5 @@
1
+ ## This is the sequencer's folder.
2
+
3
+ The code here is responsible for playing back the parsed MIDI sequence with the synthesizer.
4
+
5
+ - `sequencer_engine` - the core sequencer engine