spessasynth_lib 3.24.13 → 3.24.16

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 (61) hide show
  1. package/midi_parser/basic_midi.js +457 -68
  2. package/midi_parser/midi_loader.js +18 -503
  3. package/midi_parser/midi_message.js +18 -5
  4. package/midi_parser/midi_sequence.js +2 -2
  5. package/package.json +1 -1
  6. package/sequencer/worklet_sequencer/process_event.js +1 -6
  7. package/synthetizer/synthetizer.js +13 -7
  8. package/synthetizer/worklet_processor.min.js +12 -12
  9. package/synthetizer/worklet_system/README.md +2 -2
  10. package/synthetizer/worklet_system/main_processor.js +106 -95
  11. package/synthetizer/worklet_system/message_protocol/handle_message.js +22 -17
  12. package/synthetizer/worklet_system/message_protocol/worklet_message.js +2 -1
  13. package/synthetizer/worklet_system/snapshot/apply_synthesizer_snapshot.js +14 -0
  14. package/synthetizer/worklet_system/snapshot/channel_snapshot.js +166 -0
  15. package/synthetizer/worklet_system/snapshot/send_synthesizer_snapshot.js +14 -0
  16. package/synthetizer/worklet_system/snapshot/synthesizer_snapshot.js +121 -0
  17. package/synthetizer/worklet_system/worklet_methods/controller_control/controller_change.js +196 -0
  18. package/synthetizer/worklet_system/worklet_methods/controller_control/master_parameters.js +34 -0
  19. package/synthetizer/worklet_system/worklet_methods/{reset_controllers.js → controller_control/reset_controllers.js} +33 -39
  20. package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +26 -0
  21. package/synthetizer/worklet_system/worklet_methods/{data_entry.js → data_entry/data_entry_coarse.js} +38 -105
  22. package/synthetizer/worklet_system/worklet_methods/data_entry/data_entry_fine.js +64 -0
  23. package/synthetizer/worklet_system/worklet_methods/mute_channel.js +17 -0
  24. package/synthetizer/worklet_system/worklet_methods/note_on.js +36 -34
  25. package/synthetizer/worklet_system/worklet_methods/program_change.js +49 -0
  26. package/synthetizer/worklet_system/worklet_methods/{voice_control.js → render_voice.js} +37 -120
  27. package/synthetizer/worklet_system/worklet_methods/soundfont_management/clear_sound_font.js +35 -0
  28. package/synthetizer/worklet_system/worklet_methods/soundfont_management/get_preset.js +20 -0
  29. package/synthetizer/worklet_system/worklet_methods/soundfont_management/reload_sound_font.js +43 -0
  30. package/synthetizer/worklet_system/worklet_methods/soundfont_management/send_preset_list.js +31 -0
  31. package/synthetizer/worklet_system/worklet_methods/soundfont_management/set_embedded_sound_font.js +21 -0
  32. package/synthetizer/worklet_system/worklet_methods/stopping_notes/kill_note.js +20 -0
  33. package/synthetizer/worklet_system/worklet_methods/stopping_notes/note_off.js +55 -0
  34. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_channels.js +16 -0
  35. package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +30 -0
  36. package/synthetizer/worklet_system/worklet_methods/stopping_notes/voice_killing.js +63 -0
  37. package/synthetizer/worklet_system/worklet_methods/system_exclusive.js +31 -30
  38. package/synthetizer/worklet_system/worklet_methods/tuning_control/channel_pressure.js +24 -0
  39. package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +33 -0
  40. package/synthetizer/worklet_system/worklet_methods/tuning_control/poly_pressure.js +31 -0
  41. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_master_tuning.js +15 -0
  42. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_modulation_depth.js +27 -0
  43. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_octave_tuning.js +15 -0
  44. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning.js +24 -0
  45. package/synthetizer/worklet_system/worklet_methods/tuning_control/set_tuning_semitones.js +19 -0
  46. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_all_channels.js +15 -0
  47. package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +31 -0
  48. package/synthetizer/worklet_system/worklet_utilities/controller_tables.js +10 -1
  49. package/synthetizer/worklet_system/worklet_utilities/lfo.js +2 -1
  50. package/synthetizer/worklet_system/worklet_utilities/modulation_envelope.js +4 -4
  51. package/synthetizer/worklet_system/worklet_utilities/modulator_curves.js +4 -5
  52. package/synthetizer/worklet_system/worklet_utilities/stereo_panner.js +18 -18
  53. package/synthetizer/worklet_system/worklet_utilities/wavetable_oscillator.js +210 -206
  54. package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +354 -108
  55. package/synthetizer/worklet_system/worklet_utilities/worklet_voice.js +22 -9
  56. package/synthetizer/worklet_system/snapshot/snapshot.js +0 -311
  57. package/synthetizer/worklet_system/worklet_methods/controller_control.js +0 -260
  58. package/synthetizer/worklet_system/worklet_methods/note_off.js +0 -119
  59. package/synthetizer/worklet_system/worklet_methods/program_control.js +0 -282
  60. package/synthetizer/worklet_system/worklet_methods/tuning_control.js +0 -233
  61. package/synthetizer/worklet_system/worklet_methods/vibrato_control.js +0 -29
@@ -1,14 +1,14 @@
1
- import { dataBytesAmount, getChannel, messageTypes, MidiMessage } from "./midi_message.js";
1
+ import { dataBytesAmount, getChannel, MidiMessage } from "./midi_message.js";
2
2
  import { IndexedByteArray } from "../utils/indexed_array.js";
3
- import { consoleColors, formatTitle, sanitizeKarLyrics } from "../utils/other.js";
3
+ import { consoleColors } from "../utils/other.js";
4
4
  import { SpessaSynthGroupCollapsed, SpessaSynthGroupEnd, SpessaSynthInfo, SpessaSynthWarn } from "../utils/loggin.js";
5
5
  import { readRIFFChunk } from "../soundfont/basic_soundfont/riff_chunk.js";
6
6
  import { readVariableLengthQuantity } from "../utils/byte_functions/variable_length_quantity.js";
7
7
  import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
8
- import { getStringBytes, readBytesAsString } from "../utils/byte_functions/string.js";
8
+ import { readBytesAsString } from "../utils/byte_functions/string.js";
9
9
  import { readLittleEndian } from "../utils/byte_functions/little_endian.js";
10
10
  import { RMIDINFOChunks } from "./rmidi_writer.js";
11
- import { BasicMIDI, MIDIticksToSeconds } from "./basic_midi.js";
11
+ import { BasicMIDI } from "./basic_midi.js";
12
12
 
13
13
  /**
14
14
  * midi_loader.js
@@ -32,22 +32,11 @@ class MIDI extends BasicMIDI
32
32
  {
33
33
  super();
34
34
  SpessaSynthGroupCollapsed(`%cParsing MIDI File...`, consoleColors.info);
35
+ this.fileName = fileName;
35
36
  const binaryData = new IndexedByteArray(arrayBuffer);
36
37
  let fileByteArray;
37
38
 
38
39
  // check for rmid
39
- let copyrightDetected = false;
40
-
41
- let nameDetected = false;
42
-
43
- let DLSRMID = false;
44
-
45
- /**
46
- * Will be joined with "\n" to form the final string
47
- * @type {string[]}
48
- */
49
- let copyrightComponents = [];
50
-
51
40
  const initialString = readBytesAsString(binaryData, 4);
52
41
  binaryData.currentIndex -= 4;
53
42
  if (initialString === "RIFF")
@@ -70,7 +59,7 @@ class MIDI extends BasicMIDI
70
59
  // this is a rmid, load the midi into an array for parsing
71
60
  fileByteArray = riff.chunkData;
72
61
 
73
- // keep loading chunks until we get sfbk
62
+ // keep loading chunks until we get the "SFBK" header
74
63
  while (binaryData.currentIndex <= binaryData.length)
75
64
  {
76
65
  const startIndex = binaryData.currentIndex;
@@ -90,7 +79,7 @@ class MIDI extends BasicMIDI
90
79
  if (type === "dls ")
91
80
  {
92
81
  // Assume bank offset of 0 by default. If we find any bank selects, then the offset is 1.
93
- DLSRMID = true;
82
+ this.isDLSRMIDI = true;
94
83
  }
95
84
  }
96
85
  else if (currentChunk.header === "LIST")
@@ -108,7 +97,6 @@ class MIDI extends BasicMIDI
108
97
  if (this.RMIDInfo["ICOP"])
109
98
  {
110
99
  // special case, overwrites the copyright components array
111
- copyrightDetected = true;
112
100
  this.copyright = readBytesAsString(
113
101
  this.RMIDInfo["ICOP"],
114
102
  this.RMIDInfo["ICOP"].length,
@@ -119,20 +107,20 @@ class MIDI extends BasicMIDI
119
107
  if (this.RMIDInfo["INAM"])
120
108
  {
121
109
  this.rawMidiName = this.RMIDInfo[RMIDINFOChunks.name];
110
+ // noinspection JSCheckFunctionSignatures
122
111
  this.midiName = readBytesAsString(
123
112
  this.rawMidiName,
124
113
  this.rawMidiName.length,
125
114
  undefined,
126
115
  false
127
116
  ).replaceAll("\n", " ");
128
- nameDetected = true;
129
117
  }
130
118
  // these can be used interchangeably
131
119
  if (this.RMIDInfo["IALB"] && !this.RMIDInfo["IPRD"])
132
120
  {
133
121
  this.RMIDInfo["IPRD"] = this.RMIDInfo["IALB"];
134
122
  }
135
- if (this.RMIDInfo["PRD"] && !this.RMIDInfo["IALB"])
123
+ if (this.RMIDInfo["IPRD"] && !this.RMIDInfo["IALB"])
136
124
  {
137
125
  this.RMIDInfo["IALB"] = this.RMIDInfo["IPRD"];
138
126
  }
@@ -145,7 +133,7 @@ class MIDI extends BasicMIDI
145
133
  }
146
134
  }
147
135
 
148
- if (DLSRMID)
136
+ if (this.isDLSRMIDI)
149
137
  {
150
138
  // Assume bank offset of 0 by default. If we find any bank selects, then the offset is 1.
151
139
  this.bankOffset = 0;
@@ -180,64 +168,7 @@ class MIDI extends BasicMIDI
180
168
  this.tracksAmount = readBytesAsUintBigEndian(headerChunk.data, 2);
181
169
  // time division
182
170
  this.timeDivision = readBytesAsUintBigEndian(headerChunk.data, 2);
183
-
184
- /**
185
- * The MIDI's key range
186
- * @type {{min: number, max: number}}
187
- */
188
- this.keyRange = { min: 127, max: 0 };
189
-
190
- /**
191
- * Contains the lyrics as binary chunks
192
- * @type {Uint8Array[]}
193
- */
194
- this.lyrics = [];
195
-
196
- /**
197
- * Contains all the tempo changes in the file. (Ordered from last to first)
198
- * @type {{
199
- * ticks: number,
200
- * tempo: number
201
- * }[]}
202
- */
203
- this.tempoChanges = [{ ticks: 0, tempo: 120 }];
204
-
205
- let loopStart = null;
206
- let loopEnd = null;
207
-
208
- /**
209
- * For karaoke files, text events starting with @T are considered titles,
210
- * usually the first one is the title, and the latter is things such as "sequenced by" etc.
211
- * @type {boolean}
212
- */
213
- let karaokeHasTitle = false;
214
-
215
- this.lastVoiceEventTick = 0;
216
-
217
- /**
218
- * Midi port numbers for each one of the tracks
219
- * @type {number[]}
220
- */
221
- this.midiPorts = [];
222
-
223
- let portOffset = 0;
224
- /**
225
- * Channel offsets for each port, using the SpessaSynth method
226
- * @type {number[]}
227
- */
228
- this.midiPortChannelOffsets = [];
229
-
230
- /**
231
- * All channels that each track uses. Note: these channels range from 0 to 15, excluding the port offsets!
232
- * @type {Set<number>[]}
233
- */
234
- this.usedChannelsOnTrack = [];
235
-
236
- /**
237
- * Read all the tracks
238
- * @type {MidiMessage[][]}
239
- */
240
- this.tracks = [];
171
+ // read all the tracks
241
172
  for (let i = 0; i < this.tracksAmount; i++)
242
173
  {
243
174
  /**
@@ -245,8 +176,6 @@ class MIDI extends BasicMIDI
245
176
  */
246
177
  const track = [];
247
178
  const trackChunk = this.readMIDIChunk(fileByteArray);
248
- const usedChannels = new Set();
249
- this.midiPorts.push(-1);
250
179
 
251
180
  if (trackChunk.type !== "MTrk")
252
181
  {
@@ -254,7 +183,6 @@ class MIDI extends BasicMIDI
254
183
  throw new SyntaxError(`Invalid track header! Expected "MTrk" got "${trackChunk.type}"`);
255
184
  }
256
185
 
257
- let trackHasVoiceMessages = false;
258
186
 
259
187
  /**
260
188
  * MIDI running byte
@@ -322,19 +250,7 @@ class MIDI extends BasicMIDI
322
250
  default:
323
251
  // voice message
324
252
  // gets the midi message length
325
- if (totalTicks > this.lastVoiceEventTick)
326
- {
327
- this.lastVoiceEventTick = totalTicks;
328
- }
329
253
  eventDataLength = dataBytesAmount[statusByte >> 4];
330
- if ((statusByte & 0xF0) === messageTypes.noteOn)
331
- {
332
- usedChannels.add(statusByteChannel);
333
- const note = trackChunk.data[trackChunk.data.currentIndex];
334
- this.keyRange.min = Math.min(this.keyRange.min, note);
335
- this.keyRange.max = Math.max(this.keyRange.max, note);
336
- }
337
-
338
254
  // save the status byte
339
255
  runningByte = statusByte;
340
256
  break;
@@ -342,211 +258,16 @@ class MIDI extends BasicMIDI
342
258
 
343
259
  // put the event data into the array
344
260
  const eventData = new IndexedByteArray(eventDataLength);
345
- const messageData = trackChunk.data.slice(
261
+ eventData.set(trackChunk.data.slice(
346
262
  trackChunk.data.currentIndex,
347
263
  trackChunk.data.currentIndex + eventDataLength
348
- );
264
+ ), 0);
265
+ const event = new MidiMessage(totalTicks, statusByte, eventData);
266
+ track.push(event);
267
+ // advance the track chunk
349
268
  trackChunk.data.currentIndex += eventDataLength;
350
- eventData.set(messageData, 0);
351
-
352
- const message = new MidiMessage(totalTicks, statusByte, eventData);
353
- track.push(message);
354
-
355
- switch (statusByteChannel)
356
- {
357
- case -2:
358
- // since this is a meta-message
359
- const eventText = readBytesAsString(eventData, eventData.length);
360
- switch (statusByte)
361
- {
362
- case messageTypes.setTempo:
363
- // add the tempo change
364
- this.tempoChanges.push({
365
- ticks: totalTicks,
366
- tempo: 60000000 / readBytesAsUintBigEndian(messageData, 3)
367
- });
368
- break;
369
-
370
- case messageTypes.marker:
371
- // check for loop markers
372
- const text = eventText.trim().toLowerCase();
373
- switch (text)
374
- {
375
- default:
376
- break;
377
-
378
- case "start":
379
- case "loopstart":
380
- loopStart = totalTicks;
381
- break;
382
-
383
- case "loopend":
384
- loopEnd = totalTicks;
385
- }
386
- eventData.currentIndex = 0;
387
- break;
388
-
389
- case messageTypes.midiPort:
390
- const port = eventData[0];
391
- this.midiPorts[i] = port;
392
- if (this.midiPortChannelOffsets[port] === undefined)
393
- {
394
- this.midiPortChannelOffsets[port] = portOffset;
395
- portOffset += 16;
396
- }
397
- break;
398
-
399
- case messageTypes.copyright:
400
- if (!copyrightDetected)
401
- {
402
-
403
- eventData.currentIndex = 0;
404
- copyrightComponents.push(readBytesAsString(
405
- eventData,
406
- eventData.length,
407
- undefined,
408
- false
409
- ));
410
- }
411
- break;
412
-
413
- case messageTypes.lyric:
414
-
415
- // note here: .kar files sometimes just use...
416
- // lyrics instead of text because why not (of course)
417
- // perform the same check for @KMIDI KARAOKE FILE
418
- if (eventText.trim().startsWith("@KMIDI KARAOKE FILE"))
419
- {
420
- this.isKaraokeFile = true;
421
- SpessaSynthInfo("%cKaraoke MIDI detected!", consoleColors.recognized);
422
- }
423
-
424
- if (this.isKaraokeFile)
425
- {
426
- // replace the type of the message with text
427
- message.messageStatusByte = messageTypes.text;
428
- statusByte = messageTypes.text;
429
- }
430
- else
431
- {
432
- // add lyrics like a regular midi file
433
- this.lyrics.push(eventData);
434
- this.lyricsTicks.push(totalTicks);
435
- break;
436
- }
437
-
438
- // kar: treat the same as text
439
- // fallthrough
440
- case messageTypes.text:
441
- // possibly Soft Karaoke MIDI file
442
- // it has a text event at the start of the file
443
- // "@KMIDI KARAOKE FILE"
444
- const checkedText = eventText.trim();
445
- if (checkedText.startsWith("@KMIDI KARAOKE FILE"))
446
- {
447
- this.isKaraokeFile = true;
448
-
449
- SpessaSynthInfo("%cKaraoke MIDI detected!", consoleColors.recognized);
450
- }
451
- else if (this.isKaraokeFile)
452
- {
453
- // check for @T (title)
454
- // or @A because it is a title too sometimes?
455
- // IDK it's strange
456
- if (checkedText.startsWith("@T") || checkedText.startsWith("@A"))
457
- {
458
- if (!karaokeHasTitle)
459
- {
460
- this.midiName = checkedText.substring(2).trim();
461
- karaokeHasTitle = true;
462
- nameDetected = true;
463
- // encode to rawMidiName
464
- this.rawMidiName = getStringBytes(this.midiName);
465
- }
466
- else
467
- {
468
- // append to copyright
469
- copyrightComponents.push(checkedText.substring(2).trim());
470
- }
471
- }
472
- else if (checkedText[0] !== "@")
473
- {
474
- // non @: the lyrics
475
- this.lyrics.push(sanitizeKarLyrics(eventData));
476
- this.lyricsTicks.push(totalTicks);
477
- }
478
- }
479
- break;
480
- }
481
- break;
482
-
483
- case -3:
484
- // since this is a sysex message, do nothing
485
- break;
486
-
487
-
488
- default:
489
- // since this is a voice message
490
- // check for loop (CC 2/4)
491
- trackHasVoiceMessages = true;
492
- // voice messages are 7-bit always
493
- for (let j = 0; j < eventData.length; j++)
494
- {
495
- eventData[j] = Math.min(127, eventData[j]);
496
- }
497
- if ((statusByte & 0xF0) === messageTypes.controllerChange)
498
- {
499
- switch (eventData[0])
500
- {
501
- case 2:
502
- case 116:
503
- loopStart = totalTicks;
504
- break;
505
-
506
- case 4:
507
- case 117:
508
- if (loopEnd === null)
509
- {
510
- loopEnd = totalTicks;
511
- }
512
- else
513
- {
514
- // this controller has occured more than once;
515
- // this means
516
- // that it doesn't indicate the loop
517
- loopEnd = 0;
518
- }
519
- break;
520
-
521
- case 0:
522
- // check RMID
523
- if (DLSRMID && eventData[1] !== 0 && eventData[1] !== 127)
524
- {
525
- SpessaSynthInfo(
526
- "%cDLS RMIDI with offset 1 detected!",
527
- consoleColors.recognized
528
- );
529
- this.bankOffset = 1;
530
- }
531
- }
532
- }
533
- }
534
269
  }
535
270
  this.tracks.push(track);
536
- this.usedChannelsOnTrack.push(usedChannels);
537
-
538
- // If the track has no voice messages, its "track name" event (if it has any)
539
- // is some metadata. Add it to copyright
540
- if (!trackHasVoiceMessages)
541
- {
542
- const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
543
- if (trackName)
544
- {
545
- trackName.messageData.currentIndex = 0;
546
- const name = readBytesAsString(trackName.messageData, trackName.messageData.length);
547
- copyrightComponents.push(name);
548
- }
549
- }
550
271
 
551
272
  SpessaSynthInfo(
552
273
  `%cParsed %c${this.tracks.length}%c / %c${this.tracksAmount}`,
@@ -561,213 +282,8 @@ class MIDI extends BasicMIDI
561
282
  `%cAll tracks parsed correctly!`,
562
283
  consoleColors.recognized
563
284
  );
564
-
565
- SpessaSynthGroupCollapsed(
566
- `%cCorrecting loops, ports and detecting notes...`,
567
- consoleColors.info
568
- );
569
-
570
- const firstNoteOns = [];
571
- for (const t of this.tracks)
572
- {
573
- const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
574
- if (firstNoteOn)
575
- {
576
- firstNoteOns.push(firstNoteOn.ticks);
577
- }
578
- }
579
- this.firstNoteOn = Math.min(...firstNoteOns);
580
-
581
- SpessaSynthInfo(
582
- `%cFirst note-on detected at: %c${this.firstNoteOn}%c ticks!`,
583
- consoleColors.info,
584
- consoleColors.recognized,
585
- consoleColors.info
586
- );
587
-
588
-
589
- if (loopStart !== null && loopEnd === null)
590
- {
591
- // not a loop
592
- loopStart = this.firstNoteOn;
593
- loopEnd = this.lastVoiceEventTick;
594
- }
595
- else
596
- {
597
- if (loopStart === null)
598
- {
599
- loopStart = this.firstNoteOn;
600
- }
601
-
602
- if (loopEnd === null || loopEnd === 0)
603
- {
604
- loopEnd = this.lastVoiceEventTick;
605
- }
606
- }
607
-
608
- /**
609
- *
610
- * @type {{start: number, end: number}}
611
- */
612
- this.loop = { start: loopStart, end: loopEnd };
613
-
614
- SpessaSynthInfo(
615
- `%cLoop points: start: %c${this.loop.start}%c end: %c${this.loop.end}`,
616
- consoleColors.info,
617
- consoleColors.recognized,
618
- consoleColors.info,
619
- consoleColors.recognized
620
- );
621
-
622
- // fix midi ports:
623
- // midi tracks without ports will have a value of -1
624
- // if all ports have a value of -1, set it to 0,
625
- // otherwise take the first midi port and replace all -1 with it,
626
- // why would we do this?
627
- // some midis (for some reason) specify all channels to port 1 or else,
628
- // but leave the conductor track with no port pref.
629
- // this spessasynth to reserve the first 16 channels for the conductor track
630
- // (which doesn't play anything) and use the additional 16 for the actual ports.
631
- let defaultPort = 0;
632
- for (let port of this.midiPorts)
633
- {
634
- if (port !== -1)
635
- {
636
- defaultPort = port;
637
- break;
638
- }
639
- }
640
- this.midiPorts = this.midiPorts.map(port => port === -1 ? defaultPort : port);
641
- // add fake port if empty
642
- if (this.midiPortChannelOffsets.length === 0)
643
- {
644
- this.midiPortChannelOffsets = [0];
645
- }
646
- if (this.midiPortChannelOffsets.length < 2)
647
- {
648
- SpessaSynthInfo(`%cNo additional MIDI Ports detected.`, consoleColors.info);
649
- }
650
- else
651
- {
652
- SpessaSynthInfo(`%cMIDI Ports detected!`, consoleColors.recognized);
653
- }
654
-
655
- // midi name
656
- if (!nameDetected)
657
- {
658
- if (this.tracks.length > 1)
659
- {
660
- // if more than 1 track and the first track has no notes, just find the first trackName in the first track
661
- if (
662
- this.tracks[0].find(
663
- message => message.messageStatusByte >= messageTypes.noteOn
664
- &&
665
- message.messageStatusByte < messageTypes.polyPressure
666
- ) === undefined
667
- )
668
- {
669
-
670
- let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
671
- if (name)
672
- {
673
- this.rawMidiName = name.messageData;
674
- name.messageData.currentIndex = 0;
675
- this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
676
- }
677
- }
678
- }
679
- else
680
- {
681
- // if only 1 track, find the first "track name" event
682
- let name = this.tracks[0].find(message => message.messageStatusByte === messageTypes.trackName);
683
- if (name)
684
- {
685
- this.rawMidiName = name.messageData;
686
- name.messageData.currentIndex = 0;
687
- this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
688
- }
689
- }
690
- }
691
-
692
- if (!copyrightDetected)
693
- {
694
- this.copyright = copyrightComponents
695
- // trim and group newlines into one
696
- .map(c => c.trim().replace(/(\r?\n)+/g, "\n"))
697
- // remove empty strings
698
- .filter(c => c.length > 0)
699
- // join with newlines
700
- .join("\n") || "";
701
- }
702
-
703
- this.fileName = fileName;
704
- this.midiName = this.midiName.trim();
705
- this.midiNameUsesFileName = false;
706
- // if midiName is "", use the file name
707
- if (this.midiName.length === 0)
708
- {
709
- SpessaSynthInfo(
710
- `%cNo name detected. Using the alt name!`,
711
- consoleColors.info
712
- );
713
- this.midiName = formatTitle(fileName);
714
- this.midiNameUsesFileName = true;
715
- // encode it too
716
- this.rawMidiName = new Uint8Array(this.midiName.length);
717
- for (let i = 0; i < this.midiName.length; i++)
718
- {
719
- this.rawMidiName[i] = this.midiName.charCodeAt(i);
720
- }
721
- }
722
- else
723
- {
724
- SpessaSynthInfo(
725
- `%cMIDI Name detected! %c"${this.midiName}"`,
726
- consoleColors.info,
727
- consoleColors.recognized
728
- );
729
- }
730
-
731
- // lyrics fix:
732
- // sometimes, all lyrics events lack spaces at the start or end of the lyric
733
- // then, and only then, add space at the end of each lyric
734
- // space ASCII is 32
735
- let lacksSpaces = true;
736
- for (const lyric of this.lyrics)
737
- {
738
- if (lyric[0] === 32 || lyric[lyric.length - 1] === 32)
739
- {
740
- lacksSpaces = false;
741
- break;
742
- }
743
- }
744
-
745
- if (lacksSpaces)
746
- {
747
- this.lyrics = this.lyrics.map(lyric =>
748
- {
749
- // One exception: hyphens at the end. Don't add a space to them
750
- if (lyric[lyric.length - 1] === 45)
751
- {
752
- return lyric;
753
- }
754
- const withSpaces = new Uint8Array(lyric.length + 1);
755
- withSpaces.set(lyric, 0);
756
- withSpaces[lyric.length] = 32;
757
- return withSpaces;
758
- });
759
- }
760
-
761
-
762
- // reverse the tempo changes
763
- this.tempoChanges.reverse();
764
-
765
- /**
766
- * The total playback time, in seconds
767
- * @type {number}
768
- */
769
- this.duration = MIDIticksToSeconds(this.lastVoiceEventTick, this);
770
-
285
+ // parse the events
286
+ this._parseInternal();
771
287
  SpessaSynthGroupEnd();
772
288
  SpessaSynthInfo(
773
289
  `%cMIDI file parsed. Total tick time: %c${this.lastVoiceEventTick}%c, total seconds time: %c${this.duration}`,
@@ -776,7 +292,6 @@ class MIDI extends BasicMIDI
776
292
  consoleColors.info,
777
293
  consoleColors.recognized
778
294
  );
779
- SpessaSynthGroupEnd();
780
295
  }
781
296
 
782
297
  /**
@@ -7,6 +7,24 @@ import { IndexedByteArray } from "../utils/indexed_array.js";
7
7
 
8
8
  export class MidiMessage
9
9
  {
10
+ /**
11
+ * Absolute number of MIDI ticks from the start of the track.
12
+ * @type {number}
13
+ */
14
+ ticks;
15
+
16
+ /**
17
+ * The MIDI message status byte. Note that for meta events, it is the second byte. (not 0xFF)
18
+ * @type {number}
19
+ */
20
+ messageStatusByte;
21
+
22
+ /**
23
+ * Message's binary data
24
+ * @type {IndexedByteArray}
25
+ */
26
+ messageData;
27
+
10
28
  /**
11
29
  * @param ticks {number}
12
30
  * @param byte {number} the message status byte
@@ -14,13 +32,8 @@ export class MidiMessage
14
32
  */
15
33
  constructor(ticks, byte, data)
16
34
  {
17
- // absolute ticks from the start
18
35
  this.ticks = ticks;
19
- // message status byte (for meta it's the second byte)
20
36
  this.messageStatusByte = byte;
21
- /**
22
- * @type {IndexedByteArray}
23
- */
24
37
  this.messageData = data;
25
38
  }
26
39
  }
@@ -124,9 +124,9 @@ export class MIDISequenceData
124
124
  format = 0;
125
125
 
126
126
  /**
127
- * The RMID (Resource Interchangeable MIDI) info data, if the file is RMID formatted.
127
+ * The RMID (Resource-Interchangeable MIDI) info data, if the file is RMID formatted.
128
128
  * Otherwise, this field is undefined.
129
- * Chunk type (e.g. "INAM"): Chunk data as binary array.
129
+ * Chunk type (e.g. "INAM"): Chunk data as a binary array.
130
130
  * @type {Object<string, IndexedByteArray>}
131
131
  */
132
132
  RMIDInfo = {};