spessasynth_lib 3.23.14 → 3.24.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.
@@ -3,62 +3,68 @@
3
3
  * purpose: manages the synthesizer's event system, calling assinged functions when synthesizer requests dispatching the event
4
4
  */
5
5
  /**
6
- *
7
- * @typedef {{
8
- * midiNote: number,
9
- * channel: number,
10
- * velocity: number
11
- * }} NoteOnCallback
12
- *
13
- * @typedef {{
14
- * midiNote: number,
15
- * channel: number
16
- * }} NoteOffCallback
17
- *
18
- * @typedef {{
19
- * channel: number,
20
- * isDrumChannel: boolean
21
- * }} DrumChangeCallback
22
- *
23
- * @typedef {{
24
- * channel: number,
25
- * program: number,
26
- * bank: number,
27
- * userCalled: boolean
28
- * }} ProgramChangeCallback
29
- *
30
- * @typedef {{
31
- * channel: number,
32
- * controllerNumber: number,
33
- * controllerValue: number
34
- * }} ControllerChangeCallback
35
- *
36
- * @typedef {{
37
- * channel:number,
38
- * isMuted: boolean
39
- * }} MuteChannelCallback
40
- *
41
- * @typedef {{
42
- * presetName: string,
43
- * bank: number,
44
- * program: number
45
- * }[]} PresetListChangeCallback
46
- *
47
- *
48
- * @typedef {{
49
- * channel: number,
50
- * MSB: number,
51
- * LSB: number
52
- * }} PitchWheelCallback
53
- *
54
- * @typedef {{
55
- * channel: number,
56
- * pressure: number
57
- * }} ChannelPressureCallback
58
- *
59
- * @typedef {string} SoundfontErrorCallback
60
- *
61
- *
6
+ * @typedef {Object} NoteOnCallback
7
+ * @property {number} midiNote - The MIDI note number.
8
+ * @property {number} channel - The MIDI channel number.
9
+ * @property {number} velocity - The velocity of the note.
10
+ */
11
+ /**
12
+ * @typedef {Object} NoteOffCallback
13
+ * @property {number} midiNote - The MIDI note number.
14
+ * @property {number} channel - The MIDI channel number.
15
+ */
16
+ /**
17
+ * @typedef {Object} DrumChangeCallback
18
+ * @property {number} channel - The MIDI channel number.
19
+ * @property {boolean} isDrumChannel - Indicates if the channel is a drum channel.
20
+ */
21
+ /**
22
+ * @typedef {Object} ProgramChangeCallback
23
+ * @property {number} channel - The MIDI channel number.
24
+ * @property {number} program - The program number.
25
+ * @property {number} bank - The bank number.
26
+ * @property {boolean} userCalled - Indicates if the change was user-initiated.
27
+ */
28
+ /**
29
+ * @typedef {Object} ControllerChangeCallback
30
+ * @property {number} channel - The MIDI channel number.
31
+ * @property {number} controllerNumber - The controller number.
32
+ * @property {number} controllerValue - The value of the controller.
33
+ */
34
+ /**
35
+ * @typedef {Object} MuteChannelCallback
36
+ * @property {number} channel - The MIDI channel number.
37
+ * @property {boolean} isMuted - Indicates if the channel is muted.
38
+ */
39
+ /**
40
+ * @typedef {Object} PresetListChangeCallbackSingle
41
+ * @property {string} presetName - The name of the preset.
42
+ * @property {number} bank - The bank number.
43
+ * @property {number} program - The program number.
44
+ */
45
+ /**
46
+ * @typedef {PresetListChangeCallbackSingle[]} PresetListChangeCallback - A list of preset objects.
47
+ */
48
+ /**
49
+ * @typedef {Object} SynthDisplayCallback
50
+ * @property {Uint8Array} displayData - The data to display.
51
+ * @property {SynthDisplayType} displayType - The type of display.
52
+ */
53
+ /**
54
+ * @typedef {Object} PitchWheelCallback
55
+ * @property {number} channel - The MIDI channel number.
56
+ * @property {number} MSB - The most significant byte of the pitch wheel value.
57
+ * @property {number} LSB - The least significant byte of the pitch wheel value.
58
+ */
59
+ /**
60
+ * @typedef {Object} ChannelPressureCallback
61
+ * @property {number} channel - The MIDI channel number.
62
+ * @property {number} pressure - The pressure value.
63
+ */
64
+ /**
65
+ * @typedef {Error} SoundfontErrorCallback - The error message for soundfont errors.
66
+ */
67
+ /**
62
68
  * @typedef {
63
69
  * NoteOnCallback |
64
70
  * NoteOffCallback |
@@ -70,6 +76,7 @@
70
76
  * PitchWheelCallback |
71
77
  * SoundfontErrorCallback |
72
78
  * ChannelPressureCallback |
79
+ * SynthDisplayCallback |
73
80
  * undefined
74
81
  * } EventCallbackData
75
82
  */
@@ -88,7 +95,8 @@
88
95
  * "mutechannel"|
89
96
  * "presetlistchange"|
90
97
  * "allcontrollerreset"|
91
- * "soundfonterror"} EventTypes
98
+ * "soundfonterror"|
99
+ * "synthdisplay"} EventTypes
92
100
  */
93
101
  export class EventHandler {
94
102
  /**
@@ -122,45 +130,134 @@ export class EventHandler {
122
130
  callEvent(name: EventTypes, eventData: EventCallbackData): void;
123
131
  }
124
132
  export type NoteOnCallback = {
133
+ /**
134
+ * - The MIDI note number.
135
+ */
125
136
  midiNote: number;
137
+ /**
138
+ * - The MIDI channel number.
139
+ */
126
140
  channel: number;
141
+ /**
142
+ * - The velocity of the note.
143
+ */
127
144
  velocity: number;
128
145
  };
129
146
  export type NoteOffCallback = {
147
+ /**
148
+ * - The MIDI note number.
149
+ */
130
150
  midiNote: number;
151
+ /**
152
+ * - The MIDI channel number.
153
+ */
131
154
  channel: number;
132
155
  };
133
156
  export type DrumChangeCallback = {
157
+ /**
158
+ * - The MIDI channel number.
159
+ */
134
160
  channel: number;
161
+ /**
162
+ * - Indicates if the channel is a drum channel.
163
+ */
135
164
  isDrumChannel: boolean;
136
165
  };
137
166
  export type ProgramChangeCallback = {
167
+ /**
168
+ * - The MIDI channel number.
169
+ */
138
170
  channel: number;
171
+ /**
172
+ * - The program number.
173
+ */
139
174
  program: number;
175
+ /**
176
+ * - The bank number.
177
+ */
140
178
  bank: number;
179
+ /**
180
+ * - Indicates if the change was user-initiated.
181
+ */
141
182
  userCalled: boolean;
142
183
  };
143
184
  export type ControllerChangeCallback = {
185
+ /**
186
+ * - The MIDI channel number.
187
+ */
144
188
  channel: number;
189
+ /**
190
+ * - The controller number.
191
+ */
145
192
  controllerNumber: number;
193
+ /**
194
+ * - The value of the controller.
195
+ */
146
196
  controllerValue: number;
147
197
  };
148
198
  export type MuteChannelCallback = {
199
+ /**
200
+ * - The MIDI channel number.
201
+ */
149
202
  channel: number;
203
+ /**
204
+ * - Indicates if the channel is muted.
205
+ */
150
206
  isMuted: boolean;
151
207
  };
152
- export type PresetListChangeCallback = {
208
+ export type PresetListChangeCallbackSingle = {
209
+ /**
210
+ * - The name of the preset.
211
+ */
153
212
  presetName: string;
213
+ /**
214
+ * - The bank number.
215
+ */
154
216
  bank: number;
217
+ /**
218
+ * - The program number.
219
+ */
155
220
  program: number;
156
- }[];
221
+ };
222
+ /**
223
+ * - A list of preset objects.
224
+ */
225
+ export type PresetListChangeCallback = PresetListChangeCallbackSingle[];
226
+ export type SynthDisplayCallback = {
227
+ /**
228
+ * - The data to display.
229
+ */
230
+ displayData: Uint8Array;
231
+ /**
232
+ * - The type of display.
233
+ */
234
+ displayType: SynthDisplayType;
235
+ };
157
236
  export type PitchWheelCallback = {
237
+ /**
238
+ * - The MIDI channel number.
239
+ */
158
240
  channel: number;
241
+ /**
242
+ * - The most significant byte of the pitch wheel value.
243
+ */
159
244
  MSB: number;
245
+ /**
246
+ * - The least significant byte of the pitch wheel value.
247
+ */
160
248
  LSB: number;
161
249
  };
162
250
  export type ChannelPressureCallback = {
251
+ /**
252
+ * - The MIDI channel number.
253
+ */
163
254
  channel: number;
255
+ /**
256
+ * - The pressure value.
257
+ */
164
258
  pressure: number;
165
259
  };
166
- export type SoundfontErrorCallback = string;
260
+ /**
261
+ * - The error message for soundfont errors.
262
+ */
263
+ export type SoundfontErrorCallback = Error;
package/README.md CHANGED
@@ -82,6 +82,7 @@ document.getElementById("button").onclick = async () =>
82
82
  - **Easy MIDI editing:** Use [helper functions](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#modifymidi) to modify the song to your needs!
83
83
  - **Loop detection:** Automatically detects loops in MIDIs (e.g., from _Touhou Project_)
84
84
  - **First note detection:** Skip unnecessary silence at the start by jumping to the first note!
85
+ - **Lyrics support:** Both regular MIDI and .kar files!
85
86
  - **[Write MIDI files from scratch](https://github.com/spessasus/SpessaSynth/wiki/Creating-MIDI-Files)**
86
87
  - **Easy saving:** Save with just [one function!](https://github.com/spessasus/SpessaSynth/wiki/Writing-MIDI-Files#writemidifile)
87
88
 
@@ -122,7 +123,6 @@ document.getElementById("button").onclick = async () =>
122
123
  - **Loop multiple times:** *Render two (or more) loops into the file for seamless transitions!*
123
124
  - *That's right, saving as WAV is also [just one function!](https://github.com/spessasus/SpessaSynth/wiki/Writing-Wave-Files#audiobuffertowav)*
124
125
 
125
-
126
126
  # License
127
127
 
128
128
  MIT License, except for the stbvorbis_sync.js in the `externals` folder which is licensed under the Apache-2.0 license.
@@ -10,9 +10,6 @@ import { readLittleEndian } from "../utils/byte_functions/little_endian.js";
10
10
  import { RMIDINFOChunks } from "./rmidi_writer.js";
11
11
  import { BasicMIDI, MIDIticksToSeconds } from "./basic_midi.js";
12
12
 
13
-
14
- const GS_TEXT_HEADER = new Uint8Array([0x41, 0x10, 0x45, 0x12, 0x10, 0x00, 0x00]);
15
-
16
13
  /**
17
14
  * midi_loader.js
18
15
  * purpose: parses a midi file for the seqyencer, including things like marker or CC 2/4 loop detection, copyright detection etc.
@@ -43,6 +40,12 @@ class MIDI extends BasicMIDI
43
40
 
44
41
  let DLSRMID = false;
45
42
 
43
+ /**
44
+ * Will be joined with "\n" to form the final string
45
+ * @type {string[]}
46
+ */
47
+ let copyrightComponents = [];
48
+
46
49
  const initialString = readBytesAsString(binaryData, 4);
47
50
  binaryData.currentIndex -= 4;
48
51
  if (initialString === "RIFF")
@@ -102,6 +105,7 @@ class MIDI extends BasicMIDI
102
105
  }
103
106
  if (this.RMIDInfo["ICOP"])
104
107
  {
108
+ // special case, overwrites the copyright components array
105
109
  copyrightDetected = true;
106
110
  this.copyright = readBytesAsString(
107
111
  this.RMIDInfo["ICOP"],
@@ -242,6 +246,8 @@ class MIDI extends BasicMIDI
242
246
  throw new SyntaxError(`Invalid track header! Expected "MTrk" got "${trackChunk.type}"`);
243
247
  }
244
248
 
249
+ let trackHasVoiceMessages = false;
250
+
245
251
  /**
246
252
  * MIDI running byte
247
253
  * @type {number}
@@ -382,12 +388,14 @@ class MIDI extends BasicMIDI
382
388
  case messageTypes.copyright:
383
389
  if (!copyrightDetected)
384
390
  {
385
- this.copyright += readBytesAsString(
391
+
392
+ eventData.currentIndex = 0;
393
+ copyrightComponents.push(readBytesAsString(
386
394
  eventData,
387
395
  eventData.length,
388
396
  undefined,
389
397
  false
390
- ) + "\n";
398
+ ));
391
399
  }
392
400
  break;
393
401
 
@@ -446,7 +454,7 @@ class MIDI extends BasicMIDI
446
454
  else
447
455
  {
448
456
  // append to copyright
449
- this.copyright += checkedText.substring(2).trim() + " \n";
457
+ copyrightComponents.push(checkedText.substring(2).trim());
450
458
  }
451
459
  }
452
460
  else if (checkedText[0] !== "@")
@@ -461,29 +469,14 @@ class MIDI extends BasicMIDI
461
469
  break;
462
470
 
463
471
  case -3:
464
- // since this is a sysex message
465
- // check for embedded copyright (roland SC display sysex) http://www.bandtrax.com.au/sysex.htm
466
- // header goes like this: 41 10 45 12 10 00 00
467
- if (eventData.slice(0, 7).every((n, i) => GS_TEXT_HEADER[i] === n))
468
- {
469
- /**
470
- * @type {IndexedByteArray}
471
- */
472
- const cutText = eventData.slice(7, messageData.length - 3);
473
- const decoded = readBytesAsString(cutText, cutText.length) + "\n";
474
- this.copyright += decoded;
475
- SpessaSynthInfo(
476
- `%cDecoded Roland SC message! %c${decoded}`,
477
- consoleColors.recognized,
478
- consoleColors.value
479
- );
480
- }
472
+ // since this is a sysex message, do nothing
481
473
  break;
482
474
 
483
475
 
484
476
  default:
485
477
  // since this is a voice message
486
478
  // check for loop (CC 2/4)
479
+ trackHasVoiceMessages = true;
487
480
  if ((statusByte & 0xF0) === messageTypes.controllerChange)
488
481
  {
489
482
  switch (eventData[0])
@@ -522,6 +515,20 @@ class MIDI extends BasicMIDI
522
515
  }
523
516
  this.tracks.push(track);
524
517
  this.usedChannelsOnTrack.push(usedChannels);
518
+
519
+ // if the track has no voice messages, its "track name" event (if it has any)
520
+ // is some metadata. Add it to copyright
521
+ if (!trackHasVoiceMessages)
522
+ {
523
+ const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
524
+ if (trackName)
525
+ {
526
+ trackName.messageData.currentIndex = 0;
527
+ const name = readBytesAsString(trackName.messageData, trackName.messageData.length);
528
+ copyrightComponents.push(name);
529
+ }
530
+ }
531
+
525
532
  SpessaSynthInfo(
526
533
  `%cParsed %c${this.tracks.length}%c / %c${this.tracksAmount}`,
527
534
  consoleColors.info,
@@ -641,6 +648,7 @@ class MIDI extends BasicMIDI
641
648
  if (name)
642
649
  {
643
650
  this.rawMidiName = name.messageData;
651
+ name.messageData.currentIndex = 0;
644
652
  this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
645
653
  }
646
654
  }
@@ -652,11 +660,23 @@ class MIDI extends BasicMIDI
652
660
  if (name)
653
661
  {
654
662
  this.rawMidiName = name.messageData;
663
+ name.messageData.currentIndex = 0;
655
664
  this.midiName = readBytesAsString(name.messageData, name.messageData.length, undefined, false);
656
665
  }
657
666
  }
658
667
  }
659
668
 
669
+ if (!copyrightDetected)
670
+ {
671
+ this.copyright = copyrightComponents
672
+ // trim and group newlines into one
673
+ .map(c => c.trim().replace(/(\r?\n)+/g, "\n"))
674
+ // remove empty strings
675
+ .filter(c => c.length > 0)
676
+ // join with newlines
677
+ .join("\n") || "";
678
+ }
679
+
660
680
  this.fileName = fileName;
661
681
  this.midiName = this.midiName.trim();
662
682
  this.midiNameUsesFileName = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.23.14",
3
+ "version": "3.24.0",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "types": "@types/index.d.ts",
@@ -4,62 +4,80 @@
4
4
  */
5
5
 
6
6
  /**
7
- *
8
- * @typedef {{
9
- * midiNote: number,
10
- * channel: number,
11
- * velocity: number
12
- * }} NoteOnCallback
13
- *
14
- * @typedef {{
15
- * midiNote: number,
16
- * channel: number
17
- * }} NoteOffCallback
18
- *
19
- * @typedef {{
20
- * channel: number,
21
- * isDrumChannel: boolean
22
- * }} DrumChangeCallback
23
- *
24
- * @typedef {{
25
- * channel: number,
26
- * program: number,
27
- * bank: number,
28
- * userCalled: boolean
29
- * }} ProgramChangeCallback
30
- *
31
- * @typedef {{
32
- * channel: number,
33
- * controllerNumber: number,
34
- * controllerValue: number
35
- * }} ControllerChangeCallback
36
- *
37
- * @typedef {{
38
- * channel:number,
39
- * isMuted: boolean
40
- * }} MuteChannelCallback
41
- *
42
- * @typedef {{
43
- * presetName: string,
44
- * bank: number,
45
- * program: number
46
- * }[]} PresetListChangeCallback
47
- *
48
- *
49
- * @typedef {{
50
- * channel: number,
51
- * MSB: number,
52
- * LSB: number
53
- * }} PitchWheelCallback
54
- *
55
- * @typedef {{
56
- * channel: number,
57
- * pressure: number
58
- * }} ChannelPressureCallback
59
- *
60
- * @typedef {string} SoundfontErrorCallback
61
- *
62
- *
7
+ * @typedef {Object} NoteOnCallback
8
+ * @property {number} midiNote - The MIDI note number.
9
+ * @property {number} channel - The MIDI channel number.
10
+ * @property {number} velocity - The velocity of the note.
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} NoteOffCallback
15
+ * @property {number} midiNote - The MIDI note number.
16
+ * @property {number} channel - The MIDI channel number.
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} DrumChangeCallback
21
+ * @property {number} channel - The MIDI channel number.
22
+ * @property {boolean} isDrumChannel - Indicates if the channel is a drum channel.
23
+ */
24
+
25
+ /**
26
+ * @typedef {Object} ProgramChangeCallback
27
+ * @property {number} channel - The MIDI channel number.
28
+ * @property {number} program - The program number.
29
+ * @property {number} bank - The bank number.
30
+ * @property {boolean} userCalled - Indicates if the change was user-initiated.
31
+ */
32
+
33
+ /**
34
+ * @typedef {Object} ControllerChangeCallback
35
+ * @property {number} channel - The MIDI channel number.
36
+ * @property {number} controllerNumber - The controller number.
37
+ * @property {number} controllerValue - The value of the controller.
38
+ */
39
+
40
+ /**
41
+ * @typedef {Object} MuteChannelCallback
42
+ * @property {number} channel - The MIDI channel number.
43
+ * @property {boolean} isMuted - Indicates if the channel is muted.
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} PresetListChangeCallbackSingle
48
+ * @property {string} presetName - The name of the preset.
49
+ * @property {number} bank - The bank number.
50
+ * @property {number} program - The program number.
51
+ */
52
+
53
+ /**
54
+ * @typedef {PresetListChangeCallbackSingle[]} PresetListChangeCallback - A list of preset objects.
55
+ */
56
+
57
+ /**
58
+ * @typedef {Object} SynthDisplayCallback
59
+ * @property {Uint8Array} displayData - The data to display.
60
+ * @property {SynthDisplayType} displayType - The type of display.
61
+ */
62
+
63
+ /**
64
+ * @typedef {Object} PitchWheelCallback
65
+ * @property {number} channel - The MIDI channel number.
66
+ * @property {number} MSB - The most significant byte of the pitch wheel value.
67
+ * @property {number} LSB - The least significant byte of the pitch wheel value.
68
+ */
69
+
70
+ /**
71
+ * @typedef {Object} ChannelPressureCallback
72
+ * @property {number} channel - The MIDI channel number.
73
+ * @property {number} pressure - The pressure value.
74
+ */
75
+
76
+ /**
77
+ * @typedef {Error} SoundfontErrorCallback - The error message for soundfont errors.
78
+ */
79
+
80
+ /**
63
81
  * @typedef {
64
82
  * NoteOnCallback |
65
83
  * NoteOffCallback |
@@ -71,6 +89,7 @@
71
89
  * PitchWheelCallback |
72
90
  * SoundfontErrorCallback |
73
91
  * ChannelPressureCallback |
92
+ * SynthDisplayCallback |
74
93
  * undefined
75
94
  * } EventCallbackData
76
95
  */
@@ -90,7 +109,8 @@
90
109
  * "mutechannel"|
91
110
  * "presetlistchange"|
92
111
  * "allcontrollerreset"|
93
- * "soundfonterror"} EventTypes
112
+ * "soundfonterror"|
113
+ * "synthdisplay"} EventTypes
94
114
  */
95
115
  export class EventHandler
96
116
  {
@@ -104,20 +124,21 @@ export class EventHandler
104
124
  * @type {Object<EventTypes, Object<string, function(EventCallbackData)>>}
105
125
  */
106
126
  this.events = {
107
- "noteoff": {},
108
- "noteon": {},
109
- "pitchwheel": {},
110
- "controllerchange": {},
111
- "programchange": {},
112
- "channelpressure": {},
113
- "polypressure": {},
114
- "drumchange": {},
115
- "stopall": {},
116
- "newchannel": {},
117
- "mutechannel": {},
118
- "presetlistchange": {},
119
- "allcontrollerreset": {},
120
- "soundfonterror": {}
127
+ "noteoff": {}, // called on note off message
128
+ "noteon": {}, // called on note on message
129
+ "pitchwheel": {}, // called on pitch wheel change
130
+ "controllerchange": {}, // called on controller change
131
+ "programchange": {}, // called on program change
132
+ "channelpressure": {}, // called on channel pressure message
133
+ "polypressure": {}, // called on poly pressure message
134
+ "drumchange": {}, // called when channel type changes
135
+ "stopall": {}, // called when synth receives stop all command
136
+ "newchannel": {}, // called when a new channel is created
137
+ "mutechannel": {}, // called when a channel is muted/unmuted
138
+ "presetlistchange": {}, // called when the preset list changes (soundfont gets reloaded)
139
+ "allcontrollerreset": {}, // called when all controllers are reset
140
+ "soundfonterror": {}, // called when a soundfont parsing error occurs
141
+ "synthdisplay": {} // called when there's a SysEx message to display some text
121
142
  };
122
143
 
123
144
  /**