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.
- package/.idea/inspectionProfiles/Project_Default.xml +10 -0
- package/.idea/jsLibraryMappings.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/spessasynth_core.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +376 -0
- package/index.js +7 -0
- package/package.json +34 -0
- package/spessasynth_core/midi_parser/README.md +3 -0
- package/spessasynth_core/midi_parser/midi_loader.js +381 -0
- package/spessasynth_core/midi_parser/midi_message.js +231 -0
- package/spessasynth_core/sequencer/sequencer.js +192 -0
- package/spessasynth_core/sequencer/worklet_sequencer/play.js +221 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_event.js +138 -0
- package/spessasynth_core/sequencer/worklet_sequencer/process_tick.js +85 -0
- package/spessasynth_core/sequencer/worklet_sequencer/song_control.js +90 -0
- package/spessasynth_core/soundfont/README.md +4 -0
- package/spessasynth_core/soundfont/chunk/generators.js +205 -0
- package/spessasynth_core/soundfont/chunk/instruments.js +60 -0
- package/spessasynth_core/soundfont/chunk/modulators.js +232 -0
- package/spessasynth_core/soundfont/chunk/presets.js +264 -0
- package/spessasynth_core/soundfont/chunk/riff_chunk.js +46 -0
- package/spessasynth_core/soundfont/chunk/samples.js +250 -0
- package/spessasynth_core/soundfont/chunk/zones.js +264 -0
- package/spessasynth_core/soundfont/soundfont_parser.js +301 -0
- package/spessasynth_core/synthetizer/README.md +6 -0
- package/spessasynth_core/synthetizer/synthesizer.js +303 -0
- package/spessasynth_core/synthetizer/worklet_system/README.md +3 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/controller_control.js +285 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/data_entry.js +280 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_off.js +102 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/note_on.js +75 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/program_control.js +140 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/system_exclusive.js +265 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/tuning_control.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/vibrato_control.js +29 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_methods/voice_control.js +186 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lfo.js +23 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/lowpass_filter.js +95 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +73 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +86 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +76 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/unit_converter.js +66 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +194 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +83 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_modulator.js +173 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +105 -0
- package/spessasynth_core/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +313 -0
- package/spessasynth_core/utils/README.md +4 -0
- package/spessasynth_core/utils/buffer_to_wav.js +70 -0
- package/spessasynth_core/utils/byte_functions.js +141 -0
- package/spessasynth_core/utils/loggin.js +79 -0
- package/spessasynth_core/utils/other.js +49 -0
- package/spessasynth_core/utils/shiftable_array.js +26 -0
- 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,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
|
+
}
|