spessasynth_core 1.0.0

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 (55) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +10 -0
  2. package/.idea/jsLibraryMappings.xml +6 -0
  3. package/.idea/modules.xml +8 -0
  4. package/.idea/spessasynth_core.iml +12 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/README.md +376 -0
  7. package/index.js +7 -0
  8. package/package.json +34 -0
  9. package/spessasynth_core/midi_parser/README.md +3 -0
  10. package/spessasynth_core/midi_parser/midi_loader.js +381 -0
  11. package/spessasynth_core/midi_parser/midi_message.js +231 -0
  12. package/spessasynth_core/sequencer/sequencer.js +192 -0
  13. package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
  14. package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
  15. package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
  16. package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
  17. package/spessasynth_core/soundfont/README.md +4 -0
  18. package/spessasynth_core/soundfont/chunk/generators.js +205 -0
  19. package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
  20. package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
  21. package/spessasynth_core/soundfont/chunk/presets.js +264 -0
  22. package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
  23. package/spessasynth_core/soundfont/chunk/samples.js +250 -0
  24. package/spessasynth_core/soundfont/chunk/zones.js +264 -0
  25. package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
  26. package/spessasynth_core/synthetizer/README.md +6 -0
  27. package/spessasynth_core/synthetizer/synthesizer.js +303 -0
  28. package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
  29. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
  30. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
  31. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
  32. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
  33. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
  34. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
  35. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
  36. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  37. package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
  38. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  39. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
  40. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  41. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  42. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
  43. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  44. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
  45. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  46. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
  47. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
  48. package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
  49. package/spessasynth_core/utils/README.md +4 -0
  50. package/spessasynth_core/utils/buffer_to_wav.js +70 -0
  51. package/spessasynth_core/utils/byte_functions.js +141 -0
  52. package/spessasynth_core/utils/loggin.js +79 -0
  53. package/spessasynth_core/utils/other.js +49 -0
  54. package/spessasynth_core/utils/shiftable_array.js +26 -0
  55. package/spessasynth_core/utils/stbvorbis_sync.js +1877 -0
@@ -0,0 +1,10 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
5
+ <option name="processCode" value="true" />
6
+ <option name="processLiterals" value="true" />
7
+ <option name="processComments" value="true" />
8
+ </inspection_tool>
9
+ </profile>
10
+ </component>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="JavaScriptLibraryMappings">
4
+ <includedPredefinedLibrary name="Node.js Core" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/spessasynth_core.iml" filepath="$PROJECT_DIR$/.idea/spessasynth_core.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ # spessasynth_core
2
+ A SoundFont2 synthesizer library, made for use with node.js.
3
+ A fork of [SpessaSynth](https://github.com/spessasus/SpessaSynth).
4
+
5
+ `npm install --save spessasynth_core`
6
+
7
+ > [!TIP]
8
+ > Looking for a browser version? Try [SpessaSynth](https://github.com/spessasus/SpessaSynth).
9
+
10
+ ## Features:
11
+ - SoundFont2 support (both modulators and generators)
12
+ - SoundFont3 support (vorbis compressed sf2)
13
+ - GS, XG, GM2, GM1 system exclusive support
14
+ - NRPN, RPN support
15
+ - Integrated sequencer
16
+ - Additional custom modulators
17
+ - Multi-port MIDIs support (more than 16 channels)
18
+ - No SoundFont size limit
19
+ - No dependencies
20
+
21
+ ### Example: render midi file to wav
22
+ ```js
23
+ const fs = require('fs');
24
+ // spessasynth_core is an es6 module
25
+ import("spessasynth_core").then(core => {
26
+ // usage: node test.js <sf path> <midi path> <output path>
27
+ const [,, soundfontName, midiName, outputName] = process.argv;
28
+ // read the input files
29
+ const soundfont = fs.readFileSync(soundfontName);
30
+ const mid = new core.MIDI(fs.readFileSync(midiName))
31
+
32
+ // initialize synth and sequencer
33
+ const synth = new core.Synthesizer(soundfont, 44100);
34
+ const seq = new core.Sequencer(synth);
35
+ // load new song and disable the loop
36
+ seq.loadNewSongList([mid]);
37
+ seq.loop = false;
38
+
39
+ // calculate length and allocate buffers
40
+ const lengthSamples = mid.duration * 44100;
41
+ const outLeft = new Float32Array(lengthSamples);
42
+ const outRight = new Float32Array(lengthSamples);
43
+
44
+ // wait for sf3 support to load and render
45
+ synth.sf3supportReady.then(() => {
46
+ // note: this discards reverb and chorus outputs!
47
+ synth.render([outLeft, outRight]);
48
+ // write output data
49
+ const wav = core.rawDataToWav(44100, outLeft, outRight);
50
+ fs.writeFileSync(outputName, Buffer.from(wav));
51
+ });
52
+ });
53
+ ```
54
+
55
+ ## API reference
56
+ ### Contents
57
+ - [Importing the library](#importing-the-library)
58
+ - [Synthesizer](#synthesizer)
59
+ - [Sequencer](#sequencer)
60
+ - [MIDI](#midi)
61
+ - [SoundFont2](#soundfont2)
62
+
63
+ ### Importing the library
64
+ spessasynth_core is an es6 package.
65
+ ```js
66
+ // es6
67
+ import {Synthesizer} from "spessasynth_core"
68
+ // commonjs
69
+ import("spessasynth_core").then(core => {
70
+ // use core.Synthesizer
71
+ })
72
+ ```
73
+ ## Synthesizer
74
+ The main synth module.
75
+ ### Initialization
76
+ ```js
77
+ const synth = new Synthesizer(soundFontBuffer, sampleRate, blockSize)
78
+ ```
79
+ - soundFontBuffer - a `Buffer` or `ArrayBufferLike`, represents the soundfont file.
80
+ - sampleRate - number, the output sample rate in hertz.
81
+ - blockSize - optional, a number. Sets the interval of the synth updating parameters like the sequencer tick processing and modulation envelope.
82
+ Default value is 128, and it's recommended to leave it as the default.
83
+
84
+ ### render
85
+ Synthesizes audio the output buffers
86
+ ```js
87
+ synth.render(outputChannels, reverbOutputChannels, chorusOutputChannels);
88
+ ```
89
+ - outputChannels - two `Float32Arrays` that get filled with the audio data. Left is the left channel and right is the right channel. Can be any length. (except zero)
90
+ - reverbOutputChannels - two `Float32Arrays` that get filled with the unprocessed audio data for reverb processing. Left is the left channel and right is the right channel. Can be undefined.
91
+ - reverbOutputChannels - two `Float32Arrays` that get filled with the unprocessed audio data for chorus processing. Left is the left channel and right is the right channel. Can be undefined.
92
+ > [!IMPORTANT]
93
+ > All arrays must be the same length.
94
+
95
+ ### noteOn
96
+
97
+ Plays the given note.
98
+
99
+ ```js
100
+ synth.noteOn(channel, midiNote, velocity, enableDebugging);
101
+ ```
102
+
103
+ - channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
104
+ - midiNote - the note to play. Ranges from 0 to 127.
105
+ - velocity - controls how loud the note is. 127 is normal loudness and 1 is the quietest. Note that velocity of 0 has the same effect as using `noteOff`. Ranges from 0 to 127.
106
+ - enableDebugging - boolean, used only for debugging. When `true`, the console will print out tables of the soundfont generator data used to play the note.
107
+
108
+ ### noteOff
109
+
110
+ Stops the given note.
111
+
112
+ ```js
113
+ synth.noteOff(channel, midiNote);
114
+ ```
115
+
116
+ - channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
117
+ - midiNote - the note to play. Ranges from 0 to 127.
118
+ > [!TIP]
119
+ > To stop a note instantly, use `synth.killNote` (takes the same arguments)
120
+
121
+ ### stopAllChannels
122
+
123
+ Stops all notes. Equivalent of MIDI "panic".
124
+
125
+ ```js
126
+ synth.stopAllChannels(force);
127
+ ```
128
+ - force - `boolean`, if true, ignores the release time and stops everything instantly. Defaults to false.
129
+ > [!TIP]
130
+ > To stop all notes on a specific channel, use `synth.stopAll(channel, force)`. channel is the channel number.
131
+
132
+ ### programChange
133
+
134
+ Changes the preset for the given channel.
135
+
136
+ ```js
137
+ synth.programChange(channel, programNumber);
138
+ ```
139
+
140
+ - channel - the MIDI channel to change. Usually ranges from 0 to 15, but it depends on the channel count.
141
+ - programNumber - the MIDI program number to use. Ranges from 0 to 127. To use other banks, go to [controllerChange](#controllerchange).
142
+ > [!TIP]
143
+ > To lock the preset (prevent MIDI file from changing it) use `synth.workletProcessorChannels[channel].lockPreset = true;`
144
+
145
+ ### pitchWheel
146
+
147
+ Changes the channel's pitch, including the currently playing notes.
148
+
149
+ ```js
150
+ synth.pitchWheel(channel, MSB, LSB);
151
+ ```
152
+
153
+ - channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
154
+ - MSB and LSB. 7-bit numbers that form a 14-bit pitch bend value.
155
+ > [!TIP]
156
+ > [I highly recommend this article for more info.](https://www.recordingblogs.com/wiki/midi-pitch-wheel-message)
157
+
158
+ ### systemExclusive
159
+
160
+ Handles a MIDI System Exclusive message.
161
+
162
+ ```js
163
+ synth.systemExclusive(messageData);
164
+ ```
165
+
166
+ - message data - Uint8Array, the message byte data **Excluding the 0xF0 byte!**
167
+ > [!TIP]
168
+ > Refer to [this table](https://github.com/spessasus/SpessaSynth/wiki/Synthetizer-Class#supported-system-exclusives) for the list of supported System Exclusives.
169
+
170
+ ### setMainVolume
171
+ Sets the main volume of the synthesizer.
172
+ ```js
173
+ synth.setMainVolume(volume);
174
+ ```
175
+ - volume - the volume, ranges from 0 to 1.
176
+
177
+ ### setMasterPan
178
+ Sets the master panning of the synthesizer.
179
+ ```js
180
+ synth.setMasterPan(pan);
181
+ ```
182
+ - pan - ranges from -1 to 1, -1 is left, 0 is middle, 1 is right.
183
+
184
+ ### lockController
185
+
186
+ Causes the given midi channel to ignore controller messages for the given controller number.
187
+
188
+ ```js
189
+ synth.lockController(channel, controllerNumber, isLocked);
190
+ ```
191
+
192
+ - channel - the channel to lock. Usually ranges from 0 to 15, but it depends on the channel count.
193
+ - controllerNumber - the MIDI CC to lock. Ranges from 0 to 127.
194
+ - isLocked - boolean, if true then locked, if false then unlocked.
195
+
196
+ ### muteChannel
197
+
198
+ Mutes or unmutes a given channel
199
+
200
+ ```js
201
+ synth.muteChannel(channel, isMuted);
202
+ ```
203
+
204
+ - channel - the channel to mute/unmute. Usually ranges from 0 to 15, but it depends on the channel count.
205
+ - isMuted - if the channel should be muted. boolean.
206
+
207
+ ### transpose
208
+
209
+ Transposes the synth up or down in semitones. Floating point values can be used for more precise tuning.
210
+
211
+ ```js
212
+ synth.transpose(semitones);
213
+ ```
214
+
215
+ - semitones - the amount of semitones to transpose the synth by. Can be positive or negative or zero. Zero resets the pitch.
216
+
217
+ ### controllerChange
218
+
219
+ Sets a given MIDI controller to a given value.
220
+
221
+ ```js
222
+ synth.controllerChange(channel, controllerNumber, controllerValue);
223
+ ```
224
+
225
+ - channel - the MIDI channel to use. Usually ranges from 0 to 15, but it depends on the channel count.
226
+ - controllerName - the MIDI CC number. Refer to [this table](https://github.com/spessasus/SpessaSynth/wiki/Synthetizer-Class#default-supported-controllers) for the list of controllers supported by default.
227
+ - controllerValue - the value to set the given controller to. Ranges from 0 to 127.
228
+ > [!NOTE]
229
+ > Note that theoreticallly all controllers are supported as it depends on the SoundFont's modulators.
230
+
231
+ ### resetAllControllers
232
+ Resets all controllers and programs to their default values. Also resets the system.
233
+ ```js
234
+ synth.resetAllControllers();
235
+ ```
236
+
237
+ ### addNewChannel
238
+
239
+ Adds a new channel.
240
+ ```js
241
+ synth.addNewChannel();
242
+ ```
243
+
244
+ ### reloadSoundfont
245
+
246
+ Changes the soundfont of a Synthesizer's instance.
247
+
248
+ ```js
249
+ synth.reloadSoundFont(soundFontBuffer);
250
+ ```
251
+
252
+ - soundFont - the soundfont to change to, an `ArrayBuffer` instance of the file.
253
+
254
+ ### setDrums
255
+ Sets the given channel to a drum channel.
256
+ ```js
257
+ synth.setDrums(channel, isDrum);
258
+ ```
259
+ - channel - the channel to change. Usually ranges from 0 to 15, but it depends on the channel count.
260
+ - isDrum - `boolean` indicates if the channel should be a drum channel.
261
+
262
+ ### Accesing controller values
263
+ use `synth.workletProcessorChannels` to get the current values. A single channel is defined as follows:
264
+ ```js
265
+ /**
266
+ * @typedef {Object} WorkletProcessorChannel
267
+ * @property {Int16Array} midiControllers - array of MIDI controller values
268
+ * @property {boolean[]} lockedControllers - array indicating if a controller is locked
269
+ * @property {boolean} holdPedal - indicates whether the hold pedal is active
270
+ * @property {boolean} drumChannel - indicates whether the channel is a drum channel
271
+ *
272
+ * @property {Preset} preset - the channel's preset
273
+ * @property {boolean} lockPreset - indicates whether the program on the channel is locked
274
+ *
275
+ * @property {boolean} lockVibrato - indicates whether the custom vibrato is locked
276
+ * @property {Object} channelVibrato - vibrato settings for the channel
277
+ * @property {number} channelVibrato.depth - depth of the vibrato effect (cents)
278
+ * @property {number} channelVibrato.delay - delay before the vibrato effect starts (seconds)
279
+ * @property {number} channelVibrato.rate - rate of the vibrato oscillation (Hz)
280
+
281
+ * @property {boolean} isMuted - indicates whether the channel is muted
282
+ * @property {WorkletVoice[]} voices - array of voices currently active on the channel
283
+ * @property {WorkletVoice[]} sustainedVoices - array of voices that are sustained on the channel
284
+ */
285
+
286
+ ```
287
+ Note: this definition is stripped from internal values.
288
+
289
+ ## Sequencer
290
+ ### Initialization
291
+ ```js
292
+ const sequencer = new Sequencer(synthesizer);
293
+ ```
294
+ - synthesizer - a `Synthesizer` instance to play to.
295
+
296
+ ### loadNewSongList
297
+ Loads a new song list.
298
+ ```js
299
+ sequencer.loadNewSongList(parsedMidis);
300
+ ```
301
+ - parsedMidis - an array of `MIDI` instances representing the songs to play. If there's only one, the loop will be enabled.
302
+
303
+ ### play
304
+ Starts playing the sequence. If the sequence was paused, it won't change any controllers, but if it wasn't (ex. the time was changed) then it will go through all the controller changes from the start before playing. **This function does NOT modify the current playback time!**
305
+ ```js
306
+ sequencer.play(resetTime);
307
+ ```
308
+ - resetTime - boolean, if set to `true` then the playback will start from 0. Defaults to `false`;
309
+
310
+ ### pause
311
+ Pauses the playback of the sequence.
312
+ ```js
313
+ sequencer.pause();
314
+ ```
315
+
316
+ ### stop
317
+ Stops the playback of the sequence. Currently only used internally by the `pause` function.
318
+ ```js
319
+ sequencer.stop();
320
+ ```
321
+
322
+ ### nextSong
323
+ Plays the next song in the list.
324
+ ```js
325
+ sequencer.nextSong();
326
+ ```
327
+
328
+ ### previousSong
329
+ Plays the previous song in the list.
330
+ ```js
331
+ sequencer.previousSong();
332
+ ```
333
+
334
+ ### paused
335
+ Read-only boolean, indicating that if the sequencer's playback is paused.
336
+ ```js
337
+ if(sequencer.paused)
338
+ {
339
+ console.log("Sequencer paused!");
340
+ }
341
+ else
342
+ {
343
+ console.log("Sequencer playing or stopped!");
344
+ }
345
+ ```
346
+
347
+ ### loop
348
+ Boolean that controls if the sequencer loops.
349
+ ```js
350
+ sequencer.loop = false; // the playback will stop after reaching the end
351
+ ```
352
+
353
+ ### currentTime
354
+ Property used for changing and reading the current playback time.
355
+ #### get
356
+ Returns the current playback time in seconds.
357
+ ```js
358
+ console.log("The sequences is playing for"+sequencer.currentTime+" seconds.");
359
+ ```
360
+ #### set
361
+ Sets the current playback time. Calls `stop` and then `play` internally.
362
+ ```js
363
+ sequencer.currentTime = 0; // go to the start
364
+ ```
365
+
366
+ ### duration
367
+ Length of the track in seconds. Equivalent of `Audio.duration`;
368
+ ```js
369
+ console.log(`The track lasts for ${sequencer.duration} seconds!`);
370
+ ```
371
+
372
+ ## MIDI
373
+ See [MIDI on SpessaSynth wiki](https://github.com/spessasus/SpessaSynth/wiki/MIDI-Class)
374
+
375
+ ## SoundFont2
376
+ See [SoundFont2 on SpessaSynth wiki](https://github.com/spessasus/SpessaSynth/wiki/SoundFont2-Class)
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import {Synthesizer} from "./spessasynth_core/synthetizer/synthesizer.js";
2
+ import {Sequencer} from "./spessasynth_core/sequencer/sequencer.js";
3
+ import {MIDI} from "./spessasynth_core/midi_parser/midi_loader.js";
4
+ import {rawDataToWav} from "./spessasynth_core/utils/buffer_to_wav.js";
5
+ import {SoundFont2} from "./spessasynth_core/soundfont/soundfont_parser.js";
6
+
7
+ export { Synthesizer, Sequencer, MIDI, SoundFont2, rawDataToWav };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "spessasynth_core",
3
+ "version": "1.0.0",
4
+ "description": "SoundFont2 synthesizer library for node.js",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/spessasus/spessasynth_core.git"
13
+ },
14
+ "keywords": [
15
+ "sf2",
16
+ "sf3",
17
+ "soundfont",
18
+ "synthesizer",
19
+ "synth",
20
+ "midi",
21
+ "midi",
22
+ "player"
23
+ ],
24
+ "author": {
25
+ "name": "spessasus",
26
+ "url": "https://github.com/spessasus"
27
+ },
28
+ "license": "(MIT AND Apache-2.0)",
29
+ "bugs": {
30
+ "url": "https://github.com/spessasus/spessasynth_core/issues",
31
+ "email": "spesekspesek@gmail.com"
32
+ },
33
+ "homepage": "https://github.com/spessasus/spessasynth_core#readme"
34
+ }
@@ -0,0 +1,3 @@
1
+ ## This is the MIDI file parsing folder.
2
+ The code here is responsible for parsing the MIDI files and interpreting the messsages.
3
+ All the events are defined in the `midi_message.js` file.