spessasynth_lib 3.25.7 → 3.25.9

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.
@@ -1,4 +1,32 @@
1
1
  ## This is the MIDI file parsing folder.
2
2
 
3
3
  The code here is responsible for parsing the MIDI files and interpreting the messsages.
4
- All the events are defined in the `midi_message.js` file.
4
+ All the events are defined in the `midi_message.js` file.
5
+
6
+ ### MIDI Classes hierarchy
7
+
8
+ #### MIDI Sequence
9
+ - The most basic class, containing all the metadata that inheritors have.
10
+ - It does not contain track data or embedded sound bank.
11
+ - Contains the function for calculating time from ticks.
12
+ - Contains the copying code.
13
+
14
+ #### MIDI Data
15
+ - Inherits from MIDI Sequence.
16
+ - Has an `isEmbedded` property to mark the existence of embedded bank. This is the class that is available in `sequencer.midiData`.
17
+
18
+ #### Basic MIDI
19
+ - Inherits from MIDI Sequence.
20
+ - The actual MIDI representation, containing the track data and embedded sound banks.
21
+ - Contains the code for parsing the MIDI and filling in the metadata automatically.
22
+ - Contains the SMF/RMI writing functions.
23
+ - Contains the code for determining used channels on tracks.
24
+
25
+ #### MIDI Builder
26
+ - Inherits from Basic MIDI.
27
+ - Used for building MIDIs from scratch.
28
+
29
+ ### MIDI
30
+ - Inherits from Basic MIDI.
31
+ - The SMF/RMI/XMF file parser.
32
+ - Called by the sequencer if an `ArrayBuffer` is provided.
@@ -44,38 +44,10 @@ class BasicMIDI extends MIDISequenceData
44
44
  static copyFrom(mid)
45
45
  {
46
46
  const m = new BasicMIDI();
47
+ m._copyFromSequence(mid);
47
48
 
48
- m.midiName = mid.midiName;
49
- m.midiNameUsesFileName = mid.midiNameUsesFileName;
50
- m.fileName = mid.fileName;
51
- m.timeDivision = mid.timeDivision;
52
- m.duration = mid.duration;
53
- m.copyright = mid.copyright;
54
- m.tracksAmount = mid.tracksAmount;
55
- m.firstNoteOn = mid.firstNoteOn;
56
- m.keyRange = { ...mid.keyRange }; // Deep copy of keyRange
57
- m.lastVoiceEventTick = mid.lastVoiceEventTick;
58
- m.loop = { ...mid.loop }; // Deep copy of loop
59
- m.format = mid.format;
60
- m.bankOffset = mid.bankOffset;
61
- m.isKaraokeFile = mid.isKaraokeFile;
62
- m.isMultiPort = mid.isMultiPort;
63
49
  m.isDLSRMIDI = mid.isDLSRMIDI;
64
-
65
- // Copying arrays
66
- m.tempoChanges = [...mid.tempoChanges]; // Shallow copy
67
- m.lyrics = mid.lyrics.map(arr => new Uint8Array(arr)); // Deep copy of each binary chunk
68
- m.lyricsTicks = [...mid.lyricsTicks]; // Shallow copy
69
- m.midiPorts = [...mid.midiPorts]; // Shallow copy
70
- m.midiPortChannelOffsets = [...mid.midiPortChannelOffsets]; // Shallow copy
71
- m.usedChannelsOnTrack = mid.usedChannelsOnTrack.map(set => new Set(set)); // Deep copy
72
- m.rawMidiName = mid.rawMidiName ? new Uint8Array(mid.rawMidiName) : undefined; // Deep copy
73
50
  m.embeddedSoundFont = mid.embeddedSoundFont ? mid.embeddedSoundFont.slice(0) : undefined; // Deep copy
74
-
75
- // Copying RMID Info object (deep copy)
76
- m.RMIDInfo = { ...mid.RMIDInfo };
77
-
78
- // Copying track data (deep copy of each track)
79
51
  m.tracks = mid.tracks.map(track => [...track]); // Shallow copy of each track array
80
52
 
81
53
  return m;
@@ -310,21 +282,27 @@ class BasicMIDI extends MIDISequenceData
310
282
  }
311
283
  }
312
284
  break;
285
+
286
+ case messageTypes.trackName:
287
+ break;
313
288
  }
314
289
  }
315
290
  // add used channels
316
291
  this.usedChannelsOnTrack.push(usedChannels);
317
292
 
318
- // If the track has no voice messages, its "track name" event (if it has any)
319
- // is some metadata.
320
- // Add it to copyright
321
- if (!trackHasVoiceMessages)
293
+ // track name
294
+ this.trackNames[i] = "";
295
+ const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
296
+ if (trackName)
322
297
  {
323
- const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
324
- if (trackName)
298
+ trackName.messageData.currentIndex = 0;
299
+ const name = readBytesAsString(trackName.messageData, trackName.messageData.length);
300
+ this.trackNames[i] = name;
301
+ // If the track has no voice messages, its "track name" event (if it has any)
302
+ // is some metadata.
303
+ // Add it to copyright
304
+ if (!trackHasVoiceMessages)
325
305
  {
326
- trackName.messageData.currentIndex = 0;
327
- const name = readBytesAsString(trackName.messageData, trackName.messageData.length);
328
306
  copyrightComponents.push(name);
329
307
  }
330
308
  }
@@ -22,29 +22,7 @@ export class MIDIData extends MIDISequenceData
22
22
  constructor(midi)
23
23
  {
24
24
  super();
25
- this.timeDivision = midi.timeDivision;
26
- this.duration = midi.duration;
27
- this.tempoChanges = midi.tempoChanges;
28
- this.copyright = midi.copyright;
29
- this.tracksAmount = midi.tracksAmount;
30
- this.lyrics = midi.lyrics;
31
- this.lyricsTicks = midi.lyricsTicks;
32
- this.firstNoteOn = midi.firstNoteOn;
33
- this.keyRange = midi.keyRange;
34
- this.lastVoiceEventTick = midi.lastVoiceEventTick;
35
- this.midiPorts = midi.midiPorts;
36
- this.midiPortChannelOffsets = midi.midiPortChannelOffsets;
37
- this.usedChannelsOnTrack = midi.usedChannelsOnTrack;
38
- this.loop = midi.loop;
39
- this.midiName = midi.midiName;
40
- this.midiNameUsesFileName = midi.midiNameUsesFileName;
41
- this.fileName = midi.fileName;
42
- this.rawMidiName = midi.rawMidiName;
43
- this.format = midi.format;
44
- this.RMIDInfo = midi.RMIDInfo;
45
- this.bankOffset = midi.bankOffset;
46
- this.isKaraokeFile = midi.isKaraokeFile;
47
- this.isMultiPort = midi.isMultiPort;
25
+ this._copyFromSequence(midi);
48
26
 
49
27
  // Set isEmbedded based on the presence of an embeddedSoundFont
50
28
  this.isEmbedded = midi.embeddedSoundFont !== undefined;
@@ -155,7 +155,7 @@ class MIDI extends BasicMIDI
155
155
  {
156
156
  fileByteArray = binaryData;
157
157
  }
158
- const headerChunk = this.readMIDIChunk(fileByteArray);
158
+ const headerChunk = this._readMIDIChunk(fileByteArray);
159
159
  if (headerChunk.type !== "MThd")
160
160
  {
161
161
  SpessaSynthGroupEnd();
@@ -181,7 +181,7 @@ class MIDI extends BasicMIDI
181
181
  * @type {MIDIMessage[]}
182
182
  */
183
183
  const track = [];
184
- const trackChunk = this.readMIDIChunk(fileByteArray);
184
+ const trackChunk = this._readMIDIChunk(fileByteArray);
185
185
 
186
186
  if (trackChunk.type !== "MTrk")
187
187
  {
@@ -303,8 +303,9 @@ class MIDI extends BasicMIDI
303
303
  /**
304
304
  * @param fileByteArray {IndexedByteArray}
305
305
  * @returns {{type: string, size: number, data: IndexedByteArray}}
306
+ * @private
306
307
  */
307
- readMIDIChunk(fileByteArray)
308
+ _readMIDIChunk(fileByteArray)
308
309
  {
309
310
  const chunk = {};
310
311
  // type
@@ -38,6 +38,12 @@ class MIDISequenceData
38
38
  */
39
39
  tracksAmount = 0;
40
40
 
41
+ /**
42
+ * The track names in the MIDI file, an empty string if not set.
43
+ * @type {[]}
44
+ */
45
+ trackNames = [];
46
+
41
47
  /**
42
48
  * An array containing the lyrics of the sequence, stored as binary chunks (Uint8Array).
43
49
  * @type {Uint8Array[]}
@@ -175,6 +181,45 @@ class MIDISequenceData
175
181
 
176
182
  return totalSeconds;
177
183
  }
184
+
185
+ /**
186
+ * INTERNAL USE ONLY!
187
+ * DO NOT USE IN SPESSASYNTH_LIB
188
+ * @param sequence {MIDISequenceData}
189
+ * @protected
190
+ */
191
+ _copyFromSequence(sequence)
192
+ {
193
+ // properties can be assigned
194
+ this.midiName = sequence.midiName;
195
+ this.midiNameUsesFileName = sequence.midiNameUsesFileName;
196
+ this.fileName = sequence.fileName;
197
+ this.timeDivision = sequence.timeDivision;
198
+ this.duration = sequence.duration;
199
+ this.copyright = sequence.copyright;
200
+ this.tracksAmount = sequence.tracksAmount;
201
+ this.firstNoteOn = sequence.firstNoteOn;
202
+ this.lastVoiceEventTick = sequence.lastVoiceEventTick;
203
+ this.format = sequence.format;
204
+ this.bankOffset = sequence.bankOffset;
205
+ this.isKaraokeFile = sequence.isKaraokeFile;
206
+ this.isMultiPort = sequence.isMultiPort;
207
+
208
+ // copying arrays
209
+ this.tempoChanges = [...sequence.tempoChanges];
210
+ this.lyrics = sequence.lyrics.map(arr => new Uint8Array(arr));
211
+ this.lyricsTicks = [...sequence.lyricsTicks];
212
+ this.midiPorts = [...sequence.midiPorts];
213
+ this.trackNames = [...sequence.trackNames];
214
+ this.midiPortChannelOffsets = [...sequence.midiPortChannelOffsets];
215
+ this.usedChannelsOnTrack = sequence.usedChannelsOnTrack.map(set => new Set(set));
216
+ this.rawMidiName = sequence.rawMidiName ? new Uint8Array(sequence.rawMidiName) : undefined;
217
+
218
+ // copying objects
219
+ this.loop = { ...sequence.loop };
220
+ this.keyRange = { ...sequence.keyRange };
221
+ this.RMIDInfo = { ...sequence.RMIDInfo };
222
+ }
178
223
  }
179
224
 
180
225
  export { MIDISequenceData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spessasynth_lib",
3
- "version": "3.25.7",
3
+ "version": "3.25.9",
4
4
  "description": "MIDI and SoundFont2/DLS library with no compromises",
5
5
  "browser": "index.js",
6
6
  "type": "module",
@@ -166,11 +166,11 @@ class BasicSoundBank
166
166
  // MUST be a valid XG bank.
167
167
  // allowed banks: (see XG specification)
168
168
  // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
169
- // 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48,
169
+ // 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
170
170
  // 64, 65, 66, 126, 127
171
171
  const allowedPrograms = new Set([
172
172
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 24,
173
- 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48,
173
+ 25, 27, 28, 29, 30, 31, 32, 33, 40, 41, 48, 56, 57, 58,
174
174
  64, 65, 66, 126, 127
175
175
  ]);
176
176
  for (const preset of this.presets)
@@ -182,6 +182,12 @@ class BasicSoundBank
182
182
  {
183
183
  // not valid!
184
184
  this.isXGBank = false;
185
+ SpessaSynthInfo(
186
+ `%cThis bank is not valid XG. Preset %c${preset.bank}:${preset.program}%c is not a valid XG drum. XG mode will use presets on bank 128.`,
187
+ consoleColors.info,
188
+ consoleColors.value,
189
+ consoleColors.info
190
+ );
185
191
  break;
186
192
  }
187
193
  }
@@ -77,6 +77,12 @@ export class Synthetizer
77
77
  */
78
78
  channelProperties = [];
79
79
 
80
+ /**
81
+ * The current preset list
82
+ * @type {{presetName: string, bank: number, program: number}[]}
83
+ */
84
+ presetList = [];
85
+
80
86
  /**
81
87
  * Creates a new instance of the SpessaSynth synthesizer.
82
88
  * @param targetNode {AudioNode}
@@ -242,10 +248,16 @@ export class Synthetizer
242
248
  }
243
249
 
244
250
  // attach a new channel to keep track of channels count
245
- this.eventHandler.addEvent("newchannel", "synth-new-channel", () =>
251
+ this.eventHandler.addEvent("newchannel", `synth-new-channel-${Math.random()}`, () =>
246
252
  {
247
253
  this.channelsAmount++;
248
254
  });
255
+
256
+ // attach a new event for preset list change
257
+ this.eventHandler.addEvent("presetlistchange", `synth-preset-list-change-${Math.random()}`, e =>
258
+ {
259
+ this.presetList = e;
260
+ });
249
261
  }
250
262
 
251
263
  /**
@@ -396,11 +408,17 @@ export class Synthetizer
396
408
  const messageData = message.messageData;
397
409
  switch (message.messageType)
398
410
  {
399
- case returnMessageType.channelProperties:
411
+ case returnMessageType.channelPropertyChange:
412
+ /**
413
+ * @type {number}
414
+ */
415
+ const channelNumber = messageData[0];
400
416
  /**
401
- * @type {ChannelProperty[]}
417
+ * @type {ChannelProperty}
402
418
  */
403
- this.channelProperties = messageData;
419
+ const property = messageData[1];
420
+
421
+ this.channelProperties[channelNumber] = property;
404
422
 
405
423
  this._voicesAmount = this.channelProperties.reduce((sum, voices) => sum + voices.voicesAmount, 0);
406
424
  break;
@@ -485,7 +503,9 @@ export class Synthetizer
485
503
  pitchBendRangeSemitones: 0,
486
504
  isMuted: false,
487
505
  isDrum: false,
488
- transposition: 0
506
+ transposition: 0,
507
+ program: 0,
508
+ bank: this.channelsAmount % 16 === DEFAULT_PERCUSSION ? 128 : 0
489
509
  });
490
510
  if (!postMessage)
491
511
  {