midi-audio-player 1.0.3 → 1.1.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.
- package/README.md +19 -12
- package/dist/index.js +1610 -0
- package/dist/index.js.map +7 -0
- package/dist/index.mjs +18 -0
- package/dist/index.mjs.map +7 -0
- package/dist/midi-audio-player.js +1607 -0
- package/dist/midi-audio-player.js.map +7 -0
- package/dist/midi-audio-player.min.js +18 -0
- package/dist/midi-audio-player.min.js.map +7 -0
- package/package.json +42 -9
- package/src/midiaudioplayer.js +18 -18
- package/src/webaudiofontplayer.js +31 -32
- package/index.js +0 -1
- /package/src/{defaultinstrument.json → presets/defaultpreset.json} +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1610 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
|
|
3
|
+
██████╗ ██╗ █████╗ ███╗ ███╗███████╗████████╗██████╗ ██╗ ██████╗██╗ ██╗
|
|
4
|
+
██╔══██╗██║██╔══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗██║██╔════╝██║ ██╔╝
|
|
5
|
+
██║ ██║██║███████║██╔████╔██║█████╗ ██║ ██████╔╝██║██║ █████╔╝
|
|
6
|
+
██║ ██║██║██╔══██║██║╚██╔╝██║██╔══╝ ██║ ██╔══██╗██║██║ ██╔═██╗
|
|
7
|
+
██████╔╝██║██║ ██║██║ ╚═╝ ██║███████╗ ██║ ██║ ██║██║╚██████╗██║ ██╗
|
|
8
|
+
╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝
|
|
9
|
+
|
|
10
|
+
Version: 1.1.0
|
|
11
|
+
Généré: 2026-05-09 22:49:53
|
|
12
|
+
Auteur: Maxime Larrivée-Roy <mlarriveeroy@gmail.com>
|
|
13
|
+
Github: https://github.com/ZmotriN/midi-audio-player/
|
|
14
|
+
Website: https://zmotrin.github.io/midi-audio-player/
|
|
15
|
+
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// node_modules/midi-player-js/build/index.browser.js
|
|
19
|
+
function _classCallCheck(instance, Constructor) {
|
|
20
|
+
if (!(instance instanceof Constructor)) {
|
|
21
|
+
throw new TypeError("Cannot call a class as a function");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function _defineProperties(target, props) {
|
|
25
|
+
for (var i = 0; i < props.length; i++) {
|
|
26
|
+
var descriptor = props[i];
|
|
27
|
+
descriptor.enumerable = descriptor.enumerable || false;
|
|
28
|
+
descriptor.configurable = true;
|
|
29
|
+
if ("value" in descriptor) descriptor.writable = true;
|
|
30
|
+
Object.defineProperty(target, descriptor.key, descriptor);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function _createClass(Constructor, protoProps, staticProps) {
|
|
34
|
+
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
35
|
+
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
36
|
+
Object.defineProperty(Constructor, "prototype", {
|
|
37
|
+
writable: false
|
|
38
|
+
});
|
|
39
|
+
return Constructor;
|
|
40
|
+
}
|
|
41
|
+
var Constants = {
|
|
42
|
+
VERSION: "2.0.17",
|
|
43
|
+
NOTES: [],
|
|
44
|
+
HEADER_CHUNK_LENGTH: 14,
|
|
45
|
+
CIRCLE_OF_FOURTHS: ["C", "F", "Bb", "Eb", "Ab", "Db", "Gb", "Cb", "Fb", "Bbb", "Ebb", "Abb"],
|
|
46
|
+
CIRCLE_OF_FIFTHS: ["C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#", "E#"]
|
|
47
|
+
};
|
|
48
|
+
var allNotes = [["C"], ["C#", "Db"], ["D"], ["D#", "Eb"], ["E"], ["F"], ["F#", "Gb"], ["G"], ["G#", "Ab"], ["A"], ["A#", "Bb"], ["B"]];
|
|
49
|
+
var counter = 0;
|
|
50
|
+
var _loop = function _loop2(i) {
|
|
51
|
+
allNotes.forEach(function(noteGroup) {
|
|
52
|
+
noteGroup.forEach(function(note) {
|
|
53
|
+
return Constants.NOTES[counter] = note + i;
|
|
54
|
+
});
|
|
55
|
+
counter++;
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
for (i = -1; i <= 9; i++) {
|
|
59
|
+
_loop(i);
|
|
60
|
+
}
|
|
61
|
+
var i;
|
|
62
|
+
var Utils = /* @__PURE__ */ (function() {
|
|
63
|
+
function Utils2() {
|
|
64
|
+
_classCallCheck(this, Utils2);
|
|
65
|
+
}
|
|
66
|
+
_createClass(Utils2, null, [{
|
|
67
|
+
key: "byteToHex",
|
|
68
|
+
value: (
|
|
69
|
+
/**
|
|
70
|
+
* Converts a single byte to a hex string.
|
|
71
|
+
* @param {number} byte
|
|
72
|
+
* @return {string}
|
|
73
|
+
*/
|
|
74
|
+
function byteToHex(_byte) {
|
|
75
|
+
return ("0" + _byte.toString(16)).slice(-2);
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
/**
|
|
79
|
+
* Converts an array of bytes to a hex string.
|
|
80
|
+
* @param {array} byteArray
|
|
81
|
+
* @return {string}
|
|
82
|
+
*/
|
|
83
|
+
}, {
|
|
84
|
+
key: "bytesToHex",
|
|
85
|
+
value: function bytesToHex(byteArray) {
|
|
86
|
+
var hex = [];
|
|
87
|
+
byteArray.forEach(function(_byte2) {
|
|
88
|
+
return hex.push(Utils2.byteToHex(_byte2));
|
|
89
|
+
});
|
|
90
|
+
return hex.join("");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Converts a hex string to a number.
|
|
94
|
+
* @param {string} hexString
|
|
95
|
+
* @return {number}
|
|
96
|
+
*/
|
|
97
|
+
}, {
|
|
98
|
+
key: "hexToNumber",
|
|
99
|
+
value: function hexToNumber(hexString) {
|
|
100
|
+
return parseInt(hexString, 16);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Converts an array of bytes to a number.
|
|
104
|
+
* @param {array} byteArray
|
|
105
|
+
* @return {number}
|
|
106
|
+
*/
|
|
107
|
+
}, {
|
|
108
|
+
key: "bytesToNumber",
|
|
109
|
+
value: function bytesToNumber(byteArray) {
|
|
110
|
+
return Utils2.hexToNumber(Utils2.bytesToHex(byteArray));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Converts an array of bytes to letters.
|
|
114
|
+
* @param {array} byteArray
|
|
115
|
+
* @return {string}
|
|
116
|
+
*/
|
|
117
|
+
}, {
|
|
118
|
+
key: "bytesToLetters",
|
|
119
|
+
value: function bytesToLetters(byteArray) {
|
|
120
|
+
var letters = [];
|
|
121
|
+
byteArray.forEach(function(_byte3) {
|
|
122
|
+
return letters.push(String.fromCharCode(_byte3));
|
|
123
|
+
});
|
|
124
|
+
return letters.join("");
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Converts a decimal to it's binary representation.
|
|
128
|
+
* @param {number} dec
|
|
129
|
+
* @return {string}
|
|
130
|
+
*/
|
|
131
|
+
}, {
|
|
132
|
+
key: "decToBinary",
|
|
133
|
+
value: function decToBinary(dec) {
|
|
134
|
+
return (dec >>> 0).toString(2);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Determines the length in bytes of a variable length quaantity. The first byte in given range is assumed to be beginning of var length quantity.
|
|
138
|
+
* @param {array} byteArray
|
|
139
|
+
* @return {number}
|
|
140
|
+
*/
|
|
141
|
+
}, {
|
|
142
|
+
key: "getVarIntLength",
|
|
143
|
+
value: function getVarIntLength(byteArray) {
|
|
144
|
+
var currentByte = byteArray[0];
|
|
145
|
+
var byteCount = 1;
|
|
146
|
+
while (currentByte >= 128) {
|
|
147
|
+
currentByte = byteArray[byteCount];
|
|
148
|
+
byteCount++;
|
|
149
|
+
}
|
|
150
|
+
return byteCount;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Reads a variable length value.
|
|
154
|
+
* @param {array} byteArray
|
|
155
|
+
* @return {number}
|
|
156
|
+
*/
|
|
157
|
+
}, {
|
|
158
|
+
key: "readVarInt",
|
|
159
|
+
value: function readVarInt(byteArray) {
|
|
160
|
+
var result = 0;
|
|
161
|
+
byteArray.forEach(function(number) {
|
|
162
|
+
var b = number;
|
|
163
|
+
if (b & 128) {
|
|
164
|
+
result += b & 127;
|
|
165
|
+
result <<= 7;
|
|
166
|
+
} else {
|
|
167
|
+
result += b;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Decodes base-64 encoded string
|
|
174
|
+
* @param {string} string
|
|
175
|
+
* @return {string}
|
|
176
|
+
*/
|
|
177
|
+
}, {
|
|
178
|
+
key: "atob",
|
|
179
|
+
value: (function(_atob) {
|
|
180
|
+
function atob2(_x) {
|
|
181
|
+
return _atob.apply(this, arguments);
|
|
182
|
+
}
|
|
183
|
+
atob2.toString = function() {
|
|
184
|
+
return _atob.toString();
|
|
185
|
+
};
|
|
186
|
+
return atob2;
|
|
187
|
+
})(function(string) {
|
|
188
|
+
if (typeof atob === "function") return atob(string);
|
|
189
|
+
return Buffer.from(string, "base64").toString("binary");
|
|
190
|
+
})
|
|
191
|
+
}]);
|
|
192
|
+
return Utils2;
|
|
193
|
+
})();
|
|
194
|
+
var Track = /* @__PURE__ */ (function() {
|
|
195
|
+
function Track2(index2, data) {
|
|
196
|
+
_classCallCheck(this, Track2);
|
|
197
|
+
this.enabled = true;
|
|
198
|
+
this.eventIndex = 0;
|
|
199
|
+
this.pointer = 0;
|
|
200
|
+
this.lastTick = 0;
|
|
201
|
+
this.lastStatus = null;
|
|
202
|
+
this.index = index2;
|
|
203
|
+
this.data = data;
|
|
204
|
+
this.delta = 0;
|
|
205
|
+
this.runningDelta = 0;
|
|
206
|
+
this.events = [];
|
|
207
|
+
var lastThreeBytes = this.data.subarray(this.data.length - 3, this.data.length);
|
|
208
|
+
if (!(lastThreeBytes[0] === 255 && lastThreeBytes[1] === 47 && lastThreeBytes[2] === 0)) {
|
|
209
|
+
throw "Invalid MIDI file; Last three bytes of track " + this.index + "must be FF 2F 00 to mark end of track";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
_createClass(Track2, [{
|
|
213
|
+
key: "reset",
|
|
214
|
+
value: function reset() {
|
|
215
|
+
this.enabled = true;
|
|
216
|
+
this.eventIndex = 0;
|
|
217
|
+
this.pointer = 0;
|
|
218
|
+
this.lastTick = 0;
|
|
219
|
+
this.lastStatus = null;
|
|
220
|
+
this.delta = 0;
|
|
221
|
+
this.runningDelta = 0;
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Sets this track to be enabled during playback.
|
|
226
|
+
* @return {Track}
|
|
227
|
+
*/
|
|
228
|
+
}, {
|
|
229
|
+
key: "enable",
|
|
230
|
+
value: function enable() {
|
|
231
|
+
this.enabled = true;
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Sets this track to be disabled during playback.
|
|
236
|
+
* @return {Track}
|
|
237
|
+
*/
|
|
238
|
+
}, {
|
|
239
|
+
key: "disable",
|
|
240
|
+
value: function disable() {
|
|
241
|
+
this.enabled = false;
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Sets the track event index to the nearest event to the given tick.
|
|
246
|
+
* @param {number} tick
|
|
247
|
+
* @return {Track}
|
|
248
|
+
*/
|
|
249
|
+
}, {
|
|
250
|
+
key: "setEventIndexByTick",
|
|
251
|
+
value: function setEventIndexByTick(tick) {
|
|
252
|
+
tick = tick || 0;
|
|
253
|
+
for (var i = 0; i < this.events.length; i++) {
|
|
254
|
+
if (this.events[i].tick >= tick) {
|
|
255
|
+
this.eventIndex = i;
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Gets byte located at pointer position.
|
|
262
|
+
* @return {number}
|
|
263
|
+
*/
|
|
264
|
+
}, {
|
|
265
|
+
key: "getCurrentByte",
|
|
266
|
+
value: function getCurrentByte() {
|
|
267
|
+
return this.data[this.pointer];
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Gets count of delta bytes and current pointer position.
|
|
271
|
+
* @return {number}
|
|
272
|
+
*/
|
|
273
|
+
}, {
|
|
274
|
+
key: "getDeltaByteCount",
|
|
275
|
+
value: function getDeltaByteCount() {
|
|
276
|
+
return Utils.getVarIntLength(this.data.subarray(this.pointer));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Get delta value at current pointer position.
|
|
280
|
+
* @return {number}
|
|
281
|
+
*/
|
|
282
|
+
}, {
|
|
283
|
+
key: "getDelta",
|
|
284
|
+
value: function getDelta() {
|
|
285
|
+
return Utils.readVarInt(this.data.subarray(this.pointer, this.pointer + this.getDeltaByteCount()));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Handles event within a given track starting at specified index
|
|
289
|
+
* @param {number} currentTick
|
|
290
|
+
* @param {boolean} dryRun - If true events will be parsed and returned regardless of time.
|
|
291
|
+
*/
|
|
292
|
+
}, {
|
|
293
|
+
key: "handleEvent",
|
|
294
|
+
value: function handleEvent(currentTick, dryRun) {
|
|
295
|
+
dryRun = dryRun || false;
|
|
296
|
+
if (dryRun) {
|
|
297
|
+
var elapsedTicks = currentTick - this.lastTick;
|
|
298
|
+
var delta = this.getDelta();
|
|
299
|
+
var eventReady = elapsedTicks >= delta;
|
|
300
|
+
if (this.pointer < this.data.length && (dryRun || eventReady)) {
|
|
301
|
+
var event = this.parseEvent();
|
|
302
|
+
if (this.enabled) return event;
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
var events = [];
|
|
306
|
+
while (this.events[this.eventIndex] && this.events[this.eventIndex].tick <= currentTick) {
|
|
307
|
+
if (this.enabled) events.push(this.events[this.eventIndex]);
|
|
308
|
+
this.eventIndex++;
|
|
309
|
+
}
|
|
310
|
+
if (events.length > 0) return events;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Get string data from event.
|
|
316
|
+
* @param {number} eventStartIndex
|
|
317
|
+
* @return {string}
|
|
318
|
+
*/
|
|
319
|
+
}, {
|
|
320
|
+
key: "getStringData",
|
|
321
|
+
value: function getStringData(eventStartIndex) {
|
|
322
|
+
var varIntLength = Utils.getVarIntLength(this.data.subarray(eventStartIndex + 2));
|
|
323
|
+
var varIntValue = Utils.readVarInt(this.data.subarray(eventStartIndex + 2, eventStartIndex + 2 + varIntLength));
|
|
324
|
+
var letters = Utils.bytesToLetters(this.data.subarray(eventStartIndex + 2 + varIntLength, eventStartIndex + 2 + varIntLength + varIntValue));
|
|
325
|
+
return letters;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Parses event into JSON and advances pointer for the track
|
|
329
|
+
* @return {object}
|
|
330
|
+
*/
|
|
331
|
+
}, {
|
|
332
|
+
key: "parseEvent",
|
|
333
|
+
value: function parseEvent() {
|
|
334
|
+
var eventStartIndex = this.pointer + this.getDeltaByteCount();
|
|
335
|
+
var eventJson = {};
|
|
336
|
+
var deltaByteCount = this.getDeltaByteCount();
|
|
337
|
+
eventJson.track = this.index + 1;
|
|
338
|
+
eventJson.delta = this.getDelta();
|
|
339
|
+
this.lastTick = this.lastTick + eventJson.delta;
|
|
340
|
+
this.runningDelta += eventJson.delta;
|
|
341
|
+
eventJson.tick = this.runningDelta;
|
|
342
|
+
eventJson.byteIndex = this.pointer;
|
|
343
|
+
if (this.data[eventStartIndex] == 255) {
|
|
344
|
+
switch (this.data[eventStartIndex + 1]) {
|
|
345
|
+
case 0:
|
|
346
|
+
eventJson.name = "Sequence Number";
|
|
347
|
+
break;
|
|
348
|
+
case 1:
|
|
349
|
+
eventJson.name = "Text Event";
|
|
350
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
351
|
+
break;
|
|
352
|
+
case 2:
|
|
353
|
+
eventJson.name = "Copyright Notice";
|
|
354
|
+
break;
|
|
355
|
+
case 3:
|
|
356
|
+
eventJson.name = "Sequence/Track Name";
|
|
357
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
358
|
+
break;
|
|
359
|
+
case 4:
|
|
360
|
+
eventJson.name = "Instrument Name";
|
|
361
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
362
|
+
break;
|
|
363
|
+
case 5:
|
|
364
|
+
eventJson.name = "Lyric";
|
|
365
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
366
|
+
break;
|
|
367
|
+
case 6:
|
|
368
|
+
eventJson.name = "Marker";
|
|
369
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
370
|
+
break;
|
|
371
|
+
case 7:
|
|
372
|
+
eventJson.name = "Cue Point";
|
|
373
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
374
|
+
break;
|
|
375
|
+
case 9:
|
|
376
|
+
eventJson.name = "Device Name";
|
|
377
|
+
eventJson.string = this.getStringData(eventStartIndex);
|
|
378
|
+
break;
|
|
379
|
+
case 32:
|
|
380
|
+
eventJson.name = "MIDI Channel Prefix";
|
|
381
|
+
break;
|
|
382
|
+
case 33:
|
|
383
|
+
eventJson.name = "MIDI Port";
|
|
384
|
+
eventJson.data = Utils.bytesToNumber([this.data[eventStartIndex + 3]]);
|
|
385
|
+
break;
|
|
386
|
+
case 47:
|
|
387
|
+
eventJson.name = "End of Track";
|
|
388
|
+
break;
|
|
389
|
+
case 81:
|
|
390
|
+
eventJson.name = "Set Tempo";
|
|
391
|
+
eventJson.data = Math.round(6e7 / Utils.bytesToNumber(this.data.subarray(eventStartIndex + 3, eventStartIndex + 6)));
|
|
392
|
+
this.tempo = eventJson.data;
|
|
393
|
+
break;
|
|
394
|
+
case 84:
|
|
395
|
+
eventJson.name = "SMTPE Offset";
|
|
396
|
+
break;
|
|
397
|
+
case 88:
|
|
398
|
+
eventJson.name = "Time Signature";
|
|
399
|
+
eventJson.data = this.data.subarray(eventStartIndex + 3, eventStartIndex + 7);
|
|
400
|
+
eventJson.timeSignature = "" + eventJson.data[0] + "/" + Math.pow(2, eventJson.data[1]);
|
|
401
|
+
break;
|
|
402
|
+
case 89:
|
|
403
|
+
eventJson.name = "Key Signature";
|
|
404
|
+
eventJson.data = this.data.subarray(eventStartIndex + 3, eventStartIndex + 5);
|
|
405
|
+
var sf = eventJson.data[0] > 127 ? eventJson.data[0] - 256 : eventJson.data[0];
|
|
406
|
+
if (sf >= 0) {
|
|
407
|
+
eventJson.keySignature = Constants.CIRCLE_OF_FIFTHS[sf];
|
|
408
|
+
} else {
|
|
409
|
+
eventJson.keySignature = Constants.CIRCLE_OF_FOURTHS[Math.abs(sf)];
|
|
410
|
+
}
|
|
411
|
+
if (eventJson.data[1] == 0) {
|
|
412
|
+
eventJson.keySignature += " Major";
|
|
413
|
+
} else if (eventJson.data[1] == 1) {
|
|
414
|
+
eventJson.keySignature += " Minor";
|
|
415
|
+
}
|
|
416
|
+
break;
|
|
417
|
+
case 127:
|
|
418
|
+
eventJson.name = "Sequencer-Specific Meta-event";
|
|
419
|
+
break;
|
|
420
|
+
default:
|
|
421
|
+
eventJson.name = "Unknown: " + this.data[eventStartIndex + 1].toString(16);
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
var varIntLength = Utils.getVarIntLength(this.data.subarray(eventStartIndex + 2));
|
|
425
|
+
var length = Utils.readVarInt(this.data.subarray(eventStartIndex + 2, eventStartIndex + 2 + varIntLength));
|
|
426
|
+
this.pointer += deltaByteCount + 2 + varIntLength + length;
|
|
427
|
+
} else if (this.data[eventStartIndex] === 240) {
|
|
428
|
+
eventJson.name = "Sysex";
|
|
429
|
+
var varQuantityByteLength = Utils.getVarIntLength(this.data.subarray(eventStartIndex + 1));
|
|
430
|
+
var varQuantityByteValue = Utils.readVarInt(this.data.subarray(eventStartIndex + 1, eventStartIndex + 1 + varQuantityByteLength));
|
|
431
|
+
eventJson.data = this.data.subarray(eventStartIndex + 1 + varQuantityByteLength, eventStartIndex + 1 + varQuantityByteLength + varQuantityByteValue);
|
|
432
|
+
this.pointer += deltaByteCount + 1 + varQuantityByteLength + varQuantityByteValue;
|
|
433
|
+
} else if (this.data[eventStartIndex] === 247) {
|
|
434
|
+
eventJson.name = "Sysex (escape)";
|
|
435
|
+
var _varQuantityByteLength = Utils.getVarIntLength(this.data.subarray(eventStartIndex + 1));
|
|
436
|
+
var _varQuantityByteValue = Utils.readVarInt(this.data.subarray(eventStartIndex + 1, eventStartIndex + 1 + _varQuantityByteLength));
|
|
437
|
+
eventJson.data = this.data.subarray(eventStartIndex + 1 + _varQuantityByteLength, eventStartIndex + 1 + _varQuantityByteLength + _varQuantityByteValue);
|
|
438
|
+
this.pointer += deltaByteCount + 1 + _varQuantityByteLength + _varQuantityByteValue;
|
|
439
|
+
} else {
|
|
440
|
+
if (this.data[eventStartIndex] < 128) {
|
|
441
|
+
eventJson.running = true;
|
|
442
|
+
eventJson.noteNumber = this.data[eventStartIndex];
|
|
443
|
+
eventJson.noteName = Constants.NOTES[this.data[eventStartIndex]];
|
|
444
|
+
eventJson.velocity = this.data[eventStartIndex + 1];
|
|
445
|
+
if (this.lastStatus <= 143) {
|
|
446
|
+
eventJson.name = "Note off";
|
|
447
|
+
eventJson.channel = this.lastStatus - 128 + 1;
|
|
448
|
+
this.pointer += deltaByteCount + 2;
|
|
449
|
+
} else if (this.lastStatus <= 159) {
|
|
450
|
+
eventJson.name = "Note on";
|
|
451
|
+
eventJson.channel = this.lastStatus - 144 + 1;
|
|
452
|
+
this.pointer += deltaByteCount + 2;
|
|
453
|
+
} else if (this.lastStatus <= 175) {
|
|
454
|
+
eventJson.name = "Polyphonic Key Pressure";
|
|
455
|
+
eventJson.channel = this.lastStatus - 160 + 1;
|
|
456
|
+
eventJson.note = Constants.NOTES[this.data[eventStartIndex]];
|
|
457
|
+
eventJson.pressure = this.data[eventStartIndex + 1];
|
|
458
|
+
this.pointer += deltaByteCount + 2;
|
|
459
|
+
} else if (this.lastStatus <= 191) {
|
|
460
|
+
eventJson.name = "Controller Change";
|
|
461
|
+
eventJson.channel = this.lastStatus - 176 + 1;
|
|
462
|
+
eventJson.number = this.data[eventStartIndex];
|
|
463
|
+
eventJson.value = this.data[eventStartIndex + 1];
|
|
464
|
+
this.pointer += deltaByteCount + 2;
|
|
465
|
+
} else if (this.lastStatus <= 207) {
|
|
466
|
+
eventJson.name = "Program Change";
|
|
467
|
+
eventJson.channel = this.lastStatus - 192 + 1;
|
|
468
|
+
eventJson.value = this.data[eventStartIndex + 1];
|
|
469
|
+
this.pointer += deltaByteCount + 1;
|
|
470
|
+
} else if (this.lastStatus <= 223) {
|
|
471
|
+
eventJson.name = "Channel Key Pressure";
|
|
472
|
+
eventJson.channel = this.lastStatus - 208 + 1;
|
|
473
|
+
this.pointer += deltaByteCount + 1;
|
|
474
|
+
} else if (this.lastStatus <= 239) {
|
|
475
|
+
eventJson.name = "Pitch Bend";
|
|
476
|
+
eventJson.channel = this.lastStatus - 224 + 1;
|
|
477
|
+
eventJson.value = (this.data[eventStartIndex + 1] & 127) << 7 | this.data[eventStartIndex] & 127;
|
|
478
|
+
this.pointer += deltaByteCount + 2;
|
|
479
|
+
} else {
|
|
480
|
+
throw "Unknown event (running): ".concat(this.lastStatus);
|
|
481
|
+
}
|
|
482
|
+
} else {
|
|
483
|
+
this.lastStatus = this.data[eventStartIndex];
|
|
484
|
+
if (this.data[eventStartIndex] <= 143) {
|
|
485
|
+
eventJson.name = "Note off";
|
|
486
|
+
eventJson.channel = this.lastStatus - 128 + 1;
|
|
487
|
+
eventJson.noteNumber = this.data[eventStartIndex + 1];
|
|
488
|
+
eventJson.noteName = Constants.NOTES[this.data[eventStartIndex + 1]];
|
|
489
|
+
eventJson.velocity = Math.round(this.data[eventStartIndex + 2] / 127 * 100);
|
|
490
|
+
this.pointer += deltaByteCount + 3;
|
|
491
|
+
} else if (this.data[eventStartIndex] <= 159) {
|
|
492
|
+
eventJson.name = "Note on";
|
|
493
|
+
eventJson.channel = this.lastStatus - 144 + 1;
|
|
494
|
+
eventJson.noteNumber = this.data[eventStartIndex + 1];
|
|
495
|
+
eventJson.noteName = Constants.NOTES[this.data[eventStartIndex + 1]];
|
|
496
|
+
eventJson.velocity = Math.round(this.data[eventStartIndex + 2] / 127 * 100);
|
|
497
|
+
this.pointer += deltaByteCount + 3;
|
|
498
|
+
} else if (this.data[eventStartIndex] <= 175) {
|
|
499
|
+
eventJson.name = "Polyphonic Key Pressure";
|
|
500
|
+
eventJson.channel = this.lastStatus - 160 + 1;
|
|
501
|
+
eventJson.note = Constants.NOTES[this.data[eventStartIndex + 1]];
|
|
502
|
+
eventJson.pressure = this.data[eventStartIndex + 2];
|
|
503
|
+
this.pointer += deltaByteCount + 3;
|
|
504
|
+
} else if (this.data[eventStartIndex] <= 191) {
|
|
505
|
+
eventJson.name = "Controller Change";
|
|
506
|
+
eventJson.channel = this.lastStatus - 176 + 1;
|
|
507
|
+
eventJson.number = this.data[eventStartIndex + 1];
|
|
508
|
+
eventJson.value = this.data[eventStartIndex + 2];
|
|
509
|
+
this.pointer += deltaByteCount + 3;
|
|
510
|
+
} else if (this.data[eventStartIndex] <= 207) {
|
|
511
|
+
eventJson.name = "Program Change";
|
|
512
|
+
eventJson.channel = this.lastStatus - 192 + 1;
|
|
513
|
+
eventJson.value = this.data[eventStartIndex + 1];
|
|
514
|
+
this.pointer += deltaByteCount + 2;
|
|
515
|
+
} else if (this.data[eventStartIndex] <= 223) {
|
|
516
|
+
eventJson.name = "Channel Key Pressure";
|
|
517
|
+
eventJson.channel = this.lastStatus - 208 + 1;
|
|
518
|
+
this.pointer += deltaByteCount + 2;
|
|
519
|
+
} else if (this.data[eventStartIndex] <= 239) {
|
|
520
|
+
eventJson.name = "Pitch Bend";
|
|
521
|
+
eventJson.channel = this.lastStatus - 224 + 1;
|
|
522
|
+
eventJson.value = (this.data[eventStartIndex + 2] & 127) << 7 | this.data[eventStartIndex + 1] & 127;
|
|
523
|
+
this.pointer += deltaByteCount + 3;
|
|
524
|
+
} else {
|
|
525
|
+
throw "Unknown event: ".concat(this.data[eventStartIndex]);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
this.delta += eventJson.delta;
|
|
530
|
+
this.events.push(eventJson);
|
|
531
|
+
return eventJson;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Returns true if pointer has reached the end of the track.
|
|
535
|
+
* @param {boolean}
|
|
536
|
+
*/
|
|
537
|
+
}, {
|
|
538
|
+
key: "endOfTrack",
|
|
539
|
+
value: function endOfTrack() {
|
|
540
|
+
if (this.data[this.pointer + 1] == 255 && this.data[this.pointer + 2] == 47 && this.data[this.pointer + 3] == 0) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
}]);
|
|
546
|
+
return Track2;
|
|
547
|
+
})();
|
|
548
|
+
if (!Uint8Array.prototype.forEach) {
|
|
549
|
+
Object.defineProperty(Uint8Array.prototype, "forEach", {
|
|
550
|
+
value: Array.prototype.forEach
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
var Player = /* @__PURE__ */ (function() {
|
|
554
|
+
function Player2(eventHandler, buffer) {
|
|
555
|
+
_classCallCheck(this, Player2);
|
|
556
|
+
this.sampleRate = 5;
|
|
557
|
+
this.startTime = 0;
|
|
558
|
+
this.buffer = buffer || null;
|
|
559
|
+
this.midiChunksByteLength = null;
|
|
560
|
+
this.division;
|
|
561
|
+
this.format;
|
|
562
|
+
this.setTimeoutId = false;
|
|
563
|
+
this.scheduledTime = 0;
|
|
564
|
+
this.tracks = [];
|
|
565
|
+
this.instruments = [];
|
|
566
|
+
this.defaultTempo = 120;
|
|
567
|
+
this.tempo = null;
|
|
568
|
+
this.startTick = 0;
|
|
569
|
+
this.tick = 0;
|
|
570
|
+
this.lastTick = null;
|
|
571
|
+
this.inLoop = false;
|
|
572
|
+
this.totalTicks = 0;
|
|
573
|
+
this.events = [];
|
|
574
|
+
this.totalEvents = 0;
|
|
575
|
+
this.tempoMap = [];
|
|
576
|
+
this.eventListeners = {};
|
|
577
|
+
if (typeof eventHandler === "function") this.on("midiEvent", eventHandler);
|
|
578
|
+
}
|
|
579
|
+
_createClass(Player2, [{
|
|
580
|
+
key: "loadFile",
|
|
581
|
+
value: function loadFile(path) {
|
|
582
|
+
{
|
|
583
|
+
throw "loadFile is only supported on Node.js";
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Load an array buffer into the player.
|
|
588
|
+
* @param {array} arrayBuffer - Array buffer of file to be loaded.
|
|
589
|
+
* @return {Player}
|
|
590
|
+
*/
|
|
591
|
+
}, {
|
|
592
|
+
key: "loadArrayBuffer",
|
|
593
|
+
value: function loadArrayBuffer(arrayBuffer) {
|
|
594
|
+
this.buffer = new Uint8Array(arrayBuffer);
|
|
595
|
+
return this.fileLoaded();
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Load a data URI into the player.
|
|
599
|
+
* @param {string} dataUri - Data URI to be loaded.
|
|
600
|
+
* @return {Player}
|
|
601
|
+
*/
|
|
602
|
+
}, {
|
|
603
|
+
key: "loadDataUri",
|
|
604
|
+
value: function loadDataUri(dataUri) {
|
|
605
|
+
var byteString = Utils.atob(dataUri.split(",")[1]);
|
|
606
|
+
var ia = new Uint8Array(byteString.length);
|
|
607
|
+
for (var i = 0; i < byteString.length; i++) {
|
|
608
|
+
ia[i] = byteString.charCodeAt(i);
|
|
609
|
+
}
|
|
610
|
+
this.buffer = ia;
|
|
611
|
+
return this.fileLoaded();
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get filesize of loaded file in number of bytes.
|
|
615
|
+
* @return {number} - The filesize.
|
|
616
|
+
*/
|
|
617
|
+
}, {
|
|
618
|
+
key: "getFilesize",
|
|
619
|
+
value: function getFilesize() {
|
|
620
|
+
return this.buffer ? this.buffer.length : 0;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Sets default tempo, parses file for necessary information, and does a dry run to calculate total length.
|
|
624
|
+
* Populates this.events & this.totalTicks.
|
|
625
|
+
* @return {Player}
|
|
626
|
+
*/
|
|
627
|
+
}, {
|
|
628
|
+
key: "fileLoaded",
|
|
629
|
+
value: function fileLoaded() {
|
|
630
|
+
if (!this.validate()) throw "Invalid MIDI file; should start with MThd";
|
|
631
|
+
this.defaultTempo = 120;
|
|
632
|
+
return this.setTempo(this.defaultTempo).getDivision().getFormat().getTracks().dryRun();
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Validates file using simple means - first four bytes should == MThd.
|
|
636
|
+
* @return {boolean}
|
|
637
|
+
*/
|
|
638
|
+
}, {
|
|
639
|
+
key: "validate",
|
|
640
|
+
value: function validate() {
|
|
641
|
+
return Utils.bytesToLetters(this.buffer.subarray(0, 4)) === "MThd";
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Gets MIDI file format for loaded file.
|
|
645
|
+
* @return {Player}
|
|
646
|
+
*/
|
|
647
|
+
}, {
|
|
648
|
+
key: "getFormat",
|
|
649
|
+
value: function getFormat() {
|
|
650
|
+
this.format = Utils.bytesToNumber(this.buffer.subarray(8, 10));
|
|
651
|
+
return this;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Parses out tracks, places them in this.tracks and initializes this.pointers
|
|
655
|
+
* @return {Player}
|
|
656
|
+
*/
|
|
657
|
+
}, {
|
|
658
|
+
key: "getTracks",
|
|
659
|
+
value: function getTracks() {
|
|
660
|
+
this.tracks = [];
|
|
661
|
+
var trackOffset = 0;
|
|
662
|
+
while (trackOffset < this.buffer.length) {
|
|
663
|
+
if (Utils.bytesToLetters(this.buffer.subarray(trackOffset, trackOffset + 4)) == "MTrk") {
|
|
664
|
+
var trackLength = Utils.bytesToNumber(this.buffer.subarray(trackOffset + 4, trackOffset + 8));
|
|
665
|
+
this.tracks.push(new Track(this.tracks.length, this.buffer.subarray(trackOffset + 8, trackOffset + 8 + trackLength)));
|
|
666
|
+
}
|
|
667
|
+
trackOffset += Utils.bytesToNumber(this.buffer.subarray(trackOffset + 4, trackOffset + 8)) + 8;
|
|
668
|
+
}
|
|
669
|
+
var trackChunksByteLength = 0;
|
|
670
|
+
this.tracks.forEach(function(track) {
|
|
671
|
+
trackChunksByteLength += 8 + track.data.length;
|
|
672
|
+
});
|
|
673
|
+
this.midiChunksByteLength = Constants.HEADER_CHUNK_LENGTH + trackChunksByteLength;
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Enables a track for playing.
|
|
678
|
+
* @param {number} trackNumber - Track number
|
|
679
|
+
* @return {Player}
|
|
680
|
+
*/
|
|
681
|
+
}, {
|
|
682
|
+
key: "enableTrack",
|
|
683
|
+
value: function enableTrack(trackNumber) {
|
|
684
|
+
this.tracks[trackNumber - 1].enable();
|
|
685
|
+
return this;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Disables a track for playing.
|
|
689
|
+
* @param {number} - Track number
|
|
690
|
+
* @return {Player}
|
|
691
|
+
*/
|
|
692
|
+
}, {
|
|
693
|
+
key: "disableTrack",
|
|
694
|
+
value: function disableTrack(trackNumber) {
|
|
695
|
+
this.tracks[trackNumber - 1].disable();
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Gets quarter note division of loaded MIDI file.
|
|
700
|
+
* @return {Player}
|
|
701
|
+
*/
|
|
702
|
+
}, {
|
|
703
|
+
key: "getDivision",
|
|
704
|
+
value: function getDivision() {
|
|
705
|
+
this.division = Utils.bytesToNumber(this.buffer.subarray(12, Constants.HEADER_CHUNK_LENGTH));
|
|
706
|
+
return this;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* The main play loop.
|
|
710
|
+
* @param {boolean} - Indicates whether or not this is being called simply for parsing purposes. Disregards timing if so.
|
|
711
|
+
* @return {undefined}
|
|
712
|
+
*/
|
|
713
|
+
}, {
|
|
714
|
+
key: "playLoop",
|
|
715
|
+
value: function playLoop(dryRun) {
|
|
716
|
+
if (!this.inLoop) {
|
|
717
|
+
this.inLoop = true;
|
|
718
|
+
this.tick = this.getCurrentTick();
|
|
719
|
+
this.tracks.forEach(function(track, index2) {
|
|
720
|
+
if (!dryRun && this.endOfFile()) {
|
|
721
|
+
this.stop();
|
|
722
|
+
this.triggerPlayerEvent("endOfFile");
|
|
723
|
+
} else {
|
|
724
|
+
var result = track.handleEvent(this.tick, dryRun);
|
|
725
|
+
if (dryRun && result) {
|
|
726
|
+
if (result.hasOwnProperty("name") && result.name === "Set Tempo") {
|
|
727
|
+
this.setTempo(result.data);
|
|
728
|
+
}
|
|
729
|
+
if (result.hasOwnProperty("name") && result.name === "Program Change") {
|
|
730
|
+
if (!this.instruments.includes(result.value)) {
|
|
731
|
+
this.instruments.push(result.value);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
} else if (result) {
|
|
735
|
+
var events = Array.isArray(result) ? result : [result];
|
|
736
|
+
events.forEach(function(event) {
|
|
737
|
+
if (event.hasOwnProperty("name") && event.name === "Set Tempo") {
|
|
738
|
+
this.setTempo(event.data);
|
|
739
|
+
}
|
|
740
|
+
this.emitEvent(event);
|
|
741
|
+
}, this);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}, this);
|
|
745
|
+
if (!dryRun && this.isPlaying()) this.triggerPlayerEvent("playing", {
|
|
746
|
+
tick: this.tick
|
|
747
|
+
});
|
|
748
|
+
this.inLoop = false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Setter for tempo.
|
|
753
|
+
* @param {number} - Tempo in bpm (defaults to 120)
|
|
754
|
+
*/
|
|
755
|
+
}, {
|
|
756
|
+
key: "setTempo",
|
|
757
|
+
value: function setTempo(tempo) {
|
|
758
|
+
this.tempo = tempo;
|
|
759
|
+
return this;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Setter for startTime.
|
|
763
|
+
* @param {number} - UTC timestamp
|
|
764
|
+
* @return {Player}
|
|
765
|
+
*/
|
|
766
|
+
}, {
|
|
767
|
+
key: "setStartTime",
|
|
768
|
+
value: function setStartTime(startTime) {
|
|
769
|
+
this.startTime = startTime;
|
|
770
|
+
return this;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Start playing loaded MIDI file if not already playing.
|
|
774
|
+
* @return {Player}
|
|
775
|
+
*/
|
|
776
|
+
}, {
|
|
777
|
+
key: "play",
|
|
778
|
+
value: function play() {
|
|
779
|
+
if (this.isPlaying()) throw "Already playing...";
|
|
780
|
+
if (!this.startTime) this.startTime = (/* @__PURE__ */ new Date()).getTime();
|
|
781
|
+
this.scheduledTime = Date.now();
|
|
782
|
+
this.schedulePlayLoop(this.sampleRate);
|
|
783
|
+
return this;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Schedules the next play loop iteration, correcting for timer drift.
|
|
787
|
+
* @param {number} delay - Delay in milliseconds before next iteration.
|
|
788
|
+
* @return {undefined}
|
|
789
|
+
*/
|
|
790
|
+
}, {
|
|
791
|
+
key: "schedulePlayLoop",
|
|
792
|
+
value: function schedulePlayLoop(delay) {
|
|
793
|
+
var _this = this;
|
|
794
|
+
this.setTimeoutId = setTimeout(function() {
|
|
795
|
+
_this.playLoop();
|
|
796
|
+
if (_this.setTimeoutId !== false) {
|
|
797
|
+
_this.scheduledTime += _this.sampleRate;
|
|
798
|
+
var drift = Date.now() - _this.scheduledTime;
|
|
799
|
+
_this.schedulePlayLoop(Math.max(0, _this.sampleRate - drift));
|
|
800
|
+
}
|
|
801
|
+
}, delay);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Pauses playback if playing.
|
|
805
|
+
* @return {Player}
|
|
806
|
+
*/
|
|
807
|
+
}, {
|
|
808
|
+
key: "pause",
|
|
809
|
+
value: function pause() {
|
|
810
|
+
clearTimeout(this.setTimeoutId);
|
|
811
|
+
this.setTimeoutId = false;
|
|
812
|
+
this.scheduledTime = 0;
|
|
813
|
+
this.startTick = this.tick;
|
|
814
|
+
this.startTime = 0;
|
|
815
|
+
return this;
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Stops playback if playing.
|
|
819
|
+
* @return {Player}
|
|
820
|
+
*/
|
|
821
|
+
}, {
|
|
822
|
+
key: "stop",
|
|
823
|
+
value: function stop() {
|
|
824
|
+
clearTimeout(this.setTimeoutId);
|
|
825
|
+
this.setTimeoutId = false;
|
|
826
|
+
this.scheduledTime = 0;
|
|
827
|
+
this.startTick = 0;
|
|
828
|
+
this.startTime = 0;
|
|
829
|
+
this.resetTracks();
|
|
830
|
+
return this;
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Skips player pointer to specified tick.
|
|
834
|
+
* @param {number} - Tick to skip to.
|
|
835
|
+
* @return {Player}
|
|
836
|
+
*/
|
|
837
|
+
}, {
|
|
838
|
+
key: "skipToTick",
|
|
839
|
+
value: function skipToTick(tick) {
|
|
840
|
+
this.stop();
|
|
841
|
+
this.startTick = tick;
|
|
842
|
+
for (var i = this.tempoMap.length - 1; i >= 0; i--) {
|
|
843
|
+
if (this.tempoMap[i].tick <= tick) {
|
|
844
|
+
this.setTempo(this.tempoMap[i].tempo);
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
this.collectStateAtTick(tick).forEach(function(event) {
|
|
849
|
+
this.emitEvent(event);
|
|
850
|
+
}, this);
|
|
851
|
+
this.tracks.forEach(function(track) {
|
|
852
|
+
track.setEventIndexByTick(tick);
|
|
853
|
+
});
|
|
854
|
+
return this;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Collects the last state-changing MIDI events (Program Change, Controller Change, Pitch Bend)
|
|
858
|
+
* before the specified tick across all tracks.
|
|
859
|
+
* @param {number} tick - Target tick to collect state up to.
|
|
860
|
+
* @return {array} - Array of state events representing the MIDI state at the target tick.
|
|
861
|
+
*/
|
|
862
|
+
}, {
|
|
863
|
+
key: "collectStateAtTick",
|
|
864
|
+
value: function collectStateAtTick(tick) {
|
|
865
|
+
var dominated = {};
|
|
866
|
+
this.events.forEach(function(trackEvents) {
|
|
867
|
+
trackEvents.forEach(function(event) {
|
|
868
|
+
if (event.tick >= tick) return;
|
|
869
|
+
var key;
|
|
870
|
+
if (event.name === "Program Change") {
|
|
871
|
+
key = "pc:" + event.channel;
|
|
872
|
+
} else if (event.name === "Controller Change") {
|
|
873
|
+
key = "cc:" + event.channel + ":" + event.number;
|
|
874
|
+
} else if (event.name === "Pitch Bend") {
|
|
875
|
+
key = "pb:" + event.channel;
|
|
876
|
+
}
|
|
877
|
+
if (key) {
|
|
878
|
+
dominated[key] = event;
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
return Object.keys(dominated).map(function(key) {
|
|
883
|
+
return dominated[key];
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Skips player pointer to specified percentage.
|
|
888
|
+
* @param {number} - Percent value in integer format.
|
|
889
|
+
* @return {Player}
|
|
890
|
+
*/
|
|
891
|
+
}, {
|
|
892
|
+
key: "skipToPercent",
|
|
893
|
+
value: function skipToPercent(percent) {
|
|
894
|
+
if (percent < 0 || percent > 100) throw "Percent must be number between 1 and 100.";
|
|
895
|
+
this.skipToTick(Math.round(percent / 100 * this.totalTicks));
|
|
896
|
+
return this;
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Skips player pointer to specified seconds.
|
|
900
|
+
* @param {number} - Seconds to skip to.
|
|
901
|
+
* @return {Player}
|
|
902
|
+
*/
|
|
903
|
+
}, {
|
|
904
|
+
key: "skipToSeconds",
|
|
905
|
+
value: function skipToSeconds(seconds) {
|
|
906
|
+
var songTime = this.getSongTime();
|
|
907
|
+
if (seconds < 0 || seconds > songTime) throw seconds + " seconds not within song time of " + songTime;
|
|
908
|
+
this.skipToTick(this.secondsToTicks(seconds));
|
|
909
|
+
return this;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Checks if player is playing
|
|
913
|
+
* @return {boolean}
|
|
914
|
+
*/
|
|
915
|
+
}, {
|
|
916
|
+
key: "isPlaying",
|
|
917
|
+
value: function isPlaying() {
|
|
918
|
+
return this.setTimeoutId !== false;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Plays the loaded MIDI file without regard for timing and saves events in this.events. Essentially used as a parser.
|
|
922
|
+
* @return {Player}
|
|
923
|
+
*/
|
|
924
|
+
}, {
|
|
925
|
+
key: "dryRun",
|
|
926
|
+
value: function dryRun() {
|
|
927
|
+
this.resetTracks();
|
|
928
|
+
while (!this.endOfFile()) {
|
|
929
|
+
this.playLoop(true);
|
|
930
|
+
}
|
|
931
|
+
this.events = this.getEvents();
|
|
932
|
+
this.totalEvents = this.getTotalEvents();
|
|
933
|
+
this.totalTicks = this.getTotalTicks();
|
|
934
|
+
this.buildTempoMap();
|
|
935
|
+
this.startTick = 0;
|
|
936
|
+
this.startTime = 0;
|
|
937
|
+
this.resetTracks();
|
|
938
|
+
this.triggerPlayerEvent("fileLoaded", this);
|
|
939
|
+
return this;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Resets play pointers for all tracks.
|
|
943
|
+
* @return {Player}
|
|
944
|
+
*/
|
|
945
|
+
}, {
|
|
946
|
+
key: "resetTracks",
|
|
947
|
+
value: function resetTracks() {
|
|
948
|
+
this.tracks.forEach(function(track) {
|
|
949
|
+
return track.reset();
|
|
950
|
+
});
|
|
951
|
+
return this;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Gets an array of events grouped by track.
|
|
955
|
+
* @return {array}
|
|
956
|
+
*/
|
|
957
|
+
}, {
|
|
958
|
+
key: "getEvents",
|
|
959
|
+
value: function getEvents() {
|
|
960
|
+
return this.tracks.map(function(track) {
|
|
961
|
+
return track.events;
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Gets total number of ticks in the loaded MIDI file.
|
|
966
|
+
* @return {number}
|
|
967
|
+
*/
|
|
968
|
+
}, {
|
|
969
|
+
key: "getTotalTicks",
|
|
970
|
+
value: function getTotalTicks() {
|
|
971
|
+
return Math.max.apply(null, this.tracks.map(function(track) {
|
|
972
|
+
return track.delta;
|
|
973
|
+
}));
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Gets total number of events in the loaded MIDI file.
|
|
977
|
+
* @return {number}
|
|
978
|
+
*/
|
|
979
|
+
}, {
|
|
980
|
+
key: "getTotalEvents",
|
|
981
|
+
value: function getTotalEvents() {
|
|
982
|
+
return this.tracks.reduce(function(a, b) {
|
|
983
|
+
return {
|
|
984
|
+
events: {
|
|
985
|
+
length: a.events.length + b.events.length
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}, {
|
|
989
|
+
events: {
|
|
990
|
+
length: 0
|
|
991
|
+
}
|
|
992
|
+
}).events.length;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* Builds a tempo map from all Set Tempo events across all tracks.
|
|
996
|
+
* @return {Player}
|
|
997
|
+
*/
|
|
998
|
+
}, {
|
|
999
|
+
key: "buildTempoMap",
|
|
1000
|
+
value: function buildTempoMap() {
|
|
1001
|
+
var tempoEvents = [];
|
|
1002
|
+
this.events.forEach(function(trackEvents) {
|
|
1003
|
+
trackEvents.forEach(function(event) {
|
|
1004
|
+
if (event.name === "Set Tempo") {
|
|
1005
|
+
tempoEvents.push({
|
|
1006
|
+
tick: event.tick,
|
|
1007
|
+
tempo: event.data
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
});
|
|
1012
|
+
tempoEvents.sort(function(a, b) {
|
|
1013
|
+
return a.tick - b.tick;
|
|
1014
|
+
});
|
|
1015
|
+
this.tempoMap = [{
|
|
1016
|
+
tick: 0,
|
|
1017
|
+
tempo: this.defaultTempo
|
|
1018
|
+
}];
|
|
1019
|
+
tempoEvents.forEach(function(event) {
|
|
1020
|
+
var last = this.tempoMap[this.tempoMap.length - 1];
|
|
1021
|
+
if (event.tick === last.tick) {
|
|
1022
|
+
last.tempo = event.tempo;
|
|
1023
|
+
} else {
|
|
1024
|
+
this.tempoMap.push({
|
|
1025
|
+
tick: event.tick,
|
|
1026
|
+
tempo: event.tempo
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}, this);
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Converts a tick range to seconds using the tempo map.
|
|
1034
|
+
* @param {number} startTick
|
|
1035
|
+
* @param {number} endTick
|
|
1036
|
+
* @return {number}
|
|
1037
|
+
*/
|
|
1038
|
+
}, {
|
|
1039
|
+
key: "ticksToSeconds",
|
|
1040
|
+
value: function ticksToSeconds(startTick, endTick) {
|
|
1041
|
+
var seconds = 0;
|
|
1042
|
+
var currentTick = startTick;
|
|
1043
|
+
for (var i = 0; i < this.tempoMap.length; i++) {
|
|
1044
|
+
var entry = this.tempoMap[i];
|
|
1045
|
+
var nextTick = i + 1 < this.tempoMap.length ? this.tempoMap[i + 1].tick : endTick;
|
|
1046
|
+
if (nextTick <= startTick) continue;
|
|
1047
|
+
var segStart = Math.max(entry.tick, startTick);
|
|
1048
|
+
var segEnd = Math.min(nextTick, endTick);
|
|
1049
|
+
if (segStart >= endTick) break;
|
|
1050
|
+
var segmentTicks = segEnd - segStart;
|
|
1051
|
+
seconds += segmentTicks / this.division / entry.tempo * 60;
|
|
1052
|
+
currentTick = segEnd;
|
|
1053
|
+
}
|
|
1054
|
+
if (currentTick < endTick) {
|
|
1055
|
+
var lastEntry = this.tempoMap[this.tempoMap.length - 1];
|
|
1056
|
+
seconds += (endTick - currentTick) / this.division / lastEntry.tempo * 60;
|
|
1057
|
+
}
|
|
1058
|
+
return seconds;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Converts seconds to a tick position using the tempo map.
|
|
1062
|
+
* @param {number} seconds
|
|
1063
|
+
* @return {number}
|
|
1064
|
+
*/
|
|
1065
|
+
}, {
|
|
1066
|
+
key: "secondsToTicks",
|
|
1067
|
+
value: function secondsToTicks(seconds) {
|
|
1068
|
+
var remainingSeconds = seconds;
|
|
1069
|
+
var currentTick = 0;
|
|
1070
|
+
for (var i = 0; i < this.tempoMap.length; i++) {
|
|
1071
|
+
var entry = this.tempoMap[i];
|
|
1072
|
+
var nextTick = i + 1 < this.tempoMap.length ? this.tempoMap[i + 1].tick : Infinity;
|
|
1073
|
+
var segmentTicks = nextTick - entry.tick;
|
|
1074
|
+
var segmentSeconds = segmentTicks / this.division / entry.tempo * 60;
|
|
1075
|
+
if (remainingSeconds <= segmentSeconds) {
|
|
1076
|
+
currentTick = entry.tick + Math.round(remainingSeconds / 60 * entry.tempo * this.division);
|
|
1077
|
+
return currentTick;
|
|
1078
|
+
}
|
|
1079
|
+
remainingSeconds -= segmentSeconds;
|
|
1080
|
+
currentTick = nextTick;
|
|
1081
|
+
}
|
|
1082
|
+
return this.totalTicks;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Gets song duration in seconds.
|
|
1086
|
+
* @return {number}
|
|
1087
|
+
*/
|
|
1088
|
+
}, {
|
|
1089
|
+
key: "getSongTime",
|
|
1090
|
+
value: function getSongTime() {
|
|
1091
|
+
return this.ticksToSeconds(0, this.totalTicks);
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Gets remaining number of seconds in playback.
|
|
1095
|
+
* @return {number}
|
|
1096
|
+
*/
|
|
1097
|
+
}, {
|
|
1098
|
+
key: "getSongTimeRemaining",
|
|
1099
|
+
value: function getSongTimeRemaining() {
|
|
1100
|
+
return Math.round(this.ticksToSeconds(this.getCurrentTick(), this.totalTicks));
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Gets remaining percent of playback.
|
|
1104
|
+
* @return {number}
|
|
1105
|
+
*/
|
|
1106
|
+
}, {
|
|
1107
|
+
key: "getSongPercentRemaining",
|
|
1108
|
+
value: function getSongPercentRemaining() {
|
|
1109
|
+
return Math.round(this.getSongTimeRemaining() / this.getSongTime() * 100);
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Number of bytes processed in the loaded MIDI file.
|
|
1113
|
+
* @return {number}
|
|
1114
|
+
*/
|
|
1115
|
+
}, {
|
|
1116
|
+
key: "bytesProcessed",
|
|
1117
|
+
value: function bytesProcessed() {
|
|
1118
|
+
return Constants.HEADER_CHUNK_LENGTH + this.tracks.length * 8 + this.tracks.reduce(function(a, b) {
|
|
1119
|
+
return {
|
|
1120
|
+
pointer: a.pointer + b.pointer
|
|
1121
|
+
};
|
|
1122
|
+
}, {
|
|
1123
|
+
pointer: 0
|
|
1124
|
+
}).pointer;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Number of events played up to this point.
|
|
1128
|
+
* @return {number}
|
|
1129
|
+
*/
|
|
1130
|
+
}, {
|
|
1131
|
+
key: "eventsPlayed",
|
|
1132
|
+
value: function eventsPlayed() {
|
|
1133
|
+
return this.tracks.reduce(function(a, b) {
|
|
1134
|
+
return {
|
|
1135
|
+
eventIndex: a.eventIndex + b.eventIndex
|
|
1136
|
+
};
|
|
1137
|
+
}, {
|
|
1138
|
+
eventIndex: 0
|
|
1139
|
+
}).eventIndex;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Determines if the player pointer has reached the end of the loaded MIDI file.
|
|
1143
|
+
* Used in two ways:
|
|
1144
|
+
* 1. If playing result is based on loaded JSON events.
|
|
1145
|
+
* 2. If parsing (dryRun) it's based on the actual buffer length vs bytes processed.
|
|
1146
|
+
* @return {boolean}
|
|
1147
|
+
*/
|
|
1148
|
+
}, {
|
|
1149
|
+
key: "endOfFile",
|
|
1150
|
+
value: function endOfFile() {
|
|
1151
|
+
if (this.isPlaying()) {
|
|
1152
|
+
return this.totalTicks - this.tick <= 0;
|
|
1153
|
+
}
|
|
1154
|
+
return this.bytesProcessed() >= this.midiChunksByteLength;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Gets the current tick number in playback.
|
|
1158
|
+
* @return {number}
|
|
1159
|
+
*/
|
|
1160
|
+
}, {
|
|
1161
|
+
key: "getCurrentTick",
|
|
1162
|
+
value: function getCurrentTick() {
|
|
1163
|
+
if (!this.startTime) return this.startTick;
|
|
1164
|
+
var elapsedSeconds = ((/* @__PURE__ */ new Date()).getTime() - this.startTime) / 1e3;
|
|
1165
|
+
var startSeconds = this.ticksToSeconds(0, this.startTick);
|
|
1166
|
+
return this.secondsToTicks(startSeconds + elapsedSeconds);
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Sends MIDI event out to listener.
|
|
1170
|
+
* @param {object}
|
|
1171
|
+
* @return {Player}
|
|
1172
|
+
*/
|
|
1173
|
+
}, {
|
|
1174
|
+
key: "emitEvent",
|
|
1175
|
+
value: function emitEvent(event) {
|
|
1176
|
+
this.triggerPlayerEvent("midiEvent", event);
|
|
1177
|
+
return this;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Subscribes events to listeners
|
|
1181
|
+
* @param {string} - Name of event to subscribe to.
|
|
1182
|
+
* @param {function} - Callback to fire when event is broadcast.
|
|
1183
|
+
* @return {Player}
|
|
1184
|
+
*/
|
|
1185
|
+
}, {
|
|
1186
|
+
key: "on",
|
|
1187
|
+
value: function on(playerEvent, fn) {
|
|
1188
|
+
if (!this.eventListeners.hasOwnProperty(playerEvent)) this.eventListeners[playerEvent] = [];
|
|
1189
|
+
this.eventListeners[playerEvent].push(fn);
|
|
1190
|
+
return this;
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Broadcasts event to trigger subscribed callbacks.
|
|
1194
|
+
* @param {string} - Name of event.
|
|
1195
|
+
* @param {object} - Data to be passed to subscriber callback.
|
|
1196
|
+
* @return {Player}
|
|
1197
|
+
*/
|
|
1198
|
+
}, {
|
|
1199
|
+
key: "triggerPlayerEvent",
|
|
1200
|
+
value: function triggerPlayerEvent(playerEvent, data) {
|
|
1201
|
+
if (this.eventListeners.hasOwnProperty(playerEvent)) this.eventListeners[playerEvent].forEach(function(fn) {
|
|
1202
|
+
return fn(data || {});
|
|
1203
|
+
});
|
|
1204
|
+
return this;
|
|
1205
|
+
}
|
|
1206
|
+
}]);
|
|
1207
|
+
return Player2;
|
|
1208
|
+
})();
|
|
1209
|
+
var index = {
|
|
1210
|
+
Player,
|
|
1211
|
+
Utils,
|
|
1212
|
+
Constants
|
|
1213
|
+
};
|
|
1214
|
+
|
|
1215
|
+
// src/webaudiofontplayer.js
|
|
1216
|
+
var WebAudioFontPlayer = class {
|
|
1217
|
+
#audioCtx = null;
|
|
1218
|
+
#preset = null;
|
|
1219
|
+
#envelopes = [];
|
|
1220
|
+
#afterTime = 0.05;
|
|
1221
|
+
#nearZero = 1e-6;
|
|
1222
|
+
constructor(audioCtx, preset) {
|
|
1223
|
+
this.#audioCtx = audioCtx;
|
|
1224
|
+
this.#preset = preset;
|
|
1225
|
+
this.#preset.zones.map((zone) => this.#adjustZone(zone));
|
|
1226
|
+
}
|
|
1227
|
+
queueWaveTable(when, pitch, duration, volume, slides) {
|
|
1228
|
+
if (this.#audioCtx.state === "suspended") this.#audioCtx.resume().catch(() => {
|
|
1229
|
+
});
|
|
1230
|
+
const vol = this.#limitVolume(volume);
|
|
1231
|
+
const zone = this.#findZone(pitch);
|
|
1232
|
+
if (!zone?.buffer) return null;
|
|
1233
|
+
const baseDetune = zone.originalPitch - 100 * zone.coarseTune - zone.fineTune;
|
|
1234
|
+
const playbackRate = Math.pow(2, (100 * pitch - baseDetune) / 1200);
|
|
1235
|
+
const startWhen = Math.max(when, this.#audioCtx.currentTime);
|
|
1236
|
+
let waveDuration = duration + this.#afterTime;
|
|
1237
|
+
const loop = zone.loopStart >= 1 && zone.loopStart < zone.loopEnd;
|
|
1238
|
+
if (!loop) waveDuration = Math.min(waveDuration, zone.buffer.duration / playbackRate);
|
|
1239
|
+
const envelope = this.#findEnvelope();
|
|
1240
|
+
this.#setupEnvelope(envelope, zone, vol, startWhen, waveDuration, duration);
|
|
1241
|
+
const source = this.#audioCtx.createBufferSource();
|
|
1242
|
+
source.buffer = zone.buffer;
|
|
1243
|
+
source.playbackRate.setValueAtTime(playbackRate, 0);
|
|
1244
|
+
if (slides?.length > 0) {
|
|
1245
|
+
source.playbackRate.setValueAtTime(playbackRate, startWhen);
|
|
1246
|
+
slides.forEach((s) => {
|
|
1247
|
+
const newRate = Math.pow(2, (100 * (pitch + s.delta) - baseDetune) / 1200);
|
|
1248
|
+
source.playbackRate.linearRampToValueAtTime(newRate, startWhen + s.when);
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
source.loop = loop;
|
|
1252
|
+
if (loop) {
|
|
1253
|
+
const d = zone.delay ?? 0;
|
|
1254
|
+
source.loopStart = zone.loopStart / zone.sampleRate + d;
|
|
1255
|
+
source.loopEnd = zone.loopEnd / zone.sampleRate + d;
|
|
1256
|
+
}
|
|
1257
|
+
source.connect(envelope);
|
|
1258
|
+
source.start(startWhen, zone.delay ?? 0);
|
|
1259
|
+
source.stop(startWhen + waveDuration);
|
|
1260
|
+
envelope.audioBufferSourceNode = source;
|
|
1261
|
+
envelope.when = startWhen;
|
|
1262
|
+
envelope.duration = waveDuration;
|
|
1263
|
+
return envelope;
|
|
1264
|
+
}
|
|
1265
|
+
queueChord(prst, w, pchs, d, v, s) {
|
|
1266
|
+
const vol = this.#limitVolume(v);
|
|
1267
|
+
return pchs.map((p, i) => this.queueWaveTable(this.#audioCtx, this.#audioCtx.destination, prst, w, p, d, vol - Math.random() * 0.01, s?.[i])).filter(Boolean);
|
|
1268
|
+
}
|
|
1269
|
+
async cancelQueue() {
|
|
1270
|
+
this.#envelopes.forEach((e) => {
|
|
1271
|
+
e.gain.cancelScheduledValues(0);
|
|
1272
|
+
e.gain.setValueAtTime(this.#nearZero, this.#audioCtx.currentTime);
|
|
1273
|
+
e.when = -1;
|
|
1274
|
+
try {
|
|
1275
|
+
e.audioBufferSourceNode?.disconnect();
|
|
1276
|
+
} catch (e2) {
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
#adjustZone(zone) {
|
|
1281
|
+
if (zone.buffer) return Promise.resolve(zone);
|
|
1282
|
+
zone.delay = 0;
|
|
1283
|
+
if (zone.sample) {
|
|
1284
|
+
const binaryString = atob(zone.sample);
|
|
1285
|
+
const len = binaryString.length;
|
|
1286
|
+
const bytes = new Uint8Array(len);
|
|
1287
|
+
for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i);
|
|
1288
|
+
const int16Samples = new Int16Array(bytes.buffer);
|
|
1289
|
+
const numSamples = int16Samples.length;
|
|
1290
|
+
zone.buffer = this.#audioCtx.createBuffer(1, numSamples, zone.sampleRate);
|
|
1291
|
+
const float32Array = zone.buffer.getChannelData(0);
|
|
1292
|
+
for (let i = 0; i < numSamples; i++) float32Array[i] = int16Samples[i] / 32768;
|
|
1293
|
+
this.#applyZoneParameters(zone);
|
|
1294
|
+
return zone;
|
|
1295
|
+
} else if (zone.file) {
|
|
1296
|
+
const decoded = atob(zone.file);
|
|
1297
|
+
const uint8Array = new Uint8Array(decoded.length);
|
|
1298
|
+
for (let i = 0; i < decoded.length; i++) uint8Array[i] = decoded.charCodeAt(i);
|
|
1299
|
+
this.#audioCtx.decodeAudioData(
|
|
1300
|
+
uint8Array.buffer,
|
|
1301
|
+
(audioBuffer) => {
|
|
1302
|
+
zone.buffer = audioBuffer;
|
|
1303
|
+
this.#applyZoneParameters(zone);
|
|
1304
|
+
return zone;
|
|
1305
|
+
},
|
|
1306
|
+
(error) => {
|
|
1307
|
+
console.error("Audio decoding error:", error);
|
|
1308
|
+
return false;
|
|
1309
|
+
}
|
|
1310
|
+
);
|
|
1311
|
+
} else {
|
|
1312
|
+
this.#applyZoneParameters(zone);
|
|
1313
|
+
return zone;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
#applyZoneParameters(zone) {
|
|
1317
|
+
zone.loopStart = this.#numValue(zone.loopStart, 0);
|
|
1318
|
+
zone.loopEnd = this.#numValue(zone.loopEnd, 0);
|
|
1319
|
+
zone.coarseTune = this.#numValue(zone.coarseTune, 0);
|
|
1320
|
+
zone.fineTune = this.#numValue(zone.fineTune, 0);
|
|
1321
|
+
zone.originalPitch = this.#numValue(zone.originalPitch, 6e3);
|
|
1322
|
+
zone.sampleRate = this.#numValue(zone.sampleRate, 44100);
|
|
1323
|
+
}
|
|
1324
|
+
#setupEnvelope(envelope, zone, volume, when, sampleDuration, noteDuration) {
|
|
1325
|
+
envelope.gain.setValueAtTime(this.#noZeroVolume(0), this.#audioCtx.currentTime);
|
|
1326
|
+
const duration = Math.min(noteDuration, sampleDuration - this.#afterTime);
|
|
1327
|
+
const ahdsr = zone.ahdsr && zone.ahdsr.length > 0 ? zone.ahdsr : [
|
|
1328
|
+
{ duration: 0, volume: 1 },
|
|
1329
|
+
{ duration, volume: 1 }
|
|
1330
|
+
];
|
|
1331
|
+
envelope.gain.cancelScheduledValues(when);
|
|
1332
|
+
const initialVol = (ahdsr[0]?.volume ?? 1) * volume;
|
|
1333
|
+
envelope.gain.setValueAtTime(this.#noZeroVolume(initialVol), when);
|
|
1334
|
+
let lastTime = 0;
|
|
1335
|
+
let lastVolume = ahdsr[0]?.volume ?? 1;
|
|
1336
|
+
for (const stage of ahdsr) {
|
|
1337
|
+
const { duration: stageDuration, volume: stageVolume } = stage;
|
|
1338
|
+
if (stageDuration <= 0) continue;
|
|
1339
|
+
const remainingTime = duration - lastTime;
|
|
1340
|
+
if (stageDuration > remainingTime) {
|
|
1341
|
+
const ratio = remainingTime / stageDuration;
|
|
1342
|
+
const interpolatedVolume = lastVolume + ratio * (stageVolume - lastVolume);
|
|
1343
|
+
envelope.gain.linearRampToValueAtTime(
|
|
1344
|
+
this.#noZeroVolume(volume * interpolatedVolume),
|
|
1345
|
+
when + duration
|
|
1346
|
+
);
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
lastTime += stageDuration;
|
|
1350
|
+
lastVolume = stageVolume;
|
|
1351
|
+
envelope.gain.linearRampToValueAtTime(
|
|
1352
|
+
this.#noZeroVolume(volume * lastVolume),
|
|
1353
|
+
when + lastTime
|
|
1354
|
+
);
|
|
1355
|
+
}
|
|
1356
|
+
envelope.gain.linearRampToValueAtTime(this.#noZeroVolume(0), when + duration + this.#afterTime);
|
|
1357
|
+
}
|
|
1358
|
+
#findEnvelope() {
|
|
1359
|
+
let envelope = this.#envelopes.find((e) => e.target === this.#audioCtx.destination && this.#audioCtx.currentTime > e.when + e.duration + 1e-3);
|
|
1360
|
+
if (envelope) {
|
|
1361
|
+
if (envelope.audioBufferSourceNode) {
|
|
1362
|
+
try {
|
|
1363
|
+
envelope.audioBufferSourceNode.stop(0);
|
|
1364
|
+
envelope.audioBufferSourceNode.disconnect();
|
|
1365
|
+
} catch (e) {
|
|
1366
|
+
}
|
|
1367
|
+
envelope.audioBufferSourceNode = null;
|
|
1368
|
+
}
|
|
1369
|
+
} else {
|
|
1370
|
+
envelope = this.#audioCtx.createGain();
|
|
1371
|
+
envelope.gain.value = 0;
|
|
1372
|
+
envelope.target = this.#audioCtx.destination;
|
|
1373
|
+
envelope.connect(this.#audioCtx.destination);
|
|
1374
|
+
envelope.cancel = () => {
|
|
1375
|
+
if (envelope.when + envelope.duration > this.#audioCtx.currentTime) {
|
|
1376
|
+
envelope.gain.cancelScheduledValues(0);
|
|
1377
|
+
envelope.gain.setTargetAtTime(this.#nearZero, this.#audioCtx.currentTime, 0.1);
|
|
1378
|
+
envelope.when = this.#audioCtx.currentTime + 1e-5;
|
|
1379
|
+
envelope.duration = 0;
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
this.#envelopes.push(envelope);
|
|
1383
|
+
}
|
|
1384
|
+
return envelope;
|
|
1385
|
+
}
|
|
1386
|
+
#findZone(pitch) {
|
|
1387
|
+
const zone = this.#preset.zones.findLast((z) => pitch >= z.keyRangeLow && pitch <= z.keyRangeHigh + 1);
|
|
1388
|
+
if (zone) this.#adjustZone(zone);
|
|
1389
|
+
return zone;
|
|
1390
|
+
}
|
|
1391
|
+
#limitVolume(v) {
|
|
1392
|
+
const requestedVolume = v ? 1 * v : 0.5;
|
|
1393
|
+
return Math.min(requestedVolume, 0.8);
|
|
1394
|
+
}
|
|
1395
|
+
#noZeroVolume(n) {
|
|
1396
|
+
return n > this.#nearZero ? n : this.#nearZero;
|
|
1397
|
+
}
|
|
1398
|
+
#numValue(a, b) {
|
|
1399
|
+
return typeof a === "number" ? a : b;
|
|
1400
|
+
}
|
|
1401
|
+
};
|
|
1402
|
+
|
|
1403
|
+
// src/presets/defaultpreset.json
|
|
1404
|
+
var defaultpreset_default = {
|
|
1405
|
+
zones: [
|
|
1406
|
+
{
|
|
1407
|
+
midi: 81,
|
|
1408
|
+
originalPitch: 4200,
|
|
1409
|
+
keyRangeLow: 0,
|
|
1410
|
+
keyRangeHigh: 43,
|
|
1411
|
+
loopStart: 256,
|
|
1412
|
+
loopEnd: 768,
|
|
1413
|
+
coarseTune: 0,
|
|
1414
|
+
fineTune: 0,
|
|
1415
|
+
sampleRate: 48e3,
|
|
1416
|
+
ahdsr: false,
|
|
1417
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAAGAACgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJALAAAAAAAAABgA5CeiPAAAAAAAAAAAAAAAAAAAA//vUxAAAB+gfQ1TwACTywCx/NYAJAABZOW7fg6F8L8A3AOwb5L2e7x5R5EoCADB8HwfPxACAYrB8H8oCDsHw/lAQdlwPqDHh/UCEp4fhiU8/ynv6BAGOZS1NzNUNm83W5HHJGz4Kk0TfzwzSZu8CgJ3TZ11pu3hi1hoxY8qMFcPAcBX40CM1RcwUo9EFQgiY2VPldy90c24GUwVQROZnA0ZXohQyMsyIBlAWEQK/zrsHfSBGmF4i+yiKKKNrP5FDrYJM+8XdeIRR1FaEwk0mLq5Upl0G35bBliJz8DxCcgSVtAYMpU27BVbW0tSXOzHsLU+88MTkCQ3PwO05aTrtqwF1G2YFHc7M1hamc5RYnJXbn6S9OU7L2apytcaEpk5bQVbXIszWrUzuzNatW5HKL05K+z9Jyip+vcyqG3yZTDELa1DcIazEIXV3jW1lV3jW1lV7h/M/7h/M/7h/9/+f/f/n/3/58qr2JTUtyqvYlNy3TXbFLct012xS3Kemu0lLWp6arSexu7G62Ja2Ja2Ja0QsKrWx13Wb36/a/7/ew5kMjLhQNcmAxqICUIjQsMqB4zeSzGokEALDBOxI2OrTTJJMLBUwcCQYAVMDdlQ0A6Zkis4q5jIAQzknMdGQMBLAltWVxZrJm4ma0hGaGCmrpKmfS87RrhMci3muq5p4+b4wKbPCqVlsBQHbcmCKhmxoZcGGmnZmJeZOEmiGS7nGjrWX5mn+j92HZJcMqKDIgYz0vMnJTHwUzYoMiIGdQNMuTIbj/U13dLc1aMqCjSTkzEvMpCTQjQy4qMkBjPTGR3X9pMI1N9q1aWtWpqtUyspMhBTOCwyYkMeAjNSsyUjMdATMCYyIgpZJZygGm+AqXGzWrWqtWzWrWjGgAy0lMhHwEXmVERjw4AigycfMdGwMVmSDhjQwCiSU1solVxiNamhqlpYZx3jllljvHLWWLlv/F3cfyKO+/8vfx/JY/7/y9/H8lj/v/Pv5Dk5D8bn4xGJyNxuf1llvHHWWW8cdZZbx//u0xLeAL2H/Z7nNkIFVkCR3sMAFx1llvHHWVbHGrllWAbmu1l2+wACIJmablgZJEkGiBRmRmRKSQMOakoAW1jywyxmXRDkBISlWnK1b7K0xdxcdLomjI+e1lat9latrlly7rLnrbVat8FFQgpgKeCmxTcgrgVVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
|
|
1418
|
+
anchor: 533333e-8
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
midi: 81,
|
|
1422
|
+
originalPitch: 5400,
|
|
1423
|
+
keyRangeLow: 44,
|
|
1424
|
+
keyRangeHigh: 55,
|
|
1425
|
+
loopStart: 128,
|
|
1426
|
+
loopEnd: 384,
|
|
1427
|
+
coarseTune: 0,
|
|
1428
|
+
fineTune: 0,
|
|
1429
|
+
sampleRate: 48e3,
|
|
1430
|
+
ahdsr: false,
|
|
1431
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAAEsADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJATAAAAAAAAABLAMEvhQAAAAAAAAAAAAAAAAAAAA//vUxAAACSwNaVQwACRXSC6/NYAIAAa9txyS8qHAwMDPlz5QEIIROfLn1AhBBy3lz/BCt5c/wQz5c/RBCXPlw/EEEHLPlz9EEOt8QQQcs+XPykQVh8uH4gcIHLD6w+JgcGkGbGakSCqjColKJDEMCR4mYQ8kQZ1UAiwCAIbAc4b1maEch6LAEVzMhwxuYQGCgLBWbmQSPi+kAzkOM6i/Ei2XrGgFiMbisXednEOO7NP9KJdLKedbxd7vvezh3pl/YdmreFqxnZjEgf+NyN/I1TXaWzqnqWqTOzTyyihuX0cMUlNa3S81TYWrGd63q/RRuno4xSUUbt3YzSYU1fuH8z/uH8zp6OMUlFK6e/KKTG5zKZy+ax/+4fzP+4fzP70rp78opL1PXv0lSzVy+tjjVyyrY41e4fzf91/N/3X83/dfzf91/N/3X5/3D+b/uv5v+6/m/7r/3/6/9/+sst446yy3jVyyrY41csq2ONXLKtjjVyyrKiLXdbW5JI24SEB1CQjhFAAXAIxdxbUKO0ekXEuKphJ5DkSgYKJVFFxQVwUwFDcRcUFcFcCjsTYoK4K4FHYmxQXwrgUdibFBfCvCjsTcRXhXhT8TcRXhXhT8TcRXhXhT8TcRXhXhT8TcakxBTUUzLjk5LjWqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//tkxMWDzQgxS7zzACgAADSAAAAEqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
1432
|
+
anchor: 266667e-8
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
midi: 81,
|
|
1436
|
+
originalPitch: 6600,
|
|
1437
|
+
keyRangeLow: 56,
|
|
1438
|
+
keyRangeHigh: 67,
|
|
1439
|
+
loopStart: 64,
|
|
1440
|
+
loopEnd: 192,
|
|
1441
|
+
coarseTune: 0,
|
|
1442
|
+
fineTune: 0,
|
|
1443
|
+
sampleRate: 48e3,
|
|
1444
|
+
ahdsr: false,
|
|
1445
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAAE4ADs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAXAAAAAAAAABODQyktQAAAAAAAAAAAAAAAAAAAA//vkxAAABywBgbQAACYVyC6/P9AwKAASUcckkt4YWD4fLn1AhBCJz5c+oEFghy5/ghy5+cghy7+GKZd/DHLn1HIYk5d/DHL+6H5d5TOdLnbLTLKrBqyq+vlkqeD4lCzTAngBgvMYACAAGg+NZZoFwogYAGAZCwCsDgBJsJhRJLyYvYUvg4BLFAA9AKpqYxQCumE3hMhEBIkBkBLuNR5oOGzUbs0Jk7jR1yTFgpzGY2TMgWV8qtT4fmaf4xDKgxlNgzTGRZScSFbPVQxarGZbWMLiNMUC1MshXMJx+MRSeaZHnuceOvbGrV2ls6MOQUMAAnMDAzMMgRMAQkMCQufiahT9zMJfim7ul5rIDBEFgJBgHgoHQuBANAwFA1WkUbqzssyns944flv09UjUcU9kjkkk9UjZfjOyzKep8Z2kxucyq9+ax9JFNJNJNZNFNFNVNJNJNbKRRvGQxjKejfJ2Udq/9bf1darY41U0U0U1U0k0k+k0U0U+U0k0k+k0e5/zD+5/zD+5/zX93/Nf3f81/d////////7/9f+//X/v/1/7/9Jop8ppJpJ9Jop6qwppJ7KxJGp6qwpHKrKxI2qqqwo3KrKxI2qqq9RuVWV8jaqqr1G5TEFNRTMuOTkuNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxNoDwAAB/hwAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
|
|
1446
|
+
anchor: 133333e-8
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
midi: 81,
|
|
1450
|
+
originalPitch: 7800,
|
|
1451
|
+
keyRangeLow: 68,
|
|
1452
|
+
keyRangeHigh: 79,
|
|
1453
|
+
loopStart: 32,
|
|
1454
|
+
loopEnd: 96,
|
|
1455
|
+
coarseTune: 0,
|
|
1456
|
+
fineTune: 0,
|
|
1457
|
+
sampleRate: 48e3,
|
|
1458
|
+
ahdsr: false,
|
|
1459
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADwADm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAZAAAAAAAAAA8CsVDBMAAAAAAAAAAAAAAAAAAAA//vExAAABmQBjaAAACGQBa9497AdtbAkWdckklxRyjhQ5BBYYqOFHRAXDHKHOGOUOcMco7nOJHawxynnOU5cMcHOXOcHOl3NXGMqsiEAqoMI8QsSUcIsgeiQgJISFCg5ABAyw+VQaiK8yY18FNiuxBUorwU0FdiC4ivimgrsQXEV4KaCuxBcTfBXAv4isTfiuBf6KxN+L4r8RUU3wXoL+JsU3wXoL+JsV3gvRSpMQU1FMy45OS41qqqqqqqqqqqqqqqqqqqqqqqq6goZoAABEOiCXDs8P3G8oicJAhZjVVVBlsStNFVVUT8StNVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxOuDxEQnByYExigAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
|
|
1460
|
+
anchor: 66667e-8
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
midi: 81,
|
|
1464
|
+
originalPitch: 9e3,
|
|
1465
|
+
keyRangeLow: 80,
|
|
1466
|
+
keyRangeHigh: 91,
|
|
1467
|
+
loopStart: 16,
|
|
1468
|
+
loopEnd: 48,
|
|
1469
|
+
coarseTune: 0,
|
|
1470
|
+
fineTune: 0,
|
|
1471
|
+
sampleRate: 48e3,
|
|
1472
|
+
ahdsr: false,
|
|
1473
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADwADm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAaAAAAAAAAAA8ChXkahAAAAAAAAAAAAAAAAAAAA//vExAAABkQDh+AAACGUhq9895iZZVlzMgFEYk0nBACCLQfD6gTB8/qBDBwMflC4f/gQMfqBMH/4OHP4Df+XBw5+UBN/6wJu1fapqZhlttyF2FyUYQ0W0QkFSJiRIro4TAYk8aQCAQCAKjiRIkSr+xQUFOBQUV/5BQU4KChv+CgoL4KCu/4QUN8KCn/8UFN8FFf/wgp/hQ3/8FBXYgoL/+FBX5BQ3/8FBfigopVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxNoDwAAB/gAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
|
|
1474
|
+
anchor: 33333e-8
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
midi: 81,
|
|
1478
|
+
originalPitch: 10200,
|
|
1479
|
+
keyRangeLow: 92,
|
|
1480
|
+
keyRangeHigh: 103,
|
|
1481
|
+
loopStart: 8,
|
|
1482
|
+
loopEnd: 56,
|
|
1483
|
+
coarseTune: 0,
|
|
1484
|
+
fineTune: 0,
|
|
1485
|
+
sampleRate: 48e3,
|
|
1486
|
+
ahdsr: false,
|
|
1487
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADwADm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAaAAAAAAAAAA8AljJJeAAAAAAAAAAAAAAAAAAAA//vExAAABmgDh+AAACG7Cq649JmAh3unfGYwJRhSgQBBEXB8PjogBDn8EwfP6gQdicP/g4GOXB/lHfgmH905/UclwcOf/8oA/2r/qekIFM1D+AxK8OEgo9IhpLSJFdJaehzC3CbBom0GChJpEiRn/tRxIkSJT6OJJB3BUFXSwNB3BUFQVgqCoaqBoGsqd4iBo8oGgaTWCoaxEDR6VBUFTpUFQV//BoGoiBoO//rxKCv//iIGv/+WTEFNRTMuOTkuNVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVMQU1FMy45OS41VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxNoDwAAB/gAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
|
|
1488
|
+
anchor: 16667e-8
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
midi: 81,
|
|
1492
|
+
originalPitch: 11400,
|
|
1493
|
+
keyRangeLow: 104,
|
|
1494
|
+
keyRangeHigh: 115,
|
|
1495
|
+
loopStart: 12,
|
|
1496
|
+
loopEnd: 52,
|
|
1497
|
+
coarseTune: 0,
|
|
1498
|
+
fineTune: 0,
|
|
1499
|
+
sampleRate: 48e3,
|
|
1500
|
+
ahdsr: false,
|
|
1501
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADwADm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAaAAAAAAAAAA8CNr1v4AAAAAAAAAAAAAAAAAAAA//vExAAAB1SVheCEbeI4SC349ApomIy4jYiPtA6x5AACs4xjvkAAPmMY/wAXzGMf4AL/X+IgG4PvwQOfIQQOZcH//wQlwfBA5/1h/iB2sP/17/1GMvIAEJ8BiUYMEW0NSAKQjJWj5HCbB/EGHqQ5m2plUuYL1WvVqeGZmb/VVVf9mZr/1VVX9hYGwKgFgbB9TCwNgbB8BG6lKUreYxjfKUrfQxvylK3oYxjeUKAgICJYoCAgICUvoYxvylL9DGN9SlL9P+UrfMYxjOoYCAgIUb6lL////////qUpSzASTEFNRTMuOTkuNaqqqqpMQU1FMy45OS41qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//sUxNoDwAAB/gAAACAAADSAAAAEqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
1502
|
+
anchor: 8333e-8
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
midi: 81,
|
|
1506
|
+
originalPitch: 12600,
|
|
1507
|
+
keyRangeLow: 116,
|
|
1508
|
+
keyRangeHigh: 127,
|
|
1509
|
+
loopStart: 6,
|
|
1510
|
+
loopEnd: 42,
|
|
1511
|
+
coarseTune: 0,
|
|
1512
|
+
fineTune: 0,
|
|
1513
|
+
sampleRate: 48e3,
|
|
1514
|
+
ahdsr: false,
|
|
1515
|
+
file: "SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU3LjcyLjEwMQAAAAAAAAAAAAAA//tUwAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAAEIADo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo//////////////////////////////////////////////////////////////////8AAAAATGF2YzU3Ljk2AAAAAAAAAAAAAAAAJAaQAAAAAAAABCDWHfeqAAAAAAAAAAAAAAAAAAAA//vUxAAACUUrfeCE1eMLSCx88a54l3jMh4t/trUApEAAAUAGYxja+WMYx/yAAH+MY/+AYx/4AAX8xjH/7/8AF//zHyAMdoIAgAAADAYWTt4IA/Kc5/////////y4Pg/v3FZropQkAAAEiDVBBhkjdBSk5JyDlHqDmWi+kJRsJiOZ69eva6x+zAQEBCm/9VVVb/9mVVL/9mZmP/6qqqn/8ZlVV/6zMzM3/VCgICanXX+5I2JRJHaana+SSNwIQEQAwAQAQEw6j5KBCASACACAKAmBGHcdr5RNR2jtJRsed/DkTU1NTra/3Oc5znf+1rWuc7/3Na1ra/9znOc7/9rWtc7/9zWta3/4c5znO/9rTU1NXOvbW4lDtDyHkPI7TqQIQAxMQU1FMy45OS41qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpMQU1FMy45OS41qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq//sUxNoDwAABpAAAACAAADSAAAAEqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
|
|
1516
|
+
anchor: 4167e-8
|
|
1517
|
+
}
|
|
1518
|
+
]
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
// src/midiaudioplayer.js
|
|
1522
|
+
var MidiAudioPlayer = class extends index.Player {
|
|
1523
|
+
#audioCtx = null;
|
|
1524
|
+
#audioPlayer = null;
|
|
1525
|
+
#activeNotes = null;
|
|
1526
|
+
#opts = {
|
|
1527
|
+
preset: defaultpreset_default,
|
|
1528
|
+
volume: 0.012,
|
|
1529
|
+
onEndFile: null
|
|
1530
|
+
};
|
|
1531
|
+
constructor(opts = {}) {
|
|
1532
|
+
super((event) => this.#handleMidiPipeline(event));
|
|
1533
|
+
this.#opts = { ...this.#opts, ...opts };
|
|
1534
|
+
this.#activeNotes = /* @__PURE__ */ new Map();
|
|
1535
|
+
this.#audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
1536
|
+
this.#audioPlayer = new WebAudioFontPlayer(this.#audioCtx, this.#opts.preset);
|
|
1537
|
+
this.on("endOfFile", async () => {
|
|
1538
|
+
await new Promise((resolve) => requestAnimationFrame(() => setTimeout(resolve, 1)));
|
|
1539
|
+
await this.#endOfFile();
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
async play(content = null) {
|
|
1543
|
+
if (content) await this.#load(content);
|
|
1544
|
+
await this.#audioCtx.resume();
|
|
1545
|
+
await super.play();
|
|
1546
|
+
}
|
|
1547
|
+
async pause() {
|
|
1548
|
+
await super.pause();
|
|
1549
|
+
await this.#clearActiveNotes();
|
|
1550
|
+
await this.#audioPlayer.cancelQueue();
|
|
1551
|
+
}
|
|
1552
|
+
async stop() {
|
|
1553
|
+
await super.stop();
|
|
1554
|
+
await this.#clearActiveNotes();
|
|
1555
|
+
await this.#audioPlayer.cancelQueue();
|
|
1556
|
+
}
|
|
1557
|
+
async #endOfFile() {
|
|
1558
|
+
if (typeof this.#opts.onEndFile == "function") await this.#opts.onEndFile();
|
|
1559
|
+
}
|
|
1560
|
+
async #handleMidiPipeline(event) {
|
|
1561
|
+
if (event.name !== "Note on" && event.name !== "Note off") return;
|
|
1562
|
+
if (!this.isPlaying()) return;
|
|
1563
|
+
if (event.noteNumber === void 0) return;
|
|
1564
|
+
const now = this.#audioCtx.currentTime;
|
|
1565
|
+
switch (event.name) {
|
|
1566
|
+
case "Note on":
|
|
1567
|
+
if (event.velocity > 0 && event.velocity <= 127) {
|
|
1568
|
+
this.#stopNotePipe(event.noteNumber);
|
|
1569
|
+
const vol = event.velocity / 127 * this.#opts.volume;
|
|
1570
|
+
const envelope = this.#audioPlayer.queueWaveTable(0, event.noteNumber, 2, vol);
|
|
1571
|
+
this.#activeNotes.set(event.noteNumber, envelope);
|
|
1572
|
+
} else {
|
|
1573
|
+
this.#stopNotePipe(event.noteNumber);
|
|
1574
|
+
}
|
|
1575
|
+
break;
|
|
1576
|
+
case "Note off":
|
|
1577
|
+
this.#stopNotePipe(event.noteNumber);
|
|
1578
|
+
break;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
#stopNotePipe(noteNumber) {
|
|
1582
|
+
const envelope = this.#activeNotes.get(noteNumber);
|
|
1583
|
+
if (envelope) {
|
|
1584
|
+
envelope.cancel();
|
|
1585
|
+
this.#activeNotes.delete(noteNumber);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
#clearActiveNotes() {
|
|
1589
|
+
if (this.#activeNotes) {
|
|
1590
|
+
this.#activeNotes.forEach((envelope, note) => {
|
|
1591
|
+
if (envelope && envelope.cancel) envelope.cancel();
|
|
1592
|
+
});
|
|
1593
|
+
this.#activeNotes.clear();
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
async #load(content) {
|
|
1597
|
+
if (this.isPlaying()) this.stop();
|
|
1598
|
+
this.#clearActiveNotes();
|
|
1599
|
+
await this.loadArrayBuffer(content);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// index.js
|
|
1604
|
+
if (typeof window !== "undefined") window.MidiAudioPlayer = MidiAudioPlayer;
|
|
1605
|
+
var index_default = MidiAudioPlayer;
|
|
1606
|
+
export {
|
|
1607
|
+
MidiAudioPlayer,
|
|
1608
|
+
index_default as default
|
|
1609
|
+
};
|
|
1610
|
+
//# sourceMappingURL=index.js.map
|