spessasynth_core 1.1.3 → 1.1.5

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 +74 -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,222 @@
1
+ import { combineArrays, IndexedByteArray } from "../../../utils/indexed_array.js";
2
+ import { RiffChunk, writeRIFFChunk } from "../riff_chunk.js";
3
+ import { writeStringAsBytes } from "../../../utils/byte_functions/string.js";
4
+ import { consoleColors } from "../../../utils/other.js";
5
+ import { getIGEN } from "./igen.js";
6
+ import { getSDTA } from "./sdta.js";
7
+ import { getSHDR } from "./shdr.js";
8
+ import { getIMOD } from "./imod.js";
9
+ import { getIBAG } from "./ibag.js";
10
+ import { getINST } from "./inst.js";
11
+ import { getPGEN } from "./pgen.js";
12
+ import { getPMOD } from "./pmod.js";
13
+ import { getPBAG } from "./pbag.js";
14
+ import { getPHDR } from "./phdr.js";
15
+ import { writeWord } from "../../../utils/byte_functions/little_endian.js";
16
+ import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../../utils/loggin.js";
17
+ /**
18
+ * @typedef {Object} SoundFont2WriteOptions
19
+ * @property {boolean} compress - if the soundfont should be compressed with the Ogg Vorbis codec
20
+ * @property {number} compressionQuality - the vorbis compression quality, from -0.1 to 1
21
+ * @property {EncodeVorbisFunction|undefined} compressionFunction - the encode vorbis function.
22
+ * Can be undefined if not compressed.
23
+ */
24
+
25
+ /**
26
+ * @type {SoundFont2WriteOptions}
27
+ */
28
+ const DEFAULT_WRITE_OPTIONS = {
29
+ compress: false,
30
+ compressionQuality: 0.5,
31
+ compressionFunction: undefined
32
+ };
33
+
34
+ /**
35
+ * Write the soundfont as an .sf2 file. This method is DESTRUCTIVE
36
+ * @this {BasicSoundBank}
37
+ * @param {SoundFont2WriteOptions} options
38
+ * @returns {Uint8Array}
39
+ */
40
+ export function write(options = DEFAULT_WRITE_OPTIONS)
41
+ {
42
+ if (options.compress)
43
+ {
44
+ if (typeof options.compressionFunction !== "function")
45
+ {
46
+ throw new TypeError("No compression function supplied but compression enabled.");
47
+ }
48
+ }
49
+ SpessaSynthGroupCollapsed(
50
+ "%cSaving soundfont...",
51
+ consoleColors.info
52
+ );
53
+ SpessaSynthInfo(
54
+ `%cCompression: %c${options?.compress || "false"}%c quality: %c${options?.compressionQuality || "none"}`,
55
+ consoleColors.info,
56
+ consoleColors.recognized,
57
+ consoleColors.info,
58
+ consoleColors.recognized
59
+ );
60
+ SpessaSynthInfo(
61
+ "%cWriting INFO...",
62
+ consoleColors.info
63
+ );
64
+ /**
65
+ * Write INFO
66
+ * @type {IndexedByteArray[]}
67
+ */
68
+ const infoArrays = [];
69
+ this.soundFontInfo["ISFT"] = "SpessaSynth"; // ( ͡° ͜ʖ ͡°)
70
+ if (options?.compress)
71
+ {
72
+ this.soundFontInfo["ifil"] = "3.0"; // set version to 3
73
+ }
74
+
75
+ for (const [type, data] of Object.entries(this.soundFontInfo))
76
+ {
77
+ if (type === "ifil" || type === "iver")
78
+ {
79
+ const major = parseInt(data.split(".")[0]);
80
+ const minor = parseInt(data.split(".")[1]);
81
+ const ckdata = new IndexedByteArray(4);
82
+ writeWord(ckdata, major);
83
+ writeWord(ckdata, minor);
84
+ infoArrays.push(writeRIFFChunk(new RiffChunk(
85
+ type,
86
+ 4,
87
+ ckdata
88
+ )));
89
+ }
90
+ else if (type === "DMOD")
91
+ {
92
+ infoArrays.push(writeRIFFChunk(new RiffChunk(
93
+ type,
94
+ data.length,
95
+ data
96
+ )));
97
+ }
98
+ else
99
+ {
100
+ const arr = new IndexedByteArray(data.length);
101
+ writeStringAsBytes(arr, data);
102
+ infoArrays.push(writeRIFFChunk(new RiffChunk(
103
+ type,
104
+ data.length,
105
+ arr
106
+ )));
107
+ }
108
+ }
109
+ const combined = combineArrays([
110
+ new IndexedByteArray([73, 78, 70, 79]), // INFO
111
+ ...infoArrays
112
+ ]);
113
+ const infoChunk = writeRIFFChunk(new RiffChunk("LIST", combined.length, combined));
114
+
115
+ SpessaSynthInfo(
116
+ "%cWriting SDTA...",
117
+ consoleColors.info
118
+ );
119
+ // write sdta
120
+ const smplStartOffsets = [];
121
+ const smplEndOffsets = [];
122
+ const sdtaChunk = getSDTA.call(
123
+ this,
124
+ smplStartOffsets,
125
+ smplEndOffsets,
126
+ options?.compress,
127
+ options?.compressionQuality ?? 0.5,
128
+ options.compressionFunction
129
+ );
130
+
131
+ SpessaSynthInfo(
132
+ "%cWriting PDTA...",
133
+ consoleColors.info
134
+ );
135
+ // write pdta
136
+ // go in reverse so the indexes are correct
137
+ // instruments
138
+ SpessaSynthInfo(
139
+ "%cWriting SHDR...",
140
+ consoleColors.info
141
+ );
142
+ const shdrChunk = getSHDR.call(this, smplStartOffsets, smplEndOffsets);
143
+ SpessaSynthInfo(
144
+ "%cWriting IGEN...",
145
+ consoleColors.info
146
+ );
147
+ const igenChunk = getIGEN.call(this);
148
+ SpessaSynthInfo(
149
+ "%cWriting IMOD...",
150
+ consoleColors.info
151
+ );
152
+ const imodChunk = getIMOD.call(this);
153
+ SpessaSynthInfo(
154
+ "%cWriting IBAG...",
155
+ consoleColors.info
156
+ );
157
+ const ibagChunk = getIBAG.call(this);
158
+ SpessaSynthInfo(
159
+ "%cWriting INST...",
160
+ consoleColors.info
161
+ );
162
+ const instChunk = getINST.call(this);
163
+ // presets
164
+ const pgenChunk = getPGEN.call(this);
165
+ SpessaSynthInfo(
166
+ "%cWriting PMOD...",
167
+ consoleColors.info
168
+ );
169
+ const pmodChunk = getPMOD.call(this);
170
+ SpessaSynthInfo(
171
+ "%cWriting PBAG...",
172
+ consoleColors.info
173
+ );
174
+ const pbagChunk = getPBAG.call(this);
175
+ SpessaSynthInfo(
176
+ "%cWriting PHDR...",
177
+ consoleColors.info
178
+ );
179
+ const phdrChunk = getPHDR.call(this);
180
+ // combine in the sfspec order
181
+ const pdtadata = combineArrays([
182
+ new IndexedByteArray([112, 100, 116, 97]), // "pdta"
183
+ phdrChunk,
184
+ pbagChunk,
185
+ pmodChunk,
186
+ pgenChunk,
187
+ instChunk,
188
+ ibagChunk,
189
+ imodChunk,
190
+ igenChunk,
191
+ shdrChunk
192
+ ]);
193
+ const pdtaChunk = writeRIFFChunk(new RiffChunk(
194
+ "LIST",
195
+ pdtadata.length,
196
+ pdtadata
197
+ ));
198
+ SpessaSynthInfo(
199
+ "%cWriting the output file...",
200
+ consoleColors.info
201
+ );
202
+ // finally, combine everything
203
+ const riffdata = combineArrays([
204
+ new IndexedByteArray([115, 102, 98, 107]), // "sfbk"
205
+ infoChunk,
206
+ sdtaChunk,
207
+ pdtaChunk
208
+ ]);
209
+
210
+ const main = writeRIFFChunk(new RiffChunk(
211
+ "RIFF",
212
+ riffdata.length,
213
+ riffdata
214
+ ));
215
+ SpessaSynthInfo(
216
+ `%cSaved succesfully! Final file size: %c${main.length}`,
217
+ consoleColors.info,
218
+ consoleColors.recognized
219
+ );
220
+ SpessaSynthGroupEnd();
221
+ return main;
222
+ }
@@ -0,0 +1,396 @@
1
+ import { DLSSources } from "./dls_sources.js";
2
+ import { getModSourceEnum, Modulator, modulatorCurveTypes, modulatorSources } from "../basic_soundfont/modulator.js";
3
+ import { midiControllers } from "../../midi/midi_message.js";
4
+ import { DLSDestinations } from "./dls_destinations.js";
5
+
6
+ import { generatorTypes } from "../basic_soundfont/generator.js";
7
+ import { consoleColors } from "../../utils/other.js";
8
+ import { SpessaSynthWarn } from "../../utils/loggin.js";
9
+
10
+ /**
11
+ * @param source {number}
12
+ * @returns {{enum: number, isCC: boolean}|undefined}
13
+ */
14
+ function getSF2SourceFromDLS(source)
15
+ {
16
+ let sourceEnum = undefined;
17
+ let isCC = false;
18
+ switch (source)
19
+ {
20
+ default:
21
+ case DLSSources.modLfo:
22
+ case DLSSources.vibratoLfo:
23
+ case DLSSources.coarseTune:
24
+ case DLSSources.fineTune:
25
+ case DLSSources.modEnv:
26
+ return undefined; // cannot be this in sf2
27
+
28
+ case DLSSources.keyNum:
29
+ sourceEnum = modulatorSources.noteOnKeyNum;
30
+ break;
31
+ case DLSSources.none:
32
+ sourceEnum = modulatorSources.noController;
33
+ break;
34
+ case DLSSources.modulationWheel:
35
+ sourceEnum = midiControllers.modulationWheel;
36
+ isCC = true;
37
+ break;
38
+ case DLSSources.pan:
39
+ sourceEnum = midiControllers.pan;
40
+ isCC = true;
41
+ break;
42
+ case DLSSources.reverb:
43
+ sourceEnum = midiControllers.reverbDepth;
44
+ isCC = true;
45
+ break;
46
+ case DLSSources.chorus:
47
+ sourceEnum = midiControllers.chorusDepth;
48
+ isCC = true;
49
+ break;
50
+ case DLSSources.expression:
51
+ sourceEnum = midiControllers.expressionController;
52
+ isCC = true;
53
+ break;
54
+ case DLSSources.volume:
55
+ sourceEnum = midiControllers.mainVolume;
56
+ isCC = true;
57
+ break;
58
+ case DLSSources.velocity:
59
+ sourceEnum = modulatorSources.noteOnVelocity;
60
+ break;
61
+ case DLSSources.polyPressure:
62
+ sourceEnum = modulatorSources.polyPressure;
63
+ break;
64
+ case DLSSources.channelPressure:
65
+ sourceEnum = modulatorSources.channelPressure;
66
+ break;
67
+ case DLSSources.pitchWheel:
68
+ sourceEnum = modulatorSources.pitchWheel;
69
+ break;
70
+ case DLSSources.pitchWheelRange:
71
+ sourceEnum = modulatorSources.pitchWheelRange;
72
+ break;
73
+ }
74
+ if (sourceEnum === undefined)
75
+ {
76
+ throw new Error(`Unknown DLS Source: ${source}`);
77
+ }
78
+ return { enum: sourceEnum, isCC: isCC };
79
+ }
80
+
81
+ /**
82
+ * @param destination {number}
83
+ * @param amount {number}
84
+ * @returns {generatorTypes|{gen: generatorTypes, newAmount: number}} // transform amount to sf2 units
85
+ */
86
+ function getSF2GeneratorFromDLS(destination, amount)
87
+ {
88
+ switch (destination)
89
+ {
90
+ default:
91
+ case DLSDestinations.none:
92
+ return undefined;
93
+ case DLSDestinations.pan:
94
+ return generatorTypes.pan;
95
+ case DLSDestinations.gain:
96
+ return { gen: generatorTypes.initialAttenuation, newAmount: amount * -1 };
97
+ case DLSDestinations.pitch:
98
+ return generatorTypes.fineTune;
99
+ case DLSDestinations.keyNum:
100
+ return generatorTypes.overridingRootKey;
101
+
102
+ // vol env
103
+ case DLSDestinations.volEnvDelay:
104
+ return generatorTypes.delayVolEnv;
105
+ case DLSDestinations.volEnvAttack:
106
+ return generatorTypes.attackVolEnv;
107
+ case DLSDestinations.volEnvHold:
108
+ return generatorTypes.holdVolEnv;
109
+ case DLSDestinations.volEnvDecay:
110
+ return generatorTypes.decayVolEnv;
111
+ case DLSDestinations.volEnvSustain:
112
+ return { gen: generatorTypes.sustainVolEnv, newAmount: 1000 - amount };
113
+ case DLSDestinations.volEnvRelease:
114
+ return generatorTypes.releaseVolEnv;
115
+
116
+ // mod env
117
+ case DLSDestinations.modEnvDelay:
118
+ return generatorTypes.delayModEnv;
119
+ case DLSDestinations.modEnvAttack:
120
+ return generatorTypes.attackModEnv;
121
+ case DLSDestinations.modEnvHold:
122
+ return generatorTypes.holdModEnv;
123
+ case DLSDestinations.modEnvDecay:
124
+ return generatorTypes.decayModEnv;
125
+ case DLSDestinations.modEnvSustain:
126
+ return { gen: generatorTypes.sustainModEnv, newAmount: (1000 - amount) / 10 };
127
+ case DLSDestinations.modEnvRelease:
128
+ return generatorTypes.releaseModEnv;
129
+
130
+ case DLSDestinations.filterCutoff:
131
+ return generatorTypes.initialFilterFc;
132
+ case DLSDestinations.filterQ:
133
+ return generatorTypes.initialFilterQ;
134
+ case DLSDestinations.chorusSend:
135
+ return generatorTypes.chorusEffectsSend;
136
+ case DLSDestinations.reverbSend:
137
+ return generatorTypes.reverbEffectsSend;
138
+
139
+ // lfo
140
+ case DLSDestinations.modLfoFreq:
141
+ return generatorTypes.freqModLFO;
142
+ case DLSDestinations.modLfoDelay:
143
+ return generatorTypes.delayModLFO;
144
+ case DLSDestinations.vibLfoFreq:
145
+ return generatorTypes.freqVibLFO;
146
+ case DLSDestinations.vibLfoDelay:
147
+ return generatorTypes.delayVibLFO;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * checks for combos such as mod lfo as source and pitch as destination which results in modLfoToPitch
153
+ * @param source {number}
154
+ * @param destination {number}
155
+ * @returns {generatorTypes} real destination
156
+ */
157
+ function checkForSpecialDLSCombo(source, destination)
158
+ {
159
+ if (source === DLSSources.vibratoLfo && destination === DLSDestinations.pitch)
160
+ {
161
+ // vibrato lfo to pitch
162
+ return generatorTypes.vibLfoToPitch;
163
+ }
164
+ else if (source === DLSSources.modLfo && destination === DLSDestinations.pitch)
165
+ {
166
+ // mod lfo to pitch
167
+ return generatorTypes.modLfoToPitch;
168
+ }
169
+ else if (source === DLSSources.modLfo && destination === DLSDestinations.filterCutoff)
170
+ {
171
+ // mod lfo to filter
172
+ return generatorTypes.modLfoToFilterFc;
173
+ }
174
+ else if (source === DLSSources.modLfo && destination === DLSDestinations.gain)
175
+ {
176
+ // mod lfo to volume
177
+ return generatorTypes.modLfoToVolume;
178
+ }
179
+ else if (source === DLSSources.modEnv && destination === DLSDestinations.filterCutoff)
180
+ {
181
+ // mod envelope to filter
182
+ return generatorTypes.modEnvToFilterFc;
183
+ }
184
+ else if (source === DLSSources.modEnv && destination === DLSDestinations.pitch)
185
+ {
186
+ // mod envelope to pitch
187
+ return generatorTypes.modEnvToPitch;
188
+ }
189
+ else
190
+ {
191
+ return undefined;
192
+ }
193
+ }
194
+
195
+ // noinspection JSUnusedGlobalSymbols
196
+ /**
197
+ * @param source {number}
198
+ * @param control {number}
199
+ * @param destination {number}
200
+ * @param value {number}
201
+ * @param transform {number}
202
+ * @param msg {string}
203
+ */
204
+ export function modulatorConverterDebug(
205
+ source,
206
+ control,
207
+ destination,
208
+ value,
209
+ transform,
210
+ msg = "Attempting to convert the following DLS Articulator to SF2 Modulator:"
211
+ )
212
+ {
213
+ const type = Object.keys(DLSDestinations).find(k => DLSDestinations[k] === destination);
214
+ const srcType = Object.keys(DLSSources).find(k => DLSSources[k] === source);
215
+ const ctrlType = Object.keys(DLSSources).find(k => DLSSources[k] === control);
216
+ const typeString = type ? type : destination.toString(16);
217
+ const srcString = srcType ? srcType : source.toString(16);
218
+ const ctrlString = ctrlType ? ctrlType : control.toString(16);
219
+ console.debug(
220
+ `%c${msg}
221
+ Source: %c${srcString}%c
222
+ Control: %c${ctrlString}%c
223
+ Destination: %c${typeString}%c
224
+ Amount: %c${value}%c
225
+ Transform: %c${transform}%c...`,
226
+ consoleColors.info,
227
+ consoleColors.recognized,
228
+ consoleColors.info,
229
+ consoleColors.recognized,
230
+ consoleColors.info,
231
+ consoleColors.recognized,
232
+ consoleColors.info,
233
+ consoleColors.recognized,
234
+ consoleColors.info,
235
+ consoleColors.recognized,
236
+ consoleColors.info
237
+ );
238
+ }
239
+
240
+ /**
241
+ * @param source {number}
242
+ * @param control {number}
243
+ * @param destination {number}
244
+ * @param transform {number}
245
+ * @param value {number}
246
+ * @returns {Modulator|undefined}
247
+ */
248
+ export function getSF2ModulatorFromArticulator(
249
+ source,
250
+ control,
251
+ destination,
252
+ transform,
253
+ value
254
+ )
255
+ {
256
+ // modulatorConverterDebug(
257
+ // source,
258
+ // control,
259
+ // destination,
260
+ // value,
261
+ // transform
262
+ // );
263
+ // check for special combinations
264
+ const specialDestination = checkForSpecialDLSCombo(source, destination);
265
+ /**
266
+ * @type {generatorTypes}
267
+ */
268
+ let destinationGenerator;
269
+ /**
270
+ * @type {{enum: number, isCC: boolean}}
271
+ */
272
+ let sf2Source;
273
+ let swapSources = false;
274
+ let isSourceNoController = false;
275
+ let newValue = value;
276
+ if (specialDestination === undefined)
277
+ {
278
+ // determine destination
279
+ const sf2GenDestination = getSF2GeneratorFromDLS(destination, value);
280
+ if (sf2GenDestination === undefined)
281
+ {
282
+ // cannot be a valid modulator
283
+ SpessaSynthWarn(`Invalid destination: ${destination}`);
284
+ return undefined;
285
+ }
286
+ /**
287
+ * @type {generatorTypes}
288
+ */
289
+ destinationGenerator = sf2GenDestination;
290
+ if (sf2GenDestination.newAmount !== undefined)
291
+ {
292
+ newValue = sf2GenDestination.newAmount;
293
+ destinationGenerator = sf2GenDestination.gen;
294
+ }
295
+ sf2Source = getSF2SourceFromDLS(source);
296
+ if (sf2Source === undefined)
297
+ {
298
+ // cannot be a valid modulator
299
+ SpessaSynthWarn(`Invalid source: ${source}`);
300
+ return undefined;
301
+ }
302
+ }
303
+ else
304
+ {
305
+ destinationGenerator = specialDestination;
306
+ swapSources = true;
307
+ sf2Source = { enum: modulatorSources.noController, isCC: false };
308
+ isSourceNoController = true;
309
+ }
310
+ let sf2SecondSource = getSF2SourceFromDLS(control);
311
+ if (sf2SecondSource === undefined)
312
+ {
313
+ // cannot be a valid modulator
314
+ SpessaSynthWarn(`Invalid control: ${control}`);
315
+ return undefined;
316
+ }
317
+
318
+ // get transforms and final enums
319
+ let sourceEnumFinal;
320
+ if (isSourceNoController)
321
+ {
322
+ // we force it into this state because before it was some strange value,
323
+ // like vibrato lfo bipolar, for example,
324
+ // since we turn it into NoController -> vibLfoToPitch,
325
+ // the result is the same and bipolar controller is technically 0
326
+ sourceEnumFinal = 0x0;
327
+ }
328
+ else
329
+ {
330
+ // output transform is ignored as it's not a thing in sfont format
331
+ // unless the curve type of source is linear, then output is copied
332
+ const outputTransform = transform & 0b1111;
333
+ // source curve type maps to a desfont curve type in section 2.10, table 9
334
+ let sourceTransform = (transform >> 10) & 0b1111;
335
+ if (sourceTransform === modulatorCurveTypes.linear && outputTransform !== modulatorCurveTypes.linear)
336
+ {
337
+ sourceTransform = outputTransform;
338
+ }
339
+ const sourceIsBipolar = (transform >> 14) & 1;
340
+ let sourceIsNegative = (transform >> 15) & 1;
341
+ // special case: for attenuation, invert source (dls gain is the opposite of sf2 attenuation)
342
+ if (destinationGenerator === generatorTypes.initialAttenuation)
343
+ {
344
+ // if the value is negative, the source shall be negative!
345
+ // why?
346
+ // IDK, it makes it work with ROCK.RMI and NOKIA_S30.dls
347
+ if (value < 0)
348
+ {
349
+ sourceIsNegative = 1;
350
+ }
351
+ }
352
+ sourceEnumFinal = getModSourceEnum(
353
+ sourceTransform,
354
+ sourceIsBipolar,
355
+ sourceIsNegative,
356
+ sf2Source.isCC,
357
+ sf2Source.enum
358
+ );
359
+ }
360
+
361
+ // a corrupted rendition of gm.dls was found under
362
+ // https://sembiance.com/fileFormatSamples/audio/downloadableSoundBank/
363
+ // which specifies a whopping -32,768 decibels of attenuation
364
+ if (destinationGenerator === generatorTypes.initialAttenuation)
365
+ {
366
+ newValue = Math.max(960, Math.min(0, newValue));
367
+ }
368
+
369
+ const secSourceTransform = (transform >> 4) & 0b1111;
370
+ const secSourceIsBipolar = (transform >> 8) & 1;
371
+ const secSourceIsNegative = transform >> 9 & 1;
372
+ let secSourceEnumFinal = getModSourceEnum(
373
+ secSourceTransform,
374
+ secSourceIsBipolar,
375
+ secSourceIsNegative,
376
+ sf2SecondSource.isCC,
377
+ sf2SecondSource.enum
378
+ );
379
+
380
+ if (swapSources)
381
+ {
382
+ const temp = secSourceEnumFinal;
383
+ secSourceEnumFinal = sourceEnumFinal;
384
+ sourceEnumFinal = temp;
385
+ }
386
+
387
+ // return the modulator!
388
+ return new Modulator(
389
+ sourceEnumFinal,
390
+ secSourceEnumFinal,
391
+ destinationGenerator,
392
+ newValue,
393
+ 0x0
394
+ );
395
+
396
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ *
3
+ * @enum {number}
4
+ */
5
+ export const DLSDestinations = {
6
+ none: 0x0, // no destination
7
+ gain: 0x1, // linear gain
8
+ reserved: 0x2, // reserved
9
+ pitch: 0x3, // pitch in cents
10
+ pan: 0x4, // pan 10ths of a percent
11
+ keyNum: 0x5, // MIDI key number
12
+ // nuh uh, the channel controllers are not supported!
13
+ chorusSend: 0x80, // chorus send level 10ths of a percent
14
+ reverbSend: 0x81, // reverb send level 10ths of a percent
15
+
16
+ modLfoFreq: 0x104, // modulation LFO frequency
17
+ modLfoDelay: 0x105, // modulation LFO delay
18
+
19
+ vibLfoFreq: 0x114, // vibrato LFO frequency
20
+ vibLfoDelay: 0x115, // vibrato LFO delay
21
+
22
+ volEnvAttack: 0x206, // volume envelope attack
23
+ volEnvDecay: 0x207, // volume envelope decay
24
+ volEnvRelease: 0x209, // volume envelope release
25
+ volEnvSustain: 0x20a, // volume envelope sustain
26
+ volEnvDelay: 0x20b, // volume envelope delay
27
+ volEnvHold: 0x20c, // volume envelope hold
28
+
29
+ modEnvAttack: 0x30a, // modulation envelope attack
30
+ modEnvDecay: 0x30b, // modulation envelope decay
31
+ modEnvRelease: 0x30d, // modulation envelope release
32
+ modEnvSustain: 0x30e, // modulation envelope sustain
33
+ modEnvDelay: 0x30f, // modulation envelope delay
34
+ modEnvHold: 0x310, // modulation envelope hold
35
+
36
+ filterCutoff: 0x500, // low pass filter cutoff frequency
37
+ filterQ: 0x501 // low pass filter resonance
38
+ };
@@ -0,0 +1,44 @@
1
+ import { BasicPreset } from "../basic_soundfont/basic_preset.js";
2
+ import { BasicPresetZone } from "../basic_soundfont/basic_zones.js";
3
+ import { BasicInstrument } from "../basic_soundfont/basic_instrument.js";
4
+
5
+ export class DLSPreset extends BasicPreset
6
+ {
7
+ /**
8
+ * Creates a new DLS preset
9
+ * @param dls {BasicSoundBank}
10
+ * @param ulBank {number}
11
+ * @param ulInstrument {number}
12
+ */
13
+ constructor(dls, ulBank, ulInstrument)
14
+ {
15
+ // use stock default modulators, dls won't ever have DMOD chunk
16
+ super(dls);
17
+ this.program = ulInstrument & 127;
18
+ const bankMSB = (ulBank >> 8) & 127;
19
+ const bankLSB = ulBank & 127;
20
+ // switch accordingly
21
+ if (bankMSB > 0)
22
+ {
23
+ this.bank = bankMSB;
24
+ }
25
+ else
26
+ {
27
+ this.bank = bankLSB;
28
+ }
29
+ const isDrums = ulBank >> 31;
30
+ if (isDrums)
31
+ {
32
+ // soundfont bank is 128, so we change it here
33
+ this.bank = 128;
34
+ }
35
+
36
+ this.DLSInstrument = new BasicInstrument();
37
+ this.DLSInstrument.addUseCount();
38
+
39
+ const zone = new BasicPresetZone();
40
+ zone.instrument = this.DLSInstrument;
41
+
42
+ this.presetZones = [zone];
43
+ }
44
+ }