spessasynth_lib 3.9.9

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 (148) hide show
  1. package/@types/externals/stbvorbis_sync/stbvorbis_sync.min.d.ts +1 -0
  2. package/@types/index.d.ts +34 -0
  3. package/@types/midi_handler/midi_handler.d.ts +39 -0
  4. package/@types/midi_handler/web_midi_link.d.ts +12 -0
  5. package/@types/midi_parser/midi_data.d.ts +95 -0
  6. package/@types/midi_parser/midi_editor.d.ts +45 -0
  7. package/@types/midi_parser/midi_loader.d.ts +100 -0
  8. package/@types/midi_parser/midi_message.d.ts +154 -0
  9. package/@types/midi_parser/midi_writer.d.ts +6 -0
  10. package/@types/midi_parser/rmidi_writer.d.ts +9 -0
  11. package/@types/midi_parser/used_keys_loaded.d.ts +7 -0
  12. package/@types/sequencer/sequencer.d.ts +180 -0
  13. package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +28 -0
  14. package/@types/soundfont/read/generators.d.ts +98 -0
  15. package/@types/soundfont/read/instruments.d.ts +50 -0
  16. package/@types/soundfont/read/modulators.d.ts +73 -0
  17. package/@types/soundfont/read/presets.d.ts +87 -0
  18. package/@types/soundfont/read/riff_chunk.d.ts +31 -0
  19. package/@types/soundfont/read/samples.d.ts +134 -0
  20. package/@types/soundfont/read/zones.d.ts +141 -0
  21. package/@types/soundfont/soundfont.d.ts +76 -0
  22. package/@types/soundfont/write/ibag.d.ts +6 -0
  23. package/@types/soundfont/write/igen.d.ts +6 -0
  24. package/@types/soundfont/write/imod.d.ts +6 -0
  25. package/@types/soundfont/write/inst.d.ts +6 -0
  26. package/@types/soundfont/write/pbag.d.ts +6 -0
  27. package/@types/soundfont/write/pgen.d.ts +6 -0
  28. package/@types/soundfont/write/phdr.d.ts +6 -0
  29. package/@types/soundfont/write/pmod.d.ts +6 -0
  30. package/@types/soundfont/write/sdta.d.ts +11 -0
  31. package/@types/soundfont/write/shdr.d.ts +8 -0
  32. package/@types/soundfont/write/soundfont_trimmer.d.ts +6 -0
  33. package/@types/soundfont/write/write.d.ts +21 -0
  34. package/@types/synthetizer/audio_effects/effects_config.d.ts +29 -0
  35. package/@types/synthetizer/audio_effects/fancy_chorus.d.ts +93 -0
  36. package/@types/synthetizer/audio_effects/reverb.d.ts +7 -0
  37. package/@types/synthetizer/synth_event_handler.d.ts +161 -0
  38. package/@types/synthetizer/synthetizer.d.ts +286 -0
  39. package/@types/synthetizer/worklet_system/message_protocol/worklet_message.d.ts +88 -0
  40. package/@types/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.d.ts +134 -0
  41. package/@types/synthetizer/worklet_url.d.ts +5 -0
  42. package/@types/utils/buffer_to_wav.d.ts +8 -0
  43. package/@types/utils/byte_functions/big_endian.d.ts +13 -0
  44. package/@types/utils/byte_functions/little_endian.d.ts +35 -0
  45. package/@types/utils/byte_functions/string.d.ts +22 -0
  46. package/@types/utils/byte_functions/variable_length_quantity.d.ts +12 -0
  47. package/@types/utils/indexed_array.d.ts +21 -0
  48. package/@types/utils/loggin.d.ts +26 -0
  49. package/@types/utils/other.d.ts +32 -0
  50. package/LICENSE +26 -0
  51. package/README.md +83 -0
  52. package/externals/NOTICE +9 -0
  53. package/externals/libvorbis/@types/OggVorbisEncoder.d.ts +34 -0
  54. package/externals/libvorbis/OggVorbisEncoder.min.js +1 -0
  55. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +12 -0
  56. package/externals/stbvorbis_sync/LICENSE +202 -0
  57. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +1 -0
  58. package/index.js +73 -0
  59. package/midi_handler/README.md +3 -0
  60. package/midi_handler/midi_handler.js +118 -0
  61. package/midi_handler/web_midi_link.js +41 -0
  62. package/midi_parser/README.md +3 -0
  63. package/midi_parser/midi_data.js +121 -0
  64. package/midi_parser/midi_editor.js +557 -0
  65. package/midi_parser/midi_loader.js +502 -0
  66. package/midi_parser/midi_message.js +234 -0
  67. package/midi_parser/midi_writer.js +95 -0
  68. package/midi_parser/rmidi_writer.js +271 -0
  69. package/midi_parser/used_keys_loaded.js +172 -0
  70. package/package.json +44 -0
  71. package/sequencer/README.md +23 -0
  72. package/sequencer/sequencer.js +439 -0
  73. package/sequencer/worklet_sequencer/events.js +92 -0
  74. package/sequencer/worklet_sequencer/play.js +309 -0
  75. package/sequencer/worklet_sequencer/process_event.js +167 -0
  76. package/sequencer/worklet_sequencer/process_tick.js +85 -0
  77. package/sequencer/worklet_sequencer/sequencer_message.js +39 -0
  78. package/sequencer/worklet_sequencer/song_control.js +193 -0
  79. package/sequencer/worklet_sequencer/worklet_sequencer.js +218 -0
  80. package/soundfont/README.md +8 -0
  81. package/soundfont/read/generators.js +212 -0
  82. package/soundfont/read/instruments.js +125 -0
  83. package/soundfont/read/modulators.js +249 -0
  84. package/soundfont/read/presets.js +300 -0
  85. package/soundfont/read/riff_chunk.js +81 -0
  86. package/soundfont/read/samples.js +398 -0
  87. package/soundfont/read/zones.js +310 -0
  88. package/soundfont/soundfont.js +357 -0
  89. package/soundfont/write/ibag.js +39 -0
  90. package/soundfont/write/igen.js +75 -0
  91. package/soundfont/write/imod.js +46 -0
  92. package/soundfont/write/inst.js +34 -0
  93. package/soundfont/write/pbag.js +39 -0
  94. package/soundfont/write/pgen.js +77 -0
  95. package/soundfont/write/phdr.js +42 -0
  96. package/soundfont/write/pmod.js +46 -0
  97. package/soundfont/write/sdta.js +72 -0
  98. package/soundfont/write/shdr.js +54 -0
  99. package/soundfont/write/soundfont_trimmer.js +169 -0
  100. package/soundfont/write/write.js +180 -0
  101. package/synthetizer/README.md +6 -0
  102. package/synthetizer/audio_effects/effects_config.js +21 -0
  103. package/synthetizer/audio_effects/fancy_chorus.js +120 -0
  104. package/synthetizer/audio_effects/impulse_response_2.flac +0 -0
  105. package/synthetizer/audio_effects/reverb.js +24 -0
  106. package/synthetizer/synth_event_handler.js +156 -0
  107. package/synthetizer/synthetizer.js +750 -0
  108. package/synthetizer/worklet_processor.min.js +13 -0
  109. package/synthetizer/worklet_system/README.md +6 -0
  110. package/synthetizer/worklet_system/main_processor.js +363 -0
  111. package/synthetizer/worklet_system/message_protocol/handle_message.js +193 -0
  112. package/synthetizer/worklet_system/message_protocol/message_sending.js +74 -0
  113. package/synthetizer/worklet_system/message_protocol/worklet_message.js +118 -0
  114. package/synthetizer/worklet_system/minify_processor.sh +4 -0
  115. package/synthetizer/worklet_system/worklet_methods/controller_control.js +230 -0
  116. package/synthetizer/worklet_system/worklet_methods/data_entry.js +277 -0
  117. package/synthetizer/worklet_system/worklet_methods/note_off.js +109 -0
  118. package/synthetizer/worklet_system/worklet_methods/note_on.js +91 -0
  119. package/synthetizer/worklet_system/worklet_methods/program_control.js +183 -0
  120. package/synthetizer/worklet_system/worklet_methods/reset_controllers.js +177 -0
  121. package/synthetizer/worklet_system/worklet_methods/snapshot.js +129 -0
  122. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +272 -0
  123. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +195 -0
  124. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
  125. package/synthetizer/worklet_system/worklet_methods/voice_control.js +233 -0
  126. package/synthetizer/worklet_system/worklet_processor.js +9 -0
  127. package/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
  128. package/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +130 -0
  129. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
  130. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
  131. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +81 -0
  132. package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
  133. package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +265 -0
  134. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
  135. package/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +234 -0
  136. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +116 -0
  137. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +272 -0
  138. package/synthetizer/worklet_url.js +5 -0
  139. package/utils/README.md +4 -0
  140. package/utils/buffer_to_wav.js +101 -0
  141. package/utils/byte_functions/big_endian.js +28 -0
  142. package/utils/byte_functions/little_endian.js +74 -0
  143. package/utils/byte_functions/string.js +97 -0
  144. package/utils/byte_functions/variable_length_quantity.js +37 -0
  145. package/utils/encode_vorbis.js +30 -0
  146. package/utils/indexed_array.js +41 -0
  147. package/utils/loggin.js +79 -0
  148. package/utils/other.js +54 -0
@@ -0,0 +1,750 @@
1
+ import { IndexedByteArray } from '../utils/indexed_array.js'
2
+ import { consoleColors } from '../utils/other.js'
3
+ import { getEvent, messageTypes, midiControllers } from '../midi_parser/midi_message.js'
4
+ import { EventHandler } from './synth_event_handler.js'
5
+ import { FancyChorus } from './audio_effects/fancy_chorus.js'
6
+ import { getReverbProcessor } from './audio_effects/reverb.js'
7
+ import {
8
+ ALL_CHANNELS_OR_DIFFERENT_ACTION, masterParameterType,
9
+ returnMessageType,
10
+ workletMessageType,
11
+ } from './worklet_system/message_protocol/worklet_message.js'
12
+ import { SpessaSynthInfo, SpessaSynthWarn } from '../utils/loggin.js'
13
+ import { DEFAULT_EFFECTS_CONFIG } from './audio_effects/effects_config.js'
14
+
15
+
16
+ /**
17
+ * synthesizer.js
18
+ * purpose: responds to midi messages and called functions, managing the channels and passing the messages to them
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} StartRenderingDataConfig
23
+ * @property {MIDI} parsedMIDI - the MIDI to render
24
+ * @property {SynthesizerSnapshot} snapshot - the snapshot to apply
25
+ * @property {boolean|undefined} oneOutput - if synth should use one output with 32 channels (2 audio channels for each midi channel). this disables chorus and reverb.
26
+ */
27
+
28
+ export const WORKLET_PROCESSOR_NAME = "spessasynth-worklet-system";
29
+
30
+ export const VOICE_CAP = 450;
31
+
32
+ export const DEFAULT_PERCUSSION = 9;
33
+ export const MIDI_CHANNEL_COUNT = 16;
34
+ export const DEFAULT_SYNTH_MODE = "gs";
35
+
36
+ /**
37
+ * Creates a new instance of the SpessaSynth synthesizer
38
+ * @param targetNode {AudioNode}
39
+ * @param soundFontBuffer {ArrayBuffer} the soundfont file array buffer
40
+ * @param enableEventSystem {boolean} enables the event system. Defaults to true
41
+ * @param startRenderingData {StartRenderingDataConfig} if set, starts playing this immediately and restores the values
42
+ * @param effectsConfig {EffectsConfig} optional configuration for the audio effects.
43
+ */
44
+ export class Synthetizer {
45
+ /**
46
+ * Creates a new instance of the SpessaSynth synthesizer
47
+ * @param targetNode {AudioNode}
48
+ * @param soundFontBuffer {ArrayBuffer} the soundfont file array buffer
49
+ * @param enableEventSystem {boolean} enables the event system. Defaults to true
50
+ * @param startRenderingData {StartRenderingDataConfig} if set, starts playing this immediately and restores the values
51
+ * @param effectsConfig {EffectsConfig} optional configuration for the audio effects.
52
+ */
53
+ constructor(targetNode,
54
+ soundFontBuffer,
55
+ enableEventSystem = true,
56
+ startRenderingData = undefined,
57
+ effectsConfig = DEFAULT_EFFECTS_CONFIG) {
58
+ SpessaSynthInfo("%cInitializing SpessaSynth synthesizer...", consoleColors.info);
59
+ this.context = targetNode.context;
60
+ const oneOutputMode = startRenderingData?.oneOutput === true;
61
+
62
+ /**
63
+ * Allows to set up custom event listeners for the synthesizer
64
+ * @type {EventHandler}
65
+ */
66
+ this.eventHandler = new EventHandler();
67
+
68
+ this._voiceCap = VOICE_CAP;
69
+
70
+ /**
71
+ * the new channels will have their audio sent to the moduled output by this constant.
72
+ * what does that mean? e.g. if outputsAmount is 16, then channel's 16 audio will be sent to channel 0
73
+ * @type {number}
74
+ * @private
75
+ */
76
+ this._outputsAmount = MIDI_CHANNEL_COUNT;
77
+
78
+ /**
79
+ * the amount of midi channels
80
+ * @type {number}
81
+ */
82
+ this.channelsAmount = this._outputsAmount;
83
+
84
+ /**
85
+ * Indicates if the synth is fully ready
86
+ * @type {Promise<void>}
87
+ */
88
+ this.isReady = new Promise(resolve => this._resolveReady = resolve);
89
+
90
+
91
+ /**
92
+ * individual channel voices amount
93
+ * @type {ChannelProperty[]}
94
+ */
95
+ this.channelProperties = [];
96
+ for (let i = 0; i < this.channelsAmount; i++)
97
+ {
98
+ this.addNewChannel(false);
99
+ }
100
+ this.channelProperties[DEFAULT_PERCUSSION].isDrum = true;
101
+ this._voicesAmount = 0;
102
+
103
+ /**
104
+ * For Black MIDI's - forces release time to 50ms
105
+ * @type {boolean}
106
+ */
107
+ this._highPerformanceMode = false;
108
+
109
+ // create a worklet processor
110
+ let processorChannelCount = Array(this._outputsAmount + 2).fill(2);
111
+ let processorOutputsCount = this._outputsAmount + 2;
112
+ if(oneOutputMode)
113
+ {
114
+ processorOutputsCount = 1;
115
+ processorChannelCount = [32];
116
+ }
117
+
118
+ // first two outputs: reverb, chorsu, the others are the channel outputs
119
+ try {
120
+ this.worklet = new AudioWorkletNode(this.context, WORKLET_PROCESSOR_NAME, {
121
+ outputChannelCount: processorChannelCount,
122
+ numberOfOutputs: processorOutputsCount,
123
+ processorOptions: {
124
+ midiChannels: this._outputsAmount,
125
+ soundfont: soundFontBuffer,
126
+ enableEventSystem: enableEventSystem,
127
+ startRenderingData: startRenderingData
128
+ }
129
+ });
130
+ }
131
+ catch (e)
132
+ {
133
+ throw new Error("Could not create the audioWorklet. Did you forget to addModule()?");
134
+ }
135
+
136
+ /**
137
+ * @typedef {Object} PresetListElement
138
+ * @property {string} presetName
139
+ * @property {number} program
140
+ * @property {number} bank
141
+ *
142
+ * used in "presetlistchange" event
143
+ */
144
+
145
+ // worklet sends us some data back
146
+ this.worklet.port.onmessage = e => this.handleMessage(e.data);
147
+
148
+ /**
149
+ * @type {function(SynthesizerSnapshot)}
150
+ * @private
151
+ */
152
+ this._snapshotCallback = undefined;
153
+
154
+ /**
155
+ * for the worklet sequencer's messages
156
+ * @type {function(WorkletSequencerReturnMessageType, any)}
157
+ */
158
+ this.sequencerCallbackFunction = undefined;
159
+
160
+ // add reverb
161
+ if(effectsConfig.reverbEnabled && !oneOutputMode)
162
+ {
163
+ this.reverbProcessor = getReverbProcessor(this.context, effectsConfig.reverbImpulseResponse);
164
+ this.reverbProcessor.connect(targetNode);
165
+ this.worklet.connect(this.reverbProcessor, 0);
166
+ }
167
+
168
+ if(effectsConfig.chorusEnabled && !oneOutputMode)
169
+ {
170
+ this.chorusProcessor = new FancyChorus(targetNode, effectsConfig.chorusConfig);
171
+ this.worklet.connect(this.chorusProcessor.input, 1);
172
+ }
173
+
174
+ if(oneOutputMode)
175
+ {
176
+ // one output mode: one output (duh)
177
+ this.worklet.connect(targetNode, 0);
178
+ }
179
+ else
180
+ {
181
+ // connect all outputs to the output node
182
+ for (let i = 2; i < this.channelsAmount + 2; i++)
183
+ {
184
+ this.worklet.connect(targetNode, i);
185
+ }
186
+ }
187
+
188
+ // attach newchannel to keep track of channels count
189
+ this.eventHandler.addEvent("newchannel", "synth-new-channel", () => {
190
+ this.channelsAmount++;
191
+ });
192
+ }
193
+
194
+ /**
195
+ * The maximum amount of voices allowed at once
196
+ * @returns {number}
197
+ */
198
+ get voiceCap()
199
+ {
200
+ return this._voiceCap;
201
+ }
202
+
203
+ /**
204
+ * The maximum amount of voices allowed at once
205
+ * @param value {number}
206
+ */
207
+ set voiceCap(value)
208
+ {
209
+ this.post({
210
+ messageType: workletMessageType.setMasterParameter,
211
+ messageData: [masterParameterType.voicesCap, value]
212
+ })
213
+ this._voiceCap = value;
214
+ }
215
+
216
+ /**
217
+ * For Black MIDI's - forces release time to 50ms
218
+ * @param {boolean} value
219
+ */
220
+ set highPerformanceMode(value)
221
+ {
222
+ this._highPerformanceMode = value;
223
+
224
+ }
225
+
226
+ get highPerformanceMode()
227
+ {
228
+ return this._highPerformanceMode;
229
+ }
230
+
231
+ /**
232
+ * Handles the messages received from the worklet
233
+ * @param message {WorkletReturnMessage}
234
+ * @private
235
+ */
236
+ handleMessage(message)
237
+ {
238
+ const messageData = message.messageData;
239
+ switch (message.messageType)
240
+ {
241
+ case returnMessageType.channelProperties:
242
+ /**
243
+ * @type {ChannelProperty[]}
244
+ */
245
+ this.channelProperties = messageData;
246
+
247
+ this._voicesAmount = this.channelProperties.reduce((sum, voices) => sum + voices.voicesAmount, 0);
248
+ break;
249
+
250
+ case returnMessageType.eventCall:
251
+ this.eventHandler.callEvent(messageData.eventName, messageData.eventData);
252
+ break;
253
+
254
+ case returnMessageType.sequencerSpecific:
255
+ if(this.sequencerCallbackFunction)
256
+ {
257
+ this.sequencerCallbackFunction(messageData.messageType, messageData.messageData);
258
+ }
259
+ break;
260
+
261
+ case returnMessageType.synthesizerSnapshot:
262
+ if(this._snapshotCallback)
263
+ {
264
+ this._snapshotCallback(messageData);
265
+ }
266
+ break;
267
+
268
+ case returnMessageType.ready:
269
+ this._resolveReady();
270
+ break;
271
+
272
+ case returnMessageType.soundfontError:
273
+ SpessaSynthWarn(new Error(messageData));
274
+ this.eventHandler.callEvent("soundfonterror", messageData);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Gets a complete snapshot of the synthesizer, including controllers
280
+ * @returns {Promise<SynthesizerSnapshot>}
281
+ */
282
+ async getSynthesizerSnapshot()
283
+ {
284
+ return new Promise(resolve => {
285
+ this._snapshotCallback = s => {
286
+ this._snapshotCallback = undefined;
287
+ resolve(s);
288
+ };
289
+ this.post({
290
+ messageType: workletMessageType.requestSynthesizerSnapshot,
291
+ messageData: undefined,
292
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION
293
+ });
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Adds a new channel to the synthesizer
299
+ * @param postMessage {boolean} leave at true, set to false only at initialization
300
+ */
301
+ addNewChannel(postMessage = true)
302
+ {
303
+ this.channelProperties.push({
304
+ voicesAmount: 0,
305
+ pitchBend: 0,
306
+ pitchBendRangeSemitones: 0,
307
+ isMuted: false,
308
+ isDrum: false
309
+ });
310
+ if(!postMessage)
311
+ {
312
+ return;
313
+ }
314
+ this.post({
315
+ channelNumber: 0,
316
+ messageType: workletMessageType.addNewChannel,
317
+ messageData: null
318
+ });
319
+ }
320
+
321
+ /**
322
+ * @param channel {number}
323
+ * @param value {{delay: number, depth: number, rate: number}}
324
+ */
325
+ setVibrato(channel, value)
326
+ {
327
+ this.post({
328
+ channelNumber: channel,
329
+ messageType: workletMessageType.setChannelVibrato,
330
+ messageData: value
331
+ });
332
+ }
333
+
334
+ /**
335
+ * Connects the individual audio outputs to the given audio nodes. In the app it's used by the renderer.
336
+ * @param audioNodes {AudioNode[]}
337
+ */
338
+ connectIndividualOutputs(audioNodes)
339
+ {
340
+ if(audioNodes.length !== this._outputsAmount)
341
+ {
342
+ throw new Error(`input nodes amount differs from the system's outputs amount!
343
+ Expected ${this._outputsAmount} got ${audioNodes.length}`);
344
+ }
345
+ for (let outputNumber = 0; outputNumber < this._outputsAmount; outputNumber++) {
346
+ // + 2 because chorus and reverb come first!
347
+ this.worklet.connect(audioNodes[outputNumber], outputNumber + 2);
348
+ }
349
+ }
350
+
351
+ /*
352
+ * Prevents any further changes to the vibrato via NRPN messages and sets it to disabled
353
+ */
354
+ lockAndResetChannelVibrato()
355
+ {
356
+ // rate -1 disables, see worklet_message.js line 9
357
+ // channel -1 is all
358
+ this.setVibrato(ALL_CHANNELS_OR_DIFFERENT_ACTION, {depth: 0, rate: -1, delay: 0});
359
+ }
360
+
361
+ /**
362
+ * A message for debugging
363
+ */
364
+ debugMessage()
365
+ {
366
+ SpessaSynthInfo(this);
367
+ this.post({
368
+ channelNumber: 0,
369
+ messageType: workletMessageType.debugMessage,
370
+ messageData: undefined
371
+ });
372
+ }
373
+
374
+ /**
375
+ * Starts playing a note
376
+ * @param channel {number} usually 0-15: the channel to play the note
377
+ * @param midiNote {number} 0-127 the key number of the note
378
+ * @param velocity {number} 0-127 the velocity of the note (generally controls loudness)
379
+ * @param enableDebugging {boolean} set to true to log technical details to console
380
+ */
381
+ noteOn(channel, midiNote, velocity, enableDebugging = false) {
382
+ this.post({
383
+ channelNumber: channel,
384
+ messageType: workletMessageType.noteOn,
385
+ messageData: [midiNote, velocity, enableDebugging]
386
+ });
387
+ }
388
+
389
+ /**
390
+ * Stops playing a note
391
+ * @param channel {number} usually 0-15: the channel of the note
392
+ * @param midiNote {number} 0-127 the key number of the note
393
+ * @param force {boolean} instantly kills the note if true
394
+ */
395
+ noteOff(channel, midiNote, force = false) {
396
+ if(force)
397
+ {
398
+ this.post({
399
+ channelNumber: channel,
400
+ messageType: workletMessageType.killNote,
401
+ messageData: midiNote
402
+ });
403
+ }
404
+ else {
405
+ this.post({
406
+ channelNumber: channel,
407
+ messageType: workletMessageType.noteOff,
408
+ messageData: midiNote
409
+ });
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Stops all notes
415
+ * @param force {boolean} if we should instantly kill the note, defaults to false
416
+ */
417
+ stopAll(force=false) {
418
+ this.post({
419
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
420
+ messageType: workletMessageType.stopAll,
421
+ messageData: force ? 1 : 0
422
+ });
423
+
424
+ }
425
+
426
+ /**
427
+ * Changes the given controller
428
+ * @param channel {number} usually 0-15: the channel to change the controller
429
+ * @param controllerNumber {number} 0-127 the MIDI CC number
430
+ * @param controllerValue {number} 0-127 the controller value
431
+ * @param force {boolean} forces the controller change, even if it's locked or gm system is set and the cc is bank select
432
+ */
433
+ controllerChange(channel, controllerNumber, controllerValue, force=false)
434
+ {
435
+ controllerValue = Math.floor(controllerValue);
436
+ controllerNumber = Math.floor(controllerNumber);
437
+ this.post({
438
+ channelNumber: channel,
439
+ messageType: workletMessageType.ccChange,
440
+ messageData: [controllerNumber, controllerValue, force]
441
+ });
442
+ }
443
+
444
+ /**
445
+ * Resets all controllers (for every channel)
446
+ */
447
+ resetControllers()
448
+ {
449
+ this.post({
450
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
451
+ messageType: workletMessageType.ccReset,
452
+ messageData: undefined
453
+ })
454
+ }
455
+
456
+ /**
457
+ * Applies pressure to a given channel
458
+ * @param channel {number} usually 0-15: the channel to change the controller
459
+ * @param pressure {number} 0-127: the pressure to apply
460
+ */
461
+ channelPressure(channel, pressure)
462
+ {
463
+ this.post({
464
+ channelNumber: channel,
465
+ messageType: workletMessageType.channelPressure,
466
+ messageData: pressure
467
+ });
468
+ }
469
+
470
+ /**
471
+ * Applies pressure to a given note
472
+ * @param channel {number} usually 0-15: the channel to change the controller
473
+ * @param midiNote {number} 0-127: the MIDI note
474
+ * @param pressure {number} 0-127: the pressure to apply
475
+ */
476
+ polyPressure(channel, midiNote, pressure)
477
+ {
478
+ this.post({
479
+ channelNumber: channel,
480
+ messageType: workletMessageType.polyPressure,
481
+ messageData: [midiNote, pressure]
482
+ });
483
+ }
484
+
485
+ /**
486
+ * @param data {WorkletMessage}
487
+ */
488
+ post(data)
489
+ {
490
+ this.worklet.port.postMessage(data);
491
+ }
492
+
493
+ /**
494
+ * Sets the pitch of the given channel
495
+ * @param channel {number} usually 0-15: the channel to change pitch
496
+ * @param MSB {number} SECOND byte of the MIDI pitchWheel message
497
+ * @param LSB {number} FIRST byte of the MIDI pitchWheel message
498
+ */
499
+ pitchWheel(channel, MSB, LSB)
500
+ {
501
+ this.post({
502
+ channelNumber: channel,
503
+ messageType: workletMessageType.pitchWheel,
504
+ messageData: [MSB, LSB],
505
+ });
506
+ }
507
+
508
+ /**
509
+ * Transposes the synthetizer's pitch by given semitones amount (percussion channels do not get affected)
510
+ * @param semitones {number} the semitones to transpose by. Can be a floating point number for more precision
511
+ */
512
+ transpose(semitones)
513
+ {
514
+ this.transposeChannel(ALL_CHANNELS_OR_DIFFERENT_ACTION, semitones, false);
515
+ }
516
+
517
+ /**
518
+ * Transposes the channel by given amount of semitones
519
+ * @param channel {number} the channel number
520
+ * @param semitones {number} the transposition of the channel, can be a float
521
+ * @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel
522
+ */
523
+ transposeChannel(channel, semitones, force=false)
524
+ {
525
+ this.post({
526
+ channelNumber: channel,
527
+ messageType: workletMessageType.transpose,
528
+ messageData: [semitones, force]
529
+ });
530
+ }
531
+
532
+ /**
533
+ * Sets the main volume
534
+ * @param volume {number} 0-1 the volume
535
+ */
536
+ setMainVolume(volume)
537
+ {
538
+ this.post({
539
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
540
+ messageType: workletMessageType.setMasterParameter,
541
+ messageData: [masterParameterType.mainVolume, volume]
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Sets the master stereo panning
547
+ * @param pan {number} -1 to 1, the pan (-1 is left, 0 is midde, 1 is right)
548
+ */
549
+ setMasterPan(pan)
550
+ {
551
+ this.post({
552
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
553
+ messageType: workletMessageType.setMasterParameter,
554
+ messageData: [masterParameterType.masterPan, pan]
555
+ });
556
+ }
557
+
558
+ /**
559
+ * Sets the channel's pitch bend range, in semitones
560
+ * @param channel {number} usually 0-15: the channel to change
561
+ * @param pitchBendRangeSemitones {number} the bend range in semitones
562
+ */
563
+ setPitchBendRange(channel, pitchBendRangeSemitones)
564
+ {
565
+ // set range
566
+ this.controllerChange(channel, midiControllers.RPNMsb, 0);
567
+ this.controllerChange(channel, midiControllers.dataEntryMsb, pitchBendRangeSemitones);
568
+
569
+ // reset rpn
570
+ this.controllerChange(channel, midiControllers.RPNMsb, 127);
571
+ this.controllerChange(channel, midiControllers.RPNLsb, 127);
572
+ this.controllerChange(channel, midiControllers.dataEntryMsb, 0);
573
+ }
574
+
575
+ /**
576
+ * Changes the patch for a given channel
577
+ * @param channel {number} usually 0-15: the channel to change
578
+ * @param programNumber {number} 0-127 the MIDI patch number
579
+ * @param userChange {boolean} indicates if the program change has been called by user. defaults to false
580
+ */
581
+ programChange(channel, programNumber, userChange=false)
582
+ {
583
+ this.post({
584
+ channelNumber: channel,
585
+ messageType: workletMessageType.programChange,
586
+ messageData: [programNumber, userChange]
587
+ })
588
+ }
589
+
590
+ /**
591
+ * Causes the given midi channel to ignore controller messages for the given controller number
592
+ * @param channel {number} usually 0-15: the channel to lock
593
+ * @param controllerNumber {number} 0-127 MIDI CC number NOTE: -1 locks the preset
594
+ * @param isLocked {boolean} true if locked, false if unlocked
595
+ */
596
+ lockController(channel, controllerNumber, isLocked)
597
+ {
598
+ this.post({
599
+ channelNumber: channel,
600
+ messageType: workletMessageType.lockController,
601
+ messageData: [controllerNumber, isLocked]
602
+ });
603
+ }
604
+
605
+ /**
606
+ * Mutes or unmutes the given channel
607
+ * @param channel {number} usually 0-15: the channel to lock
608
+ * @param isMuted {boolean} indicates if the channel is muted
609
+ */
610
+ muteChannel(channel, isMuted)
611
+ {
612
+ this.post({
613
+ channelNumber: channel,
614
+ messageType: workletMessageType.muteChannel,
615
+ messageData: isMuted
616
+ });
617
+ }
618
+
619
+ /**
620
+ * Reloads the sounfont.
621
+ * @param soundFontBuffer {ArrayBuffer} the new soundfont file array buffer
622
+ * @return {Promise<void>}
623
+ */
624
+ async reloadSoundFont(soundFontBuffer)
625
+ {
626
+ // copy and use transferable
627
+ const bufferCopy = soundFontBuffer.slice(0);
628
+ await new Promise(resolve => {
629
+ this._resolveReady = resolve;
630
+ this.worklet.port.postMessage({
631
+ channelNumber: 0,
632
+ messageType: workletMessageType.reloadSoundFont,
633
+ messageData: bufferCopy
634
+ }, [bufferCopy]);
635
+ });
636
+ }
637
+
638
+ /**
639
+ * Sends a MIDI Sysex message to the synthesizer
640
+ * @param messageData {IndexedByteArray} the message's data (excluding the F0 byte, but including the F7 at the end)
641
+ */
642
+ systemExclusive(messageData)
643
+ {
644
+ this.post({
645
+ channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
646
+ messageType: workletMessageType.systemExclusive,
647
+ messageData: Array.from(messageData)
648
+ });
649
+ }
650
+
651
+ /**
652
+ * Toggles drums on a given channel
653
+ * @param channel {number}
654
+ * @param isDrum {boolean}
655
+ */
656
+ setDrums(channel, isDrum)
657
+ {
658
+ this.post({
659
+ channelNumber: channel,
660
+ messageType: workletMessageType.setDrums,
661
+ messageData: isDrum
662
+ });
663
+ }
664
+
665
+ /**
666
+ * sends a raw MIDI message to the synthesizer
667
+ * @param message {ArrayLike<number>} the midi message, each number is a byte
668
+ */
669
+ sendMessage(message)
670
+ {
671
+ // discard as soon as possible if high perf
672
+ const statusByteData = getEvent(message[0]);
673
+
674
+
675
+ // process the event
676
+ switch (statusByteData.status)
677
+ {
678
+ case messageTypes.noteOn:
679
+ const velocity = message[2];
680
+ if(velocity > 0) {
681
+ this.noteOn(statusByteData.channel, message[1], velocity);
682
+ }
683
+ else
684
+ {
685
+ this.noteOff(statusByteData.channel, message[1]);
686
+ }
687
+ break;
688
+
689
+ case messageTypes.noteOff:
690
+ this.noteOff(statusByteData.channel, message[1]);
691
+ break;
692
+
693
+ case messageTypes.pitchBend:
694
+ this.pitchWheel(statusByteData.channel, message[2], message[1]);
695
+ break;
696
+
697
+ case messageTypes.controllerChange:
698
+ this.controllerChange(statusByteData.channel, message[1], message[2]);
699
+ break;
700
+
701
+ case messageTypes.programChange:
702
+ this.programChange(statusByteData.channel, message[1]);
703
+ break;
704
+
705
+ case messageTypes.polyPressure:
706
+ this.polyPressure(statusByteData.channel, message[0], message[1]);
707
+ break;
708
+
709
+ case messageTypes.channelPressure:
710
+ this.channelPressure(statusByteData.channel, message[1]);
711
+ break;
712
+
713
+ case messageTypes.systemExclusive:
714
+ this.systemExclusive(new IndexedByteArray(message.slice(1)));
715
+ break;
716
+
717
+ case messageTypes.reset:
718
+ this.stopAll(true);
719
+ this.resetControllers();
720
+ break;
721
+
722
+ default:
723
+ break;
724
+ }
725
+ }
726
+
727
+ /**
728
+ * @returns {number} the audioContext's current time
729
+ */
730
+ get currentTime()
731
+ {
732
+ return this.context.currentTime;
733
+ }
734
+
735
+ /**
736
+ * @returns {number} the current amount of voices playing
737
+ */
738
+ get voicesAmount()
739
+ {
740
+ return this._voicesAmount;
741
+ }
742
+
743
+ reverbateEverythingBecauseWhyNot()
744
+ {
745
+ for (let i = 0; i < this.channelsAmount; i++) {
746
+ this.controllerChange(i, midiControllers.effects1Depth, 127);
747
+ }
748
+ return "That's the spirit!";
749
+ }
750
+ }