midi-audio-player 1.0.3 → 1.1.1

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/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 19:05:06
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