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