spessasynth_lib 3.23.3 → 3.23.8
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/@types/midi_parser/basic_midi.d.ts +41 -21
- package/@types/midi_parser/midi_builder.d.ts +0 -4
- package/@types/midi_parser/midi_data.d.ts +38 -20
- package/@types/sequencer/sequencer.d.ts +2 -1
- package/@types/soundfont/basic_soundfont/basic_sample.d.ts +5 -0
- package/@types/soundfont/basic_soundfont/basic_soundfont.d.ts +8 -0
- package/@types/soundfont/basic_soundfont/modulator.d.ts +2 -1
- package/@types/soundfont/dls/articulator_converter.d.ts +9 -0
- package/@types/soundfont/dls/dls_sample.d.ts +0 -5
- package/@types/soundfont/dls/dls_sources.d.ts +5 -0
- package/@types/soundfont/read_sf2/samples.d.ts +0 -1
- package/@types/synthetizer/synth_event_handler.d.ts +5 -0
- package/@types/utils/byte_functions/string.d.ts +5 -0
- package/midi_parser/basic_midi.js +269 -109
- package/midi_parser/midi_builder.js +1 -106
- package/midi_parser/midi_data.js +140 -90
- package/midi_parser/midi_loader.js +2 -0
- package/midi_parser/rmidi_writer.js +4 -4
- package/package.json +1 -1
- package/sequencer/sequencer.js +3 -2
- package/soundfont/basic_soundfont/basic_sample.js +15 -6
- package/soundfont/basic_soundfont/basic_soundfont.js +64 -0
- package/soundfont/basic_soundfont/modulator.js +4 -2
- package/soundfont/basic_soundfont/riff_chunk.js +1 -1
- package/soundfont/basic_soundfont/write_dls/art2.js +17 -0
- package/soundfont/basic_soundfont/write_dls/ins.js +2 -2
- package/soundfont/basic_soundfont/write_dls/modulator_converter.js +12 -6
- package/soundfont/basic_soundfont/write_dls/rgn2.js +30 -25
- package/soundfont/basic_soundfont/write_dls/wave.js +8 -3
- package/soundfont/basic_soundfont/write_dls/write_dls.js +3 -2
- package/soundfont/basic_soundfont/write_dls/wsmp.js +2 -2
- package/soundfont/dls/articulator_converter.js +12 -10
- package/soundfont/dls/dls_sample.js +9 -8
- package/soundfont/dls/dls_sources.js +36 -1
- package/soundfont/dls/read_articulation.js +3 -15
- package/soundfont/dls/read_instrument.js +3 -14
- package/synthetizer/synth_event_handler.js +38 -1
- package/synthetizer/synth_soundfont_manager.js +0 -0
- package/synthetizer/worklet_processor.min.js +10 -10
- package/utils/byte_functions/string.js +9 -0
|
@@ -1,128 +1,288 @@
|
|
|
1
|
+
import { messageTypes } from "./midi_message.js";
|
|
2
|
+
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
3
|
+
|
|
1
4
|
export class BasicMIDI
|
|
2
5
|
{
|
|
3
|
-
|
|
6
|
+
/**
|
|
7
|
+
* The time division of the sequence, representing the number of ticks per beat.
|
|
8
|
+
* @type {number}
|
|
9
|
+
*/
|
|
10
|
+
timeDivision = 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The duration of the sequence, in seconds.
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
16
|
+
duration = 0;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The tempo changes in the sequence, ordered from the last change to the first.
|
|
20
|
+
* Each change is represented by an object with a tick position and a tempo value in beats per minute.
|
|
21
|
+
* @type {{ticks: number, tempo: number}[]}
|
|
22
|
+
*/
|
|
23
|
+
tempoChanges = [{ ticks: 0, tempo: 120 }];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A string containing the copyright information for the MIDI sequence if detected.
|
|
27
|
+
* @type {string}
|
|
28
|
+
*/
|
|
29
|
+
copyright = "";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The number of tracks in the MIDI sequence.
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
tracksAmount = 0;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* An array containing the lyrics of the sequence, stored as binary chunks (Uint8Array).
|
|
39
|
+
* @type {Uint8Array[]}
|
|
40
|
+
*/
|
|
41
|
+
lyrics = [];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The tick position of the first note-on event in the MIDI sequence.
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
firstNoteOn = 0;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The MIDI key range used in the sequence, represented by a minimum and maximum note value.
|
|
51
|
+
* @type {{min: number, max: number}}
|
|
52
|
+
*/
|
|
53
|
+
keyRange = { min: 0, max: 127 };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The tick position of the last voice event (such as note-on, note-off, or control change) in the sequence.
|
|
57
|
+
* @type {number}
|
|
58
|
+
*/
|
|
59
|
+
lastVoiceEventTick = 0;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* An array of MIDI port numbers used by each track in the sequence.
|
|
63
|
+
* @type {number[]}
|
|
64
|
+
*/
|
|
65
|
+
midiPorts = [0];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* An array of channel offsets for each MIDI port, using the SpessaSynth method.
|
|
69
|
+
* @type {number[]}
|
|
70
|
+
*/
|
|
71
|
+
midiPortChannelOffsets = [0];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A list of sets, where each set contains the MIDI channels used by each track in the sequence.
|
|
75
|
+
* @type {Set<number>[]}
|
|
76
|
+
*/
|
|
77
|
+
usedChannelsOnTrack = [];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The loop points (in ticks) of the sequence, including both start and end points.
|
|
81
|
+
* @type {{start: number, end: number}}
|
|
82
|
+
*/
|
|
83
|
+
loop = { start: 0, end: 0 };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The name of the MIDI sequence.
|
|
87
|
+
* @type {string}
|
|
88
|
+
*/
|
|
89
|
+
midiName = "";
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A boolean indicating if the sequence's name is the same as the file name.
|
|
93
|
+
* @type {boolean}
|
|
94
|
+
*/
|
|
95
|
+
midiNameUsesFileName = false;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The file name of the MIDI sequence, if provided during parsing.
|
|
99
|
+
* @type {string}
|
|
100
|
+
*/
|
|
101
|
+
fileName = "";
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The raw, encoded MIDI name, represented as a Uint8Array.
|
|
105
|
+
* Useful when the MIDI file uses a different code page.
|
|
106
|
+
* @type {Uint8Array}
|
|
107
|
+
*/
|
|
108
|
+
rawMidiName = undefined;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The embedded soundfont in the MIDI file, represented as an ArrayBuffer, if available.
|
|
112
|
+
* @type {ArrayBuffer|undefined}
|
|
113
|
+
*/
|
|
114
|
+
embeddedSoundFont = undefined;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The format of the MIDI file, which can be 0, 1, or 2, indicating the type of the MIDI file.
|
|
118
|
+
* @type {number}
|
|
119
|
+
*/
|
|
120
|
+
format = 0;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The RMID (Resource Interchangeable MIDI) info data, if the file is RMID formatted.
|
|
124
|
+
* Otherwise, this field is undefined.
|
|
125
|
+
* Chunk type (e.g. "INAM"): Chunk data as binary array.
|
|
126
|
+
* @type {Object<string, IndexedByteArray>}
|
|
127
|
+
*/
|
|
128
|
+
RMIDInfo = {};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The bank offset used for RMID files.
|
|
132
|
+
* @type {number}
|
|
133
|
+
*/
|
|
134
|
+
bankOffset = 0;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The actual track data of the MIDI file, represented as an array of tracks.
|
|
138
|
+
* Tracks are arrays of MidiMessage objects.
|
|
139
|
+
* @type {MidiMessage[][]}
|
|
140
|
+
*/
|
|
141
|
+
tracks = [];
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Copies a MIDI
|
|
145
|
+
* @param mid {BasicMIDI}
|
|
146
|
+
* @returns {BasicMIDI}
|
|
147
|
+
*/
|
|
148
|
+
static copyFrom(mid)
|
|
4
149
|
{
|
|
5
|
-
|
|
6
|
-
* The time division of the sequence
|
|
7
|
-
* @type {number}
|
|
8
|
-
*/
|
|
9
|
-
this.timeDivision = 0;
|
|
10
|
-
/**
|
|
11
|
-
* The duration of the sequence, in seconds
|
|
12
|
-
* @type {number}
|
|
13
|
-
*/
|
|
14
|
-
this.duration = 0;
|
|
15
|
-
/**
|
|
16
|
-
* The tempo changes in the sequence, ordered from last to first
|
|
17
|
-
* @type {{ticks: number, tempo: number}[]}
|
|
18
|
-
*/
|
|
19
|
-
this.tempoChanges = [{ ticks: 0, tempo: 120 }];
|
|
20
|
-
/**
|
|
21
|
-
* Contains the copyright strings
|
|
22
|
-
* @type {string}
|
|
23
|
-
*/
|
|
24
|
-
this.copyright = "";
|
|
150
|
+
const m = new BasicMIDI();
|
|
25
151
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
152
|
+
m.midiName = mid.midiName;
|
|
153
|
+
m.midiNameUsesFileName = mid.midiNameUsesFileName;
|
|
154
|
+
m.fileName = mid.fileName;
|
|
155
|
+
m.timeDivision = mid.timeDivision;
|
|
156
|
+
m.duration = mid.duration;
|
|
157
|
+
m.copyright = mid.copyright;
|
|
158
|
+
m.tracksAmount = mid.tracksAmount;
|
|
159
|
+
m.firstNoteOn = mid.firstNoteOn;
|
|
160
|
+
m.keyRange = { ...mid.keyRange }; // Deep copy of keyRange
|
|
161
|
+
m.lastVoiceEventTick = mid.lastVoiceEventTick;
|
|
162
|
+
m.loop = { ...mid.loop }; // Deep copy of loop
|
|
163
|
+
m.format = mid.format;
|
|
164
|
+
m.bankOffset = mid.bankOffset;
|
|
31
165
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* @type {number}
|
|
41
|
-
*/
|
|
42
|
-
this.firstNoteOn = 0;
|
|
166
|
+
// Copying arrays
|
|
167
|
+
m.tempoChanges = [...mid.tempoChanges]; // Shallow copy
|
|
168
|
+
m.lyrics = mid.lyrics.map(arr => new Uint8Array(arr)); // Deep copy of each binary chunk
|
|
169
|
+
m.midiPorts = [...mid.midiPorts]; // Shallow copy
|
|
170
|
+
m.midiPortChannelOffsets = [...mid.midiPortChannelOffsets]; // Shallow copy
|
|
171
|
+
m.usedChannelsOnTrack = mid.usedChannelsOnTrack.map(set => new Set(set)); // Deep copy
|
|
172
|
+
m.rawMidiName = mid.rawMidiName ? new Uint8Array(mid.rawMidiName) : undefined; // Deep copy
|
|
173
|
+
m.embeddedSoundFont = mid.embeddedSoundFont ? mid.embeddedSoundFont.slice() : undefined; // Deep copy
|
|
43
174
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* @type {{min: number, max: number}}
|
|
47
|
-
*/
|
|
48
|
-
this.keyRange = { min: 0, max: 127 };
|
|
175
|
+
// Copying RMID Info object (deep copy)
|
|
176
|
+
m.RMIDInfo = { ...mid.RMIDInfo };
|
|
49
177
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @type {number}
|
|
53
|
-
*/
|
|
54
|
-
this.lastVoiceEventTick = 0;
|
|
178
|
+
// Copying track data (deep copy of each track)
|
|
179
|
+
m.tracks = mid.tracks.map(track => [...track]); // Shallow copy of each track array
|
|
55
180
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
181
|
+
return m;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Updates all internal values
|
|
186
|
+
*/
|
|
187
|
+
flush()
|
|
188
|
+
{
|
|
61
189
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
190
|
+
// find first note on
|
|
191
|
+
const firstNoteOns = [];
|
|
192
|
+
for (const t of this.tracks)
|
|
193
|
+
{
|
|
194
|
+
// sost the track by ticks
|
|
195
|
+
t.sort((e1, e2) => e1.ticks - e2.ticks);
|
|
196
|
+
const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
|
|
197
|
+
if (firstNoteOn)
|
|
198
|
+
{
|
|
199
|
+
firstNoteOns.push(firstNoteOn.ticks);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.firstNoteOn = Math.min(...firstNoteOns);
|
|
67
203
|
|
|
204
|
+
// find tempo changes
|
|
205
|
+
// and used channels on tracks
|
|
206
|
+
// and midi ports
|
|
207
|
+
// and last voice event tick
|
|
208
|
+
// and loop
|
|
209
|
+
this.lastVoiceEventTick = 0;
|
|
210
|
+
this.tempoChanges = [{ ticks: 0, tempo: 120 }];
|
|
211
|
+
this.midiPorts = [];
|
|
212
|
+
this.midiPortChannelOffsets = [];
|
|
213
|
+
let portOffset = 0;
|
|
68
214
|
/**
|
|
69
|
-
* All channels that each track uses
|
|
70
215
|
* @type {Set<number>[]}
|
|
71
216
|
*/
|
|
72
|
-
this.usedChannelsOnTrack =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
217
|
+
this.usedChannelsOnTrack = this.tracks.map(() => new Set());
|
|
218
|
+
this.tracks.forEach((t, trackNum) =>
|
|
219
|
+
{
|
|
220
|
+
this.midiPorts.push(-1);
|
|
221
|
+
t.forEach(e =>
|
|
222
|
+
{
|
|
223
|
+
// last voice event tick
|
|
224
|
+
if (e.messageStatusByte >= 0x80 && e.messageStatusByte < 0xF0)
|
|
225
|
+
{
|
|
226
|
+
if (e.ticks > this.lastVoiceEventTick)
|
|
227
|
+
{
|
|
228
|
+
this.lastVoiceEventTick = e.ticks;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// tempo, used channels, port
|
|
233
|
+
if (e.messageStatusByte === messageTypes.setTempo)
|
|
234
|
+
{
|
|
235
|
+
this.tempoChanges.push({
|
|
236
|
+
ticks: e.ticks,
|
|
237
|
+
tempo: 60000000 / readBytesAsUintBigEndian(
|
|
238
|
+
e.messageData,
|
|
239
|
+
3
|
|
240
|
+
)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
else if ((e.messageStatusByte & 0xF0) === messageTypes.noteOn)
|
|
244
|
+
{
|
|
245
|
+
this.usedChannelsOnTrack[trackNum].add(e.messageData[0]);
|
|
246
|
+
}
|
|
247
|
+
else if (e.messageStatusByte === messageTypes.midiPort)
|
|
248
|
+
{
|
|
249
|
+
const port = e.messageData[0];
|
|
250
|
+
this.midiPorts[trackNum] = port;
|
|
251
|
+
if (this.midiPortChannelOffsets[port] === undefined)
|
|
252
|
+
{
|
|
253
|
+
this.midiPortChannelOffsets[port] = portOffset;
|
|
254
|
+
portOffset += 16;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
});
|
|
91
259
|
|
|
92
|
-
|
|
93
|
-
* The raw, encoded MIDI name.
|
|
94
|
-
* @type {Uint8Array}
|
|
95
|
-
*/
|
|
96
|
-
this.rawMidiName = undefined;
|
|
260
|
+
this.loop = { start: this.firstNoteOn, end: this.lastVoiceEventTick };
|
|
97
261
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*/
|
|
102
|
-
this.embeddedSoundFont = undefined;
|
|
262
|
+
// reverse tempo and compute duration
|
|
263
|
+
this.tempoChanges.reverse();
|
|
264
|
+
this.duration = MIDIticksToSeconds(this.lastVoiceEventTick, this);
|
|
103
265
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
*/
|
|
125
|
-
this.tracks = [];
|
|
266
|
+
// fix midi ports:
|
|
267
|
+
// midi tracks without ports will have a value of -1
|
|
268
|
+
// if all ports have a value of -1, set it to 0, otherwise take the first midi port and replace all -1 with it
|
|
269
|
+
// why do this? some midis (for some reason) specify all channels to port 1 or else, but leave the conductor track with no port pref.
|
|
270
|
+
// this spessasynth to reserve the first 16 channels for the conductor track (which doesn't play anything) and use additional 16 for the actual ports.
|
|
271
|
+
let defaultP = 0;
|
|
272
|
+
for (let port of this.midiPorts)
|
|
273
|
+
{
|
|
274
|
+
if (port !== -1)
|
|
275
|
+
{
|
|
276
|
+
defaultP = port;
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
this.midiPorts = this.midiPorts.map(port => port === -1 ? defaultP : port);
|
|
281
|
+
// add dummy port if empty
|
|
282
|
+
if (this.midiPortChannelOffsets.length === 0)
|
|
283
|
+
{
|
|
284
|
+
this.midiPortChannelOffsets = [0];
|
|
285
|
+
}
|
|
126
286
|
}
|
|
127
287
|
}
|
|
128
288
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { BasicMIDI
|
|
1
|
+
import { BasicMIDI } from "./basic_midi.js";
|
|
2
2
|
import { messageTypes, MidiMessage } from "./midi_message.js";
|
|
3
3
|
import { IndexedByteArray } from "../utils/indexed_array.js";
|
|
4
|
-
import { readBytesAsUintBigEndian } from "../utils/byte_functions/big_endian.js";
|
|
5
4
|
import { SpessaSynthWarn } from "../utils/loggin.js";
|
|
6
5
|
|
|
7
6
|
export class MIDIBuilder extends BasicMIDI
|
|
@@ -24,110 +23,6 @@ export class MIDIBuilder extends BasicMIDI
|
|
|
24
23
|
this.addSetTempo(0, initialTempo);
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
/**
|
|
28
|
-
* Updates all internal values
|
|
29
|
-
*/
|
|
30
|
-
flush()
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
// find first note on
|
|
34
|
-
const firstNoteOns = [];
|
|
35
|
-
for (const t of this.tracks)
|
|
36
|
-
{
|
|
37
|
-
// sost the track by ticks
|
|
38
|
-
t.sort((e1, e2) => e1.ticks - e2.ticks);
|
|
39
|
-
const firstNoteOn = t.find(e => (e.messageStatusByte & 0xF0) === messageTypes.noteOn);
|
|
40
|
-
if (firstNoteOn)
|
|
41
|
-
{
|
|
42
|
-
firstNoteOns.push(firstNoteOn.ticks);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
this.firstNoteOn = Math.min(...firstNoteOns);
|
|
46
|
-
|
|
47
|
-
// find tempo changes
|
|
48
|
-
// and used channels on tracks
|
|
49
|
-
// and midi ports
|
|
50
|
-
// and last voice event tick
|
|
51
|
-
// and loop
|
|
52
|
-
this.lastVoiceEventTick = 0;
|
|
53
|
-
this.tempoChanges = [{ ticks: 0, tempo: 120 }];
|
|
54
|
-
this.midiPorts = [];
|
|
55
|
-
this.midiPortChannelOffsets = [];
|
|
56
|
-
let portOffset = 0;
|
|
57
|
-
/**
|
|
58
|
-
* @type {Set<number>[]}
|
|
59
|
-
*/
|
|
60
|
-
this.usedChannelsOnTrack = this.tracks.map(() => new Set());
|
|
61
|
-
this.tracks.forEach((t, trackNum) =>
|
|
62
|
-
{
|
|
63
|
-
this.midiPorts.push(-1);
|
|
64
|
-
t.forEach(e =>
|
|
65
|
-
{
|
|
66
|
-
// last voice event tick
|
|
67
|
-
if (e.messageStatusByte >= 0x80 && e.messageStatusByte < 0xF0)
|
|
68
|
-
{
|
|
69
|
-
if (e.ticks > this.lastVoiceEventTick)
|
|
70
|
-
{
|
|
71
|
-
this.lastVoiceEventTick = e.ticks;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// tempo, used channels, port
|
|
76
|
-
if (e.messageStatusByte === messageTypes.setTempo)
|
|
77
|
-
{
|
|
78
|
-
this.tempoChanges.push({
|
|
79
|
-
ticks: e.ticks,
|
|
80
|
-
tempo: 60000000 / readBytesAsUintBigEndian(
|
|
81
|
-
e.messageData,
|
|
82
|
-
3
|
|
83
|
-
)
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
else if ((e.messageStatusByte & 0xF0) === messageTypes.noteOn)
|
|
87
|
-
{
|
|
88
|
-
this.usedChannelsOnTrack[trackNum].add(e.messageData[0]);
|
|
89
|
-
}
|
|
90
|
-
else if (e.messageStatusByte === messageTypes.midiPort)
|
|
91
|
-
{
|
|
92
|
-
const port = e.messageData[0];
|
|
93
|
-
this.midiPorts[trackNum] = port;
|
|
94
|
-
if (this.midiPortChannelOffsets[port] === undefined)
|
|
95
|
-
{
|
|
96
|
-
this.midiPortChannelOffsets[port] = portOffset;
|
|
97
|
-
portOffset += 16;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
this.loop = { start: this.firstNoteOn, end: this.lastVoiceEventTick };
|
|
104
|
-
|
|
105
|
-
// reverse tempo and compute duration
|
|
106
|
-
this.tempoChanges.reverse();
|
|
107
|
-
this.duration = MIDIticksToSeconds(this.lastVoiceEventTick, this);
|
|
108
|
-
|
|
109
|
-
// fix midi ports:
|
|
110
|
-
// midi tracks without ports will have a value of -1
|
|
111
|
-
// if all ports have a value of -1, set it to 0, otherwise take the first midi port and replace all -1 with it
|
|
112
|
-
// why do this? some midis (for some reason) specify all channels to port 1 or else, but leave the conductor track with no port pref.
|
|
113
|
-
// this spessasynth to reserve the first 16 channels for the conductor track (which doesn't play anything) and use additional 16 for the actual ports.
|
|
114
|
-
let defaultP = 0;
|
|
115
|
-
for (let port of this.midiPorts)
|
|
116
|
-
{
|
|
117
|
-
if (port !== -1)
|
|
118
|
-
{
|
|
119
|
-
defaultP = port;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
this.midiPorts = this.midiPorts.map(port => port === -1 ? defaultP : port);
|
|
124
|
-
// add dummy port if empty
|
|
125
|
-
if (this.midiPortChannelOffsets.length === 0)
|
|
126
|
-
{
|
|
127
|
-
this.midiPortChannelOffsets = [0];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
26
|
/**
|
|
132
27
|
* Adds a new "set tempo" message
|
|
133
28
|
* @param ticks {number} the tick number of the event
|