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