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,303 @@
1
+ import {
2
+ addNewChannel,
3
+ } from './worklet_system/worklet_utilities/worklet_processor_channel.js'
4
+
5
+ import { SoundFont2 } from '../soundfont/soundfont_parser.js'
6
+ import { systemExclusive } from './worklet_system/worklet_methods/system_exclusive.js'
7
+ import { noteOn } from './worklet_system/worklet_methods/note_on.js'
8
+ import { dataEntryCoarse, dataEntryFine } from './worklet_system/worklet_methods/data_entry.js'
9
+ import { killNote, noteOff, stopAll, stopAllChannels } from './worklet_system/worklet_methods/note_off.js'
10
+ import {
11
+ controllerChange, muteChannel,
12
+ resetAllControllers,
13
+ resetControllers,
14
+ resetParameters, setMainVolume, setMasterPan,
15
+ } from './worklet_system/worklet_methods/controller_control.js'
16
+ import {
17
+ pitchWheel,
18
+ setChannelTuning,
19
+ setMasterTuning, setModulationDepth,
20
+ transposeAllChannels,
21
+ transposeChannel,
22
+ } from './worklet_system/worklet_methods/tuning_control.js'
23
+ import {
24
+ programChange,
25
+ reloadSoundFont,
26
+ sampleDump,
27
+ setDrums,
28
+ setPreset,
29
+ } from './worklet_system/worklet_methods/program_control.js'
30
+ import { disableAndLockVibrato, setVibrato } from './worklet_system/worklet_methods/vibrato_control.js'
31
+ import { SpessaSynthInfo } from '../utils/loggin.js'
32
+ import { consoleColors } from '../utils/other.js'
33
+ import { releaseVoice, renderVoice, voiceKilling } from './worklet_system/worklet_methods/voice_control.js'
34
+ import {stbvorbis} from "../utils/stbvorbis_sync.js";
35
+
36
+
37
+ export const VOICE_CAP = 450;
38
+
39
+ export const DEFAULT_PERCUSSION = 9;
40
+ export const DEFAULT_CHANNEL_COUNT = 16;
41
+ export const DEFAULT_SYNTH_MODE = "gs";
42
+
43
+ export const MIN_NOTE_LENGTH = 0.07; // if the note is released faster than that, it forced to last that long
44
+
45
+ export const SYNTHESIZER_GAIN = 1.0;
46
+
47
+ const BLOCK_SIZE = 128;
48
+
49
+ class Synthesizer {
50
+ /**
51
+ * Creates a new synthesizer
52
+ * @param soundFontBuffer {Buffer|ArrayBufferLike} the soundfont file buffer.
53
+ * @param sampleRate {number} the sample rate, in hertz.
54
+ * @param blockSize {number} the block size in samples, represets the interval of updating the sequencer, modulation envelope, etc. Defaults to 128.
55
+ */
56
+ constructor(soundFontBuffer, sampleRate, blockSize = BLOCK_SIZE) {
57
+
58
+ this.currentTime = 0;
59
+ this.sampleRate = sampleRate;
60
+ this.blockSize = blockSize;
61
+
62
+ this._outputsAmount = DEFAULT_CHANNEL_COUNT;
63
+
64
+ /**
65
+ * @type {function}
66
+ */
67
+ this.processTickCallback = undefined;
68
+
69
+ this.transposition = 0;
70
+
71
+ /**
72
+ * The volume gain
73
+ * @type {number}
74
+ */
75
+ this.mainVolume = SYNTHESIZER_GAIN;
76
+
77
+ /**
78
+ * -1 to 1
79
+ * @type {number}
80
+ */
81
+ this.pan = 0.0;
82
+ /**
83
+ * the pan of the left channel
84
+ * @type {number}
85
+ */
86
+ this.panLeft = 0.5 * this.mainVolume;
87
+
88
+ this.highPerformanceMode = false;
89
+
90
+ /**
91
+ * If using sf3 soundfonts, wait for this promise
92
+ * @type {Promise<unknown>}
93
+ */
94
+ this.sf3supportReady = stbvorbis.isReady;
95
+
96
+ /**
97
+ * the pan of the right channel
98
+ * @type {number}
99
+ */
100
+ this.panRight = 0.5 * this.mainVolume;
101
+ /**
102
+ * @type {SoundFont2}
103
+ */
104
+ this.soundfont = new SoundFont2(soundFontBuffer);
105
+
106
+ this.defaultPreset = this.soundfont.getPreset(0, 0);
107
+ this.drumPreset = this.soundfont.getPreset(128, 0);
108
+
109
+ /**
110
+ * @type {Float32Array[]}
111
+ */
112
+ this.workletDumpedSamplesList = [];
113
+ /**
114
+ * contains all the channels with their voices on the processor size
115
+ * @type {WorkletProcessorChannel[]}
116
+ */
117
+ this.workletProcessorChannels = [];
118
+ for (let i = 0; i < this._outputsAmount; i++) {
119
+ this.addNewChannel(false);
120
+ }
121
+
122
+ this.workletProcessorChannels[DEFAULT_PERCUSSION].preset = this.drumPreset;
123
+ this.workletProcessorChannels[DEFAULT_PERCUSSION].drumChannel = true;
124
+
125
+ // in seconds, time between two samples (very, very short)
126
+ this.sampleTime = 1 / this.sampleRate;
127
+
128
+ /**
129
+ * Controls the system
130
+ * @typedef {"gm"|"gm2"|"gs"|"xg"} SynthSystem
131
+ * @type {SynthSystem}
132
+ */
133
+ this.system = DEFAULT_SYNTH_MODE;
134
+
135
+ this.totalVoicesAmount = 0;
136
+ SpessaSynthInfo(`%cSpessaSynth is ready!`,
137
+ consoleColors.info)
138
+ }
139
+
140
+ debugMessage()
141
+ {
142
+ SpessaSynthInfo({
143
+ channels: this.workletProcessorChannels,
144
+ voicesAmount: this.totalVoicesAmount,
145
+ outputAmount: this._outputsAmount,
146
+ dumpedSamples: this.workletDumpedSamplesList
147
+ });
148
+ }
149
+
150
+ /**
151
+ * @param channel {number}
152
+ * @param controllerNumber {number}
153
+ * @param isLocked {boolean}
154
+ */
155
+ lockController(channel, controllerNumber, isLocked)
156
+ {
157
+ this.workletProcessorChannels[channel].lockedControllers[controllerNumber] = isLocked;
158
+ }
159
+
160
+ /**
161
+ * Syntesizes the voice to output buffers.
162
+ * @param outputChannels {Float32Array[]} - the dry audio output data of 2 arrays. The first array is left the second is right.
163
+ * @param reverbOutputChannels {Float32Array[]} - the dry audio output data for the reverb of 2 arrays. The first array is left the second is right.
164
+ * @param chorusOutputChannels {Float32Array[]} - the dry audio output data for the chorus of 2 arrays. The first array is left the second is right.
165
+ */
166
+ render(outputChannels, reverbOutputChannels = undefined, chorusOutputChannels = undefined) {
167
+ // render in blocks until we reach the output
168
+ let samplesToRender = outputChannels[0].length;
169
+ let renderedSamples = 0;
170
+ while(samplesToRender > 0)
171
+ {
172
+ /**
173
+ * @type {Float32Array[]}
174
+ */
175
+ let block;
176
+ let reverbBlock;
177
+ let chorusBlock;
178
+ if(samplesToRender < this.blockSize)
179
+ {
180
+ block = [new Float32Array(samplesToRender), new Float32Array(samplesToRender)];
181
+ reverbBlock = [new Float32Array(samplesToRender), new Float32Array(samplesToRender)];
182
+ chorusBlock = [new Float32Array(samplesToRender), new Float32Array(samplesToRender)];
183
+ }
184
+ else
185
+ {
186
+ block = [new Float32Array(this.blockSize), new Float32Array(this.blockSize)];
187
+ chorusBlock = [new Float32Array(this.blockSize), new Float32Array(this.blockSize)];
188
+ reverbBlock = [new Float32Array(this.blockSize), new Float32Array(this.blockSize)];
189
+ }
190
+ samplesToRender -= this.blockSize;
191
+ // for every channel
192
+ let totalCurrentVoices = 0;
193
+ this.workletProcessorChannels.forEach(channel => {
194
+ if(channel.voices.length < 1 || channel.isMuted)
195
+ {
196
+ // skip the channels
197
+ return;
198
+ }
199
+ const tempV = channel.voices;
200
+
201
+ // reset voices
202
+ channel.voices = [];
203
+
204
+ // for every voice
205
+ tempV.forEach(v => {
206
+ // render voice
207
+ this.renderVoice(channel, v, block, reverbBlock, chorusBlock);
208
+ if(!v.finished)
209
+ {
210
+ // if not finished, add it back
211
+ channel.voices.push(v);
212
+ }
213
+ });
214
+
215
+ totalCurrentVoices += tempV.length;
216
+ });
217
+
218
+ // if voice count changed, update voice amount
219
+ if(totalCurrentVoices !== this.totalVoicesAmount)
220
+ {
221
+ this.totalVoicesAmount = totalCurrentVoices;
222
+ }
223
+
224
+ // if sequencer connected, process
225
+ if(this.processTickCallback)
226
+ {
227
+ this.processTickCallback();
228
+ }
229
+ // append to blocks
230
+ outputChannels[0].set(block[0], renderedSamples);
231
+ outputChannels[1].set(block[1], renderedSamples);
232
+
233
+ if(reverbOutputChannels)
234
+ {
235
+ reverbOutputChannels[0].set(reverbBlock[0], renderedSamples);
236
+ reverbOutputChannels[1].set(reverbBlock[1], renderedSamples);
237
+ }
238
+
239
+ if(chorusOutputChannels)
240
+ {
241
+ chorusOutputChannels[1].set(chorusBlock[1], renderedSamples);
242
+ chorusOutputChannels[1].set(chorusBlock[1], renderedSamples);
243
+ }
244
+
245
+ renderedSamples += block[0].length;
246
+ this.currentTime += this.sampleTime * block[0].length;
247
+ }
248
+ }
249
+ }
250
+
251
+ // include other methods
252
+ // voice related
253
+ Synthesizer.prototype.renderVoice = renderVoice;
254
+ Synthesizer.prototype.releaseVoice = releaseVoice;
255
+ Synthesizer.prototype.voiceKilling = voiceKilling;
256
+
257
+ // system exlcusive related
258
+ Synthesizer.prototype.systemExclusive = systemExclusive;
259
+
260
+ // note messages related
261
+ Synthesizer.prototype.noteOn = noteOn;
262
+ Synthesizer.prototype.noteOff = noteOff;
263
+ Synthesizer.prototype.killNote = killNote;
264
+ Synthesizer.prototype.stopAll = stopAll;
265
+ Synthesizer.prototype.stopAllChannels = stopAllChannels;
266
+ Synthesizer.prototype.muteChannel = muteChannel;
267
+
268
+ // vustom vibrato related
269
+ Synthesizer.prototype.setVibrato = setVibrato;
270
+ Synthesizer.prototype.disableAndLockVibrato = disableAndLockVibrato;
271
+
272
+ // data entry related
273
+ Synthesizer.prototype.dataEntryCoarse = dataEntryCoarse;
274
+ Synthesizer.prototype.dataEntryFine = dataEntryFine;
275
+
276
+ // channel related
277
+ Synthesizer.prototype.addNewChannel = addNewChannel;
278
+ Synthesizer.prototype.controllerChange = controllerChange;
279
+ Synthesizer.prototype.resetAllControllers = resetAllControllers;
280
+ Synthesizer.prototype.resetControllers = resetControllers;
281
+ Synthesizer.prototype.resetParameters = resetParameters;
282
+
283
+ // master parameter related
284
+ Synthesizer.prototype.setMainVolume = setMainVolume;
285
+ Synthesizer.prototype.setMasterPan = setMasterPan;
286
+
287
+ // tuning related
288
+ Synthesizer.prototype.transposeAllChannels = transposeAllChannels;
289
+ Synthesizer.prototype.transposeChannel = transposeChannel;
290
+ Synthesizer.prototype.setChannelTuning = setChannelTuning;
291
+ Synthesizer.prototype.setMasterTuning = setMasterTuning;
292
+ Synthesizer.prototype.setModulationDepth = setModulationDepth;
293
+ Synthesizer.prototype.pitchWheel = pitchWheel;
294
+
295
+ // program related
296
+ Synthesizer.prototype.programChange = programChange;
297
+ Synthesizer.prototype.setPreset = setPreset;
298
+ Synthesizer.prototype.setDrums = setDrums;
299
+ Synthesizer.prototype.reloadSoundFont = reloadSoundFont;
300
+ Synthesizer.prototype.sampleDump = sampleDump;
301
+
302
+
303
+ export { Synthesizer }
@@ -0,0 +1,3 @@
1
+ ## This the worklet system synthesis folder.
2
+ The code here is responsible for a single midi channel, synthesizing the sound to it.
3
+ `worklet_utilities` contains the various digital signal processing functions such as the wavetable oscillator, low pass filter, etc.
@@ -0,0 +1,285 @@
1
+ import { consoleColors } from '../../../utils/other.js'
2
+ import { midiControllers } from '../../../midi_parser/midi_message.js'
3
+ import { DEFAULT_PERCUSSION, DEFAULT_SYNTH_MODE } from '../../synthesizer.js'
4
+ import {
5
+ customControllers,
6
+ customResetArray,
7
+ dataEntryStates,
8
+ resetArray,
9
+ } from '../worklet_utilities/worklet_processor_channel.js'
10
+ import { computeModulators } from '../worklet_utilities/worklet_modulator.js'
11
+ import { SpessaSynthInfo } from '../../../utils/loggin.js'
12
+ import { SYNTHESIZER_GAIN } from '../../synthesizer.js'
13
+
14
+ /**
15
+ * @param channel {number}
16
+ * @param controllerNumber {number}
17
+ * @param controllerValue {number}
18
+ * @this {Synthesizer}
19
+ */
20
+ export function controllerChange(channel, controllerNumber, controllerValue)
21
+ {
22
+ /**
23
+ * @type {WorkletProcessorChannel}
24
+ */
25
+ const channelObject = this.workletProcessorChannels[channel];
26
+ switch (controllerNumber) {
27
+ case midiControllers.allNotesOff:
28
+ this.stopAll(channel);
29
+ break;
30
+
31
+ case midiControllers.allSoundOff:
32
+ this.stopAll(channel, true);
33
+ break;
34
+
35
+ case midiControllers.bankSelect:
36
+ let bankNr = controllerValue;
37
+ switch (this.system)
38
+ {
39
+ case "gm":
40
+ // gm ignores bank select
41
+ SpessaSynthInfo(`%cIgnoring the Bank Select (${controllerValue}), as the synth is in GM mode.`, consoleColors.info);
42
+ return;
43
+
44
+ case "xg":
45
+ // for xg, if msb is 127, then it's drums
46
+ if (bankNr === 127)
47
+ {
48
+ channelObject.drumChannel = true;
49
+ }
50
+ break;
51
+
52
+ case "gm2":
53
+ if(bankNr === 120)
54
+ {
55
+ channelObject.drumChannel = true;
56
+ }
57
+ }
58
+
59
+ if(channelObject.drumChannel)
60
+ {
61
+ // 128 for percussion channel
62
+ bankNr = 128;
63
+ }
64
+ if(bankNr === 128 && !channelObject.drumChannel)
65
+ {
66
+ // if channel is not for percussion, default to bank current
67
+ bankNr = channelObject.midiControllers[midiControllers.bankSelect];
68
+ }
69
+
70
+ channelObject.midiControllers[midiControllers.bankSelect] = bankNr;
71
+ break;
72
+
73
+ case midiControllers.lsbForControl0BankSelect:
74
+ if(this.system === 'xg')
75
+ {
76
+ if(channelObject.midiControllers[midiControllers.bankSelect] === 0)
77
+ {
78
+ channelObject.midiControllers[midiControllers.bankSelect] = controllerValue;
79
+ }
80
+ }
81
+ else
82
+ if(this.system === "gm2")
83
+ {
84
+ channelObject.midiControllers[midiControllers.bankSelect] = controllerValue;
85
+ }
86
+ break;
87
+
88
+ case midiControllers.RPNLsb:
89
+ channelObject.RPValue = channelObject.RPValue << 7 | controllerValue;
90
+ channelObject.dataEntryState = dataEntryStates.RPFine;
91
+ break;
92
+
93
+ case midiControllers.RPNMsb:
94
+ channelObject.RPValue = controllerValue;
95
+ channelObject.dataEntryState = dataEntryStates.RPCoarse;
96
+ break;
97
+
98
+ case midiControllers.NRPNMsb:
99
+ channelObject.NRPCoarse = controllerValue;
100
+ channelObject.dataEntryState = dataEntryStates.NRPCoarse;
101
+ break;
102
+
103
+ case midiControllers.NRPNLsb:
104
+ channelObject.NRPFine = controllerValue;
105
+ channelObject.dataEntryState = dataEntryStates.NRPFine;
106
+ break;
107
+
108
+ case midiControllers.dataEntryMsb:
109
+ this.dataEntryCoarse(channel, controllerValue);
110
+ break;
111
+
112
+ case midiControllers.lsbForControl6DataEntry:
113
+ this.dataEntryFine(channel, controllerValue);
114
+ break;
115
+
116
+ default:
117
+ if(channelObject.lockedControllers[controllerNumber])
118
+ {
119
+ return;
120
+ }
121
+ // special case: hold pedal
122
+ if(controllerNumber === midiControllers.sustainPedal) {
123
+ if (controllerValue >= 64)
124
+ {
125
+ channelObject.holdPedal = true;
126
+ }
127
+ else
128
+ {
129
+ channelObject.holdPedal = false;
130
+ channelObject.sustainedVoices.forEach(v => {
131
+ this.releaseVoice(v)
132
+ });
133
+ channelObject.sustainedVoices = [];
134
+ }
135
+ }
136
+ channelObject.midiControllers[controllerNumber] = controllerValue << 7;
137
+ channelObject.voices.forEach(v => computeModulators(v, channelObject.midiControllers));
138
+ break;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * @this {Synthesizer}
144
+ */
145
+ export function resetAllControllers()
146
+ {
147
+ SpessaSynthInfo("%cResetting all controllers!", consoleColors.info);
148
+ for (let channelNumber = 0; channelNumber < this.workletProcessorChannels.length; channelNumber++)
149
+ {
150
+ this.resetControllers(channelNumber);
151
+
152
+ /**
153
+ * @type {WorkletProcessorChannel}
154
+ **/
155
+ const ch = this.workletProcessorChannels[channelNumber];
156
+
157
+ // if preset is unlocked, switch to non drums and call event
158
+ if(!ch.lockPreset)
159
+ {
160
+ ch.midiControllers[midiControllers.bankSelect] = 0;
161
+ if (channelNumber % 16 === DEFAULT_PERCUSSION)
162
+ {
163
+ this.setPreset(channelNumber, this.drumPreset);
164
+ ch.drumChannel = true;
165
+ }
166
+ else
167
+ {
168
+ ch.drumChannel = false;
169
+ this.setPreset(channelNumber, this.defaultPreset);
170
+ }
171
+ }
172
+ }
173
+ this.system = DEFAULT_SYNTH_MODE;
174
+ }
175
+
176
+ /**
177
+ * Resets all controllers for channel
178
+ * @param channel {number}
179
+ * @this {Synthesizer}
180
+ */
181
+ export function resetControllers(channel)
182
+ {
183
+ const channelObject = this.workletProcessorChannels[channel];
184
+ /**
185
+ * get excluded (locked) cc numbers as locked ccs are unaffected by reset
186
+ * @type {number[]}
187
+ */
188
+ const excludedCCs = channelObject.lockedControllers.reduce((lockedCCs, cc, ccNum) => {
189
+ if(cc)
190
+ {
191
+ lockedCCs.push(ccNum);
192
+ }
193
+ return lockedCCs;
194
+ }, []);
195
+ // save excluded controllers as reset doesn't affect them
196
+ let excludedCCvalues = excludedCCs.map(ccNum => {
197
+ return {
198
+ ccNum: ccNum,
199
+ ccVal: channelObject.midiControllers[ccNum]
200
+ }
201
+ });
202
+
203
+ // reset the array
204
+ channelObject.midiControllers.set(resetArray);
205
+ channelObject.channelVibrato = {rate: 0, depth: 0, delay: 0};
206
+ channelObject.holdPedal = false;
207
+
208
+ excludedCCvalues.forEach((cc) => {
209
+ channelObject.midiControllers[cc.ccNum] = cc.ccVal;
210
+ });
211
+
212
+ // reset custom controllers
213
+ // special case: transpose does not get affected
214
+ const transpose = channelObject.customControllers[customControllers.channelTranspose];
215
+ channelObject.customControllers.set(customResetArray);
216
+ channelObject.customControllers[customControllers.channelTranspose] = transpose;
217
+
218
+ this.resetParameters(channel);
219
+
220
+ }
221
+
222
+ /**
223
+ * @param channel {number}
224
+ * @this {Synthesizer}
225
+ */
226
+ export function resetParameters(channel)
227
+ {
228
+ const channelObject = this.workletProcessorChannels[channel];
229
+
230
+ // reset parameters
231
+ /**
232
+ * @type {number}
233
+ */
234
+ channelObject.NRPCoarse = 0;
235
+ /**
236
+ * @type {number}
237
+ */
238
+ channelObject.NRPFine = 0;
239
+ /**
240
+ * @type {number}
241
+ */
242
+ channelObject.RPValue = 0;
243
+ /**
244
+ * @type {string}
245
+ */
246
+ channelObject.dataEntryState = dataEntryStates.Idle;
247
+ }
248
+
249
+ /**
250
+ * @param volume {number} 0-1
251
+ * @this {Synthesizer}
252
+ */
253
+ export function setMainVolume(volume)
254
+ {
255
+ this.mainVolume = volume * SYNTHESIZER_GAIN;
256
+ this.setMasterPan(this.pan);
257
+ }
258
+
259
+ /**
260
+ * @param pan {number} -1 to 1
261
+ * @this {Synthesizer}
262
+ */
263
+
264
+ export function setMasterPan(pan)
265
+ {
266
+ this.pan = pan;
267
+ // clamp to 0-1 (0 is left)
268
+ pan = (pan / 2) + 0.5;
269
+ this.panLeft = (1 - pan) * this.mainVolume;
270
+ this.panRight = (pan) * this.mainVolume;
271
+ }
272
+
273
+ /**
274
+ * @param channel {number}
275
+ * @param isMuted {boolean}
276
+ * @this {Synthesizer}
277
+ */
278
+ export function muteChannel(channel, isMuted)
279
+ {
280
+ if(isMuted)
281
+ {
282
+ this.stopAll(channel, true);
283
+ }
284
+ this.workletProcessorChannels[channel].isMuted = isMuted;
285
+ }