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,565 @@
1
+ import {
2
+ SpessaSynthGroup,
3
+ SpessaSynthGroupCollapsed,
4
+ SpessaSynthGroupEnd,
5
+ SpessaSynthInfo,
6
+ SpessaSynthWarn
7
+ } from "../../utils/loggin.js";
8
+ import { consoleColors } from "../../utils/other.js";
9
+ import { write } from "./write_sf2/write.js";
10
+ import { defaultModulators, Modulator } from "./modulator.js";
11
+ import { writeDLS } from "./write_dls/write_dls.js";
12
+ import { BasicSample } from "./basic_sample.js";
13
+ import { BasicInstrumentZone, BasicPresetZone } from "./basic_zones.js";
14
+ import { Generator, generatorTypes } from "./generator.js";
15
+ import { BasicInstrument } from "./basic_instrument.js";
16
+ import { BasicPreset } from "./basic_preset.js";
17
+ import { isXGDrums } from "../../utils/xg_hacks.js";
18
+
19
+ class BasicSoundBank
20
+ {
21
+
22
+ /**
23
+ * Soundfont's info stored as name: value. ifil and iver are stored as string representation of float (e.g., 2.1)
24
+ * @type {Object<string, string|IndexedByteArray>}
25
+ */
26
+ soundFontInfo = {};
27
+
28
+ /**
29
+ * The soundfont's presets
30
+ * @type {BasicPreset[]}
31
+ */
32
+ presets = [];
33
+
34
+ /**
35
+ * The soundfont's samples
36
+ * @type {BasicSample[]}
37
+ */
38
+ samples = [];
39
+
40
+ /**
41
+ * The soundfont's instruments
42
+ * @type {BasicInstrument[]}
43
+ */
44
+ instruments = [];
45
+
46
+ /**
47
+ * Soundfont's default modulatorss
48
+ * @type {Modulator[]}
49
+ */
50
+ defaultModulators = defaultModulators.map(m => Modulator.copy(m));
51
+
52
+ /**
53
+ * Checks for XG drumsets and considers if this soundfont is XG.
54
+ * @type {boolean}
55
+ */
56
+ isXGBank = false;
57
+
58
+ /**
59
+ * Creates a new basic soundfont template
60
+ * @param data {undefined|{presets: BasicPreset[], info: Object<string, string>}}
61
+ */
62
+ constructor(data = undefined)
63
+ {
64
+ if (data?.presets)
65
+ {
66
+ this.presets.push(...data.presets);
67
+ this.soundFontInfo = data.info;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Merges soundfonts with the given order. Keep in mind that the info read is copied from the first one
73
+ * @param soundfonts {...BasicSoundBank} the soundfonts to merge, the first overwrites the last
74
+ * @returns {BasicSoundBank}
75
+ */
76
+ static mergeSoundBanks(...soundfonts)
77
+ {
78
+ const mainSf = soundfonts.shift();
79
+ const presets = mainSf.presets;
80
+ while (soundfonts.length)
81
+ {
82
+ const newPresets = soundfonts.shift().presets;
83
+ newPresets.forEach(newPreset =>
84
+ {
85
+ if (
86
+ presets.find(existingPreset => existingPreset.bank === newPreset.bank && existingPreset.program === newPreset.program) === undefined
87
+ )
88
+ {
89
+ presets.push(newPreset);
90
+ }
91
+ });
92
+ }
93
+
94
+ return new BasicSoundBank({ presets: presets, info: mainSf.soundFontInfo });
95
+ }
96
+
97
+ /**
98
+ * Creates a simple soundfont with one saw wave preset.
99
+ * @returns {ArrayBufferLike}
100
+ */
101
+ static getDummySoundfontFile()
102
+ {
103
+ const font = new BasicSoundBank();
104
+ const sample = new BasicSample(
105
+ "Saw",
106
+ 44100,
107
+ 65,
108
+ 20,
109
+ 0,
110
+ 0,
111
+ 0,
112
+ 127
113
+ );
114
+ sample.sampleData = new Float32Array(128);
115
+ for (let i = 0; i < 128; i++)
116
+ {
117
+ sample.sampleData[i] = (i / 128) * 2 - 1;
118
+ }
119
+ font.samples.push(sample);
120
+
121
+ const gZone = new BasicInstrumentZone();
122
+ gZone.isGlobal = true;
123
+ gZone.generators.push(new Generator(generatorTypes.initialAttenuation, 375));
124
+ gZone.generators.push(new Generator(generatorTypes.releaseVolEnv, -1000));
125
+ gZone.generators.push(new Generator(generatorTypes.sampleModes, 1));
126
+
127
+ const zone1 = new BasicInstrumentZone();
128
+ zone1.sample = sample;
129
+
130
+ const zone2 = new BasicInstrumentZone();
131
+ zone2.sample = sample;
132
+ zone2.generators.push(new Generator(generatorTypes.fineTune, -9));
133
+
134
+
135
+ const inst = new BasicInstrument();
136
+ inst.instrumentName = "Saw Wave";
137
+ inst.instrumentZones.push(gZone);
138
+ inst.instrumentZones.push(zone1);
139
+ inst.instrumentZones.push(zone2);
140
+ font.instruments.push(inst);
141
+
142
+ const pZone = new BasicPresetZone();
143
+ pZone.instrument = inst;
144
+
145
+ const preset = new BasicPreset(font);
146
+ preset.presetName = "Saw Wave";
147
+ preset.presetZones.push(pZone);
148
+ font.presets.push(preset);
149
+
150
+ font.soundFontInfo["ifil"] = "2.1";
151
+ font.soundFontInfo["isng"] = "EMU8000";
152
+ font.soundFontInfo["INAM"] = "Dummy";
153
+ font._parseInternal();
154
+ return font.write().buffer;
155
+ }
156
+
157
+ /**
158
+ * parses the bank after loading is done
159
+ * @protected
160
+ */
161
+ _parseInternal()
162
+ {
163
+ this.isXGBank = false;
164
+ // definitions for XG:
165
+ // at least one preset with bank 127, 126 or 120
166
+ // MUST be a valid XG bank.
167
+ // allowed banks: (see XG specification)
168
+ // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
169
+ // 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
170
+ // 64, 65, 66, 126, 127
171
+ const allowedPrograms = new Set([
172
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
173
+ 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
174
+ 64, 65, 66, 126, 127
175
+ ]);
176
+ for (const preset of this.presets)
177
+ {
178
+ if (isXGDrums(preset.bank))
179
+ {
180
+ this.isXGBank = true;
181
+ if (!allowedPrograms.has(preset.program))
182
+ {
183
+ // not valid!
184
+ this.isXGBank = false;
185
+ SpessaSynthInfo(
186
+ `%cThis bank is not valid XG. Preset %c${preset.bank}:${preset.program}%c is not a valid XG drum. XG mode will use presets on bank 128.`,
187
+ consoleColors.info,
188
+ consoleColors.value,
189
+ consoleColors.info
190
+ );
191
+ break;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Trims a sound bank to only contain samples in a given MIDI file
199
+ * @param mid {BasicMIDI} - the MIDI file
200
+ */
201
+ trimSoundBank(mid)
202
+ {
203
+ const soundfont = this;
204
+
205
+ /**
206
+ * @param instrument {Instrument}
207
+ * @param combos {{key: number, velocity: number}[]}
208
+ * @returns {number}
209
+ */
210
+ function trimInstrumentZones(instrument, combos)
211
+ {
212
+ let trimmedIZones = 0;
213
+ for (let iZoneIndex = 0; iZoneIndex < instrument.instrumentZones.length; iZoneIndex++)
214
+ {
215
+ const iZone = instrument.instrumentZones[iZoneIndex];
216
+ if (iZone.isGlobal)
217
+ {
218
+ continue;
219
+ }
220
+ const iKeyRange = iZone.keyRange;
221
+ const iVelRange = iZone.velRange;
222
+ let isIZoneUsed = false;
223
+ for (const iCombo of combos)
224
+ {
225
+ if (
226
+ (iCombo.key >= iKeyRange.min && iCombo.key <= iKeyRange.max) &&
227
+ (iCombo.velocity >= iVelRange.min && iCombo.velocity <= iVelRange.max)
228
+ )
229
+ {
230
+ isIZoneUsed = true;
231
+ break;
232
+ }
233
+ }
234
+ if (!isIZoneUsed)
235
+ {
236
+ SpessaSynthInfo(
237
+ `%c${iZone.sample.sampleName} %cremoved from %c${instrument.instrumentName}%c. Use count: %c${iZone.useCount - 1}`,
238
+ consoleColors.recognized,
239
+ consoleColors.info,
240
+ consoleColors.recognized,
241
+ consoleColors.info,
242
+ consoleColors.recognized
243
+ );
244
+ if (instrument.safeDeleteZone(iZoneIndex))
245
+ {
246
+ trimmedIZones++;
247
+ iZoneIndex--;
248
+ SpessaSynthInfo(
249
+ `%c${iZone.sample.sampleName} %cdeleted`,
250
+ consoleColors.recognized,
251
+ consoleColors.info
252
+ );
253
+ }
254
+ if (iZone.sample.useCount < 1)
255
+ {
256
+ soundfont.deleteSample(iZone.sample);
257
+ }
258
+ }
259
+
260
+ }
261
+ return trimmedIZones;
262
+ }
263
+
264
+ SpessaSynthGroup(
265
+ "%cTrimming soundfont...",
266
+ consoleColors.info
267
+ );
268
+ const usedProgramsAndKeys = mid.getUsedProgramsAndKeys(soundfont);
269
+
270
+ SpessaSynthGroupCollapsed(
271
+ "%cModifying soundfont...",
272
+ consoleColors.info
273
+ );
274
+ SpessaSynthInfo("Detected keys for midi:", usedProgramsAndKeys);
275
+ // modify the soundfont to only include programs and samples that are used
276
+ for (let presetIndex = 0; presetIndex < soundfont.presets.length; presetIndex++)
277
+ {
278
+ const p = soundfont.presets[presetIndex];
279
+ const string = p.bank + ":" + p.program;
280
+ const used = usedProgramsAndKeys[string];
281
+ if (used === undefined)
282
+ {
283
+ SpessaSynthInfo(
284
+ `%cDeleting preset %c${p.presetName}%c and its zones`,
285
+ consoleColors.info,
286
+ consoleColors.recognized,
287
+ consoleColors.info
288
+ );
289
+ soundfont.deletePreset(p);
290
+ presetIndex--;
291
+ }
292
+ else
293
+ {
294
+ const combos = [...used].map(s =>
295
+ {
296
+ const split = s.split("-");
297
+ return {
298
+ key: parseInt(split[0]),
299
+ velocity: parseInt(split[1])
300
+ };
301
+ });
302
+ SpessaSynthGroupCollapsed(
303
+ `%cTrimming %c${p.presetName}`,
304
+ consoleColors.info,
305
+ consoleColors.recognized
306
+ );
307
+ SpessaSynthInfo(`Keys for ${p.presetName}:`, combos);
308
+ let trimmedZones = 0;
309
+ // clean the preset to only use zones that are used
310
+ for (let zoneIndex = 0; zoneIndex < p.presetZones.length; zoneIndex++)
311
+ {
312
+ const zone = p.presetZones[zoneIndex];
313
+ if (zone.isGlobal)
314
+ {
315
+ continue;
316
+ }
317
+ const keyRange = zone.keyRange;
318
+ const velRange = zone.velRange;
319
+ // check if any of the combos matches the zone
320
+ let isZoneUsed = false;
321
+ for (const combo of combos)
322
+ {
323
+ if (
324
+ (combo.key >= keyRange.min && combo.key <= keyRange.max) &&
325
+ (combo.velocity >= velRange.min && combo.velocity <= velRange.max)
326
+ )
327
+ {
328
+ // zone is used, trim the instrument zones
329
+ isZoneUsed = true;
330
+ const trimmedIZones = trimInstrumentZones(zone.instrument, combos);
331
+ SpessaSynthInfo(
332
+ `%cTrimmed off %c${trimmedIZones}%c zones from %c${zone.instrument.instrumentName}`,
333
+ consoleColors.info,
334
+ consoleColors.recognized,
335
+ consoleColors.info,
336
+ consoleColors.recognized
337
+ );
338
+ break;
339
+ }
340
+ }
341
+ if (!isZoneUsed)
342
+ {
343
+ trimmedZones++;
344
+ p.deleteZone(zoneIndex);
345
+ if (zone.instrument.useCount < 1)
346
+ {
347
+ soundfont.deleteInstrument(zone.instrument);
348
+ }
349
+ zoneIndex--;
350
+ }
351
+ }
352
+ SpessaSynthInfo(
353
+ `%cTrimmed off %c${trimmedZones}%c zones from %c${p.presetName}`,
354
+ consoleColors.info,
355
+ consoleColors.recognized,
356
+ consoleColors.info,
357
+ consoleColors.recognized
358
+ );
359
+ SpessaSynthGroupEnd();
360
+ }
361
+ }
362
+ soundfont.removeUnusedElements();
363
+
364
+ soundfont.soundFontInfo["ICMT"] = `NOTE: This soundfont was trimmed by SpessaSynth to only contain presets used in "${mid.midiName}"\n\n`
365
+ + soundfont.soundFontInfo["ICMT"];
366
+
367
+ SpessaSynthInfo(
368
+ "%cSoundfont modified!",
369
+ consoleColors.recognized
370
+ );
371
+ SpessaSynthGroupEnd();
372
+ SpessaSynthGroupEnd();
373
+ }
374
+
375
+ removeUnusedElements()
376
+ {
377
+ this.instruments.forEach(i =>
378
+ {
379
+ if (i.useCount < 1)
380
+ {
381
+ i.instrumentZones.forEach(z =>
382
+ {
383
+ if (!z.isGlobal)
384
+ {
385
+ z.sample.useCount--;
386
+ }
387
+ });
388
+ }
389
+ });
390
+ this.instruments = this.instruments.filter(i => i.useCount > 0);
391
+ this.samples = this.samples.filter(s => s.useCount > 0);
392
+ }
393
+
394
+ /**
395
+ * @param instrument {BasicInstrument}
396
+ */
397
+ deleteInstrument(instrument)
398
+ {
399
+ if (instrument.useCount > 0)
400
+ {
401
+ throw new Error(`Cannot delete an instrument that has ${instrument.useCount} usages.`);
402
+ }
403
+ this.instruments.splice(this.instruments.indexOf(instrument), 1);
404
+ instrument.deleteInstrument();
405
+ this.removeUnusedElements();
406
+ }
407
+
408
+ /**
409
+ * @param preset {BasicPreset}
410
+ */
411
+ deletePreset(preset)
412
+ {
413
+ preset.deletePreset();
414
+ this.presets.splice(this.presets.indexOf(preset), 1);
415
+ this.removeUnusedElements();
416
+ }
417
+
418
+ /**
419
+ * @param sample {BasicSample}
420
+ */
421
+ deleteSample(sample)
422
+ {
423
+ if (sample.useCount > 0)
424
+ {
425
+ throw new Error(`Cannot delete sample that has ${sample.useCount} usages.`);
426
+ }
427
+ this.samples.splice(this.samples.indexOf(sample), 1);
428
+ this.removeUnusedElements();
429
+ }
430
+
431
+ /**
432
+ * Get the appropriate preset, undefined if not found
433
+ * @param bankNr {number}
434
+ * @param programNr {number}
435
+ * @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
436
+ * @return {BasicPreset}
437
+ */
438
+ getPresetNoFallback(bankNr, programNr, allowXGDrums = false)
439
+ {
440
+ const isDrum = bankNr === 128 || (allowXGDrums && isXGDrums(bankNr));
441
+ // check for exact match
442
+ let p;
443
+ if (isDrum)
444
+ {
445
+ p = this.presets.find(p => p.bank === bankNr && p.isDrumPreset(allowXGDrums) && p.program === programNr);
446
+ }
447
+ else
448
+ {
449
+ p = this.presets.find(p => p.bank === bankNr && p.program === programNr);
450
+ }
451
+ if (p)
452
+ {
453
+ return p;
454
+ }
455
+ // no match...
456
+ if (isDrum)
457
+ {
458
+ if (allowXGDrums)
459
+ {
460
+ // try any drum preset with matching program?
461
+ const p = this.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNr);
462
+ if (p)
463
+ {
464
+ return p;
465
+ }
466
+ }
467
+ }
468
+ return undefined;
469
+ }
470
+
471
+ /**
472
+ * Get the appropriate preset
473
+ * @param bankNr {number}
474
+ * @param programNr {number}
475
+ * @param allowXGDrums {boolean} if true, allows XG drum banks (120, 126 and 127) as drum preset
476
+ * @returns {BasicPreset}
477
+ */
478
+ getPreset(bankNr, programNr, allowXGDrums = false)
479
+ {
480
+ const isDrums = bankNr === 128 || (allowXGDrums && isXGDrums(bankNr));
481
+ // check for exact match
482
+ let preset;
483
+ // only allow drums if the preset is considered to be a drum preset
484
+ if (isDrums)
485
+ {
486
+ preset = this.presets.find(p => p.bank === bankNr && p.isDrumPreset(allowXGDrums) && p.program === programNr);
487
+ }
488
+ else
489
+ {
490
+ preset = this.presets.find(p => p.bank === bankNr && p.program === programNr);
491
+ }
492
+ if (preset)
493
+ {
494
+ return preset;
495
+ }
496
+ // no match...
497
+ if (isDrums)
498
+ {
499
+ // drum preset: find any preset with bank 128
500
+ preset = this.presets.find(p => p.isDrumPreset(allowXGDrums) && p.program === programNr);
501
+ if (!preset)
502
+ {
503
+ // only allow 128, otherwise it would default to XG SFX
504
+ preset = this.presets.find(p => p.isDrumPreset(allowXGDrums));
505
+ }
506
+ }
507
+ else
508
+ {
509
+ // non-drum preset: find any preset with the given program that is not a drum preset
510
+ preset = this.presets.find(p => p.program === programNr && !p.isDrumPreset(allowXGDrums));
511
+ }
512
+ if (preset)
513
+ {
514
+ SpessaSynthWarn(
515
+ `%cPreset ${bankNr}.${programNr} not found. Replaced with %c${preset.presetName} (${preset.bank}.${preset.program})`,
516
+ consoleColors.warn,
517
+ consoleColors.recognized
518
+ );
519
+ }
520
+
521
+ // no preset, use the first one available
522
+ if (!preset)
523
+ {
524
+ SpessaSynthWarn(`Preset ${programNr} not found. Defaulting to`, this.presets[0].presetName);
525
+ preset = this.presets[0];
526
+ }
527
+ return preset;
528
+ }
529
+
530
+ /**
531
+ * gets preset by name
532
+ * @param presetName {string}
533
+ * @returns {BasicPreset}
534
+ */
535
+ getPresetByName(presetName)
536
+ {
537
+ let preset = this.presets.find(p => p.presetName === presetName);
538
+ if (!preset)
539
+ {
540
+ SpessaSynthWarn("Preset not found. Defaulting to:", this.presets[0].presetName);
541
+ preset = this.presets[0];
542
+ }
543
+ return preset;
544
+ }
545
+
546
+ /**
547
+ * @param error {string}
548
+ */
549
+ parsingError(error)
550
+ {
551
+ throw new Error(`SF parsing error: ${error} The file may be corrupted.`);
552
+ }
553
+
554
+ destroySoundBank()
555
+ {
556
+ delete this.presets;
557
+ delete this.instruments;
558
+ delete this.samples;
559
+ }
560
+ }
561
+
562
+ BasicSoundBank.prototype.write = write;
563
+ BasicSoundBank.prototype.writeDLS = writeDLS;
564
+
565
+ export { BasicSoundBank };
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @typedef {Object} SoundFontRange
3
+ * @property {number} min - the minimum midi note
4
+ * @property {number} max - the maximum midi note
5
+ */
6
+
7
+ export class BasicZone
8
+ {
9
+ /**
10
+ * The zone's velocity range
11
+ * min -1 means that it is a default value
12
+ * @type {SoundFontRange}
13
+ */
14
+ velRange = { min: -1, max: 127 };
15
+
16
+ /**
17
+ * The zone's key range
18
+ * min -1 means that it is a default value
19
+ * @type {SoundFontRange}
20
+ */
21
+ keyRange = { min: -1, max: 127 };
22
+ /**
23
+ * Indicates if the zone is global
24
+ * @type {boolean}
25
+ */
26
+ isGlobal = false;
27
+ /**
28
+ * The zone's generators
29
+ * @type {Generator[]}
30
+ */
31
+ generators = [];
32
+ /**
33
+ * The zone's modulators
34
+ * @type {Modulator[]}
35
+ */
36
+ modulators = [];
37
+
38
+ /**
39
+ * @returns {boolean}
40
+ */
41
+ get hasKeyRange()
42
+ {
43
+ return this.keyRange.min !== -1;
44
+ }
45
+
46
+ /**
47
+ * @returns {boolean}
48
+ */
49
+ get hasVelRange()
50
+ {
51
+ return this.velRange.min !== -1;
52
+ }
53
+
54
+ /**
55
+ * @param generatorType {generatorTypes}
56
+ * @param notFoundValue {number}
57
+ * @returns {number}
58
+ */
59
+ getGeneratorValue(generatorType, notFoundValue)
60
+ {
61
+ return this.generators.find(g => g.generatorType === generatorType)?.generatorValue ?? notFoundValue;
62
+ }
63
+ }
64
+
@@ -0,0 +1,43 @@
1
+ import { BasicZone } from "./basic_zone.js";
2
+
3
+ export class BasicInstrumentZone extends BasicZone
4
+ {
5
+ /**
6
+ * Zone's sample. Undefined if global
7
+ * @type {BasicSample|undefined}
8
+ */
9
+ sample = undefined;
10
+ /**
11
+ * The zone's use count
12
+ * @type {number}
13
+ */
14
+ useCount = 0;
15
+
16
+ deleteZone()
17
+ {
18
+ this.useCount--;
19
+ if (this.isGlobal)
20
+ {
21
+ return;
22
+ }
23
+ this.sample.useCount--;
24
+ }
25
+ }
26
+
27
+ export class BasicPresetZone extends BasicZone
28
+ {
29
+ /**
30
+ * Zone's instrument. Undefined if global
31
+ * @type {BasicInstrument|undefined}
32
+ */
33
+ instrument = undefined;
34
+
35
+ deleteZone()
36
+ {
37
+ if (this.isGlobal)
38
+ {
39
+ return;
40
+ }
41
+ this.instrument.removeUseCount();
42
+ }
43
+ }