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.
- package/midi_parser/README.md +29 -1
- package/midi_parser/basic_midi.js +15 -37
- package/midi_parser/midi_data.js +1 -23
- package/midi_parser/midi_loader.js +4 -3
- package/midi_parser/midi_sequence.js +45 -0
- package/package.json +1 -1
- package/soundfont/basic_soundfont/basic_soundfont.js +8 -2
- package/synthetizer/synthetizer.js +25 -5
- package/synthetizer/worklet_processor.min.js +11 -11
- package/synthetizer/worklet_system/main_processor.js +10 -11
- package/synthetizer/worklet_system/message_protocol/message_sending.js +0 -42
- package/synthetizer/worklet_system/message_protocol/worklet_message.js +5 -6
- package/synthetizer/worklet_system/worklet_methods/create_worklet_channel.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/mute_channel.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/note_on.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/program_change.js +11 -2
- package/synthetizer/worklet_system/worklet_methods/stopping_notes/stop_all_notes.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/tuning_control/pitch_wheel.js +1 -1
- package/synthetizer/worklet_system/worklet_methods/tuning_control/transpose_channel.js +1 -1
- package/synthetizer/worklet_system/worklet_utilities/worklet_processor_channel.js +53 -2
package/midi_parser/README.md
CHANGED
|
@@ -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
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
293
|
+
// track name
|
|
294
|
+
this.trackNames[i] = "";
|
|
295
|
+
const trackName = track.find(e => e.messageStatusByte === messageTypes.trackName);
|
|
296
|
+
if (trackName)
|
|
322
297
|
{
|
|
323
|
-
|
|
324
|
-
|
|
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
|
}
|
package/midi_parser/midi_data.js
CHANGED
|
@@ -22,29 +22,7 @@ export class MIDIData extends MIDISequenceData
|
|
|
22
22
|
constructor(midi)
|
|
23
23
|
{
|
|
24
24
|
super();
|
|
25
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
@@ -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",
|
|
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.
|
|
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
|
-
|
|
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
|
{
|