spessasynth_lib 3.25.22 → 3.26.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 (168) hide show
  1. package/README.md +29 -114
  2. package/index.js +33 -33
  3. package/package.json +16 -6
  4. package/external_midi/README.md +0 -4
  5. package/external_midi/midi_handler.js +0 -130
  6. package/external_midi/web_midi_link.js +0 -43
  7. package/externals/fflate/LICENSE +0 -21
  8. package/externals/fflate/fflate.min.js +0 -1
  9. package/externals/stbvorbis_sync/@types/stbvorbis_sync.d.ts +0 -12
  10. package/externals/stbvorbis_sync/LICENSE +0 -202
  11. package/externals/stbvorbis_sync/NOTICE +0 -6
  12. package/externals/stbvorbis_sync/stbvorbis_sync.min.js +0 -1
  13. package/midi/README.md +0 -32
  14. package/midi/basic_midi.js +0 -565
  15. package/midi/midi_builder.js +0 -202
  16. package/midi/midi_data.js +0 -63
  17. package/midi/midi_loader.js +0 -324
  18. package/midi/midi_message.js +0 -254
  19. package/midi/midi_sequence.js +0 -225
  20. package/midi/midi_tools/get_note_times.js +0 -154
  21. package/midi/midi_tools/midi_editor.js +0 -611
  22. package/midi/midi_tools/midi_writer.js +0 -99
  23. package/midi/midi_tools/rmidi_writer.js +0 -567
  24. package/midi/midi_tools/used_keys_loaded.js +0 -238
  25. package/midi/xmf_loader.js +0 -454
  26. package/sequencer/README.md +0 -32
  27. package/sequencer/sequencer_engine/events.js +0 -207
  28. package/sequencer/sequencer_engine/play.js +0 -353
  29. package/sequencer/sequencer_engine/process_event.js +0 -169
  30. package/sequencer/sequencer_engine/process_tick.js +0 -106
  31. package/sequencer/sequencer_engine/sequencer_engine.js +0 -337
  32. package/sequencer/sequencer_engine/song_control.js +0 -229
  33. package/sequencer/worklet_wrapper/default_sequencer_options.js +0 -8
  34. package/sequencer/worklet_wrapper/sequencer.js +0 -807
  35. package/sequencer/worklet_wrapper/sequencer_message.js +0 -53
  36. package/soundfont/README.md +0 -13
  37. package/soundfont/basic_soundfont/basic_instrument.js +0 -77
  38. package/soundfont/basic_soundfont/basic_preset.js +0 -336
  39. package/soundfont/basic_soundfont/basic_sample.js +0 -197
  40. package/soundfont/basic_soundfont/basic_soundfont.js +0 -565
  41. package/soundfont/basic_soundfont/basic_zone.js +0 -64
  42. package/soundfont/basic_soundfont/basic_zones.js +0 -43
  43. package/soundfont/basic_soundfont/generator.js +0 -220
  44. package/soundfont/basic_soundfont/modulator.js +0 -378
  45. package/soundfont/basic_soundfont/riff_chunk.js +0 -149
  46. package/soundfont/basic_soundfont/write_dls/art2.js +0 -173
  47. package/soundfont/basic_soundfont/write_dls/articulator.js +0 -49
  48. package/soundfont/basic_soundfont/write_dls/combine_zones.js +0 -400
  49. package/soundfont/basic_soundfont/write_dls/ins.js +0 -103
  50. package/soundfont/basic_soundfont/write_dls/lins.js +0 -18
  51. package/soundfont/basic_soundfont/write_dls/modulator_converter.js +0 -330
  52. package/soundfont/basic_soundfont/write_dls/rgn2.js +0 -121
  53. package/soundfont/basic_soundfont/write_dls/wave.js +0 -94
  54. package/soundfont/basic_soundfont/write_dls/write_dls.js +0 -119
  55. package/soundfont/basic_soundfont/write_dls/wsmp.js +0 -78
  56. package/soundfont/basic_soundfont/write_dls/wvpl.js +0 -32
  57. package/soundfont/basic_soundfont/write_sf2/ibag.js +0 -39
  58. package/soundfont/basic_soundfont/write_sf2/igen.js +0 -80
  59. package/soundfont/basic_soundfont/write_sf2/imod.js +0 -46
  60. package/soundfont/basic_soundfont/write_sf2/inst.js +0 -34
  61. package/soundfont/basic_soundfont/write_sf2/pbag.js +0 -39
  62. package/soundfont/basic_soundfont/write_sf2/pgen.js +0 -82
  63. package/soundfont/basic_soundfont/write_sf2/phdr.js +0 -42
  64. package/soundfont/basic_soundfont/write_sf2/pmod.js +0 -46
  65. package/soundfont/basic_soundfont/write_sf2/sdta.js +0 -80
  66. package/soundfont/basic_soundfont/write_sf2/shdr.js +0 -55
  67. package/soundfont/basic_soundfont/write_sf2/write.js +0 -222
  68. package/soundfont/dls/articulator_converter.js +0 -396
  69. package/soundfont/dls/dls_destinations.js +0 -38
  70. package/soundfont/dls/dls_preset.js +0 -44
  71. package/soundfont/dls/dls_sample.js +0 -75
  72. package/soundfont/dls/dls_soundfont.js +0 -186
  73. package/soundfont/dls/dls_sources.js +0 -62
  74. package/soundfont/dls/dls_zone.js +0 -95
  75. package/soundfont/dls/read_articulation.js +0 -299
  76. package/soundfont/dls/read_instrument.js +0 -121
  77. package/soundfont/dls/read_instrument_list.js +0 -17
  78. package/soundfont/dls/read_lart.js +0 -35
  79. package/soundfont/dls/read_region.js +0 -152
  80. package/soundfont/dls/read_samples.js +0 -270
  81. package/soundfont/load_soundfont.js +0 -21
  82. package/soundfont/read_sf2/generators.js +0 -46
  83. package/soundfont/read_sf2/instruments.js +0 -66
  84. package/soundfont/read_sf2/modulators.js +0 -36
  85. package/soundfont/read_sf2/presets.js +0 -80
  86. package/soundfont/read_sf2/samples.js +0 -304
  87. package/soundfont/read_sf2/soundfont.js +0 -305
  88. package/soundfont/read_sf2/zones.js +0 -263
  89. package/synthetizer/README.md +0 -10
  90. package/synthetizer/audio_effects/effects_config.js +0 -25
  91. package/synthetizer/audio_effects/fancy_chorus.js +0 -162
  92. package/synthetizer/audio_effects/rb_compressed.min.js +0 -1
  93. package/synthetizer/audio_effects/reverb.js +0 -35
  94. package/synthetizer/audio_effects/reverb_as_binary.js +0 -18
  95. package/synthetizer/audio_engine/README.md +0 -25
  96. package/synthetizer/audio_engine/engine_components/compute_modulator.js +0 -266
  97. package/synthetizer/audio_engine/engine_components/controller_tables.js +0 -88
  98. package/synthetizer/audio_engine/engine_components/key_modifier_manager.js +0 -149
  99. package/synthetizer/audio_engine/engine_components/lfo.js +0 -26
  100. package/synthetizer/audio_engine/engine_components/lowpass_filter.js +0 -282
  101. package/synthetizer/audio_engine/engine_components/midi_audio_channel.js +0 -471
  102. package/synthetizer/audio_engine/engine_components/modulation_envelope.js +0 -181
  103. package/synthetizer/audio_engine/engine_components/modulator_curves.js +0 -89
  104. package/synthetizer/audio_engine/engine_components/soundfont_manager/sfman_message.js +0 -9
  105. package/synthetizer/audio_engine/engine_components/soundfont_manager/soundfont_manager.js +0 -254
  106. package/synthetizer/audio_engine/engine_components/stereo_panner.js +0 -120
  107. package/synthetizer/audio_engine/engine_components/unit_converter.js +0 -73
  108. package/synthetizer/audio_engine/engine_components/voice.js +0 -519
  109. package/synthetizer/audio_engine/engine_components/volume_envelope.js +0 -401
  110. package/synthetizer/audio_engine/engine_components/wavetable_oscillator.js +0 -263
  111. package/synthetizer/audio_engine/engine_methods/controller_control/controller_change.js +0 -132
  112. package/synthetizer/audio_engine/engine_methods/controller_control/master_parameters.js +0 -48
  113. package/synthetizer/audio_engine/engine_methods/controller_control/reset_controllers.js +0 -241
  114. package/synthetizer/audio_engine/engine_methods/create_midi_channel.js +0 -27
  115. package/synthetizer/audio_engine/engine_methods/data_entry/data_entry_coarse.js +0 -253
  116. package/synthetizer/audio_engine/engine_methods/data_entry/data_entry_fine.js +0 -66
  117. package/synthetizer/audio_engine/engine_methods/mute_channel.js +0 -17
  118. package/synthetizer/audio_engine/engine_methods/note_on.js +0 -174
  119. package/synthetizer/audio_engine/engine_methods/portamento_time.js +0 -92
  120. package/synthetizer/audio_engine/engine_methods/program_change.js +0 -61
  121. package/synthetizer/audio_engine/engine_methods/render_voice.js +0 -196
  122. package/synthetizer/audio_engine/engine_methods/soundfont_management/clear_sound_font.js +0 -30
  123. package/synthetizer/audio_engine/engine_methods/soundfont_management/get_preset.js +0 -22
  124. package/synthetizer/audio_engine/engine_methods/soundfont_management/reload_sound_font.js +0 -40
  125. package/synthetizer/audio_engine/engine_methods/soundfont_management/send_preset_list.js +0 -34
  126. package/synthetizer/audio_engine/engine_methods/soundfont_management/set_embedded_sound_font.js +0 -21
  127. package/synthetizer/audio_engine/engine_methods/stopping_notes/kill_note.js +0 -20
  128. package/synthetizer/audio_engine/engine_methods/stopping_notes/note_off.js +0 -55
  129. package/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_channels.js +0 -16
  130. package/synthetizer/audio_engine/engine_methods/stopping_notes/stop_all_notes.js +0 -30
  131. package/synthetizer/audio_engine/engine_methods/stopping_notes/voice_killing.js +0 -63
  132. package/synthetizer/audio_engine/engine_methods/system_exclusive.js +0 -744
  133. package/synthetizer/audio_engine/engine_methods/tuning_control/channel_pressure.js +0 -24
  134. package/synthetizer/audio_engine/engine_methods/tuning_control/pitch_wheel.js +0 -33
  135. package/synthetizer/audio_engine/engine_methods/tuning_control/poly_pressure.js +0 -31
  136. package/synthetizer/audio_engine/engine_methods/tuning_control/set_master_tuning.js +0 -15
  137. package/synthetizer/audio_engine/engine_methods/tuning_control/set_modulation_depth.js +0 -27
  138. package/synthetizer/audio_engine/engine_methods/tuning_control/set_octave_tuning.js +0 -19
  139. package/synthetizer/audio_engine/engine_methods/tuning_control/set_tuning.js +0 -27
  140. package/synthetizer/audio_engine/engine_methods/tuning_control/transpose_all_channels.js +0 -15
  141. package/synthetizer/audio_engine/engine_methods/tuning_control/transpose_channel.js +0 -34
  142. package/synthetizer/audio_engine/main_processor.js +0 -765
  143. package/synthetizer/audio_engine/message_protocol/README.md +0 -13
  144. package/synthetizer/audio_engine/message_protocol/message_sending.js +0 -22
  145. package/synthetizer/audio_engine/message_protocol/worklet_message.js +0 -107
  146. package/synthetizer/audio_engine/snapshot/apply_synthesizer_snapshot.js +0 -14
  147. package/synthetizer/audio_engine/snapshot/channel_snapshot.js +0 -175
  148. package/synthetizer/audio_engine/snapshot/synthesizer_snapshot.js +0 -122
  149. package/synthetizer/synth_constants.js +0 -20
  150. package/synthetizer/worklet_processor.min.js +0 -21
  151. package/synthetizer/worklet_wrapper/key_modifier_manager.js +0 -104
  152. package/synthetizer/worklet_wrapper/synth_event_handler.js +0 -214
  153. package/synthetizer/worklet_wrapper/synth_soundfont_manager.js +0 -111
  154. package/synthetizer/worklet_wrapper/synthetizer.js +0 -1027
  155. package/synthetizer/worklet_wrapper/worklet_processor.js +0 -340
  156. package/synthetizer/worklet_wrapper/worklet_url.js +0 -16
  157. package/utils/README.md +0 -5
  158. package/utils/buffer_to_wav.js +0 -186
  159. package/utils/byte_functions/big_endian.js +0 -32
  160. package/utils/byte_functions/little_endian.js +0 -77
  161. package/utils/byte_functions/string.js +0 -107
  162. package/utils/byte_functions/variable_length_quantity.js +0 -42
  163. package/utils/fill_with_defaults.js +0 -21
  164. package/utils/indexed_array.js +0 -52
  165. package/utils/loggin.js +0 -79
  166. package/utils/other.js +0 -92
  167. package/utils/sysex_detector.js +0 -58
  168. package/utils/xg_hacks.js +0 -193
@@ -1,807 +0,0 @@
1
- import { Synthetizer } from "../../synthetizer/worklet_wrapper/synthetizer.js";
2
- import { messageTypes } from "../../midi/midi_message.js";
3
- import {
4
- ALL_CHANNELS_OR_DIFFERENT_ACTION,
5
- workletMessageType
6
- } from "../../synthetizer/audio_engine/message_protocol/worklet_message.js";
7
- import {
8
- SongChangeType,
9
- SpessaSynthSequencerMessageType,
10
- SpessaSynthSequencerReturnMessageType
11
- } from "./sequencer_message.js";
12
- import { SpessaSynthWarn } from "../../utils/loggin.js";
13
- import { DUMMY_MIDI_DATA, MIDIData } from "../../midi/midi_data.js";
14
- import { BasicMIDI } from "../../midi/basic_midi.js";
15
- import { readBytesAsUintBigEndian } from "../../utils/byte_functions/big_endian.js";
16
- import { DEFAULT_SEQUENCER_OPTIONS } from "./default_sequencer_options.js";
17
-
18
- /**
19
- * sequencer.js
20
- * purpose: plays back the midi file decoded by midi_loader.js, including support for multichannel midis
21
- * (adding channels when more than one midi port is detected)
22
- * note: this is the sequencer class that runs on the main thread
23
- * and only communicates with the worklet sequencer which does the actual playback
24
- */
25
-
26
- /**
27
- * @typedef MidFile {Object}
28
- * @property {ArrayBuffer} binary - the binary data of the file.
29
- * @property {string|undefined} altName - the alternative name for the file
30
- */
31
-
32
- /**
33
- * @typedef {BasicMIDI|MidFile} MIDIFile
34
- */
35
-
36
- // noinspection JSUnusedGlobalSymbols
37
- /**
38
- * @typedef {Object} SequencerOptions
39
- * @property {boolean|undefined} skipToFirstNoteOn - if true, the sequencer will skip to the first note
40
- * @property {boolean|undefined} autoPlay - if true, the sequencer will automatically start playing the MIDI
41
- * @property {boolean|unescape} preservePlaybackState - if true,
42
- * the sequencer will stay paused when seeking or changing the playback rate
43
- */
44
-
45
- // noinspection JSUnusedGlobalSymbols
46
- export class Sequencer
47
- {
48
- /**
49
- * Executes when MIDI parsing has an error.
50
- * @type {function(Error)}
51
- */
52
- onError;
53
-
54
- /**
55
- * Fires on text event
56
- * @type {Function}
57
- * @param data {Uint8Array} the data text
58
- * @param type {number} the status byte of the message (the meta-status byte)
59
- * @param lyricsIndex {number} if the text is a lyric, the index of the lyric in midiData.lyrics, otherwise -1
60
- */
61
- onTextEvent;
62
-
63
- /**
64
- * The current MIDI data, with the exclusion of the embedded sound bank and event data.
65
- * @type {MIDIData}
66
- */
67
- midiData;
68
-
69
- /**
70
- * The current MIDI data for all songs, like the midiData property.
71
- * @type {MIDIData[]}
72
- */
73
- songListData = [];
74
-
75
- /**
76
- * @type {Object<string, function(MIDIData)>}
77
- * @private
78
- */
79
- onSongChange = {};
80
-
81
- /**
82
- * Fires when CurrentTime changes
83
- * @type {Object<string, function(number)>} the time that was changed to
84
- * @private
85
- */
86
- onTimeChange = {};
87
-
88
- /**
89
- * @type {Object<string, function>}
90
- * @private
91
- */
92
- onSongEnded = {};
93
-
94
- /**
95
- * Fires on tempo change
96
- * @type {Object<string, function(number)>}
97
- */
98
- onTempoChange = {};
99
-
100
- /**
101
- * Fires on meta-event
102
- * @type {Object<string, function([number, Uint8Array, number, number])>}
103
- */
104
- onMetaEvent = {};
105
-
106
- /**
107
- * Current song's tempo in BPM
108
- * @type {number}
109
- */
110
- currentTempo = 120;
111
- /**
112
- * Current song index
113
- * @type {number}
114
- */
115
- songIndex = 0;
116
- /**
117
- * @type {function(BasicMIDI)}
118
- * @private
119
- */
120
- _getMIDIResolve = undefined;
121
- /**
122
- * Indicates if the current midiData property has fake data in it (not yet loaded)
123
- * @type {boolean}
124
- */
125
- hasDummyData = true;
126
- /**
127
- * Indicates whether the sequencer has finished playing a sequence
128
- * @type {boolean}
129
- */
130
- isFinished = false;
131
- /**
132
- * The current sequence's length, in seconds
133
- * @type {number}
134
- */
135
- duration = 0;
136
-
137
- /**
138
- * Indicates if the sequencer is paused.
139
- * Paused if a number, undefined if playing
140
- * @type {undefined|number}
141
- * @private
142
- */
143
- pausedTime = undefined;
144
-
145
- /**
146
- * Creates a new Midi sequencer for playing back MIDI files
147
- * @param midiBinaries {MIDIFile[]} List of the buffers of the MIDI files
148
- * @param synth {Synthetizer} synth to send events to
149
- * @param options {SequencerOptions} the sequencer's options
150
- */
151
- constructor(midiBinaries, synth, options = DEFAULT_SEQUENCER_OPTIONS)
152
- {
153
- this.ignoreEvents = false;
154
- this.synth = synth;
155
- this.highResTimeOffset = 0;
156
-
157
- /**
158
- * Absolute playback startTime, bases on the synth's time
159
- * @type {number}
160
- */
161
- this.absoluteStartTime = this.synth.currentTime;
162
-
163
- this.synth.sequencerCallbackFunction = this._handleMessage.bind(this);
164
-
165
- /**
166
- * @type {boolean}
167
- * @private
168
- */
169
- this._skipToFirstNoteOn = options?.skipToFirstNoteOn ?? true;
170
- /**
171
- * @type {boolean}
172
- * @private
173
- */
174
- this._preservePlaybackState = options?.preservePlaybackState ?? false;
175
-
176
- if (this._skipToFirstNoteOn === false)
177
- {
178
- // setter sends message
179
- this._sendMessage(SpessaSynthSequencerMessageType.setSkipToFirstNote, false);
180
- }
181
-
182
- if (this._preservePlaybackState === true)
183
- {
184
- this._sendMessage(SpessaSynthSequencerMessageType.setPreservePlaybackState, true);
185
- }
186
-
187
- this.loadNewSongList(midiBinaries, options?.autoPlay ?? true);
188
-
189
- window.addEventListener("beforeunload", this.resetMIDIOut.bind(this));
190
- }
191
-
192
- /**
193
- * Internal loop marker
194
- * @type {boolean}
195
- * @private
196
- */
197
- _loop = true;
198
-
199
- /**
200
- * Indicates if the sequencer is currently looping
201
- * @returns {boolean}
202
- */
203
- get loop()
204
- {
205
- return this._loop;
206
- }
207
-
208
- set loop(value)
209
- {
210
- this._sendMessage(SpessaSynthSequencerMessageType.setLoop, [value, this._loopsRemaining]);
211
- this._loop = value;
212
- }
213
-
214
- /**
215
- * Internal loop count marker (-1 is infinite)
216
- * @type {number}
217
- * @private
218
- */
219
- _loopsRemaining = -1;
220
-
221
- /**
222
- * The current remaining number of loops. -1 means infinite looping
223
- * @returns {number}
224
- */
225
- get loopsRemaining()
226
- {
227
- return this._loopsRemaining;
228
- }
229
-
230
- /**
231
- * The current remaining number of loops. -1 means infinite looping
232
- * @param val {number}
233
- */
234
- set loopsRemaining(val)
235
- {
236
- this._loopsRemaining = val;
237
- this._sendMessage(SpessaSynthSequencerMessageType.setLoop, [this._loop, val]);
238
- }
239
-
240
- /**
241
- * Controls the playback's rate
242
- * @type {number}
243
- * @private
244
- */
245
- _playbackRate = 1;
246
-
247
- /**
248
- * @returns {number}
249
- */
250
- get playbackRate()
251
- {
252
- return this._playbackRate;
253
- }
254
-
255
- /**
256
- * @param value {number}
257
- */
258
- set playbackRate(value)
259
- {
260
- this._sendMessage(SpessaSynthSequencerMessageType.setPlaybackRate, value);
261
- this.highResTimeOffset *= (value / this._playbackRate);
262
- this._playbackRate = value;
263
- }
264
-
265
- /**
266
- * @type {boolean}
267
- * @private
268
- */
269
- _shuffleSongs = false;
270
-
271
- /**
272
- * Indicates if the song order is random
273
- * @returns {boolean}
274
- */
275
- get shuffleSongs()
276
- {
277
- return this._shuffleSongs;
278
- }
279
-
280
- /**
281
- * Indicates if the song order is random
282
- * @param value {boolean}
283
- */
284
- set shuffleSongs(value)
285
- {
286
- this._shuffleSongs = value;
287
- if (value)
288
- {
289
- this._sendMessage(SpessaSynthSequencerMessageType.changeSong, [SongChangeType.shuffleOn]);
290
- }
291
- else
292
- {
293
- this._sendMessage(SpessaSynthSequencerMessageType.changeSong, [SongChangeType.shuffleOff]);
294
- }
295
- }
296
-
297
- /**
298
- * Indicates if the sequencer should skip to first note on
299
- * @return {boolean}
300
- */
301
- get skipToFirstNoteOn()
302
- {
303
- return this._skipToFirstNoteOn;
304
- }
305
-
306
- /**
307
- * Indicates if the sequencer should skip to first note on
308
- * @param val {boolean}
309
- */
310
- set skipToFirstNoteOn(val)
311
- {
312
- this._skipToFirstNoteOn = val;
313
- this._sendMessage(SpessaSynthSequencerMessageType.setSkipToFirstNote, this._skipToFirstNoteOn);
314
- }
315
-
316
- /**
317
- * if true,
318
- * the sequencer will stay paused when seeking or changing the playback rate
319
- * @returns {boolean}
320
- */
321
- get preservePlaybackState()
322
- {
323
- return this._preservePlaybackState;
324
- }
325
-
326
- /**
327
- * if true,
328
- * the sequencer will stay paused when seeking or changing the playback rate
329
- * @param val {boolean}
330
- */
331
- set preservePlaybackState(val)
332
- {
333
- this._preservePlaybackState = val;
334
- this._sendMessage(SpessaSynthSequencerMessageType.setPreservePlaybackState, val);
335
- }
336
-
337
- /**
338
- * @returns {number} Current playback time, in seconds
339
- */
340
- get currentTime()
341
- {
342
- // return the paused time if it's set to something other than undefined
343
- if (this.pausedTime !== undefined)
344
- {
345
- return this.pausedTime;
346
- }
347
-
348
- return (this.synth.currentTime - this.absoluteStartTime) * this._playbackRate;
349
- }
350
-
351
- set currentTime(time)
352
- {
353
- if (!this._preservePlaybackState)
354
- {
355
- this.unpause();
356
- }
357
- this._sendMessage(SpessaSynthSequencerMessageType.setTime, time);
358
- }
359
-
360
- /**
361
- * Use for visualization as it's not affected by the audioContext stutter
362
- * @returns {number}
363
- */
364
- get currentHighResolutionTime()
365
- {
366
- if (this.pausedTime !== undefined)
367
- {
368
- return this.pausedTime;
369
- }
370
- const highResTimeOffset = this.highResTimeOffset;
371
- const absoluteStartTime = this.absoluteStartTime;
372
-
373
- // sync performance.now to current time
374
- const performanceElapsedTime = ((performance.now() / 1000) - absoluteStartTime) * this._playbackRate;
375
-
376
- let currentPerformanceTime = highResTimeOffset + performanceElapsedTime;
377
- const currentAudioTime = this.currentTime;
378
-
379
- const smoothingFactor = 0.01 * this._playbackRate;
380
-
381
- // diff times smoothing factor
382
- const timeDifference = currentAudioTime - currentPerformanceTime;
383
- this.highResTimeOffset += timeDifference * smoothingFactor;
384
-
385
- // return a smoothed performance time
386
- currentPerformanceTime = this.highResTimeOffset + performanceElapsedTime;
387
- return currentPerformanceTime;
388
- }
389
-
390
- /**
391
- * true if paused, false if playing or stopped
392
- * @returns {boolean}
393
- */
394
- get paused()
395
- {
396
- return this.pausedTime !== undefined;
397
- }
398
-
399
- /**
400
- * Adds a new event that gets called when the song changes
401
- * @param callback {function(MIDIData)}
402
- * @param id {string} must be unique
403
- */
404
- addOnSongChangeEvent(callback, id)
405
- {
406
- this.onSongChange[id] = callback;
407
- }
408
-
409
- /**
410
- * Adds a new event that gets called when the song ends
411
- * @param callback {function}
412
- * @param id {string} must be unique
413
- */
414
- addOnSongEndedEvent(callback, id)
415
- {
416
- this.onSongEnded[id] = callback;
417
- }
418
-
419
- /**
420
- * Adds a new event that gets called when the time changes
421
- * @param callback {function(number)} the new time, in seconds
422
- * @param id {string} must be unique
423
- */
424
- addOnTimeChangeEvent(callback, id)
425
- {
426
- this.onTimeChange[id] = callback;
427
- }
428
-
429
- /**
430
- * Adds a new event that gets called when the tempo changes
431
- * @param callback {function(number)} the new tempo, in BPM
432
- * @param id {string} must be unique
433
- */
434
- addOnTempoChangeEvent(callback, id)
435
- {
436
- this.onTempoChange[id] = callback;
437
- }
438
-
439
- /**
440
- * Adds a new event that gets called when a meta-event occurs
441
- * @param callback {function([number, Uint8Array, number, number])} the meta-event type,
442
- * its data, the track number and MIDI ticks
443
- * @param id {string} must be unique
444
- */
445
- addOnMetaEvent(callback, id)
446
- {
447
- this.onMetaEvent[id] = callback;
448
- }
449
-
450
- resetMIDIOut()
451
- {
452
- if (!this.MIDIout)
453
- {
454
- return;
455
- }
456
- for (let i = 0; i < 16; i++)
457
- {
458
- this.MIDIout.send([messageTypes.controllerChange | i, 120, 0]); // all notes off
459
- this.MIDIout.send([messageTypes.controllerChange | i, 123, 0]); // all sound off
460
- }
461
- this.MIDIout.send([messageTypes.reset]); // reset
462
- }
463
-
464
- /**
465
- * @param messageType {SpessaSynthSequencerMessageType}
466
- * @param messageData {any}
467
- * @private
468
- */
469
- _sendMessage(messageType, messageData = undefined)
470
- {
471
- this.synth.post({
472
- channelNumber: ALL_CHANNELS_OR_DIFFERENT_ACTION,
473
- messageType: workletMessageType.sequencerSpecific,
474
- messageData: {
475
- messageType: messageType,
476
- messageData: messageData
477
- }
478
- });
479
- }
480
-
481
- /**
482
- * Switch to the next song in the playlist
483
- */
484
- nextSong()
485
- {
486
- this._sendMessage(SpessaSynthSequencerMessageType.changeSong, [SongChangeType.forwards]);
487
- }
488
-
489
- /**
490
- * Switch to the previous song in the playlist
491
- */
492
- previousSong()
493
- {
494
- this._sendMessage(SpessaSynthSequencerMessageType.changeSong, [SongChangeType.backwards]);
495
- }
496
-
497
- /**
498
- * Sets the song index in the playlist
499
- * @param index
500
- */
501
- setSongIndex(index)
502
- {
503
- const clamped = Math.max(Math.min(this.songsAmount - 1, index), 0);
504
- this._sendMessage(SpessaSynthSequencerMessageType.changeSong, [SongChangeType.index, clamped]);
505
- }
506
-
507
- /**
508
- * @param type {Object<string, function>}
509
- * @param params {any}
510
- * @private
511
- */
512
- _callEvents(type, params)
513
- {
514
- for (const key in type)
515
- {
516
- const callback = type[key];
517
- try
518
- {
519
- callback(params);
520
- }
521
- catch (e)
522
- {
523
- SpessaSynthWarn(`Failed to execute callback for ${callback[0]}:`, e);
524
- }
525
- }
526
- }
527
-
528
- /**
529
- * @param {SpessaSynthSequencerReturnMessageType} messageType
530
- * @param {any} messageData
531
- * @private
532
- */
533
- _handleMessage(messageType, messageData)
534
- {
535
- if (this.ignoreEvents)
536
- {
537
- return;
538
- }
539
- switch (messageType)
540
- {
541
- case SpessaSynthSequencerReturnMessageType.midiEvent:
542
- /**
543
- * @type {number[]}
544
- */
545
- let midiEventData = messageData;
546
- if (this.MIDIout)
547
- {
548
- if (midiEventData[0] >= 0x80)
549
- {
550
- this.MIDIout.send(midiEventData);
551
- return;
552
- }
553
- }
554
- break;
555
-
556
- case SpessaSynthSequencerReturnMessageType.songChange:
557
- this.songIndex = messageData[0];
558
- const songChangeData = this.songListData[this.songIndex];
559
- this.midiData = songChangeData;
560
- this.hasDummyData = false;
561
- this.absoluteStartTime = 0;
562
- this.duration = this.midiData.duration;
563
- this._callEvents(this.onSongChange, songChangeData);
564
- // if is auto played, unpause
565
- if (messageData[1] === true)
566
- {
567
- this.unpause();
568
- }
569
- break;
570
-
571
- case SpessaSynthSequencerReturnMessageType.timeChange:
572
- // message data is absolute time
573
- const time = this.synth.currentTime - messageData;
574
- this._callEvents(this.onTimeChange, time);
575
- this._recalculateStartTime(time);
576
- if (this.paused && this._preservePlaybackState)
577
- {
578
- this.pausedTime = time;
579
- }
580
- else
581
- {
582
- this.unpause();
583
- }
584
- break;
585
-
586
- case SpessaSynthSequencerReturnMessageType.pause:
587
- this.pausedTime = this.currentTime;
588
- this.isFinished = messageData;
589
- if (this.isFinished)
590
- {
591
- this._callEvents(this.onSongEnded, undefined);
592
- }
593
- break;
594
-
595
- case SpessaSynthSequencerReturnMessageType.midiError:
596
- if (this.onError)
597
- {
598
- this.onError(messageData);
599
- }
600
- else
601
- {
602
- throw new Error("Sequencer error: " + messageData);
603
- }
604
- return;
605
-
606
- case SpessaSynthSequencerReturnMessageType.getMIDI:
607
- if (this._getMIDIResolve)
608
- {
609
- this._getMIDIResolve(BasicMIDI.copyFrom(messageData));
610
- }
611
- break;
612
-
613
- case SpessaSynthSequencerReturnMessageType.metaEvent:
614
- /**
615
- * @type {MIDIMessage}
616
- */
617
- const event = messageData[0];
618
- switch (event.messageStatusByte)
619
- {
620
- case messageTypes.setTempo:
621
- event.messageData.currentIndex = 0;
622
- const bpm = 60000000 / readBytesAsUintBigEndian(event.messageData, 3);
623
- event.messageData.currentIndex = 0;
624
- this.currentTempo = Math.round(bpm * 100) / 100;
625
- if (this.onTempoChange)
626
- {
627
- this._callEvents(this.onTempoChange, this.currentTempo);
628
- }
629
- break;
630
-
631
- case messageTypes.text:
632
- case messageTypes.lyric:
633
- case messageTypes.copyright:
634
- case messageTypes.trackName:
635
- case messageTypes.marker:
636
- case messageTypes.cuePoint:
637
- case messageTypes.instrumentName:
638
- case messageTypes.programName:
639
- let lyricsIndex = -1;
640
- if (event.messageStatusByte === messageTypes.lyric)
641
- {
642
- lyricsIndex = Math.min(
643
- this.midiData.lyricsTicks.indexOf(event.ticks),
644
- this.midiData.lyrics.length - 1
645
- );
646
- }
647
- let sentStatus = event.messageStatusByte;
648
- // if MIDI is a karaoke file, it uses the "text" event type or "lyrics" for lyrics (duh)
649
- // why?
650
- // because the MIDI standard is a messy pile of garbage,
651
- // and it's not my fault that it's like this :(
652
- // I'm just trying to make the best out of a bad situation.
653
- // I'm sorry
654
- // okay I should get back to work
655
- // anyway,
656
- // check for a karaoke file and change the status byte to "lyric"
657
- // if it's a karaoke file
658
- if (this.midiData.isKaraokeFile && (
659
- event.messageStatusByte === messageTypes.text ||
660
- event.messageStatusByte === messageTypes.lyric
661
- ))
662
- {
663
- lyricsIndex = Math.min(
664
- this.midiData.lyricsTicks.indexOf(event.ticks),
665
- this.midiData.lyricsTicks.length
666
- );
667
- sentStatus = messageTypes.lyric;
668
- }
669
- if (this.onTextEvent)
670
- {
671
- this.onTextEvent(event.messageData, sentStatus, lyricsIndex, event.ticks);
672
- }
673
- break;
674
- }
675
- this._callEvents(this.onMetaEvent, messageData);
676
- break;
677
-
678
- case SpessaSynthSequencerReturnMessageType.loopCountChange:
679
- this._loopsRemaining = messageData;
680
- if (this._loopsRemaining === 0)
681
- {
682
- this._loop = false;
683
- }
684
- break;
685
-
686
- case SpessaSynthSequencerReturnMessageType.songListChange:
687
- this.songListData = messageData;
688
- break;
689
-
690
- default:
691
- break;
692
- }
693
- }
694
-
695
- /**
696
- * @param time
697
- * @private
698
- */
699
- _recalculateStartTime(time)
700
- {
701
- this.absoluteStartTime = this.synth.currentTime - time / this._playbackRate;
702
- this.highResTimeOffset = (this.synth.currentTime - (performance.now() / 1000)) * this._playbackRate;
703
- }
704
-
705
- /**
706
- * @returns {Promise<MIDI>}
707
- */
708
- async getMIDI()
709
- {
710
- return new Promise(resolve =>
711
- {
712
- this._getMIDIResolve = resolve;
713
- this._sendMessage(SpessaSynthSequencerMessageType.getMIDI, undefined);
714
- });
715
- }
716
-
717
- /**
718
- * Loads a new song list
719
- * @param midiBuffers {MIDIFile[]} - the MIDI files to play
720
- * @param autoPlay {boolean} - if true, the first sequence will automatically start playing
721
- */
722
- loadNewSongList(midiBuffers, autoPlay = true)
723
- {
724
- this.pause();
725
- // add some fake data
726
- this.midiData = DUMMY_MIDI_DATA;
727
- this.hasDummyData = true;
728
- this.duration = 99999;
729
- /**
730
- * sanitize MIDIs
731
- * @type {({binary: ArrayBuffer, altName: string}|BasicMIDI)[]}
732
- */
733
- const sanitizedMidis = midiBuffers.map(m =>
734
- {
735
- if (m.binary !== undefined)
736
- {
737
- return m;
738
- }
739
- return BasicMIDI.copyFrom(m);
740
- });
741
- this._sendMessage(SpessaSynthSequencerMessageType.loadNewSongList, [sanitizedMidis, autoPlay]);
742
- this.songIndex = 0;
743
- this.songsAmount = midiBuffers.length;
744
- if (this.songsAmount > 1)
745
- {
746
- this.loop = false;
747
- }
748
- if (autoPlay === false)
749
- {
750
- this.pausedTime = this.currentTime;
751
- }
752
- }
753
-
754
- /**
755
- * @param output {MIDIOutput}
756
- */
757
- connectMidiOutput(output)
758
- {
759
- this.resetMIDIOut();
760
- this.MIDIout = output;
761
- this._sendMessage(SpessaSynthSequencerMessageType.changeMIDIMessageSending, output !== undefined);
762
- this.currentTime -= 0.1;
763
- }
764
-
765
- /**
766
- * Pauses the playback
767
- */
768
- pause()
769
- {
770
- if (this.paused)
771
- {
772
- SpessaSynthWarn("Already paused");
773
- return;
774
- }
775
- this.pausedTime = this.currentTime;
776
- this._sendMessage(SpessaSynthSequencerMessageType.pause);
777
- }
778
-
779
- unpause()
780
- {
781
- this.pausedTime = undefined;
782
- this.isFinished = false;
783
- }
784
-
785
- /**
786
- * Starts the playback
787
- * @param resetTime {boolean} If true, time is set to 0 s
788
- */
789
- play(resetTime = false)
790
- {
791
- if (this.isFinished)
792
- {
793
- resetTime = true;
794
- }
795
- this._recalculateStartTime(this.pausedTime || 0);
796
- this.unpause();
797
- this._sendMessage(SpessaSynthSequencerMessageType.play, resetTime);
798
- }
799
-
800
- /**
801
- * Stops the playback
802
- */
803
- stop()
804
- {
805
- this._sendMessage(SpessaSynthSequencerMessageType.stop);
806
- }
807
- }