spessasynth_lib 3.25.22 → 3.26.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 (168) hide show
  1. package/README.md +29 -114
  2. package/index.js +33 -33
  3. package/package.json +16 -6
  4. package/external_midi/README.md +0 -4
  5. package/external_midi/midi_handler.js +0 -130
  6. package/external_midi/web_midi_link.js +0 -43
  7. package/externals/fflate/LICENSE +0 -21
  8. package/externals/fflate/fflate.min.js +0 -1
  9. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
  10. package/externals/stbvorbis_sync/LICENSE +0 -202
  11. package/externals/stbvorbis_sync/NOTICE +0 -6
  12. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
  13. package/midi/README.md +0 -32
  14. package/midi/basic_midi.js +0 -565
  15. package/midi/midi_builder.js +0 -202
  16. package/midi/midi_data.js +0 -63
  17. package/midi/midi_loader.js +0 -324
  18. package/midi/midi_message.js +0 -254
  19. package/midi/midi_sequence.js +0 -225
  20. package/midi/midi_tools/get_note_times.js +0 -154
  21. package/midi/midi_tools/midi_editor.js +0 -611
  22. package/midi/midi_tools/midi_writer.js +0 -99
  23. package/midi/midi_tools/rmidi_writer.js +0 -567
  24. package/midi/midi_tools/used_keys_loaded.js +0 -238
  25. package/midi/xmf_loader.js +0 -454
  26. package/sequencer/README.md +0 -32
  27. package/sequencer/sequencer_engine/events.js +0 -207
  28. package/sequencer/sequencer_engine/play.js +0 -353
  29. package/sequencer/sequencer_engine/process_event.js +0 -169
  30. package/sequencer/sequencer_engine/process_tick.js +0 -106
  31. package/sequencer/sequencer_engine/sequencer_engine.js +0 -337
  32. package/sequencer/sequencer_engine/song_control.js +0 -229
  33. package/sequencer/worklet_wrapper/default_sequencer_options.js +0 -8
  34. package/sequencer/worklet_wrapper/sequencer.js +0 -807
  35. package/sequencer/worklet_wrapper/sequencer_message.js +0 -53
  36. package/soundfont/README.md +0 -13
  37. package/soundfont/basic_soundfont/basic_instrument.js +0 -77
  38. package/soundfont/basic_soundfont/basic_preset.js +0 -336
  39. package/soundfont/basic_soundfont/basic_sample.js +0 -197
  40. package/soundfont/basic_soundfont/basic_soundfont.js +0 -565
  41. package/soundfont/basic_soundfont/basic_zone.js +0 -64
  42. package/soundfont/basic_soundfont/basic_zones.js +0 -43
  43. package/soundfont/basic_soundfont/generator.js +0 -220
  44. package/soundfont/basic_soundfont/modulator.js +0 -378
  45. package/soundfont/basic_soundfont/riff_chunk.js +0 -149
  46. package/soundfont/basic_soundfont/write_dls/art2.js +0 -173
  47. package/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
  48. package/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -400
  49. package/soundfont/basic_soundfont/write_dls/ins.js +0 -103
  50. package/soundfont/basic_soundfont/write_dls/lins.js +0 -18
  51. package/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
  52. package/soundfont/basic_soundfont/write_dls/rgn2.js +0 -121
  53. package/soundfont/basic_soundfont/write_dls/wave.js +0 -94
  54. package/soundfont/basic_soundfont/write_dls/write_dls.js +0 -119
  55. package/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
  56. package/soundfont/basic_soundfont/write_dls/wvpl.js +0 -32
  57. package/soundfont/basic_soundfont/write_sf2/ibag.js +0 -39
  58. package/soundfont/basic_soundfont/write_sf2/igen.js +0 -80
  59. package/soundfont/basic_soundfont/write_sf2/imod.js +0 -46
  60. package/soundfont/basic_soundfont/write_sf2/inst.js +0 -34
  61. package/soundfont/basic_soundfont/write_sf2/pbag.js +0 -39
  62. package/soundfont/basic_soundfont/write_sf2/pgen.js +0 -82
  63. package/soundfont/basic_soundfont/write_sf2/phdr.js +0 -42
  64. package/soundfont/basic_soundfont/write_sf2/pmod.js +0 -46
  65. package/soundfont/basic_soundfont/write_sf2/sdta.js +0 -80
  66. package/soundfont/basic_soundfont/write_sf2/shdr.js +0 -55
  67. package/soundfont/basic_soundfont/write_sf2/write.js +0 -222
  68. package/soundfont/dls/articulator_converter.js +0 -396
  69. package/soundfont/dls/dls_destinations.js +0 -38
  70. package/soundfont/dls/dls_preset.js +0 -44
  71. package/soundfont/dls/dls_sample.js +0 -75
  72. package/soundfont/dls/dls_soundfont.js +0 -186
  73. package/soundfont/dls/dls_sources.js +0 -62
  74. package/soundfont/dls/dls_zone.js +0 -95
  75. package/soundfont/dls/read_articulation.js +0 -299
  76. package/soundfont/dls/read_instrument.js +0 -121
  77. package/soundfont/dls/read_instrument_list.js +0 -17
  78. package/soundfont/dls/read_lart.js +0 -35
  79. package/soundfont/dls/read_region.js +0 -152
  80. package/soundfont/dls/read_samples.js +0 -270
  81. package/soundfont/load_soundfont.js +0 -21
  82. package/soundfont/read_sf2/generators.js +0 -46
  83. package/soundfont/read_sf2/instruments.js +0 -66
  84. package/soundfont/read_sf2/modulators.js +0 -36
  85. package/soundfont/read_sf2/presets.js +0 -80
  86. package/soundfont/read_sf2/samples.js +0 -304
  87. package/soundfont/read_sf2/soundfont.js +0 -305
  88. package/soundfont/read_sf2/zones.js +0 -263
  89. package/synthetizer/README.md +0 -10
  90. package/synthetizer/audio_effects/effects_config.js +0 -25
  91. package/synthetizer/audio_effects/fancy_chorus.js +0 -162
  92. package/synthetizer/audio_effects/rb_compressed.min.js +0 -1
  93. package/synthetizer/audio_effects/reverb.js +0 -35
  94. package/synthetizer/audio_effects/reverb_as_binary.js +0 -18
  95. package/synthetizer/audio_engine/README.md +0 -25
  96. package/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -266
  97. package/synthetizer/audio_engine/engine_components/controller_tables.js +0 -88
  98. package/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -149
  99. package/synthetizer/audio_engine/engine_components/lfo.js +0 -26
  100. package/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
  101. package/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -471
  102. package/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
  103. package/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
  104. package/synthetizer/audio_engine/engine_components/soundfont_manager/sfman_message.js +0 -9
  105. package/synthetizer/audio_engine/engine_components/soundfont_manager/soundfont_manager.js +0 -254
  106. package/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -120
  107. package/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
  108. package/synthetizer/audio_engine/engine_components/voice.js +0 -519
  109. package/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -401
  110. package/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -263
  111. package/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -132
  112. package/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -48
  113. package/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -241
  114. package/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -27
  115. package/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -253
  116. package/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -66
  117. package/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
  118. package/synthetizer/audio_engine/engine_methods/note_on.js +0 -174
  119. package/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
  120. package/synthetizer/audio_engine/engine_methods/program_change.js +0 -61
  121. package/synthetizer/audio_engine/engine_methods/render_voice.js +0 -196
  122. package/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +0 -30
  123. package/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -22
  124. package/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +0 -40
  125. package/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +0 -34
  126. package/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +0 -21
  127. package/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -20
  128. package/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -55
  129. package/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
  130. package/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
  131. package/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
  132. package/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -744
  133. package/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -24
  134. package/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -33
  135. package/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -31
  136. package/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
  137. package/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -27
  138. package/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
  139. package/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
  140. package/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
  141. package/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
  142. package/synthetizer/audio_engine/main_processor.js +0 -765
  143. package/synthetizer/audio_engine/message_protocol/README.md +0 -13
  144. package/synthetizer/audio_engine/message_protocol/message_sending.js +0 -22
  145. package/synthetizer/audio_engine/message_protocol/worklet_message.js +0 -107
  146. package/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -14
  147. package/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
  148. package/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -122
  149. package/synthetizer/synth_constants.js +0 -20
  150. package/synthetizer/worklet_processor.min.js +0 -21
  151. package/synthetizer/worklet_wrapper/key_modifier_manager.js +0 -104
  152. package/synthetizer/worklet_wrapper/synth_event_handler.js +0 -214
  153. package/synthetizer/worklet_wrapper/synth_soundfont_manager.js +0 -111
  154. package/synthetizer/worklet_wrapper/synthetizer.js +0 -1027
  155. package/synthetizer/worklet_wrapper/worklet_processor.js +0 -340
  156. package/synthetizer/worklet_wrapper/worklet_url.js +0 -16
  157. package/utils/README.md +0 -5
  158. package/utils/buffer_to_wav.js +0 -186
  159. package/utils/byte_functions/big_endian.js +0 -32
  160. package/utils/byte_functions/little_endian.js +0 -77
  161. package/utils/byte_functions/string.js +0 -107
  162. package/utils/byte_functions/variable_length_quantity.js +0 -42
  163. package/utils/fill_with_defaults.js +0 -21
  164. package/utils/indexed_array.js +0 -52
  165. package/utils/loggin.js +0 -79
  166. package/utils/other.js +0 -92
  167. package/utils/sysex_detector.js +0 -58
  168. package/utils/xg_hacks.js +0 -193
@@ -1,1027 +0,0 @@
1
- import { consoleColors } from "../../utils/other.js";
2
- import { messageTypes, midiControllers } from "../../midi/midi_message.js";
3
- import { EventHandler } from "./synth_event_handler.js";
4
- import { FancyChorus } from "../audio_effects/fancy_chorus.js";
5
- import { getReverbProcessor } from "../audio_effects/reverb.js";
6
- import {
7
- ALL_CHANNELS_OR_DIFFERENT_ACTION,
8
- returnMessageType,
9
- workletMessageType
10
- } from "../audio_engine/message_protocol/worklet_message.js";
11
- import { SpessaSynthInfo, SpessaSynthWarn } from "../../utils/loggin.js";
12
- import { DEFAULT_SYNTH_CONFIG } from "../audio_effects/effects_config.js";
13
- import { SoundfontManager } from "./synth_soundfont_manager.js";
14
- import { WorkletKeyModifierManagerWrapper } from "./key_modifier_manager.js";
15
- import { channelConfiguration } from "../audio_engine/engine_components/controller_tables.js";
16
- import { DEFAULT_PERCUSSION, DEFAULT_SYNTH_MODE, MIDI_CHANNEL_COUNT, VOICE_CAP } from "../synth_constants.js";
17
- import { BasicMIDI } from "../../midi/basic_midi.js";
18
- import { fillWithDefaults } from "../../utils/fill_with_defaults.js";
19
- import { DEFAULT_SEQUENCER_OPTIONS } from "../../sequencer/worklet_wrapper/default_sequencer_options.js";
20
- import { WORKLET_PROCESSOR_NAME } from "./worklet_url.js";
21
- import { masterParameterType } from "../audio_engine/engine_methods/controller_control/master_parameters.js";
22
-
23
-
24
- /**
25
- * synthesizer.js
26
- * purpose: responds to midi messages and called functions, managing the channels and passing the messages to them
27
- */
28
-
29
- /**
30
- * @typedef {Object} SynthMethodOptions
31
- * @property {number} time - the audio context time when the event should execute, in seconds.
32
- */
33
-
34
- /**
35
- * @type {SynthMethodOptions}
36
- */
37
- const DEFAULT_SYNTH_METHOD_OPTIONS = {
38
- time: 0
39
- };
40
-
41
-
42
- // the "remote controller" of the worklet processor in the audio thread from the main thread
43
- export class Synthetizer
44
- {
45
-
46
- /**
47
- * Allows setting up custom event listeners for the synthesizer
48
- * @type {EventHandler}
49
- */
50
- eventHandler = new EventHandler();
51
-
52
- /**
53
- * Synthesizer's parent AudioContext instance
54
- * @type {BaseAudioContext}
55
- */
56
- context;
57
-
58
- /**
59
- * Synthesizer's output node
60
- * @type {AudioNode}
61
- */
62
- targetNode;
63
- /**
64
- * @type {boolean}
65
- * @private
66
- */
67
- _destroyed = false;
68
-
69
- /**
70
- * the new channels will have their audio sent to the moduled output by this constant.
71
- * what does that mean?
72
- * e.g., if outputsAmount is 16, then channel's 16 audio data will be sent to channel 0
73
- * @type {number}
74
- * @private
75
- */
76
- _outputsAmount = MIDI_CHANNEL_COUNT;
77
-
78
- /**
79
- * The current number of MIDI channels the synthesizer has
80
- * @type {number}
81
- */
82
- channelsAmount = this._outputsAmount;
83
-
84
- /**
85
- * Synth's current channel properties
86
- * @type {ChannelProperty[]}
87
- */
88
- channelProperties = [];
89
-
90
- /**
91
- * The current preset list
92
- * @type {{presetName: string, bank: number, program: number}[]}
93
- */
94
- presetList = [];
95
-
96
- /**
97
- * Creates a new instance of the SpessaSynth synthesizer.
98
- * @param targetNode {AudioNode}
99
- * @param soundFontBuffer {ArrayBuffer} the soundfont file array buffer.
100
- * @param enableEventSystem {boolean} enables the event system.
101
- * Defaults to true.
102
- * Disable only when you're rendering audio offline with no actions from the main thread
103
- * @param startRenderingData {StartRenderingDataConfig} if it is set,
104
- * starts playing this immediately and restores the values.
105
- * @param synthConfig {SynthConfig} optional configuration for the synthesizer.
106
- */
107
- constructor(targetNode,
108
- soundFontBuffer,
109
- enableEventSystem = true,
110
- startRenderingData = undefined,
111
- synthConfig = DEFAULT_SYNTH_CONFIG)
112
- {
113
- SpessaSynthInfo("%cInitializing SpessaSynth synthesizer...", consoleColors.info);
114
- this.context = targetNode.context;
115
- this.targetNode = targetNode;
116
-
117
- // ensure default values for options
118
- enableEventSystem = enableEventSystem ?? true;
119
- synthConfig = synthConfig ?? DEFAULT_SYNTH_CONFIG;
120
-
121
- // initialize internal promise resolution
122
- this._resolveWhenReady = undefined;
123
- this.isReady = new Promise(resolve => this._resolveWhenReady = resolve);
124
-
125
- // create initial channels
126
- for (let i = 0; i < this.channelsAmount; i++)
127
- {
128
- this.addNewChannel(false);
129
- }
130
- this.channelProperties[DEFAULT_PERCUSSION].isDrum = true;
131
-
132
- // determine output mode and channel configuration
133
- const oneOutputMode = startRenderingData?.oneOutput ?? false;
134
- let processorChannelCount = Array(this._outputsAmount + 2).fill(2);
135
- let processorOutputsCount = this._outputsAmount + 2;
136
- if (oneOutputMode)
137
- {
138
- processorOutputsCount = 1;
139
- processorChannelCount = [32];
140
- }
141
-
142
- // initialize effects configuration
143
- this.effectsConfig = fillWithDefaults(synthConfig, DEFAULT_SYNTH_CONFIG);
144
-
145
- // process start rendering data
146
- const sequencerRenderingData = {};
147
- if (startRenderingData?.parsedMIDI !== undefined)
148
- {
149
- sequencerRenderingData.parsedMIDI = BasicMIDI.copyFrom(startRenderingData.parsedMIDI);
150
- if (startRenderingData?.snapshot)
151
- {
152
- const snapshot = startRenderingData.snapshot;
153
- if (snapshot?.effectsConfig !== undefined)
154
- {
155
- // overwrite effects configuration with the snapshot
156
- this.effectsConfig = fillWithDefaults(snapshot.effectsConfig, DEFAULT_SYNTH_CONFIG);
157
- // delete effects config as it cannot be cloned to the worklet (and does not need to be)
158
- delete snapshot.effectsConfig;
159
- }
160
- sequencerRenderingData.snapshot = snapshot;
161
- }
162
- if (startRenderingData?.sequencerOptions)
163
- {
164
- // sequencer options
165
- sequencerRenderingData.sequencerOptions = fillWithDefaults(
166
- startRenderingData.sequencerOptions,
167
- DEFAULT_SEQUENCER_OPTIONS
168
- );
169
- }
170
-
171
- sequencerRenderingData.loopCount = startRenderingData?.loopCount ?? 0;
172
- }
173
-
174
- // create the audio worklet node
175
- try
176
- {
177
- let workletConstructor = (synthConfig?.audioNodeCreators?.worklet) ??
178
- ((context, name, options) =>
179
- {
180
- return new AudioWorkletNode(context, name, options);
181
- });
182
- this.worklet = workletConstructor(this.context, WORKLET_PROCESSOR_NAME, {
183
- outputChannelCount: processorChannelCount,
184
- numberOfOutputs: processorOutputsCount,
185
- processorOptions: {
186
- midiChannels: oneOutputMode ? 1 : this._outputsAmount,
187
- soundfont: soundFontBuffer,
188
- enableEventSystem: enableEventSystem,
189
- startRenderingData: sequencerRenderingData
190
- }
191
- });
192
- }
193
- catch (e)
194
- {
195
- console.error(e);
196
- throw new Error("Could not create the audioWorklet. Did you forget to addModule()?");
197
- }
198
-
199
- // set up message handling and managers
200
- this.worklet.port.onmessage = e => this.handleMessage(e.data);
201
- this.soundfontManager = new SoundfontManager(this);
202
- this.keyModifierManager = new WorkletKeyModifierManagerWrapper(this);
203
- this._snapshotCallback = undefined;
204
- this.sequencerCallbackFunction = undefined;
205
-
206
- // connect worklet outputs
207
- if (oneOutputMode)
208
- {
209
- this.worklet.connect(targetNode, 0);
210
- }
211
- else
212
- {
213
- const reverbOn = this.effectsConfig?.reverbEnabled ?? true;
214
- const chorusOn = this.effectsConfig?.chorusEnabled ?? true;
215
- if (reverbOn)
216
- {
217
- const proc = getReverbProcessor(this.context, this.effectsConfig.reverbImpulseResponse);
218
- this.reverbProcessor = proc.conv;
219
- this.isReady = Promise.all([this.isReady, proc.promise]);
220
- this.reverbProcessor.connect(targetNode);
221
- this.worklet.connect(this.reverbProcessor, 0);
222
- }
223
- if (chorusOn)
224
- {
225
- this.chorusProcessor = new FancyChorus(targetNode, this.effectsConfig.chorusConfig);
226
- this.worklet.connect(this.chorusProcessor.input, 1);
227
- }
228
- for (let i = 2; i < this.channelsAmount + 2; i++)
229
- {
230
- this.worklet.connect(targetNode, i);
231
- }
232
- }
233
-
234
- // attach event handlers
235
- this.eventHandler.addEvent("newchannel", `synth-new-channel-${Math.random()}`, () =>
236
- {
237
- this.channelsAmount++;
238
- });
239
- this.eventHandler.addEvent("presetlistchange", `synth-preset-list-change-${Math.random()}`, e =>
240
- {
241
- this.presetList = e;
242
- });
243
- }
244
-
245
-
246
- /**
247
- * @type {"gm"|"gm2"|"gs"|"xg"}
248
- * @private
249
- */
250
- _midiSystem = DEFAULT_SYNTH_MODE;
251
-
252
- /**
253
- * The current MIDI system used by the synthesizer
254
- * @returns {"gm"|"gm2"|"gs"|"xg"}
255
- */
256
- get midiSystem()
257
- {
258
- return this._midiSystem;
259
- }
260
-
261
- /**
262
- * The current MIDI system used by the synthesizer
263
- * @param value {"gm"|"gm2"|"gs"|"xg"}
264
- */
265
- set midiSystem(value)
266
- {
267
- this._midiSystem = value;
268
- }
269
-
270
- /**
271
- * current voice amount
272
- * @type {number}
273
- * @private
274
- */
275
- _voicesAmount = 0;
276
-
277
- /**
278
- * @returns {number} the current number of voices playing.
279
- */
280
- get voicesAmount()
281
- {
282
- return this._voicesAmount;
283
- }
284
-
285
- /**
286
- * For Black MIDI's - forces release time to 50 ms
287
- * @type {boolean}
288
- */
289
- _highPerformanceMode = false;
290
-
291
- get highPerformanceMode()
292
- {
293
- return this._highPerformanceMode;
294
- }
295
-
296
- /**
297
- * For Black MIDI's - forces release time to 50 ms.
298
- * @param {boolean} value
299
- */
300
- set highPerformanceMode(value)
301
- {
302
- this._highPerformanceMode = value;
303
- this.post({
304
- messageType: workletMessageType.highPerformanceMode,
305
- messageData: value
306
- });
307
- }
308
-
309
- /**
310
- * @type {number}
311
- * @private
312
- */
313
- _voiceCap = VOICE_CAP;
314
-
315
- /**
316
- * The maximum number of voices allowed at once.
317
- * @returns {number}
318
- */
319
- get voiceCap()
320
- {
321
- return this._voiceCap;
322
- }
323
-
324
- /**
325
- * The maximum number of voices allowed at once.
326
- * @param value {number}
327
- */
328
- set voiceCap(value)
329
- {
330
- this._setMasterParam(masterParameterType.voicesCap, value);
331
- this._voiceCap = value;
332
- }
333
-
334
- /**
335
- * @returns {number} the audioContext's current time.
336
- */
337
- get currentTime()
338
- {
339
- return this.context.currentTime;
340
- }
341
-
342
- /**
343
- * Sets the SpessaSynth's log level.
344
- * @param enableInfo {boolean} - enable info (verbose)
345
- * @param enableWarning {boolean} - enable warnings (unrecognized messages)
346
- * @param enableGroup {boolean} - enable groups (to group a lot of logs)
347
- * @param enableTable {boolean} - enable table (debug message)
348
- */
349
- setLogLevel(enableInfo, enableWarning, enableGroup, enableTable)
350
- {
351
- this.post({
352
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
353
- messageType: workletMessageType.setLogLevel,
354
- messageData: [enableInfo, enableWarning, enableGroup, enableTable]
355
- });
356
- }
357
-
358
- /**
359
- * @param type {masterParameterType}
360
- * @param data {any}
361
- * @private
362
- */
363
- _setMasterParam(type, data)
364
- {
365
- this.post({
366
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
367
- messageType: workletMessageType.setMasterParameter,
368
- messageData: [type, data]
369
- });
370
- }
371
-
372
- /**
373
- * Sets the interpolation type for the synthesizer:
374
- * 0. - linear
375
- * 1. - nearest neighbor
376
- * 2. - cubic
377
- * @param type {interpolationTypes}
378
- */
379
- setInterpolationType(type)
380
- {
381
- this._setMasterParam(masterParameterType.interpolationType, type);
382
- }
383
-
384
- /**
385
- * Handles the messages received from the worklet.
386
- * @param message {WorkletReturnMessage}
387
- * @private
388
- */
389
- handleMessage(message)
390
- {
391
- const messageData = message.messageData;
392
- switch (message.messageType)
393
- {
394
- case returnMessageType.channelPropertyChange:
395
- /**
396
- * @type {number}
397
- */
398
- const channelNumber = messageData[0];
399
- /**
400
- * @type {ChannelProperty}
401
- */
402
- const property = messageData[1];
403
-
404
- this.channelProperties[channelNumber] = property;
405
-
406
- this._voicesAmount = this.channelProperties.reduce((sum, voices) => sum + voices.voicesAmount, 0);
407
- break;
408
-
409
- case returnMessageType.eventCall:
410
- this.eventHandler.callEvent(messageData.eventName, messageData.eventData);
411
- break;
412
-
413
- case returnMessageType.sequencerSpecific:
414
- if (this.sequencerCallbackFunction)
415
- {
416
- this.sequencerCallbackFunction(messageData.messageType, messageData.messageData);
417
- }
418
- break;
419
-
420
- case returnMessageType.masterParameterChange:
421
- /**
422
- * @type {masterParameterType}
423
- */
424
- const param = messageData[0];
425
- const value = messageData[1];
426
- switch (param)
427
- {
428
- default:
429
- break;
430
-
431
- case masterParameterType.midiSystem:
432
- this._midiSystem = value;
433
- break;
434
- }
435
- break;
436
-
437
- case returnMessageType.synthesizerSnapshot:
438
- if (this._snapshotCallback)
439
- {
440
- this._snapshotCallback(messageData);
441
- }
442
- break;
443
-
444
- case returnMessageType.isFullyInitialized:
445
- this._resolveWhenReady();
446
- break;
447
-
448
- case returnMessageType.soundfontError:
449
- SpessaSynthWarn(new Error(messageData));
450
- this.eventHandler.callEvent("soundfonterror", messageData);
451
- break;
452
- }
453
- }
454
-
455
- /**
456
- * Gets a complete snapshot of the synthesizer, including controllers.
457
- * @returns {Promise<SynthesizerSnapshot>}
458
- */
459
- async getSynthesizerSnapshot()
460
- {
461
- return new Promise(resolve =>
462
- {
463
- this._snapshotCallback = s =>
464
- {
465
- this._snapshotCallback = undefined;
466
- s.effectsConfig = this.effectsConfig;
467
- resolve(s);
468
- };
469
- this.post({
470
- messageType: workletMessageType.requestSynthesizerSnapshot,
471
- messageData: undefined,
472
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION
473
- });
474
- });
475
- }
476
-
477
- /**
478
- * Adds a new channel to the synthesizer.
479
- * @param postMessage {boolean} leave at true, set to false only at initialization.
480
- */
481
- addNewChannel(postMessage = true)
482
- {
483
- this.channelProperties.push({
484
- voicesAmount: 0,
485
- pitchBend: 0,
486
- pitchBendRangeSemitones: 0,
487
- isMuted: false,
488
- isDrum: false,
489
- transposition: 0,
490
- program: 0,
491
- bank: this.channelsAmount % 16 === DEFAULT_PERCUSSION ? 128 : 0
492
- });
493
- if (!postMessage)
494
- {
495
- return;
496
- }
497
- this.post({
498
- channelNumber: 0,
499
- messageType: workletMessageType.addNewChannel,
500
- messageData: null
501
- });
502
- }
503
-
504
- /**
505
- * @param channel {number}
506
- * @param value {{delay: number, depth: number, rate: number}}
507
- */
508
- setVibrato(channel, value)
509
- {
510
- this.post({
511
- channelNumber: channel,
512
- messageType: workletMessageType.setChannelVibrato,
513
- messageData: value
514
- });
515
- }
516
-
517
- /**
518
- * Connects the individual audio outputs to the given audio nodes. In the app, it's used by the renderer.
519
- * @param audioNodes {AudioNode[]}
520
- */
521
- connectIndividualOutputs(audioNodes)
522
- {
523
- if (audioNodes.length !== this._outputsAmount)
524
- {
525
- throw new Error(`input nodes amount differs from the system's outputs amount!
526
- Expected ${this._outputsAmount} got ${audioNodes.length}`);
527
- }
528
- for (let outputNumber = 0; outputNumber < this._outputsAmount; outputNumber++)
529
- {
530
- // + 2 because chorus and reverb come first!
531
- this.worklet.connect(audioNodes[outputNumber], outputNumber + 2);
532
- }
533
- }
534
-
535
- /**
536
- * Disconnects the individual audio outputs to the given audio nodes. In the app, it's used by the renderer.
537
- * @param audioNodes {AudioNode[]}
538
- */
539
- disconnectIndividualOutputs(audioNodes)
540
- {
541
- if (audioNodes.length !== this._outputsAmount)
542
- {
543
- throw new Error(`input nodes amount differs from the system's outputs amount!
544
- Expected ${this._outputsAmount} got ${audioNodes.length}`);
545
- }
546
- for (let outputNumber = 0; outputNumber < this._outputsAmount; outputNumber++)
547
- {
548
- // + 2 because chorus and reverb come first!
549
- this.worklet.disconnect(audioNodes[outputNumber], outputNumber + 2);
550
- }
551
- }
552
-
553
- /*
554
- * Disables the GS NRPN parameters like vibrato or drum key tuning.
555
- */
556
- disableGSNRPparams()
557
- {
558
- // rate -1 disables, see worklet_message.js line 9
559
- // channel -1 is all
560
- this.setVibrato(ALL_CHANNELS_OR_DIFFERENT_ACTION, { depth: 0, rate: -1, delay: 0 });
561
- }
562
-
563
- /**
564
- * A message for debugging.
565
- */
566
- debugMessage()
567
- {
568
- SpessaSynthInfo(this);
569
- this.post({
570
- channelNumber: 0,
571
- messageType: workletMessageType.debugMessage,
572
- messageData: undefined
573
- });
574
- }
575
-
576
- /**
577
- * sends a raw MIDI message to the synthesizer.
578
- * @param message {number[]|Uint8Array} the midi message, each number is a byte.
579
- * @param channelOffset {number} the channel offset of the message.
580
- * @param eventOptions {SynthMethodOptions} additional options for this command.
581
- */
582
- sendMessage(message, channelOffset = 0, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
583
- {
584
- this._sendInternal(message, channelOffset, false, eventOptions);
585
- }
586
-
587
- /**
588
- * @param message {number[]|Uint8Array}
589
- * @param offset {number}
590
- * @param force {boolean}
591
- * @param eventOptions {SynthMethodOptions}
592
- * @private
593
- */
594
- _sendInternal(message, offset, force = false, eventOptions)
595
- {
596
- const opts = fillWithDefaults(eventOptions ?? {}, DEFAULT_SYNTH_METHOD_OPTIONS);
597
- this.post({
598
- messageType: workletMessageType.midiMessage,
599
- messageData: [new Uint8Array(message), offset, force, opts]
600
- });
601
- }
602
-
603
-
604
- /**
605
- * Starts playing a note
606
- * @param channel {number} usually 0-15: the channel to play the note.
607
- * @param midiNote {number} 0-127 the key number of the note.
608
- * @param velocity {number} 0-127 the velocity of the note (generally controls loudness).
609
- * @param eventOptions {SynthMethodOptions} additional options for this command.
610
- */
611
- noteOn(channel, midiNote, velocity, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
612
- {
613
- const ch = channel % 16;
614
- const offset = channel - ch;
615
- midiNote %= 128;
616
- velocity %= 128;
617
- // check for legacy "enableDebugging"
618
- if (eventOptions === true)
619
- {
620
- eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS;
621
- }
622
- this.sendMessage([messageTypes.noteOn | ch, midiNote, velocity], offset, eventOptions);
623
- }
624
-
625
- /**
626
- * Stops playing a note.
627
- * @param channel {number} usually 0-15: the channel of the note.
628
- * @param midiNote {number} 0-127 the key number of the note.
629
- * @param force {boolean} instantly kills the note if true.
630
- * @param eventOptions {SynthMethodOptions} additional options for this command.
631
- */
632
- noteOff(channel, midiNote, force = false, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
633
- {
634
- midiNote %= 128;
635
-
636
- const ch = channel % 16;
637
- const offset = channel - ch;
638
- this._sendInternal([messageTypes.noteOff | ch, midiNote], offset, force, eventOptions);
639
- }
640
-
641
- /**
642
- * Stops all notes.
643
- * @param force {boolean} if we should instantly kill the note, defaults to false.
644
- */
645
- stopAll(force = false)
646
- {
647
- this.post({
648
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
649
- messageType: workletMessageType.stopAll,
650
- messageData: force ? 1 : 0
651
- });
652
-
653
- }
654
-
655
- /**
656
- * Changes the given controller
657
- * @param channel {number} usually 0-15: the channel to change the controller.
658
- * @param controllerNumber {number} 0-127 the MIDI CC number.
659
- * @param controllerValue {number} 0-127 the controller value.
660
- * @param force {boolean} forces the controller-change message, even if it's locked or gm system is set and the cc is bank select.
661
- * @param eventOptions {SynthMethodOptions} additional options for this command.
662
- */
663
- controllerChange(channel, controllerNumber, controllerValue, force = false, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
664
- {
665
- if (controllerNumber > 127 || controllerNumber < 0)
666
- {
667
- throw new Error(`Invalid controller number: ${controllerNumber}`);
668
- }
669
- controllerValue = Math.floor(controllerValue) % 128;
670
- controllerNumber = Math.floor(controllerNumber) % 128;
671
- // controller change has its own message for the force property
672
- const ch = channel % 16;
673
- const offset = channel - ch;
674
- this._sendInternal(
675
- [messageTypes.controllerChange | ch, controllerNumber, controllerValue],
676
- offset,
677
- force,
678
- eventOptions
679
- );
680
- }
681
-
682
- /**
683
- * Resets all controllers (for every channel)
684
- */
685
- resetControllers()
686
- {
687
- this.post({
688
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
689
- messageType: workletMessageType.ccReset,
690
- messageData: undefined
691
- });
692
- }
693
-
694
- /**
695
- * Applies pressure to a given channel.
696
- * @param channel {number} usually 0-15: the channel to change the controller.
697
- * @param pressure {number} 0-127: the pressure to apply.
698
- * @param eventOptions {SynthMethodOptions} additional options for this command.
699
- */
700
- channelPressure(channel, pressure, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
701
- {
702
- const ch = channel % 16;
703
- const offset = channel - ch;
704
- pressure %= 128;
705
- this.sendMessage([messageTypes.channelPressure | ch, pressure], offset, eventOptions);
706
- }
707
-
708
- /**
709
- * Applies pressure to a given note.
710
- * @param channel {number} usually 0-15: the channel to change the controller.
711
- * @param midiNote {number} 0-127: the MIDI note.
712
- * @param pressure {number} 0-127: the pressure to apply.
713
- * @param eventOptions {SynthMethodOptions} additional options for this command.
714
- */
715
- polyPressure(channel, midiNote, pressure, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
716
- {
717
- const ch = channel % 16;
718
- const offset = channel - ch;
719
- midiNote %= 128;
720
- pressure %= 128;
721
- this.sendMessage([messageTypes.polyPressure | ch, midiNote, pressure], offset, eventOptions);
722
- }
723
-
724
- /**
725
- * Sets the pitch of the given channel.
726
- * @param channel {number} usually 0-15: the channel to change pitch.
727
- * @param MSB {number} SECOND byte of the MIDI pitchWheel message.
728
- * @param LSB {number} FIRST byte of the MIDI pitchWheel message.
729
- * @param eventOptions {SynthMethodOptions} additional options for this command.
730
- */
731
- pitchWheel(channel, MSB, LSB, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
732
- {
733
- const ch = channel % 16;
734
- const offset = channel - ch;
735
- this.sendMessage([messageTypes.pitchBend | ch, LSB, MSB], offset, eventOptions);
736
- }
737
-
738
- /**
739
- * @param data {WorkletMessage}
740
- */
741
- post(data)
742
- {
743
- if (this._destroyed)
744
- {
745
- throw new Error("This synthesizer instance has been destroyed!");
746
- }
747
- this.worklet.port.postMessage(data);
748
- }
749
-
750
- /**
751
- * Transposes the synthetizer's pitch by given semitones amount (percussion channels don’t get affected).
752
- * @param semitones {number} the semitones to transpose by.
753
- * It can be a floating point number for more precision.
754
- */
755
- transpose(semitones)
756
- {
757
- this.transposeChannel(ALL_CHANNELS_OR_DIFFERENT_ACTION, semitones, false);
758
- }
759
-
760
- /**
761
- * Transposes the channel by given number of semitones.
762
- * @param channel {number} the channel number.
763
- * @param semitones {number} the transposition of the channel, it can be a float.
764
- * @param force {boolean} defaults to false, if true transposes the channel even if it's a drum channel.
765
- */
766
- transposeChannel(channel, semitones, force = false)
767
- {
768
- this.post({
769
- channelNumber: channel,
770
- messageType: workletMessageType.transpose,
771
- messageData: [semitones, force]
772
- });
773
- }
774
-
775
- /**
776
- * Sets the main volume.
777
- * @param volume {number} 0-1 the volume.
778
- */
779
- setMainVolume(volume)
780
- {
781
- this._setMasterParam(masterParameterType.mainVolume, volume);
782
- }
783
-
784
- /**
785
- * Sets the master stereo panning.
786
- * @param pan {number} (-1 to 1), the pan (-1 is left, 0 is midde, 1 is right)
787
- */
788
- setMasterPan(pan)
789
- {
790
- this._setMasterParam(masterParameterType.masterPan, pan);
791
- }
792
-
793
- /**
794
- * Sets the channel's pitch bend range, in semitones
795
- * @param channel {number} usually 0-15: the channel to change
796
- * @param pitchBendRangeSemitones {number} the bend range in semitones
797
- */
798
- setPitchBendRange(channel, pitchBendRangeSemitones)
799
- {
800
- // set range
801
- this.controllerChange(channel, midiControllers.RPNMsb, 0);
802
- this.controllerChange(channel, midiControllers.dataEntryMsb, pitchBendRangeSemitones);
803
-
804
- // reset rpn
805
- this.controllerChange(channel, midiControllers.RPNMsb, 127);
806
- this.controllerChange(channel, midiControllers.RPNLsb, 127);
807
- this.controllerChange(channel, midiControllers.dataEntryMsb, 0);
808
- }
809
-
810
- /**
811
- * Changes the patch for a given channel
812
- * @param channel {number} usually 0-15: the channel to change
813
- * @param programNumber {number} 0-127 the MIDI patch number
814
- * defaults to false
815
- */
816
- programChange(channel, programNumber)
817
- {
818
- const ch = channel % 16;
819
- const offset = channel - ch;
820
- programNumber %= 128;
821
- this.sendMessage([messageTypes.programChange | ch, programNumber], offset);
822
- }
823
-
824
- /**
825
- * Overrides velocity on a given channel.
826
- * @param channel {number} usually 0-15: the channel to change.
827
- * @param velocity {number} 1-127, the velocity to use.
828
- * 0 Disables this functionality
829
- */
830
- velocityOverride(channel, velocity)
831
- {
832
- const ch = channel % 16;
833
- const offset = channel - ch;
834
- this._sendInternal(
835
- [messageTypes.controllerChange | ch, channelConfiguration.velocityOverride, velocity],
836
- offset,
837
- true,
838
- DEFAULT_SYNTH_METHOD_OPTIONS
839
- );
840
- }
841
-
842
- /**
843
- * Causes the given midi channel to ignore controller messages for the given controller number.
844
- * @param channel {number} usually 0-15: the channel to lock.
845
- * @param controllerNumber {number} 0-127 MIDI CC number NOTE: -1 locks the preset.
846
- * @param isLocked {boolean} true if locked, false if unlocked
847
- */
848
- lockController(channel, controllerNumber, isLocked)
849
- {
850
- this.post({
851
- channelNumber: channel,
852
- messageType: workletMessageType.lockController,
853
- messageData: [controllerNumber, isLocked]
854
- });
855
- }
856
-
857
- /**
858
- * Mutes or unmutes the given channel.
859
- * @param channel {number} usually 0-15: the channel to lock.
860
- * @param isMuted {boolean} indicates if the channel is muted.
861
- */
862
- muteChannel(channel, isMuted)
863
- {
864
- this.post({
865
- channelNumber: channel,
866
- messageType: workletMessageType.muteChannel,
867
- messageData: isMuted
868
- });
869
- }
870
-
871
- /**
872
- * Reloads the sounfont.
873
- * THIS IS DEPRECATED!
874
- * USE soundfontManager instead.
875
- * @param soundFontBuffer {ArrayBuffer} the new soundfont file array buffer.
876
- * @return {Promise<void>}
877
- * @deprecated Use the soundfontManager property.
878
- */
879
- async reloadSoundFont(soundFontBuffer)
880
- {
881
- SpessaSynthWarn("reloadSoundFont is deprecated. Please use the soundfontManager property instead.");
882
- await this.soundfontManager.reloadManager(soundFontBuffer);
883
- }
884
-
885
- /**
886
- * Sends a MIDI Sysex message to the synthesizer.
887
- * @param messageData {number[]|ArrayLike|Uint8Array} the message's data
888
- * (excluding the F0 byte, but including the F7 at the end).
889
- * @param channelOffset {number} channel offset for the system exclusive message, defaults to zero.
890
- * @param eventOptions {SynthMethodOptions} additional options for this command.
891
- */
892
- systemExclusive(messageData, channelOffset = 0, eventOptions = DEFAULT_SYNTH_METHOD_OPTIONS)
893
- {
894
- this._sendInternal(
895
- [messageTypes.systemExclusive, ...Array.from(messageData)],
896
- channelOffset,
897
- false,
898
- eventOptions
899
- );
900
- }
901
-
902
- // noinspection JSUnusedGlobalSymbols
903
- /**
904
- * Tune MIDI keys of a given program using the MIDI Tuning Standard.
905
- * @param program {number} 0 - 127 the MIDI program number to use.
906
- * @param tunings {{sourceKey: number, targetPitch: number}[]} - the keys and their tunings.
907
- * TargetPitch of -1 sets the tuning for this key to be tuned regularly.
908
- */
909
- tuneKeys(program, tunings)
910
- {
911
- if (tunings.length > 127)
912
- {
913
- throw new Error("Too many tunings. Maximum allowed is 127.");
914
- }
915
- const systemExclusive = [
916
- 0x7F, // real-time
917
- 0x10, // device id
918
- 0x08, // MIDI Tuning
919
- 0x02, // note change
920
- program, // tuning program number
921
- tunings.length // number of changes
922
- ];
923
- for (const tuning of tunings)
924
- {
925
- systemExclusive.push(tuning.sourceKey); // [kk] MIDI Key number
926
- if (tuning.targetPitch === -1)
927
- {
928
- // no change
929
- systemExclusive.push(0x7F, 0x7F, 0x7F);
930
- }
931
- else
932
- {
933
- const midiNote = Math.floor(tuning.targetPitch);
934
- const fraction = Math.floor((tuning.targetPitch - midiNote) / 0.000061);
935
- systemExclusive.push(
936
- midiNote,// frequency data byte 1
937
- (fraction >> 7) & 0x7F, // frequency data byte 2
938
- fraction & 0x7F // frequency data byte 3
939
- );
940
- }
941
- }
942
- systemExclusive.push(0xF7);
943
- this.systemExclusive(systemExclusive);
944
- }
945
-
946
- /**
947
- * Toggles drums on a given channel.
948
- * @param channel {number}
949
- * @param isDrum {boolean}
950
- */
951
- setDrums(channel, isDrum)
952
- {
953
- this.post({
954
- channelNumber: channel,
955
- messageType: workletMessageType.setDrums,
956
- messageData: isDrum
957
- });
958
- }
959
-
960
- /**
961
- * Updates the reverb processor with a new impulse response.
962
- * @param buffer {AudioBuffer} the new reverb impulse response.
963
- */
964
- setReverbResponse(buffer)
965
- {
966
- this.reverbProcessor.buffer = buffer;
967
- this.effectsConfig.reverbImpulseResponse = buffer;
968
- }
969
-
970
- /**
971
- * Updates the chorus processor parameters.
972
- * @param config {ChorusConfig} the new chorus.
973
- */
974
- setChorusConfig(config)
975
- {
976
- this.worklet.disconnect(this.chorusProcessor.input);
977
- this.chorusProcessor.delete();
978
- delete this.chorusProcessor;
979
- this.chorusProcessor = new FancyChorus(this.targetNode, config);
980
- this.worklet.connect(this.chorusProcessor.input, 1);
981
- this.effectsConfig.chorusConfig = config;
982
- }
983
-
984
- /**
985
- * Changes the effects gain.
986
- * @param reverbGain {number} the reverb gain, 0-1.
987
- * @param chorusGain {number} the chorus gain, 0-1.
988
- */
989
- setEffectsGain(reverbGain, chorusGain)
990
- {
991
- // noinspection JSCheckFunctionSignatures
992
- this.post({
993
- messageType: workletMessageType.setEffectsGain,
994
- messageData: [reverbGain, chorusGain]
995
- });
996
- }
997
-
998
- /**
999
- * Destroys the synthesizer instance.
1000
- */
1001
- destroy()
1002
- {
1003
- this.reverbProcessor.disconnect();
1004
- this.chorusProcessor.delete();
1005
- // noinspection JSCheckFunctionSignatures
1006
- this.post({
1007
- messageType: workletMessageType.destroyWorklet,
1008
- messageData: undefined
1009
- });
1010
- this.worklet.disconnect();
1011
- delete this.worklet;
1012
- delete this.reverbProcessor;
1013
- delete this.chorusProcessor;
1014
- this._destroyed = true;
1015
- }
1016
-
1017
- // noinspection JSUnusedGlobalSymbols
1018
- reverbateEverythingBecauseWhyNot()
1019
- {
1020
- for (let i = 0; i < this.channelsAmount; i++)
1021
- {
1022
- this.controllerChange(i, midiControllers.reverbDepth, 127);
1023
- this.lockController(i, midiControllers.reverbDepth, true);
1024
- }
1025
- return "That's the spirit!";
1026
- }
1027
- }