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,304 @@
1
+ import { RiffChunk } from "../basic_soundfont/riff_chunk.js";
2
+ import { IndexedByteArray } from "../../utils/indexed_array.js";
3
+ import { readLittleEndian, signedInt8 } from "../../utils/byte_functions/little_endian.js";
4
+ import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
5
+ import { SpessaSynthWarn } from "../../utils/loggin.js";
6
+ import { readBytesAsString } from "../../utils/byte_functions/string.js";
7
+ import { BasicSample } from "../basic_soundfont/basic_sample.js";
8
+
9
+ export class SoundFontSample extends BasicSample
10
+ {
11
+ /**
12
+ * Creates a sample
13
+ * @param sampleName {string}
14
+ * @param sampleStartIndex {number}
15
+ * @param sampleEndIndex {number}
16
+ * @param sampleLoopStartIndex {number}
17
+ * @param sampleLoopEndIndex {number}
18
+ * @param sampleRate {number}
19
+ * @param samplePitch {number}
20
+ * @param samplePitchCorrection {number}
21
+ * @param sampleLink {number}
22
+ * @param sampleType {number}
23
+ * @param smplArr {IndexedByteArray|Float32Array}
24
+ * @param sampleIndex {number} initial sample index when loading the sfont
25
+ * @param isDataRaw {boolean} if false, the data is decoded as float32.
26
+ * Used for SF2Pack support
27
+ */
28
+ constructor(
29
+ sampleName,
30
+ sampleStartIndex,
31
+ sampleEndIndex,
32
+ sampleLoopStartIndex,
33
+ sampleLoopEndIndex,
34
+ sampleRate,
35
+ samplePitch,
36
+ samplePitchCorrection,
37
+ sampleLink,
38
+ sampleType,
39
+ smplArr,
40
+ sampleIndex,
41
+ isDataRaw
42
+ )
43
+ {
44
+ super(
45
+ sampleName,
46
+ sampleRate,
47
+ samplePitch,
48
+ samplePitchCorrection,
49
+ sampleLink,
50
+ sampleType,
51
+ sampleLoopStartIndex - (sampleStartIndex / 2),
52
+ sampleLoopEndIndex - (sampleStartIndex / 2)
53
+ );
54
+ this.sampleName = sampleName;
55
+ // in bytes
56
+ this.sampleStartIndex = sampleStartIndex;
57
+ this.sampleEndIndex = sampleEndIndex;
58
+ this.isSampleLoaded = false;
59
+ this.sampleID = sampleIndex;
60
+ // in bytes
61
+ this.sampleLength = this.sampleEndIndex - this.sampleStartIndex;
62
+ this.sampleDataArray = smplArr;
63
+ this.sampleData = new Float32Array(0);
64
+ if (this.isCompressed)
65
+ {
66
+ // correct loop points
67
+ this.sampleLoopStartIndex += this.sampleStartIndex / 2;
68
+ this.sampleLoopEndIndex += this.sampleStartIndex / 2;
69
+ this.sampleLength = 99999999; // set to 999,999 before we decode it
70
+ }
71
+ this.isDataRaw = isDataRaw;
72
+ }
73
+
74
+ /**
75
+ * Get raw data, whether it's compressed or not as we simply write it to the file
76
+ * @return {Uint8Array} either s16 or vorbis data
77
+ */
78
+ getRawData()
79
+ {
80
+ const smplArr = this.sampleDataArray;
81
+ if (this.isCompressed)
82
+ {
83
+ if (this.compressedData)
84
+ {
85
+ return this.compressedData;
86
+ }
87
+ const smplStart = smplArr.currentIndex;
88
+ return smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
89
+ }
90
+ else
91
+ {
92
+ if (!this.isDataRaw)
93
+ {
94
+ // encode the f32 into s16 manually
95
+ super.getRawData();
96
+ }
97
+ const dataStartIndex = smplArr.currentIndex;
98
+ return smplArr.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Decode binary vorbis into a float32 pcm
104
+ */
105
+ decodeVorbis()
106
+ {
107
+ if (this.sampleLength < 1)
108
+ {
109
+ // eos, do not do anything
110
+ return;
111
+ }
112
+ // get the compressed byte stream
113
+ const smplArr = this.sampleDataArray;
114
+ const smplStart = smplArr.currentIndex;
115
+ const buff = smplArr.slice(this.sampleStartIndex / 2 + smplStart, this.sampleEndIndex / 2 + smplStart);
116
+ // reset array and being decoding
117
+ this.sampleData = new Float32Array(0);
118
+ try
119
+ {
120
+ /**
121
+ * @type {{data: Float32Array[], error: (string|null), sampleRate: number, eof: boolean}}
122
+ */
123
+ const vorbis = stbvorbis.decode(buff.buffer);
124
+ this.sampleData = vorbis.data[0];
125
+ if (this.sampleData === undefined)
126
+ {
127
+ SpessaSynthWarn(`Error decoding sample ${this.sampleName}: Vorbis decode returned undefined.`);
128
+ }
129
+ }
130
+ catch (e)
131
+ {
132
+ // do not error out, fill with silence
133
+ SpessaSynthWarn(`Error decoding sample ${this.sampleName}: ${e}`);
134
+ this.sampleData = new Float32Array(this.sampleLoopEndIndex + 1);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Loads the audio data and stores it for reuse
140
+ * @returns {Float32Array} The audioData
141
+ */
142
+ getAudioData()
143
+ {
144
+ if (!this.isSampleLoaded)
145
+ {
146
+ // start loading data if it is not loaded
147
+ if (this.sampleLength < 1)
148
+ {
149
+ SpessaSynthWarn(`Invalid sample ${this.sampleName}! Invalid length: ${this.sampleLength}`);
150
+ return new Float32Array(1);
151
+ }
152
+
153
+ if (this.isCompressed)
154
+ {
155
+ // if compressed, decode
156
+ this.decodeVorbis();
157
+ this.isSampleLoaded = true;
158
+ return this.sampleData;
159
+ }
160
+ else if (!this.isDataRaw)
161
+ {
162
+ return this.getUncompressedReadyData();
163
+ }
164
+ return this.loadUncompressedData();
165
+ }
166
+ return this.sampleData;
167
+ }
168
+
169
+ /**
170
+ * @returns {Float32Array}
171
+ */
172
+ loadUncompressedData()
173
+ {
174
+ if (this.isCompressed)
175
+ {
176
+ SpessaSynthWarn("Trying to load a compressed sample via loadUncompressedData()... aborting!");
177
+ return new Float32Array(0);
178
+ }
179
+
180
+ // read the sample data
181
+ let audioData = new Float32Array(this.sampleLength / 2);
182
+ const dataStartIndex = this.sampleDataArray.currentIndex;
183
+ let convertedSigned16 = new Int16Array(
184
+ this.sampleDataArray.slice(dataStartIndex + this.sampleStartIndex, dataStartIndex + this.sampleEndIndex)
185
+ .buffer
186
+ );
187
+
188
+ // convert to float
189
+ for (let i = 0; i < convertedSigned16.length; i++)
190
+ {
191
+ audioData[i] = convertedSigned16[i] / 32768;
192
+ }
193
+
194
+ this.sampleData = audioData;
195
+ this.isSampleLoaded = true;
196
+ return audioData;
197
+ }
198
+
199
+ /**
200
+ * @returns {Float32Array}
201
+ */
202
+ getUncompressedReadyData()
203
+ {
204
+ /**
205
+ * read the sample data
206
+ * @type {Float32Array}
207
+ */
208
+ let audioData = this.sampleDataArray.slice(this.sampleStartIndex / 2, this.sampleEndIndex / 2);
209
+ this.sampleData = audioData;
210
+ this.isSampleLoaded = true;
211
+ return audioData;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Reads the generatorTranslator from the shdr read
217
+ * @param sampleHeadersChunk {RiffChunk}
218
+ * @param smplChunkData {IndexedByteArray|Float32Array}
219
+ * @param isSmplDataRaw {boolean}
220
+ * @returns {SoundFontSample[]}
221
+ */
222
+ export function readSamples(sampleHeadersChunk, smplChunkData, isSmplDataRaw = true)
223
+ {
224
+ /**
225
+ * @type {SoundFontSample[]}
226
+ */
227
+ let samples = [];
228
+ let index = 0;
229
+ while (sampleHeadersChunk.chunkData.length > sampleHeadersChunk.chunkData.currentIndex)
230
+ {
231
+ const sample = readSample(index, sampleHeadersChunk.chunkData, smplChunkData, isSmplDataRaw);
232
+ samples.push(sample);
233
+ index++;
234
+ }
235
+ // remove EOS
236
+ if (samples.length > 1)
237
+ {
238
+ samples.pop();
239
+ }
240
+ return samples;
241
+ }
242
+
243
+ /**
244
+ * Reads it into a sample
245
+ * @param index {number}
246
+ * @param sampleHeaderData {IndexedByteArray}
247
+ * @param smplArrayData {IndexedByteArray|Float32Array}
248
+ * @param isDataRaw {boolean} true means binary 16-bit data, false means float32
249
+ * @returns {SoundFontSample}
250
+ */
251
+ function readSample(index, sampleHeaderData, smplArrayData, isDataRaw)
252
+ {
253
+
254
+ // read the sample name
255
+ let sampleName = readBytesAsString(sampleHeaderData, 20);
256
+
257
+ // read the sample start index
258
+ let sampleStartIndex = readLittleEndian(sampleHeaderData, 4) * 2;
259
+
260
+ // read the sample end index
261
+ let sampleEndIndex = readLittleEndian(sampleHeaderData, 4) * 2;
262
+
263
+ // read the sample looping start index
264
+ let sampleLoopStartIndex = readLittleEndian(sampleHeaderData, 4);
265
+
266
+ // read the sample looping end index
267
+ let sampleLoopEndIndex = readLittleEndian(sampleHeaderData, 4);
268
+
269
+ // read the sample rate
270
+ let sampleRate = readLittleEndian(sampleHeaderData, 4);
271
+
272
+ // read the original sample pitch
273
+ let samplePitch = sampleHeaderData[sampleHeaderData.currentIndex++];
274
+ if (samplePitch === 255)
275
+ {
276
+ // if it's 255, then default to 60
277
+ samplePitch = 60;
278
+ }
279
+
280
+ // read the sample pitch correction
281
+ let samplePitchCorrection = signedInt8(sampleHeaderData[sampleHeaderData.currentIndex++]);
282
+
283
+
284
+ // read the link to the other channel
285
+ let sampleLink = readLittleEndian(sampleHeaderData, 2);
286
+ let sampleType = readLittleEndian(sampleHeaderData, 2);
287
+
288
+
289
+ return new SoundFontSample(
290
+ sampleName,
291
+ sampleStartIndex,
292
+ sampleEndIndex,
293
+ sampleLoopStartIndex,
294
+ sampleLoopEndIndex,
295
+ sampleRate,
296
+ samplePitch,
297
+ samplePitchCorrection,
298
+ sampleLink,
299
+ sampleType,
300
+ smplArrayData,
301
+ index,
302
+ isDataRaw
303
+ );
304
+ }
@@ -0,0 +1,305 @@
1
+ import { IndexedByteArray } from "../../utils/indexed_array.js";
2
+ import { readSamples } from "./samples.js";
3
+ import { readLittleEndian } from "../../utils/byte_functions/little_endian.js";
4
+ import { readGenerators } from "./generators.js";
5
+ import { InstrumentZone, readInstrumentZones, readPresetZones } from "./zones.js";
6
+ import { readPresets } from "./presets.js";
7
+ import { readInstruments } from "./instruments.js";
8
+ import { readModulators } from "./modulators.js";
9
+ import { readRIFFChunk, RiffChunk } from "../basic_soundfont/riff_chunk.js";
10
+ import { consoleColors } from "../../utils/other.js";
11
+ import { SpessaSynthGroup, SpessaSynthGroupEnd, SpessaSynthInfo } from "../../utils/loggin.js";
12
+ import { readBytesAsString } from "../../utils/byte_functions/string.js";
13
+ import { stbvorbis } from "../../externals/stbvorbis_sync/stbvorbis_sync.min.js";
14
+ import { BasicSoundBank } from "../basic_soundfont/basic_soundfont.js";
15
+ import { Generator } from "../basic_soundfont/generator.js";
16
+ import { Modulator } from "../basic_soundfont/modulator.js";
17
+
18
+ /**
19
+ * soundfont.js
20
+ * purpose: parses a soundfont2 file
21
+ */
22
+
23
+ export class SoundFont2 extends BasicSoundBank
24
+ {
25
+ /**
26
+ * Initializes a new SoundFont2 Parser and parses the given data array
27
+ * @param arrayBuffer {ArrayBuffer}
28
+ * @param warnDeprecated {boolean}
29
+ */
30
+ constructor(arrayBuffer, warnDeprecated = true)
31
+ {
32
+ super();
33
+ if (warnDeprecated)
34
+ {
35
+ console.warn("Using the constructor directly is deprecated. Use loadSoundFont instead.");
36
+ }
37
+ this.dataArray = new IndexedByteArray(arrayBuffer);
38
+ SpessaSynthGroup("%cParsing SoundFont...", consoleColors.info);
39
+ if (!this.dataArray)
40
+ {
41
+ SpessaSynthGroupEnd();
42
+ this.parsingError("No data provided!");
43
+ }
44
+
45
+ // read the main read
46
+ let firstChunk = readRIFFChunk(this.dataArray, false);
47
+ this.verifyHeader(firstChunk, "riff");
48
+
49
+ const type = readBytesAsString(this.dataArray, 4).toLowerCase();
50
+ if (type !== "sfbk" && type !== "sfpk")
51
+ {
52
+ SpessaSynthGroupEnd();
53
+ throw new SyntaxError(`Invalid soundFont! Expected "sfbk" or "sfpk" got "${type}"`);
54
+ }
55
+ /*
56
+ Some SF2Pack description:
57
+ this is essentially sf2, but the entire smpl chunk is compressed (we only support Ogg Vorbis here)
58
+ and the only other difference is that the main chunk isn't "sfbk" but rather "sfpk"
59
+ */
60
+ const isSF2Pack = type === "sfpk";
61
+
62
+ // INFO
63
+ let infoChunk = readRIFFChunk(this.dataArray);
64
+ this.verifyHeader(infoChunk, "list");
65
+ readBytesAsString(infoChunk.chunkData, 4);
66
+
67
+ while (infoChunk.chunkData.length > infoChunk.chunkData.currentIndex)
68
+ {
69
+ let chunk = readRIFFChunk(infoChunk.chunkData);
70
+ let text;
71
+ // special cases
72
+ switch (chunk.header.toLowerCase())
73
+ {
74
+ case "ifil":
75
+ case "iver":
76
+ text = `${readLittleEndian(chunk.chunkData, 2)}.${readLittleEndian(chunk.chunkData, 2)}`;
77
+ this.soundFontInfo[chunk.header] = text;
78
+ break;
79
+
80
+ case "icmt":
81
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length, undefined, false);
82
+ this.soundFontInfo[chunk.header] = text;
83
+ break;
84
+
85
+ // dmod: default modulators
86
+ case "dmod":
87
+ const newModulators = readModulators(chunk);
88
+ newModulators.pop(); // remove the terminal record
89
+ text = `Modulators: ${newModulators.length}`;
90
+ // override default modulators
91
+ const oldDefaults = this.defaultModulators;
92
+
93
+ this.defaultModulators = newModulators;
94
+ this.defaultModulators.push(...oldDefaults.filter(m => !this.defaultModulators.find(mm => Modulator.isIdentical(
95
+ m,
96
+ mm
97
+ ))));
98
+ this.soundFontInfo[chunk.header] = chunk.chunkData;
99
+ break;
100
+
101
+ default:
102
+ text = readBytesAsString(chunk.chunkData, chunk.chunkData.length);
103
+ this.soundFontInfo[chunk.header] = text;
104
+ }
105
+
106
+ SpessaSynthInfo(
107
+ `%c"${chunk.header}": %c"${text}"`,
108
+ consoleColors.info,
109
+ consoleColors.recognized
110
+ );
111
+ }
112
+
113
+ // SDTA
114
+ const sdtaChunk = readRIFFChunk(this.dataArray, false);
115
+ this.verifyHeader(sdtaChunk, "list");
116
+ this.verifyText(readBytesAsString(this.dataArray, 4), "sdta");
117
+
118
+ // smpl
119
+ SpessaSynthInfo("%cVerifying smpl chunk...", consoleColors.warn);
120
+ let sampleDataChunk = readRIFFChunk(this.dataArray, false);
121
+ this.verifyHeader(sampleDataChunk, "smpl");
122
+ /**
123
+ * @type {IndexedByteArray|Float32Array}
124
+ */
125
+ let sampleData;
126
+ // SF2Pack: the entire data is compressed
127
+ if (isSF2Pack)
128
+ {
129
+ SpessaSynthInfo(
130
+ "%cSF2Pack detected, attempting to decode the smpl chunk...",
131
+ consoleColors.info
132
+ );
133
+ try
134
+ {
135
+ /**
136
+ * @type {Float32Array}
137
+ */
138
+ sampleData = stbvorbis.decode(this.dataArray.buffer.slice(
139
+ this.dataArray.currentIndex,
140
+ this.dataArray.currentIndex + sdtaChunk.size - 12
141
+ )).data[0];
142
+ }
143
+ catch (e)
144
+ {
145
+ SpessaSynthGroupEnd();
146
+ throw new Error(`SF2Pack Ogg Vorbis decode error: ${e}`);
147
+ }
148
+ SpessaSynthInfo(
149
+ `%cDecoded the smpl chunk! Length: %c${sampleData.length}`,
150
+ consoleColors.info,
151
+ consoleColors.value
152
+ );
153
+ }
154
+ else
155
+ {
156
+ /**
157
+ * @type {IndexedByteArray}
158
+ */
159
+ sampleData = this.dataArray;
160
+ this.sampleDataStartIndex = this.dataArray.currentIndex;
161
+ }
162
+
163
+ SpessaSynthInfo(
164
+ `%cSkipping sample chunk, length: %c${sdtaChunk.size - 12}`,
165
+ consoleColors.info,
166
+ consoleColors.value
167
+ );
168
+ this.dataArray.currentIndex += sdtaChunk.size - 12;
169
+
170
+ // PDTA
171
+ SpessaSynthInfo("%cLoading preset data chunk...", consoleColors.warn);
172
+ let presetChunk = readRIFFChunk(this.dataArray);
173
+ this.verifyHeader(presetChunk, "list");
174
+ readBytesAsString(presetChunk.chunkData, 4);
175
+
176
+ // read the hydra chunks
177
+ const presetHeadersChunk = readRIFFChunk(presetChunk.chunkData);
178
+ this.verifyHeader(presetHeadersChunk, "phdr");
179
+
180
+ const presetZonesChunk = readRIFFChunk(presetChunk.chunkData);
181
+ this.verifyHeader(presetZonesChunk, "pbag");
182
+
183
+ const presetModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
184
+ this.verifyHeader(presetModulatorsChunk, "pmod");
185
+
186
+ const presetGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
187
+ this.verifyHeader(presetGeneratorsChunk, "pgen");
188
+
189
+ const presetInstrumentsChunk = readRIFFChunk(presetChunk.chunkData);
190
+ this.verifyHeader(presetInstrumentsChunk, "inst");
191
+
192
+ const presetInstrumentZonesChunk = readRIFFChunk(presetChunk.chunkData);
193
+ this.verifyHeader(presetInstrumentZonesChunk, "ibag");
194
+
195
+ const presetInstrumentModulatorsChunk = readRIFFChunk(presetChunk.chunkData);
196
+ this.verifyHeader(presetInstrumentModulatorsChunk, "imod");
197
+
198
+ const presetInstrumentGeneratorsChunk = readRIFFChunk(presetChunk.chunkData);
199
+ this.verifyHeader(presetInstrumentGeneratorsChunk, "igen");
200
+
201
+ const presetSamplesChunk = readRIFFChunk(presetChunk.chunkData);
202
+ this.verifyHeader(presetSamplesChunk, "shdr");
203
+
204
+ /**
205
+ * read all the samples
206
+ * (the current index points to start of the smpl read)
207
+ */
208
+ this.dataArray.currentIndex = this.sampleDataStartIndex;
209
+ this.samples.push(...readSamples(presetSamplesChunk, sampleData, !isSF2Pack));
210
+
211
+ /**
212
+ * read all the instrument generators
213
+ * @type {Generator[]}
214
+ */
215
+ let instrumentGenerators = readGenerators(presetInstrumentGeneratorsChunk);
216
+
217
+ /**
218
+ * read all the instrument modulators
219
+ * @type {Modulator[]}
220
+ */
221
+ let instrumentModulators = readModulators(presetInstrumentModulatorsChunk);
222
+
223
+ /**
224
+ * read all the instrument zones
225
+ * @type {InstrumentZone[]}
226
+ */
227
+ let instrumentZones = readInstrumentZones(
228
+ presetInstrumentZonesChunk,
229
+ instrumentGenerators,
230
+ instrumentModulators,
231
+ this.samples
232
+ );
233
+
234
+ this.instruments = readInstruments(presetInstrumentsChunk, instrumentZones);
235
+
236
+ /**
237
+ * read all the preset generators
238
+ * @type {Generator[]}
239
+ */
240
+ let presetGenerators = readGenerators(presetGeneratorsChunk);
241
+
242
+ /**
243
+ * Read all the preset modulatorrs
244
+ * @type {Modulator[]}
245
+ */
246
+ let presetModulators = readModulators(presetModulatorsChunk);
247
+
248
+ let presetZones = readPresetZones(presetZonesChunk, presetGenerators, presetModulators, this.instruments);
249
+
250
+ this.presets.push(...readPresets(presetHeadersChunk, presetZones, this));
251
+ this.presets.sort((a, b) => (a.program - b.program) + (a.bank - b.bank));
252
+ this._parseInternal();
253
+ SpessaSynthInfo(
254
+ `%cParsing finished! %c"${this.soundFontInfo["INAM"]}"%c has %c${this.presets.length} %cpresets,
255
+ %c${this.instruments.length}%c instruments and %c${this.samples.length}%c samples.`,
256
+ consoleColors.info,
257
+ consoleColors.recognized,
258
+ consoleColors.info,
259
+ consoleColors.recognized,
260
+ consoleColors.info,
261
+ consoleColors.recognized,
262
+ consoleColors.info,
263
+ consoleColors.recognized,
264
+ consoleColors.info
265
+ );
266
+ SpessaSynthGroupEnd();
267
+
268
+ if (isSF2Pack)
269
+ {
270
+ delete this.dataArray;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * @param chunk {RiffChunk}
276
+ * @param expected {string}
277
+ */
278
+ verifyHeader(chunk, expected)
279
+ {
280
+ if (chunk.header.toLowerCase() !== expected.toLowerCase())
281
+ {
282
+ SpessaSynthGroupEnd();
283
+ this.parsingError(`Invalid chunk header! Expected "${expected.toLowerCase()}" got "${chunk.header.toLowerCase()}"`);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * @param text {string}
289
+ * @param expected {string}
290
+ */
291
+ verifyText(text, expected)
292
+ {
293
+ if (text.toLowerCase() !== expected.toLowerCase())
294
+ {
295
+ SpessaSynthGroupEnd();
296
+ this.parsingError(`Invalid FourCC: Expected "${expected.toLowerCase()}" got "${text.toLowerCase()}"\``);
297
+ }
298
+ }
299
+
300
+ destroySoundBank()
301
+ {
302
+ super.destroySoundBank();
303
+ delete this.dataArray;
304
+ }
305
+ }