spessasynth_lib 3.24.0 → 3.24.3

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 (72) hide show
  1. package/@types/sequencer/sequencer.d.ts +63 -28
  2. package/@types/sequencer/worklet_sequencer/sequencer_message.d.ts +1 -0
  3. package/@types/synthetizer/audio_effects/effects_config.d.ts +20 -8
  4. package/@types/synthetizer/synthetizer.d.ts +17 -23
  5. package/LICENSE +0 -0
  6. package/README.md +0 -0
  7. package/external_midi/README.md +0 -0
  8. package/external_midi/midi_handler.js +0 -0
  9. package/external_midi/web_midi_link.js +0 -0
  10. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -0
  11. package/externals/stbvorbis_sync/LICENSE +0 -0
  12. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -0
  13. package/midi_parser/README.md +0 -0
  14. package/midi_parser/midi_message.js +0 -0
  15. package/midi_parser/midi_writer.js +0 -0
  16. package/midi_parser/used_keys_loaded.js +0 -0
  17. package/package.json +1 -1
  18. package/sequencer/README.md +0 -0
  19. package/sequencer/sequencer.js +134 -77
  20. package/sequencer/worklet_sequencer/process_event.js +4 -1
  21. package/sequencer/worklet_sequencer/process_tick.js +4 -4
  22. package/sequencer/worklet_sequencer/sequencer_message.js +2 -1
  23. package/soundfont/README.md +0 -0
  24. package/soundfont/basic_soundfont/basic_instrument.js +0 -0
  25. package/soundfont/basic_soundfont/write_sf2/ibag.js +0 -0
  26. package/soundfont/basic_soundfont/write_sf2/imod.js +0 -0
  27. package/soundfont/basic_soundfont/write_sf2/inst.js +0 -0
  28. package/soundfont/basic_soundfont/write_sf2/pbag.js +0 -0
  29. package/soundfont/basic_soundfont/write_sf2/phdr.js +0 -0
  30. package/soundfont/basic_soundfont/write_sf2/pmod.js +0 -0
  31. package/soundfont/basic_soundfont/write_sf2/sdta.js +0 -0
  32. package/soundfont/basic_soundfont/write_sf2/shdr.js +0 -0
  33. package/soundfont/basic_soundfont/write_sf2/soundfont_trimmer.js +0 -0
  34. package/soundfont/dls/dls_destinations.js +0 -0
  35. package/soundfont/dls/dls_preset.js +0 -0
  36. package/soundfont/dls/dls_sources.js +0 -0
  37. package/soundfont/dls/read_instrument_list.js +0 -0
  38. package/soundfont/load_soundfont.js +0 -0
  39. package/soundfont/read_sf2/generators.js +0 -0
  40. package/soundfont/read_sf2/instruments.js +0 -0
  41. package/soundfont/read_sf2/modulators.js +0 -0
  42. package/soundfont/read_sf2/presets.js +0 -0
  43. package/soundfont/read_sf2/zones.js +0 -0
  44. package/synthetizer/README.md +0 -0
  45. package/synthetizer/audio_effects/effects_config.js +10 -6
  46. package/synthetizer/audio_effects/fancy_chorus.js +9 -16
  47. package/synthetizer/audio_effects/reverb.js +1 -1
  48. package/synthetizer/synthetizer.js +43 -37
  49. package/synthetizer/worklet_processor.min.js +8 -8
  50. package/synthetizer/worklet_system/README.md +0 -0
  51. package/synthetizer/worklet_system/message_protocol/message_sending.js +0 -0
  52. package/synthetizer/worklet_system/snapshot/snapshot.js +1 -1
  53. package/synthetizer/worklet_system/worklet_methods/data_entry.js +0 -0
  54. package/synthetizer/worklet_system/worklet_methods/note_on.js +5 -2
  55. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +0 -0
  56. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -0
  57. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -0
  58. package/synthetizer/worklet_system/worklet_methods/worklet_soundfont_manager/sfman_message.js +0 -0
  59. package/synthetizer/worklet_system/worklet_processor.js +0 -0
  60. package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +0 -0
  61. package/synthetizer/worklet_system/worklet_utilities/lfo.js +0 -0
  62. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +0 -0
  63. package/synthetizer/worklet_system/worklet_utilities/unit_converter.js +0 -0
  64. package/synthetizer/worklet_system/worklet_utilities/volume_envelope.js +0 -0
  65. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +0 -0
  66. package/synthetizer/worklet_url.js +0 -0
  67. package/utils/README.md +0 -0
  68. package/utils/byte_functions/big_endian.js +0 -0
  69. package/utils/byte_functions/little_endian.js +0 -0
  70. package/utils/byte_functions/variable_length_quantity.js +0 -0
  71. package/utils/indexed_array.js +0 -0
  72. package/utils/loggin.js +0 -0
@@ -12,7 +12,8 @@ import { BasicMIDI } from "../midi_parser/basic_midi.js";
12
12
 
13
13
  /**
14
14
  * sequencer.js
15
- * purpose: plays back the midi file decoded by midi_loader.js, including support for multi-channel midis (adding channels when more than 1 midi port is detected)
15
+ * purpose: plays back the midi file decoded by midi_loader.js, including support for multichannel midis
16
+ * (adding channels when more than one midi port is detected)
16
17
  */
17
18
 
18
19
  /**
@@ -63,7 +64,7 @@ export class Sequencer
63
64
  * Fires on text event
64
65
  * @type {function}
65
66
  * @param data {Uint8Array} the data text
66
- * @param type {number} the status byte of the message (the meta status byte)
67
+ * @param type {number} the status byte of the message (the meta-status byte)
67
68
  * @param lyricsIndex {number} if the text is a lyric, the index of the lyric in midiData.lyrics, otherwise -1
68
69
  */
69
70
  onTextEvent;
@@ -79,6 +80,51 @@ export class Sequencer
79
80
  */
80
81
  onSongEnded = {};
81
82
 
83
+ /**
84
+ * Fires on tempo change
85
+ * @type {Object<string, function(number)>}
86
+ */
87
+ onTempoChange = {};
88
+
89
+ /**
90
+ * Current song's tempo in BPM
91
+ * @type {number}
92
+ */
93
+ currentTempo = 120;
94
+ /**
95
+ * Current song index
96
+ * @type {number}
97
+ */
98
+ songIndex = 0;
99
+ /**
100
+ * @type {function(BasicMIDI)}
101
+ * @private
102
+ */
103
+ _getMIDIResolve = undefined;
104
+ /**
105
+ * Indicates if the current midiData property has fake data in it (not yet loaded)
106
+ * @type {boolean}
107
+ */
108
+ hasDummyData = true;
109
+ /**
110
+ * Indicates whether the sequencer has finished playing a sequence
111
+ * @type {boolean}
112
+ */
113
+ isFinished = false;
114
+ /**
115
+ * The current sequence's length, in seconds
116
+ * @type {number}
117
+ */
118
+ duration = 0;
119
+
120
+ /**
121
+ * Indicates if the sequencer is paused.
122
+ * Paused if a number, undefined if playing
123
+ * @type {undefined|number}
124
+ * @private
125
+ */
126
+ pausedTime = undefined;
127
+
82
128
  /**
83
129
  * Creates a new Midi sequencer for playing back MIDI files
84
130
  * @param midiBinaries {MIDIFile[]} List of the buffers of the MIDI files
@@ -97,47 +143,6 @@ export class Sequencer
97
143
  */
98
144
  this.absoluteStartTime = this.synth.currentTime;
99
145
 
100
- /**
101
- * @type {function(BasicMIDI)}
102
- * @private
103
- */
104
- this._getMIDIResolve = undefined;
105
-
106
- /**
107
- * Controls the playback's rate
108
- * @type {number}
109
- */
110
- this._playbackRate = 1;
111
-
112
- this.songIndex = 0;
113
-
114
- /**
115
- * Indicates if the current midiData property has dummy data in it (not yet loaded)
116
- * @type {boolean}
117
- */
118
- this.hasDummyData = true;
119
-
120
- this._loop = true;
121
-
122
- /**
123
- * Indicates whether the sequencer has finished playing a sequence
124
- * @type {boolean}
125
- */
126
- this.isFinished = false;
127
-
128
- /**
129
- * Indicates if the sequencer is paused.
130
- * Paused if a number, undefined if playing
131
- * @type {undefined|number}
132
- */
133
- this.pausedTime = undefined;
134
-
135
- /**
136
- * The current sequence's length, in seconds
137
- * @type {number}
138
- */
139
- this.duration = 0;
140
-
141
146
  this.synth.sequencerCallbackFunction = this._handleMessage.bind(this);
142
147
 
143
148
  /**
@@ -167,6 +172,49 @@ export class Sequencer
167
172
  window.addEventListener("beforeunload", this.resetMIDIOut.bind(this));
168
173
  }
169
174
 
175
+ /**
176
+ * Internal loop marker
177
+ * @type {boolean}
178
+ * @private
179
+ */
180
+ _loop = true;
181
+
182
+ get loop()
183
+ {
184
+ return this._loop;
185
+ }
186
+
187
+ set loop(value)
188
+ {
189
+ this._sendMessage(WorkletSequencerMessageType.setLoop, value);
190
+ this._loop = value;
191
+ }
192
+
193
+ /**
194
+ * Controls the playback's rate
195
+ * @type {number}
196
+ * @private
197
+ */
198
+ _playbackRate = 1;
199
+
200
+ /**
201
+ * @returns {number}
202
+ */
203
+ get playbackRate()
204
+ {
205
+ return this._playbackRate;
206
+ }
207
+
208
+ /**
209
+ * @param value {number}
210
+ */
211
+ set playbackRate(value)
212
+ {
213
+ this._sendMessage(WorkletSequencerMessageType.setPlaybackRate, value);
214
+ this.highResTimeOffset *= (value / this._playbackRate);
215
+ this._playbackRate = value;
216
+ }
217
+
170
218
  /**
171
219
  * Indicates if the sequencer should skip to first note on
172
220
  * @return {boolean}
@@ -230,17 +278,6 @@ export class Sequencer
230
278
  this._sendMessage(WorkletSequencerMessageType.setTime, time);
231
279
  }
232
280
 
233
- get loop()
234
- {
235
- return this._loop;
236
- }
237
-
238
- set loop(value)
239
- {
240
- this._sendMessage(WorkletSequencerMessageType.setLoop, value);
241
- this._loop = value;
242
- }
243
-
244
281
  /**
245
282
  * Use for visualization as it's not affected by the audioContext stutter
246
283
  * @returns {number}
@@ -271,24 +308,6 @@ export class Sequencer
271
308
  return currentPerformanceTime;
272
309
  }
273
310
 
274
- /**
275
- * @returns {number}
276
- */
277
- get playbackRate()
278
- {
279
- return this._playbackRate;
280
- }
281
-
282
- /**
283
- * @param value {number}
284
- */
285
- set playbackRate(value)
286
- {
287
- this._sendMessage(WorkletSequencerMessageType.setPlaybackRate, value);
288
- this.highResTimeOffset *= (value / this._playbackRate);
289
- this._playbackRate = value;
290
- }
291
-
292
311
  /**
293
312
  * true if paused, false if playing or stopped
294
313
  * @returns {boolean}
@@ -328,6 +347,16 @@ export class Sequencer
328
347
  this.onTimeChange[id] = callback;
329
348
  }
330
349
 
350
+ /**
351
+ * Adds a new event that gets called when the tempo changes
352
+ * @param callback {function(number)} the new tempo, in BPM
353
+ * @param id {string} must be unique
354
+ */
355
+ addOnTempoChangeEvent(callback, id)
356
+ {
357
+ this.onTempoChange[id] = callback;
358
+ }
359
+
331
360
  resetMIDIOut()
332
361
  {
333
362
  if (!this.MIDIout)
@@ -369,6 +398,26 @@ export class Sequencer
369
398
  this._sendMessage(WorkletSequencerMessageType.changeSong, false);
370
399
  }
371
400
 
401
+ /**
402
+ * @param type {Object<string, function>}
403
+ * @param params {any}
404
+ * @private
405
+ */
406
+ _callEvents(type, params)
407
+ {
408
+ Object.entries(type).forEach((callback) =>
409
+ {
410
+ try
411
+ {
412
+ callback[1](params);
413
+ }
414
+ catch (e)
415
+ {
416
+ SpessaSynthWarn(`Failed to execute callback for ${callback[0]}:`, e);
417
+ }
418
+ });
419
+ }
420
+
372
421
  /**
373
422
  * @param {WorkletSequencerReturnMessageType} messageType
374
423
  * @param {any} messageData
@@ -411,7 +460,7 @@ export class Sequencer
411
460
  this.hasDummyData = false;
412
461
  this.absoluteStartTime = 0;
413
462
  this.duration = this.midiData.duration;
414
- Object.entries(this.onSongChange).forEach((callback) => callback[1](songChangeData));
463
+ this._callEvents(this.onSongChange, songChangeData);
415
464
  // if is auto played, unpause
416
465
  if (messageData[2] === true)
417
466
  {
@@ -429,7 +478,7 @@ export class Sequencer
429
478
  case WorkletSequencerReturnMessageType.timeChange:
430
479
  // message data is absolute time
431
480
  const time = this.synth.currentTime - messageData;
432
- Object.entries(this.onTimeChange).forEach((callback) => callback[1](time));
481
+ this._callEvents(this.onTimeChange, time);
433
482
  this._recalculateStartTime(time);
434
483
  if (this.paused && this._preservePlaybackState)
435
484
  {
@@ -446,7 +495,7 @@ export class Sequencer
446
495
  this.isFinished = messageData;
447
496
  if (this.isFinished)
448
497
  {
449
- Object.entries(this.onSongEnded).forEach((callback) => callback[1]());
498
+ this._callEvents(this.onSongEnded, undefined);
450
499
  }
451
500
  break;
452
501
 
@@ -466,6 +515,14 @@ export class Sequencer
466
515
  {
467
516
  this._getMIDIResolve(BasicMIDI.copyFrom(messageData));
468
517
  }
518
+ break;
519
+
520
+ case WorkletSequencerReturnMessageType.tempoChange:
521
+ this.currentTempo = messageData;
522
+ if (this.onTempoChange)
523
+ {
524
+ this._callEvents(this.onTempoChange, this.currentTempo);
525
+ }
469
526
  }
470
527
  }
471
528
 
@@ -499,7 +556,7 @@ export class Sequencer
499
556
  loadNewSongList(midiBuffers, autoPlay = true)
500
557
  {
501
558
  this.pause();
502
- // add some dummy data
559
+ // add some fake data
503
560
  this.midiData = DUMMY_MIDI_DATA;
504
561
  this.hasDummyData = true;
505
562
  this.duration = 99999;
@@ -549,7 +606,7 @@ export class Sequencer
549
606
 
550
607
  /**
551
608
  * Starts the playback
552
- * @param resetTime {boolean} If true, time is set to 0s
609
+ * @param resetTime {boolean} If true, time is set to 0 s
553
610
  */
554
611
  play(resetTime = false)
555
612
  {
@@ -90,12 +90,15 @@ export function _processEvent(event, trackIndex)
90
90
  break;
91
91
 
92
92
  case messageTypes.setTempo:
93
- this.oneTickToSeconds = 60 / (getTempo(event) * this.midiData.timeDivision);
93
+ let tempoBPM = getTempo(event);
94
+ this.oneTickToSeconds = 60 / (tempoBPM * this.midiData.timeDivision);
94
95
  if (this.oneTickToSeconds === 0)
95
96
  {
96
97
  this.oneTickToSeconds = 60 / (120 * this.midiData.timeDivision);
97
98
  SpessaSynthWarn("invalid tempo! falling back to 120 BPM");
99
+ tempoBPM = 120;
98
100
  }
101
+ this.post(WorkletSequencerReturnMessageType.tempoChange, Math.floor(tempoBPM * 100) / 100);
99
102
  break;
100
103
 
101
104
  // recongized but ignored
@@ -8,18 +8,18 @@ export function _processTick()
8
8
  let current = this.currentTime;
9
9
  while (this.playedTime < current)
10
10
  {
11
- // find next event
11
+ // find the next event
12
12
  let trackIndex = this._findFirstEventIndex();
13
13
  let event = this.tracks[trackIndex][this.eventIndex[trackIndex]];
14
14
  this._processEvent(event, trackIndex);
15
15
 
16
16
  this.eventIndex[trackIndex]++;
17
17
 
18
- // find next event
18
+ // find the next event
19
19
  trackIndex = this._findFirstEventIndex();
20
20
  if (this.tracks[trackIndex].length <= this.eventIndex[trackIndex])
21
21
  {
22
- // song has ended
22
+ // the song has ended
23
23
  if (this.loop)
24
24
  {
25
25
  this.setTimeTicks(this.midiData.loop.start);
@@ -43,7 +43,7 @@ export function _processTick()
43
43
  this.setTimeTicks(this.midiData.loop.start);
44
44
  return;
45
45
  }
46
- // if song has ended
46
+ // if the song has ended
47
47
  else if (current >= this.duration)
48
48
  {
49
49
  if (this.loop && this.currentLoopCount > 0)
@@ -39,5 +39,6 @@ export const WorkletSequencerReturnMessageType = {
39
39
  timeChange: 3, // newAbsoluteTime<number>
40
40
  pause: 4, // no data
41
41
  getMIDI: 5, // midiData<MIDI>
42
- midiError: 6 // errorMSG<string>
42
+ midiError: 6, // errorMSG<string>
43
+ tempoChange: 7 // newTempoBPM<number>
43
44
  };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,21 +1,25 @@
1
1
  import { DEFAULT_CHORUS_CONFIG } from "./fancy_chorus.js";
2
2
 
3
3
  /**
4
- * @typedef {Object} EffectsConfig
4
+ * @typedef {Object} SynthConfig
5
5
  * @property {boolean} chorusEnabled - indicates if the chorus effect is enabled.
6
- * @property {ChorusConfig} chorusConfig - the configuration for chorus. Pass undefined to use defaults
6
+ * @property {ChorusConfig?} chorusConfig - the configuration for chorus. Pass undefined to use defaults
7
7
  * @property {boolean} reverbEnabled - indicates if the reverb effect is enabled.
8
- * @property {AudioBuffer} reverbImpulseResponse - the impulse response for the reverb. Pass undefined to use defaults
8
+ * @property {AudioBuffer?} reverbImpulseResponse - the impulse response for the reverb. Pass undefined to use defaults
9
+ * @property {{
10
+ * worklet: function(context: object, name: string, options?: Object)
11
+ * }} audioNodeCreators - custom audio node creation functions for Web Audio wrappers.
9
12
  */
10
13
 
11
14
 
12
15
  /**
13
- * @type {EffectsConfig}
16
+ * @type {SynthConfig}
14
17
  */
15
- export const DEFAULT_EFFECTS_CONFIG = {
18
+ export const DEFAULT_SYNTH_CONFIG = {
16
19
  chorusEnabled: true,
17
20
  chorusConfig: DEFAULT_CHORUS_CONFIG,
18
21
 
19
22
  reverbEnabled: true,
20
- reverbImpulseResponse: undefined // will load the integrated one
23
+ reverbImpulseResponse: undefined, // will load the integrated one
24
+ audioNodeCreators: undefined
21
25
  };
@@ -52,13 +52,9 @@ export class FancyChorus
52
52
  {
53
53
  const context = output.context;
54
54
 
55
- this.input = new ChannelSplitterNode(context, {
56
- numberOfOutputs: 2
57
- });
55
+ this.input = context.createChannelSplitter(2);
58
56
 
59
- const merger = new ChannelMergerNode(context, {
60
- numberOfInputs: 2
61
- });
57
+ const merger = context.createChannelMerger(2);
62
58
 
63
59
  /**
64
60
  * @type {ChorusNode[]}
@@ -142,16 +138,13 @@ export class FancyChorus
142
138
  */
143
139
  createChorusNode(freq, delay, list, input, output, outputNum, context, config)
144
140
  {
145
- const oscillator = new OscillatorNode(context, {
146
- type: "sine",
147
- frequency: freq
148
- });
149
- const gainNode = new GainNode(context, {
150
- gain: config.oscillatorGain
151
- });
152
- const delayNode = new DelayNode(context, {
153
- delayTime: delay
154
- });
141
+ const oscillator = context.createOscillator();
142
+ oscillator.type = "sine";
143
+ oscillator.frequency.value = freq;
144
+ const gainNode = context.createGain();
145
+ gainNode.gain.value = config.oscillatorGain;
146
+ const delayNode = context.createDelay();
147
+ delayNode.delayTime.value = delay;
155
148
 
156
149
  oscillator.connect(gainNode);
157
150
  gainNode.connect(delayNode.delayTime);
@@ -6,7 +6,7 @@
6
6
  */
7
7
  export function getReverbProcessor(context, reverbBuffer = undefined)
8
8
  {
9
- const convolver = new ConvolverNode(context);
9
+ const convolver = context.createConvolver();
10
10
  if (reverbBuffer)
11
11
  {
12
12
  convolver.buffer = reverbBuffer;