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,201 @@
1
+ import { consoleColors, formatTime } from "../utils/other.js";
2
+ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
3
+ import { BasicMIDI } from "../midi/basic_midi.js";
4
+
5
+ /**
6
+ * @param trackNum {number}
7
+ * @param port {number}
8
+ * @this {SpessaSynthSequencer}
9
+ */
10
+ export function assignMIDIPort(trackNum, port)
11
+ {
12
+ // do not assign ports to empty tracks
13
+ if (this.midiData.usedChannelsOnTrack[trackNum].size === 0)
14
+ {
15
+ return;
16
+ }
17
+
18
+ // assign new 16 channels if the port is not occupied yet
19
+ if (this.midiPortChannelOffset === 0)
20
+ {
21
+ this.midiPortChannelOffset += 16;
22
+ this.midiPortChannelOffsets[port] = 0;
23
+ }
24
+
25
+ if (this.midiPortChannelOffsets[port] === undefined)
26
+ {
27
+ if (this.synth.midiAudioChannels.length < this.midiPortChannelOffset + 15)
28
+ {
29
+ this._addNewMidiPort();
30
+ }
31
+ this.midiPortChannelOffsets[port] = this.midiPortChannelOffset;
32
+ this.midiPortChannelOffset += 16;
33
+ }
34
+
35
+ this.midiPorts[trackNum] = port;
36
+ }
37
+
38
+ /**
39
+ * Loads a new sequence
40
+ * @param parsedMidi {BasicMIDI}
41
+ * @param autoPlay {boolean}
42
+ * @this {SpessaSynthSequencer}
43
+ * @private
44
+ */
45
+ export function loadNewSequence(parsedMidi, autoPlay = true)
46
+ {
47
+ this.stop();
48
+ if (!parsedMidi.tracks)
49
+ {
50
+ throw new Error("This MIDI has no tracks!");
51
+ }
52
+
53
+ this.oneTickToSeconds = 60 / (120 * parsedMidi.timeDivision);
54
+
55
+ /**
56
+ * @type {BasicMIDI}
57
+ */
58
+ this.midiData = parsedMidi;
59
+
60
+ // check for embedded soundfont
61
+ if (this.midiData.embeddedSoundFont !== undefined)
62
+ {
63
+ SpessaSynthInfo("%cEmbedded soundfont detected! Using it.", consoleColors.recognized);
64
+ this.synth.setEmbeddedSoundFont(this.midiData.embeddedSoundFont, this.midiData.bankOffset);
65
+ }
66
+ else
67
+ {
68
+ if (this.synth.overrideSoundfont)
69
+ {
70
+ // clean up the embedded soundfont
71
+ this.synth.clearSoundFont(true, true);
72
+ }
73
+ SpessaSynthGroupCollapsed("%cPreloading samples...", consoleColors.info);
74
+ // smart preloading: load only samples used in the midi!
75
+ const used = this.midiData.getUsedProgramsAndKeys(this.synth.soundfontManager);
76
+ for (const [programBank, combos] of Object.entries(used))
77
+ {
78
+ const bank = parseInt(programBank.split(":")[0]);
79
+ const program = parseInt(programBank.split(":")[1]);
80
+ const preset = this.synth.getPreset(bank, program);
81
+ SpessaSynthInfo(
82
+ `%cPreloading used samples on %c${preset.presetName}%c...`,
83
+ consoleColors.info,
84
+ consoleColors.recognized,
85
+ consoleColors.info
86
+ );
87
+ for (const combo of combos)
88
+ {
89
+ const split = combo.split("-");
90
+ preset.preloadSpecific(parseInt(split[0]), parseInt(split[1]));
91
+ }
92
+ }
93
+ SpessaSynthGroupEnd();
94
+ }
95
+
96
+ /**
97
+ * the midi track data
98
+ * @type {MIDIMessage[][]}
99
+ */
100
+ this.tracks = this.midiData.tracks;
101
+
102
+ // copy over the port data
103
+ this.midiPorts = this.midiData.midiPorts.slice();
104
+
105
+ // clear last port data
106
+ this.midiPortChannelOffset = 0;
107
+ this.midiPortChannelOffsets = {};
108
+ // assign port offsets
109
+ this.midiData.midiPorts.forEach((port, trackIndex) =>
110
+ {
111
+ this.assignMIDIPort(trackIndex, port);
112
+ });
113
+
114
+ /**
115
+ * Same as "audio.duration" property (seconds)
116
+ * @type {number}
117
+ */
118
+ this.duration = this.midiData.duration;
119
+ this.firstNoteTime = this.midiData.MIDIticksToSeconds(this.midiData.firstNoteOn);
120
+ SpessaSynthInfo(`%cTotal song time: ${formatTime(Math.ceil(this.duration)).time}`, consoleColors.recognized);
121
+ this?.onSongChange?.(this.songIndex, autoPlay);
122
+
123
+ if (this.duration <= 1)
124
+ {
125
+ SpessaSynthWarn(
126
+ `%cVery short song: (${formatTime(Math.round(this.duration)).time}). Disabling loop!`,
127
+ consoleColors.warn
128
+ );
129
+ this.loop = false;
130
+ }
131
+ if (autoPlay)
132
+ {
133
+ this.play(true);
134
+ }
135
+ else
136
+ {
137
+ // this shall not play: play to the first note and then wait
138
+ const targetTime = this.skipToFirstNoteOn ? this.midiData.firstNoteOn - 1 : 0;
139
+ this.setTimeTicks(targetTime);
140
+ this.pause();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * @param midiBuffers {BasicMIDI[]}
146
+ * @param autoPlay {boolean}
147
+ * @this {SpessaSynthSequencer}
148
+ */
149
+ export function loadNewSongList(midiBuffers, autoPlay = true)
150
+ {
151
+ /**
152
+ * parse the MIDIs (only the array buffers, MIDI is unchanged)
153
+ * @type {BasicMIDI[]}
154
+ */
155
+ this.songs = midiBuffers.map(m => BasicMIDI.copyFrom(m));
156
+ if (this.songs.length < 1)
157
+ {
158
+ return;
159
+ }
160
+ this.songIndex = 0;
161
+ if (this.songs.length > 1)
162
+ {
163
+ this.loop = false;
164
+ }
165
+ this.shuffleSongIndexes();
166
+ this?.onSongListChange?.(this.songs);
167
+ this.loadCurrentSong(autoPlay);
168
+ }
169
+
170
+ /**
171
+ * @this {SpessaSynthSequencer}
172
+ */
173
+ export function nextSong()
174
+ {
175
+ if (this.songs.length === 1)
176
+ {
177
+ this.currentTime = 0;
178
+ return;
179
+ }
180
+ this.songIndex++;
181
+ this.songIndex %= this.songs.length;
182
+ this.loadCurrentSong();
183
+ }
184
+
185
+ /**
186
+ * @this {SpessaSynthSequencer}
187
+ */
188
+ export function previousSong()
189
+ {
190
+ if (this.songs.length === 1)
191
+ {
192
+ this.currentTime = 0;
193
+ return;
194
+ }
195
+ this.songIndex--;
196
+ if (this.songIndex < 0)
197
+ {
198
+ this.songIndex = this.songs.length - 1;
199
+ }
200
+ this.loadCurrentSong();
201
+ }
@@ -0,0 +1,13 @@
1
+ ## This is the SoundFont2 parsing library.
2
+
3
+ The code here is responsible for parsing the SoundFont2 file and
4
+ providing an easy way to get the data out.
5
+ Default modulators are also stored here (in `modulators.js`)
6
+
7
+ `basic_soundfont` folder contains the classes that represent the soundfont file.
8
+
9
+ `read_sf2` folder contains the code for reading an `.sf2` file.
10
+
11
+ `write` folder contains the code for writing out an `.sf2` file.
12
+
13
+ `dls` folder contains the code for reading a `.dls` file (and converting in into a soundfont representation).
@@ -0,0 +1,77 @@
1
+ export class BasicInstrument
2
+ {
3
+ /**
4
+ * The instrument's name
5
+ * @type {string}
6
+ */
7
+ instrumentName = "";
8
+
9
+ /**
10
+ * The instrument's zones
11
+ * @type {BasicInstrumentZone[]}
12
+ */
13
+ instrumentZones = [];
14
+
15
+ /**
16
+ * Instrument's use count, used for trimming
17
+ * @type {number}
18
+ * @private
19
+ */
20
+ _useCount = 0;
21
+
22
+ /**
23
+ * @returns {number}
24
+ */
25
+ get useCount()
26
+ {
27
+ return this._useCount;
28
+ }
29
+
30
+ addUseCount()
31
+ {
32
+ this._useCount++;
33
+ this.instrumentZones.forEach(z => z.useCount++);
34
+ }
35
+
36
+ removeUseCount()
37
+ {
38
+ this._useCount--;
39
+ for (let i = 0; i < this.instrumentZones.length; i++)
40
+ {
41
+ if (this.safeDeleteZone(i))
42
+ {
43
+ i--;
44
+ }
45
+ }
46
+ }
47
+
48
+ deleteInstrument()
49
+ {
50
+ this.instrumentZones.forEach(z => z.deleteZone());
51
+ this.instrumentZones.length = 0;
52
+ }
53
+
54
+ /**
55
+ * @param index {number}
56
+ * @returns {boolean} is the zone has been deleted
57
+ */
58
+ safeDeleteZone(index)
59
+ {
60
+ this.instrumentZones[index].useCount--;
61
+ if (this.instrumentZones[index].useCount < 1)
62
+ {
63
+ this.deleteZone(index);
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+
69
+ /**
70
+ * @param index {number}
71
+ */
72
+ deleteZone(index)
73
+ {
74
+ this.instrumentZones[index].deleteZone();
75
+ this.instrumentZones.splice(index, 1);
76
+ }
77
+ }
@@ -0,0 +1,336 @@
1
+ /**
2
+ * @typedef {{
3
+ * instrumentGenerators: Generator[],
4
+ * presetGenerators: Generator[],
5
+ * modulators: Modulator[],
6
+ * sample: BasicSample,
7
+ * sampleID: number,
8
+ * }} SampleAndGenerators
9
+ */
10
+ import { generatorTypes } from "./generator.js";
11
+ import { Modulator } from "./modulator.js";
12
+ import { isXGDrums } from "../../utils/xg_hacks.js";
13
+
14
+ export class BasicPreset
15
+ {
16
+ /**
17
+ * The parent soundbank instance
18
+ * Currently used for determining default modulators and XG status
19
+ * @type {BasicSoundBank}
20
+ */
21
+ parentSoundBank;
22
+
23
+ /**
24
+ * The preset's name
25
+ * @type {string}
26
+ */
27
+ presetName = "";
28
+
29
+ /**
30
+ * The preset's MIDI program number
31
+ * @type {number}
32
+ */
33
+ program = 0;
34
+
35
+ /**
36
+ * The preset's MIDI bank number
37
+ * @type {number}
38
+ */
39
+ bank = 0;
40
+
41
+ /**
42
+ * The preset's zones
43
+ * @type {BasicPresetZone[]}
44
+ */
45
+ presetZones = [];
46
+
47
+ /**
48
+ * Stores already found getSamplesAndGenerators for reuse
49
+ * @type {SampleAndGenerators[][][]}
50
+ */
51
+ foundSamplesAndGenerators = [];
52
+
53
+ /**
54
+ * unused metadata
55
+ * @type {number}
56
+ */
57
+ library = 0;
58
+ /**
59
+ * unused metadata
60
+ * @type {number}
61
+ */
62
+ genre = 0;
63
+ /**
64
+ * unused metadata
65
+ * @type {number}
66
+ */
67
+ morphology = 0;
68
+
69
+ /**
70
+ * Creates a new preset representation
71
+ * @param parentSoundBank {BasicSoundBank}
72
+ */
73
+ constructor(parentSoundBank)
74
+ {
75
+ this.parentSoundBank = parentSoundBank;
76
+ for (let i = 0; i < 128; i++)
77
+ {
78
+ this.foundSamplesAndGenerators[i] = [];
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @param allowXG {boolean}
84
+ * @param allowSFX {boolean}
85
+ * @returns {boolean}
86
+ */
87
+ isDrumPreset(allowXG, allowSFX = false)
88
+ {
89
+ const xg = allowXG && this.parentSoundBank.isXGBank;
90
+ // sfx is not cool
91
+ return this.bank === 128 || (
92
+ xg &&
93
+ (isXGDrums(this.bank) && (this.bank !== 126 || allowSFX))
94
+ );
95
+ }
96
+
97
+ deletePreset()
98
+ {
99
+ this.presetZones.forEach(z => z.deleteZone());
100
+ this.presetZones.length = 0;
101
+ }
102
+
103
+ /**
104
+ * @param index {number}
105
+ */
106
+ deleteZone(index)
107
+ {
108
+ this.presetZones[index].deleteZone();
109
+ this.presetZones.splice(index, 1);
110
+ }
111
+
112
+ // noinspection JSUnusedGlobalSymbols
113
+ /**
114
+ * Preloads all samples (async)
115
+ */
116
+ preload(keyMin, keyMax)
117
+ {
118
+ for (let key = keyMin; key < keyMax + 1; key++)
119
+ {
120
+ for (let velocity = 0; velocity < 128; velocity++)
121
+ {
122
+ this.getSamplesAndGenerators(key, velocity).forEach(samandgen =>
123
+ {
124
+ if (!samandgen.sample.isSampleLoaded)
125
+ {
126
+ samandgen.sample.getAudioData();
127
+ }
128
+ });
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Preloads a specific key/velocity combo
135
+ * @param key {number}
136
+ * @param velocity {number}
137
+ */
138
+ preloadSpecific(key, velocity)
139
+ {
140
+ this.getSamplesAndGenerators(key, velocity).forEach(samandgen =>
141
+ {
142
+ if (!samandgen.sample.isSampleLoaded)
143
+ {
144
+ samandgen.sample.getAudioData();
145
+ }
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Returns generatorTranslator and generators for given note
151
+ * @param midiNote {number}
152
+ * @param velocity {number}
153
+ * @returns {SampleAndGenerators[]}
154
+ */
155
+ getSamplesAndGenerators(midiNote, velocity)
156
+ {
157
+ const memorized = this.foundSamplesAndGenerators[midiNote][velocity];
158
+ if (memorized)
159
+ {
160
+ return memorized;
161
+ }
162
+
163
+ if (this.presetZones.length < 1)
164
+ {
165
+ return [];
166
+ }
167
+
168
+ /**
169
+ * @param range {SoundFontRange}
170
+ * @param number {number}
171
+ * @returns {boolean}
172
+ */
173
+ function isInRange(range, number)
174
+ {
175
+ return number >= range.min && number <= range.max;
176
+ }
177
+
178
+ /**
179
+ * @param main {Generator[]}
180
+ * @param adder {Generator[]}
181
+ */
182
+ function addUnique(main, adder)
183
+ {
184
+ main.push(...adder.filter(g => !main.find(mg => mg.generatorType === g.generatorType)));
185
+ }
186
+
187
+ /**
188
+ * @param main {Modulator[]}
189
+ * @param adder {Modulator[]}
190
+ */
191
+ function addUniqueMods(main, adder)
192
+ {
193
+ main.push(...adder.filter(m => !main.find(mm => Modulator.isIdentical(m, mm))));
194
+ }
195
+
196
+ /**
197
+ * @type {SampleAndGenerators[]}
198
+ */
199
+ let parsedGeneratorsAndSamples = [];
200
+
201
+ /**
202
+ * global zone is always first, so it or nothing
203
+ * @type {Generator[]}
204
+ */
205
+ let globalPresetGenerators = this.presetZones[0].isGlobal ? [...this.presetZones[0].generators] : [];
206
+
207
+ /**
208
+ * @type {Modulator[]}
209
+ */
210
+ let globalPresetModulators = this.presetZones[0].isGlobal ? [...this.presetZones[0].modulators] : [];
211
+ const globalKeyRange = this.presetZones[0].isGlobal ? this.presetZones[0].keyRange : { min: 0, max: 127 };
212
+ const globalVelRange = this.presetZones[0].isGlobal ? this.presetZones[0].velRange : { min: 0, max: 127 };
213
+
214
+ // find the preset zones in range
215
+ let presetZonesInRange = this.presetZones.filter(currentZone =>
216
+ (
217
+ isInRange(
218
+ currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
219
+ midiNote
220
+ )
221
+ &&
222
+ isInRange(
223
+ currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
224
+ velocity
225
+ )
226
+ ) && !currentZone.isGlobal);
227
+
228
+ presetZonesInRange.forEach(zone =>
229
+ {
230
+ // the global zone is already taken into account earlier
231
+ if (zone.instrument.instrumentZones.length < 1)
232
+ {
233
+ return;
234
+ }
235
+ let presetGenerators = zone.generators;
236
+ let presetModulators = zone.modulators;
237
+ const firstZone = zone.instrument.instrumentZones[0];
238
+ /**
239
+ * global zone is always first, so it or nothing
240
+ * @type {Generator[]}
241
+ */
242
+ let globalInstrumentGenerators = firstZone.isGlobal ? [...firstZone.generators] : [];
243
+ let globalInstrumentModulators = firstZone.isGlobal ? [...firstZone.modulators] : [];
244
+ const globalKeyRange = firstZone.isGlobal ? firstZone.keyRange : { min: 0, max: 127 };
245
+ const globalVelRange = firstZone.isGlobal ? firstZone.velRange : { min: 0, max: 127 };
246
+
247
+
248
+ let instrumentZonesInRange = zone.instrument.instrumentZones
249
+ .filter(currentZone =>
250
+ (
251
+ isInRange(
252
+ currentZone.hasKeyRange ? currentZone.keyRange : globalKeyRange,
253
+ midiNote
254
+ )
255
+ &&
256
+ isInRange(
257
+ currentZone.hasVelRange ? currentZone.velRange : globalVelRange,
258
+ velocity
259
+ )
260
+ ) && !currentZone.isGlobal
261
+ );
262
+
263
+ instrumentZonesInRange.forEach(instrumentZone =>
264
+ {
265
+ let instrumentGenerators = [...instrumentZone.generators];
266
+ let instrumentModulators = [...instrumentZone.modulators];
267
+
268
+ addUnique(
269
+ presetGenerators,
270
+ globalPresetGenerators
271
+ );
272
+ // add the unique global preset generators (local replace global(
273
+
274
+
275
+ // add the unique global instrument generators (local replace global)
276
+ addUnique(
277
+ instrumentGenerators,
278
+ globalInstrumentGenerators
279
+ );
280
+
281
+ addUniqueMods(
282
+ presetModulators,
283
+ globalPresetModulators
284
+ );
285
+ addUniqueMods(
286
+ instrumentModulators,
287
+ globalInstrumentModulators
288
+ );
289
+
290
+ // default mods
291
+ addUniqueMods(
292
+ instrumentModulators,
293
+ this.parentSoundBank.defaultModulators
294
+ );
295
+
296
+ /**
297
+ * sum preset modulators to instruments (amount) sf spec page 54
298
+ * @type {Modulator[]}
299
+ */
300
+ const finalModulatorList = [...instrumentModulators];
301
+ for (let i = 0; i < presetModulators.length; i++)
302
+ {
303
+ let mod = presetModulators[i];
304
+ const identicalInstrumentModulator = finalModulatorList.findIndex(
305
+ m => Modulator.isIdentical(mod, m));
306
+ if (identicalInstrumentModulator !== -1)
307
+ {
308
+ // sum the amounts
309
+ // (this makes a new modulator because otherwise it would overwrite the one in the soundfont!
310
+ finalModulatorList[identicalInstrumentModulator] = finalModulatorList[identicalInstrumentModulator].sumTransform(
311
+ mod);
312
+ }
313
+ else
314
+ {
315
+ finalModulatorList.push(mod);
316
+ }
317
+ }
318
+
319
+
320
+ // combine both generators and add to the final result
321
+ parsedGeneratorsAndSamples.push({
322
+ instrumentGenerators: instrumentGenerators,
323
+ presetGenerators: presetGenerators,
324
+ modulators: finalModulatorList,
325
+ sample: instrumentZone.sample,
326
+ sampleID: instrumentZone.generators.find(
327
+ g => g.generatorType === generatorTypes.sampleID).generatorValue
328
+ });
329
+ });
330
+ });
331
+
332
+ // save and return
333
+ this.foundSamplesAndGenerators[midiNote][velocity] = parsedGeneratorsAndSamples;
334
+ return parsedGeneratorsAndSamples;
335
+ }
336
+ }