spessasynth_lib 3.26.0 → 3.26.1
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/external_midi/README.md +4 -0
- package/external_midi/midi_handler.js +130 -0
- package/external_midi/web_midi_link.js +43 -0
- package/package.json +5 -2
- package/sequencer/README.md +32 -0
- package/sequencer/default_sequencer_options.js +8 -0
- package/sequencer/midi_data.js +63 -0
- package/sequencer/sequencer.js +808 -0
- package/sequencer/sequencer_message.js +53 -0
- package/synthetizer/README.md +38 -0
- package/synthetizer/audio_effects/effects_config.js +25 -0
- package/synthetizer/audio_effects/fancy_chorus.js +162 -0
- package/synthetizer/audio_effects/rb_compressed.min.js +1 -0
- package/synthetizer/audio_effects/reverb.js +35 -0
- package/synthetizer/audio_effects/reverb_as_binary.js +18 -0
- package/synthetizer/key_modifier_manager.js +113 -0
- package/synthetizer/sfman_message.js +9 -0
- package/synthetizer/synth_event_handler.js +217 -0
- package/synthetizer/synth_soundfont_manager.js +112 -0
- package/synthetizer/synthetizer.js +1033 -0
- package/synthetizer/worklet_message.js +120 -0
- package/synthetizer/worklet_processor.js +637 -0
- package/synthetizer/worklet_processor.min.js +22 -0
- package/synthetizer/worklet_processor.min.js.map +7 -0
- package/synthetizer/worklet_url.js +18 -0
- package/utils/buffer_to_wav.js +28 -0
- package/utils/fill_with_defaults.js +21 -0
- package/utils/other.js +11 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { consoleColors } from "../utils/other.js";
|
|
2
|
+
import {
|
|
3
|
+
ALL_CHANNELS_OR_DIFFERENT_ACTION,
|
|
4
|
+
BasicMIDI,
|
|
5
|
+
masterParameterType,
|
|
6
|
+
MIDI,
|
|
7
|
+
MIDI_CHANNEL_COUNT,
|
|
8
|
+
SpessaSynthCoreUtils as util,
|
|
9
|
+
SpessaSynthLogging,
|
|
10
|
+
SpessaSynthProcessor,
|
|
11
|
+
SpessaSynthSequencer,
|
|
12
|
+
SynthesizerSnapshot
|
|
13
|
+
} from "spessasynth_core";
|
|
14
|
+
import { returnMessageType, workletMessageType } from "./worklet_message.js";
|
|
15
|
+
import { WORKLET_PROCESSOR_NAME } from "./worklet_url.js";
|
|
16
|
+
import { workletKeyModifierMessageType } from "./key_modifier_manager.js";
|
|
17
|
+
import { WorkletSoundfontManagerMessageType } from "./sfman_message.js";
|
|
18
|
+
import {
|
|
19
|
+
SongChangeType,
|
|
20
|
+
SpessaSynthSequencerMessageType,
|
|
21
|
+
SpessaSynthSequencerReturnMessageType
|
|
22
|
+
} from "../sequencer/sequencer_message.js";
|
|
23
|
+
import { fillWithDefaults } from "../utils/fill_with_defaults.js";
|
|
24
|
+
import { DEFAULT_SEQUENCER_OPTIONS } from "../sequencer/default_sequencer_options.js";
|
|
25
|
+
import { MIDIData } from "../sequencer/midi_data.js";
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// a worklet processor wrapper for the synthesizer core
|
|
29
|
+
class WorkletSpessaProcessor extends AudioWorkletProcessor
|
|
30
|
+
{
|
|
31
|
+
/**
|
|
32
|
+
* If the worklet is alive
|
|
33
|
+
* @type {boolean}
|
|
34
|
+
*/
|
|
35
|
+
alive = true;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Instead of 18 stereo outputs, there's one with 32 channels (no effects)
|
|
39
|
+
* @type {boolean}
|
|
40
|
+
*/
|
|
41
|
+
oneOutputMode = false;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new worklet synthesis system. contains all channels
|
|
45
|
+
* @param options {{
|
|
46
|
+
* processorOptions: {
|
|
47
|
+
* midiChannels: number,
|
|
48
|
+
* soundfont: ArrayBuffer,
|
|
49
|
+
* enableEventSystem: boolean,
|
|
50
|
+
* startRenderingData: StartRenderingDataConfig
|
|
51
|
+
* }}}
|
|
52
|
+
*/
|
|
53
|
+
constructor(options)
|
|
54
|
+
{
|
|
55
|
+
super();
|
|
56
|
+
const opts = options.processorOptions;
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
// one output is indicated by setting midiChannels to 1
|
|
60
|
+
this.oneOutputMode = opts.midiChannels === 1;
|
|
61
|
+
|
|
62
|
+
// prepare synthesizer connections
|
|
63
|
+
/**
|
|
64
|
+
* @param t {returnMessageType}
|
|
65
|
+
* @param d {any}
|
|
66
|
+
*/
|
|
67
|
+
const postSyn = (t, d) =>
|
|
68
|
+
{
|
|
69
|
+
// noinspection JSCheckFunctionSignatures
|
|
70
|
+
this.postMessageToMainThread({
|
|
71
|
+
messageType: t,
|
|
72
|
+
messageData: d
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// start rendering data
|
|
77
|
+
const startRenderingData = opts?.startRenderingData;
|
|
78
|
+
/**
|
|
79
|
+
* The snapshot that synth was restored from
|
|
80
|
+
* @type {SynthesizerSnapshot|undefined}
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
const snapshot = startRenderingData?.snapshot;
|
|
84
|
+
|
|
85
|
+
// noinspection JSUnresolvedReference
|
|
86
|
+
/**
|
|
87
|
+
* Initialize the synthesis engine
|
|
88
|
+
* @type {SpessaSynthProcessor}
|
|
89
|
+
*/
|
|
90
|
+
this.synthesizer = new SpessaSynthProcessor(
|
|
91
|
+
opts.soundfont, // initial sound bank
|
|
92
|
+
sampleRate, // AudioWorkletGlobalScope
|
|
93
|
+
{
|
|
94
|
+
eventCall: (t, d) =>
|
|
95
|
+
{
|
|
96
|
+
postSyn(returnMessageType.eventCall, {
|
|
97
|
+
eventName: t,
|
|
98
|
+
eventData: d
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
ready: this.postReady.bind(this),
|
|
102
|
+
channelPropertyChange: (p, n) => postSyn(returnMessageType.channelPropertyChange, [n, p]),
|
|
103
|
+
masterParameterChange: (t, v) => postSyn(returnMessageType.masterParameterChange, [t, v])
|
|
104
|
+
},
|
|
105
|
+
!this.oneOutputMode, // one output mode disables effects
|
|
106
|
+
opts?.enableEventSystem, // enable message port?
|
|
107
|
+
currentTime, // AudioWorkletGlobalScope, sync with audioContext time
|
|
108
|
+
MIDI_CHANNEL_COUNT, // midi channel count (16)
|
|
109
|
+
snapshot
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// initialize the sequencer engine
|
|
113
|
+
this.sequencer = new SpessaSynthSequencer(this.synthesizer);
|
|
114
|
+
|
|
115
|
+
const postSeq = (type, data) =>
|
|
116
|
+
{
|
|
117
|
+
this.postMessageToMainThread({
|
|
118
|
+
messageType: returnMessageType.sequencerSpecific,
|
|
119
|
+
messageData: {
|
|
120
|
+
messageType: type,
|
|
121
|
+
messageData: data
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// receive messages from the main thread
|
|
127
|
+
this.port.onmessage = e => this.handleMessage(e.data);
|
|
128
|
+
|
|
129
|
+
// sequencer events
|
|
130
|
+
this.sequencer.onMIDIMessage = m =>
|
|
131
|
+
{
|
|
132
|
+
postSeq(SpessaSynthSequencerReturnMessageType.midiEvent, m);
|
|
133
|
+
};
|
|
134
|
+
this.sequencer.onTimeChange = t =>
|
|
135
|
+
{
|
|
136
|
+
postSeq(SpessaSynthSequencerReturnMessageType.timeChange, t);
|
|
137
|
+
};
|
|
138
|
+
this.sequencer.onPlaybackStop = p =>
|
|
139
|
+
{
|
|
140
|
+
postSeq(SpessaSynthSequencerReturnMessageType.pause, p);
|
|
141
|
+
};
|
|
142
|
+
this.sequencer.onSongChange = (i, a) =>
|
|
143
|
+
{
|
|
144
|
+
postSeq(SpessaSynthSequencerReturnMessageType.songChange, [i, a]);
|
|
145
|
+
};
|
|
146
|
+
this.sequencer.onMetaEvent = (e, i) =>
|
|
147
|
+
{
|
|
148
|
+
postSeq(SpessaSynthSequencerReturnMessageType.metaEvent, [e, i]);
|
|
149
|
+
};
|
|
150
|
+
this.sequencer.onLoopCountChange = c =>
|
|
151
|
+
{
|
|
152
|
+
postSeq(SpessaSynthSequencerReturnMessageType.loopCountChange, c);
|
|
153
|
+
};
|
|
154
|
+
this.sequencer.onSongListChange = l =>
|
|
155
|
+
{
|
|
156
|
+
const midiDataList = l.map(s => new MIDIData(s));
|
|
157
|
+
this.postMessageToMainThread({
|
|
158
|
+
messageType: returnMessageType.sequencerSpecific,
|
|
159
|
+
messageData: {
|
|
160
|
+
messageType: SpessaSynthSequencerReturnMessageType.songListChange,
|
|
161
|
+
messageData: midiDataList
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// if sent, start rendering
|
|
167
|
+
if (startRenderingData)
|
|
168
|
+
{
|
|
169
|
+
if (snapshot !== undefined)
|
|
170
|
+
{
|
|
171
|
+
this.synthesizer.applySynthesizerSnapshot(snapshot);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
util.SpessaSynthInfo("%cRendering enabled! Starting render.", consoleColors.info);
|
|
175
|
+
if (startRenderingData.parsedMIDI)
|
|
176
|
+
{
|
|
177
|
+
if (startRenderingData?.loopCount !== undefined)
|
|
178
|
+
{
|
|
179
|
+
this.sequencer.loopCount = startRenderingData?.loopCount;
|
|
180
|
+
this.sequencer.loop = true;
|
|
181
|
+
}
|
|
182
|
+
else
|
|
183
|
+
{
|
|
184
|
+
this.sequencer.loop = false;
|
|
185
|
+
}
|
|
186
|
+
// set voice cap to unlimited
|
|
187
|
+
this.synthesizer.voiceCap = Infinity;
|
|
188
|
+
this.synthesizer.processorInitialized.then(() =>
|
|
189
|
+
{
|
|
190
|
+
/**
|
|
191
|
+
* set options
|
|
192
|
+
* @type {SequencerOptions}
|
|
193
|
+
*/
|
|
194
|
+
const seqOptions = fillWithDefaults(
|
|
195
|
+
startRenderingData.sequencerOptions,
|
|
196
|
+
DEFAULT_SEQUENCER_OPTIONS
|
|
197
|
+
);
|
|
198
|
+
this.sequencer.skipToFirstNoteOn = seqOptions.skipToFirstNoteOn;
|
|
199
|
+
this.sequencer.preservePlaybackState = seqOptions.preservePlaybackState;
|
|
200
|
+
// autoplay is ignored
|
|
201
|
+
try
|
|
202
|
+
{
|
|
203
|
+
this.sequencer.loadNewSongList([startRenderingData.parsedMIDI]);
|
|
204
|
+
}
|
|
205
|
+
catch (e)
|
|
206
|
+
{
|
|
207
|
+
console.error(e);
|
|
208
|
+
postSeq(SpessaSynthSequencerReturnMessageType.midiError, e);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
postReady()
|
|
216
|
+
{
|
|
217
|
+
this.postMessageToMainThread({
|
|
218
|
+
messageType: returnMessageType.isFullyInitialized,
|
|
219
|
+
messageData: undefined
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @param data {WorkletReturnMessage}
|
|
225
|
+
*/
|
|
226
|
+
postMessageToMainThread(data)
|
|
227
|
+
{
|
|
228
|
+
this.port.postMessage(data);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @this {WorkletSpessaProcessor}
|
|
233
|
+
* @param message {WorkletMessage}
|
|
234
|
+
*/
|
|
235
|
+
handleMessage(message)
|
|
236
|
+
{
|
|
237
|
+
const data = message.messageData;
|
|
238
|
+
const channel = message.channelNumber;
|
|
239
|
+
|
|
240
|
+
let channelObject;
|
|
241
|
+
if (channel >= 0)
|
|
242
|
+
{
|
|
243
|
+
channelObject = this.synthesizer.midiAudioChannels[channel];
|
|
244
|
+
if (channelObject === undefined)
|
|
245
|
+
{
|
|
246
|
+
util.SpessaSynthWarn(`Trying to access channel ${channel} which does not exist... ignoring!`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
switch (message.messageType)
|
|
251
|
+
{
|
|
252
|
+
case workletMessageType.midiMessage:
|
|
253
|
+
this.synthesizer.processMessage(...data);
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
case workletMessageType.customCcChange:
|
|
257
|
+
// custom controller change
|
|
258
|
+
channelObject.setCustomController(data[0], data[1]);
|
|
259
|
+
channelObject.updateChannelTuning();
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case workletMessageType.ccReset:
|
|
263
|
+
if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
264
|
+
{
|
|
265
|
+
this.synthesizer.resetAllControllers();
|
|
266
|
+
}
|
|
267
|
+
else
|
|
268
|
+
{
|
|
269
|
+
channelObject.resetControllers();
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case workletMessageType.setChannelVibrato:
|
|
274
|
+
if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
275
|
+
{
|
|
276
|
+
for (let i = 0; i < this.synthesizer.midiAudioChannels.length; i++)
|
|
277
|
+
{
|
|
278
|
+
const chan = this.synthesizer.midiAudioChannels[i];
|
|
279
|
+
if (data.rate === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
280
|
+
{
|
|
281
|
+
chan.disableAndLockGSNRPN();
|
|
282
|
+
}
|
|
283
|
+
else
|
|
284
|
+
{
|
|
285
|
+
chan.setVibrato(data.depth, data.rate, data.delay);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (data.rate === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
290
|
+
{
|
|
291
|
+
channelObject.disableAndLockGSNRPN();
|
|
292
|
+
}
|
|
293
|
+
else
|
|
294
|
+
{
|
|
295
|
+
channelObject.setVibrato(data.depth, data.rate, data.delay);
|
|
296
|
+
}
|
|
297
|
+
break;
|
|
298
|
+
|
|
299
|
+
case workletMessageType.stopAll:
|
|
300
|
+
if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
301
|
+
{
|
|
302
|
+
this.synthesizer.stopAllChannels(data === 1);
|
|
303
|
+
}
|
|
304
|
+
else
|
|
305
|
+
{
|
|
306
|
+
channelObject.stopAllNotes(data === 1);
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
|
|
310
|
+
case workletMessageType.killNotes:
|
|
311
|
+
this.synthesizer.voiceKilling(data);
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case workletMessageType.muteChannel:
|
|
315
|
+
channelObject.muteChannel(data);
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
case workletMessageType.addNewChannel:
|
|
319
|
+
this.synthesizer.createWorkletChannel(true);
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
case workletMessageType.debugMessage:
|
|
323
|
+
console.debug(this.synthesizer);
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case workletMessageType.setMasterParameter:
|
|
327
|
+
/**
|
|
328
|
+
* @type {masterParameterType}
|
|
329
|
+
*/
|
|
330
|
+
const type = data[0];
|
|
331
|
+
const value = data[1];
|
|
332
|
+
this.synthesizer.setMasterParameter(type, value);
|
|
333
|
+
break;
|
|
334
|
+
|
|
335
|
+
case workletMessageType.setDrums:
|
|
336
|
+
channelObject.setDrums(data);
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case workletMessageType.transpose:
|
|
340
|
+
if (channel === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
341
|
+
{
|
|
342
|
+
this.synthesizer.transposeAllChannels(data[0], data[1]);
|
|
343
|
+
}
|
|
344
|
+
else
|
|
345
|
+
{
|
|
346
|
+
channelObject.transposeChannel(data[0], data[1]);
|
|
347
|
+
}
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case workletMessageType.highPerformanceMode:
|
|
351
|
+
this.synthesizer.highPerformanceMode = data;
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
case workletMessageType.lockController:
|
|
355
|
+
if (data[0] === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
356
|
+
{
|
|
357
|
+
channelObject.setPresetLock(data[1]);
|
|
358
|
+
}
|
|
359
|
+
else
|
|
360
|
+
{
|
|
361
|
+
channelObject.lockedControllers[data[0]] = data[1];
|
|
362
|
+
}
|
|
363
|
+
break;
|
|
364
|
+
|
|
365
|
+
case workletMessageType.sequencerSpecific:
|
|
366
|
+
const seq = this.sequencer;
|
|
367
|
+
const messageData = data.messageData;
|
|
368
|
+
const messageType = data.messageType;
|
|
369
|
+
switch (messageType)
|
|
370
|
+
{
|
|
371
|
+
default:
|
|
372
|
+
break;
|
|
373
|
+
|
|
374
|
+
case SpessaSynthSequencerMessageType.loadNewSongList:
|
|
375
|
+
try
|
|
376
|
+
{
|
|
377
|
+
/**
|
|
378
|
+
* @type {(BasicMIDI|{binary: ArrayBuffer, altName: string})[]}
|
|
379
|
+
*/
|
|
380
|
+
const sList = messageData[0];
|
|
381
|
+
const songMap = sList.map(s =>
|
|
382
|
+
{
|
|
383
|
+
if (s.duration)
|
|
384
|
+
{
|
|
385
|
+
return s;
|
|
386
|
+
}
|
|
387
|
+
return new MIDI(s.binary, s.altName);
|
|
388
|
+
});
|
|
389
|
+
seq.loadNewSongList(songMap, messageData[1]);
|
|
390
|
+
}
|
|
391
|
+
catch (e)
|
|
392
|
+
{
|
|
393
|
+
console.error(e);
|
|
394
|
+
this.postMessageToMainThread({
|
|
395
|
+
messageType: returnMessageType.sequencerSpecific,
|
|
396
|
+
messageData: {
|
|
397
|
+
messageType: SpessaSynthSequencerReturnMessageType.midiError,
|
|
398
|
+
messageData: e
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
|
|
404
|
+
case SpessaSynthSequencerMessageType.pause:
|
|
405
|
+
seq.pause();
|
|
406
|
+
break;
|
|
407
|
+
|
|
408
|
+
case SpessaSynthSequencerMessageType.play:
|
|
409
|
+
seq.play(messageData);
|
|
410
|
+
break;
|
|
411
|
+
|
|
412
|
+
case SpessaSynthSequencerMessageType.stop:
|
|
413
|
+
seq.stop();
|
|
414
|
+
break;
|
|
415
|
+
|
|
416
|
+
case SpessaSynthSequencerMessageType.setTime:
|
|
417
|
+
seq.currentTime = messageData;
|
|
418
|
+
break;
|
|
419
|
+
|
|
420
|
+
case SpessaSynthSequencerMessageType.changeMIDIMessageSending:
|
|
421
|
+
seq.sendMIDIMessages = messageData;
|
|
422
|
+
break;
|
|
423
|
+
|
|
424
|
+
case SpessaSynthSequencerMessageType.setPlaybackRate:
|
|
425
|
+
seq.playbackRate = messageData;
|
|
426
|
+
break;
|
|
427
|
+
|
|
428
|
+
case SpessaSynthSequencerMessageType.setLoop:
|
|
429
|
+
const [loop, count] = messageData;
|
|
430
|
+
seq.loop = loop;
|
|
431
|
+
if (count === ALL_CHANNELS_OR_DIFFERENT_ACTION)
|
|
432
|
+
{
|
|
433
|
+
seq.loopCount = Infinity;
|
|
434
|
+
}
|
|
435
|
+
else
|
|
436
|
+
{
|
|
437
|
+
seq.loopCount = count;
|
|
438
|
+
}
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
case SpessaSynthSequencerMessageType.changeSong:
|
|
442
|
+
switch (messageData[0])
|
|
443
|
+
{
|
|
444
|
+
case SongChangeType.forwards:
|
|
445
|
+
seq.nextSong();
|
|
446
|
+
break;
|
|
447
|
+
|
|
448
|
+
case SongChangeType.backwards:
|
|
449
|
+
seq.previousSong();
|
|
450
|
+
break;
|
|
451
|
+
|
|
452
|
+
case SongChangeType.shuffleOff:
|
|
453
|
+
seq.shuffleMode = false;
|
|
454
|
+
seq.songIndex = seq.shuffledSongIndexes[seq.songIndex];
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case SongChangeType.shuffleOn:
|
|
458
|
+
seq.shuffleMode = true;
|
|
459
|
+
seq.shuffleSongIndexes();
|
|
460
|
+
seq.songIndex = 0;
|
|
461
|
+
seq.loadCurrentSong();
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
case SongChangeType.index:
|
|
465
|
+
seq.songIndex = messageData[1];
|
|
466
|
+
seq.loadCurrentSong();
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
|
|
471
|
+
case SpessaSynthSequencerMessageType.getMIDI:
|
|
472
|
+
this.postMessageToMainThread({
|
|
473
|
+
messageType: returnMessageType.sequencerSpecific,
|
|
474
|
+
messageData: {
|
|
475
|
+
messageType: SpessaSynthSequencerReturnMessageType.getMIDI,
|
|
476
|
+
messageData: seq.midiData
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
break;
|
|
480
|
+
|
|
481
|
+
case SpessaSynthSequencerMessageType.setSkipToFirstNote:
|
|
482
|
+
seq.skipToFirstNoteOn = messageData;
|
|
483
|
+
break;
|
|
484
|
+
|
|
485
|
+
case SpessaSynthSequencerMessageType.setPreservePlaybackState:
|
|
486
|
+
seq.preservePlaybackState = messageData;
|
|
487
|
+
}
|
|
488
|
+
break;
|
|
489
|
+
|
|
490
|
+
case workletMessageType.soundFontManager:
|
|
491
|
+
try
|
|
492
|
+
{
|
|
493
|
+
const sfManager = this.synthesizer.soundfontManager;
|
|
494
|
+
const type = data[0];
|
|
495
|
+
const messageData = data[1];
|
|
496
|
+
switch (type)
|
|
497
|
+
{
|
|
498
|
+
case WorkletSoundfontManagerMessageType.addNewSoundFont:
|
|
499
|
+
sfManager.addNewSoundFont(messageData[0], messageData[1], messageData[2]);
|
|
500
|
+
break;
|
|
501
|
+
|
|
502
|
+
case WorkletSoundfontManagerMessageType.reloadSoundFont:
|
|
503
|
+
sfManager.reloadManager(messageData);
|
|
504
|
+
break;
|
|
505
|
+
|
|
506
|
+
case WorkletSoundfontManagerMessageType.deleteSoundFont:
|
|
507
|
+
sfManager.deleteSoundFont(messageData);
|
|
508
|
+
break;
|
|
509
|
+
|
|
510
|
+
case WorkletSoundfontManagerMessageType.rearrangeSoundFonts:
|
|
511
|
+
sfManager.rearrangeSoundFonts(messageData);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
catch (e)
|
|
515
|
+
{
|
|
516
|
+
this.postMessageToMainThread({
|
|
517
|
+
messageType: returnMessageType.soundfontError,
|
|
518
|
+
messageData: e
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
this.synthesizer.clearSoundFont(true, false);
|
|
522
|
+
break;
|
|
523
|
+
|
|
524
|
+
case workletMessageType.keyModifierManager:
|
|
525
|
+
/**
|
|
526
|
+
* @type {workletKeyModifierMessageType}
|
|
527
|
+
*/
|
|
528
|
+
const keyMessageType = data[0];
|
|
529
|
+
const man = this.synthesizer.keyModifierManager;
|
|
530
|
+
const keyMessageData = data[1];
|
|
531
|
+
switch (keyMessageType)
|
|
532
|
+
{
|
|
533
|
+
default:
|
|
534
|
+
return;
|
|
535
|
+
|
|
536
|
+
case workletKeyModifierMessageType.addMapping:
|
|
537
|
+
man.addMapping(...keyMessageData);
|
|
538
|
+
break;
|
|
539
|
+
|
|
540
|
+
case workletKeyModifierMessageType.clearMappings:
|
|
541
|
+
man.clearMappings();
|
|
542
|
+
break;
|
|
543
|
+
|
|
544
|
+
case workletKeyModifierMessageType.deleteMapping:
|
|
545
|
+
man.deleteMapping(...keyMessageData);
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
548
|
+
|
|
549
|
+
case workletMessageType.requestSynthesizerSnapshot:
|
|
550
|
+
const snapshot = SynthesizerSnapshot.createSynthesizerSnapshot(this.synthesizer);
|
|
551
|
+
this.postMessageToMainThread({
|
|
552
|
+
messageType: returnMessageType.synthesizerSnapshot,
|
|
553
|
+
messageData: snapshot
|
|
554
|
+
});
|
|
555
|
+
break;
|
|
556
|
+
|
|
557
|
+
case workletMessageType.setLogLevel:
|
|
558
|
+
SpessaSynthLogging(data[0], data[1], data[2], data[3]);
|
|
559
|
+
break;
|
|
560
|
+
|
|
561
|
+
case workletMessageType.setEffectsGain:
|
|
562
|
+
this.synthesizer.reverbGain = data[0];
|
|
563
|
+
this.synthesizer.chorusGain = data[1];
|
|
564
|
+
break;
|
|
565
|
+
|
|
566
|
+
case workletMessageType.destroyWorklet:
|
|
567
|
+
this.alive = false;
|
|
568
|
+
this.synthesizer.destroySynthProcessor();
|
|
569
|
+
delete this.synthesizer;
|
|
570
|
+
delete this.sequencer.midiData;
|
|
571
|
+
delete this.sequencer;
|
|
572
|
+
break;
|
|
573
|
+
|
|
574
|
+
default:
|
|
575
|
+
util.SpessaSynthWarn("Unrecognized event:", data);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// noinspection JSUnusedGlobalSymbols
|
|
581
|
+
/**
|
|
582
|
+
* the audio worklet processing logic
|
|
583
|
+
* @param inputs {Float32Array[][]} required by WebAudioAPI
|
|
584
|
+
* @param outputs {Float32Array[][]} the outputs to write to, only the first two channels are populated
|
|
585
|
+
* @returns {boolean} true unless it's not alive
|
|
586
|
+
*/
|
|
587
|
+
process(inputs, outputs)
|
|
588
|
+
{
|
|
589
|
+
if (!this.alive)
|
|
590
|
+
{
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
// process sequencer
|
|
594
|
+
this.sequencer.processTick();
|
|
595
|
+
|
|
596
|
+
if (this.oneOutputMode)
|
|
597
|
+
{
|
|
598
|
+
const out = outputs[0];
|
|
599
|
+
// 1 output with 32 channels.
|
|
600
|
+
// channels are ordered as follows:
|
|
601
|
+
// midiChannel1L, midiChannel1R,
|
|
602
|
+
// midiChannel2L, midiChannel2R
|
|
603
|
+
// and so on
|
|
604
|
+
/**
|
|
605
|
+
* @type {Float32Array[][]}
|
|
606
|
+
*/
|
|
607
|
+
const channelMap = [];
|
|
608
|
+
for (let i = 0; i < 32; i += 2)
|
|
609
|
+
{
|
|
610
|
+
channelMap.push([out[i], out[i + 1]]);
|
|
611
|
+
}
|
|
612
|
+
this.synthesizer.renderAudioSplit(
|
|
613
|
+
[], [], // effects are disabled
|
|
614
|
+
channelMap
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
else
|
|
618
|
+
{
|
|
619
|
+
// 18 outputs, each a stereo one
|
|
620
|
+
// 0: reverb
|
|
621
|
+
// 1: chorus
|
|
622
|
+
// 2: channel 1
|
|
623
|
+
// 3: channel 2
|
|
624
|
+
// and so on
|
|
625
|
+
this.synthesizer.renderAudioSplit(
|
|
626
|
+
outputs[0], // reverb
|
|
627
|
+
outputs[1], // chorus
|
|
628
|
+
outputs.slice(2)
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// noinspection JSUnresolvedReference
|
|
636
|
+
registerProcessor(WORKLET_PROCESSOR_NAME, WorkletSpessaProcessor);
|
|
637
|
+
util.SpessaSynthInfo("%cProcessor successfully registered!", consoleColors.recognized);
|