midi-audio-player 1.1.2 → 2.0.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.
@@ -1,18 +1,18 @@
1
1
  /*!
2
2
 
3
- ██████╗ ██╗ █████╗ ███╗ ███╗███████╗████████╗██████╗ ██╗ ██████╗██╗ ██╗
4
- ██╔══██╗██║██╔══██╗████╗ ████║██╔════╝╚══██╔══╝██╔══██╗██║██╔════╝██║ ██╔╝
5
- ██║ ██║██║███████║██╔████╔██║█████╗ ██║ ██████╔╝██║██║ █████╔╝
6
- ██║ ██║██║██╔══██║██║╚██╔╝██║██╔══╝ ██║ ██╔══██╗██║██║ ██╔═██╗
7
- ██████╔╝██║██║ ██║██║ ╚═╝ ██║███████╗ ██║ ██║ ██║██║╚██████╗██║ ██╗
8
- ╚═════╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝
3
+ ███╗ ███╗██╗██████╗ ██╗ █████╗ ██╗ ██╗██████╗ ██╗ ██████╗ ██████╗ ██╗ █████╗ ██╗ ██╗███████╗██████╗
4
+ ████╗ ████║██║██╔══██╗██║██╔══██╗██║ ██║██╔══██╗██║██╔═══██╗██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝██╔════╝██╔══██╗
5
+ ██╔████╔██║██║██║ ██║██║███████║██║ ██║██║ ██║██║██║ ██║██████╔╝██║ ███████║ ╚████╔╝ █████╗ ██████╔╝
6
+ ██║╚██╔╝██║██║██║ ██║██║██╔══██║██║ ██║██║ ██║██║██║ ██║██╔═══╝ ██║ ██╔══██║ ╚██╔╝ ██╔══╝ ██╔══██╗
7
+ ██║ ╚═╝ ██║██║██████╔╝██║██║ ██║╚██████╔╝██████╔╝██║╚██████╔╝██║ ███████╗██║ ██║ ██║ ███████╗██║ ██║
8
+ ╚═╝ ╚═╝╚═╝╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
9
+
10
+ Version: 2.0.1
11
+ Build: 2026-05-31 02:44:14
12
+ Author: Maxime Larrivée-Roy <mlarriveeroy@gmail.com>
13
+ Github: https://github.com/webaudiofonts/midi-audio-player/
14
+ Website: https://webaudiofonts.com/midiaudioplayer/
9
15
 
10
- Version: 1.1.2
11
- Généré: 2026-05-10 00:19:40
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
16
  */
17
17
  (() => {
18
18
  // node_modules/midi-player-js/build/index.browser.js
@@ -1212,26 +1212,94 @@
1212
1212
  Constants
1213
1213
  };
1214
1214
 
1215
- // src/webaudiofontplayer.js
1216
- var WebAudioFontPlayer = class {
1215
+ // node_modules/webaudiofontplayer/dist/index.js
1216
+ var WebAudioFontPlayer = class _WebAudioFontPlayer {
1217
1217
  #audioCtx = null;
1218
+ #compressor = null;
1218
1219
  #preset = null;
1219
1220
  #envelopes = [];
1220
1221
  #afterTime = 0.05;
1221
1222
  #nearZero = 1e-6;
1222
- constructor(audioCtx, preset) {
1223
+ #bendRange = 2;
1224
+ #mainGain = null;
1225
+ #volumeValue = 0.7;
1226
+ #expressionValue = 1;
1227
+ #expressionGain = null;
1228
+ #sustain = false;
1229
+ #pitchBendValue = 8192;
1230
+ #notesWaitingForSustain = /* @__PURE__ */ new Set();
1231
+ constructor(preset, audioCtx, compressor = null, callback = null) {
1223
1232
  this.#audioCtx = audioCtx;
1233
+ this.#compressor = compressor;
1234
+ this.#mainGain = this.#audioCtx.createGain();
1235
+ this.#mainGain.gain.setValueAtTime(this.#volumeValue, this.#audioCtx.currentTime);
1236
+ this.#expressionGain = this.#audioCtx.createGain();
1237
+ this.#expressionGain.gain.setValueAtTime(this.#expressionValue, this.#audioCtx.currentTime);
1238
+ this.#mainGain.connect(this.#expressionGain);
1239
+ this.#expressionGain.connect(this.#compressor ? this.#compressor.input : this.#audioCtx.destination);
1240
+ this.setPreset(preset).then(() => {
1241
+ if (typeof callback === "function") callback();
1242
+ });
1243
+ }
1244
+ get preset() {
1245
+ return this.#preset;
1246
+ }
1247
+ set preset(preset) {
1248
+ this.setPreset(preset);
1249
+ }
1250
+ static load(preset, audioCtx, compressor = null) {
1251
+ return new Promise((resolve) => {
1252
+ const player = new _WebAudioFontPlayer(preset, audioCtx, compressor, () => resolve(player));
1253
+ });
1254
+ }
1255
+ async setPreset(preset) {
1224
1256
  this.#preset = preset;
1225
- this.#preset.zones.map((zone) => this.#adjustZone(zone));
1257
+ await Promise.all(this.#preset.zones.map((zone) => this.#adjustZone(zone)));
1258
+ }
1259
+ close() {
1260
+ const now = this.#audioCtx.currentTime;
1261
+ this.#envelopes.forEach((envelope) => {
1262
+ try {
1263
+ envelope.gain.cancelScheduledValues(0);
1264
+ if (envelope.audioBufferSourceNode) {
1265
+ envelope.audioBufferSourceNode.stop(now);
1266
+ envelope.audioBufferSourceNode.disconnect();
1267
+ envelope.audioBufferSourceNode = null;
1268
+ }
1269
+ } catch (e) {
1270
+ }
1271
+ try {
1272
+ envelope.disconnect();
1273
+ } catch (e) {
1274
+ }
1275
+ });
1276
+ this.#envelopes = [];
1277
+ this.#notesWaitingForSustain.clear();
1278
+ try {
1279
+ this.#mainGain.disconnect();
1280
+ } catch (e) {
1281
+ }
1282
+ try {
1283
+ this.#expressionGain.disconnect();
1284
+ } catch (e) {
1285
+ }
1286
+ this.#mainGain = null;
1287
+ this.#expressionGain = null;
1288
+ this.#preset = null;
1289
+ this.#compressor = null;
1290
+ this.#audioCtx = null;
1226
1291
  }
1227
1292
  queueWaveTable(when, pitch, duration, volume, slides) {
1228
1293
  if (this.#audioCtx.state === "suspended") this.#audioCtx.resume().catch(() => {
1229
1294
  });
1230
1295
  const vol = this.#limitVolume(volume);
1231
- const zone = this.#findZone(pitch);
1296
+ const zone = this.#findZone(Math.round(pitch));
1232
1297
  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);
1298
+ const baseDetuneCents = zone.originalPitch - 100 * zone.coarseTune - zone.fineTune;
1299
+ const originalPitchCents = pitch * 100;
1300
+ const currentBendCents = (this.#pitchBendValue - 8192) / 8192 * this.#bendRange * 100;
1301
+ const totalCents = originalPitchCents - baseDetuneCents + currentBendCents;
1302
+ const playbackRate = Math.pow(2, totalCents / 1200);
1235
1303
  const startWhen = Math.max(when, this.#audioCtx.currentTime);
1236
1304
  let waveDuration = duration + this.#afterTime;
1237
1305
  const loop = zone.loopStart >= 1 && zone.loopStart < zone.loopEnd;
@@ -1244,7 +1312,9 @@
1244
1312
  if (slides?.length > 0) {
1245
1313
  source.playbackRate.setValueAtTime(playbackRate, startWhen);
1246
1314
  slides.forEach((s) => {
1247
- const newRate = Math.pow(2, (100 * (pitch + s.delta) - baseDetune) / 1200);
1315
+ const slidePitchCents = (pitch + s.delta) * 100;
1316
+ const totalSlideCents = slidePitchCents - baseDetuneCents + currentBendCents;
1317
+ const newRate = Math.pow(2, totalSlideCents / 1200);
1248
1318
  source.playbackRate.linearRampToValueAtTime(newRate, startWhen + s.when);
1249
1319
  });
1250
1320
  }
@@ -1260,12 +1330,10 @@
1260
1330
  envelope.audioBufferSourceNode = source;
1261
1331
  envelope.when = startWhen;
1262
1332
  envelope.duration = waveDuration;
1333
+ envelope.pitch = pitch;
1334
+ envelope.baseDetune = baseDetuneCents;
1263
1335
  return envelope;
1264
1336
  }
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
1337
  async cancelQueue() {
1270
1338
  this.#envelopes.forEach((e) => {
1271
1339
  e.gain.cancelScheduledValues(0);
@@ -1277,41 +1345,68 @@
1277
1345
  }
1278
1346
  });
1279
1347
  }
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;
1348
+ isSustainActive() {
1349
+ return this.#sustain;
1350
+ }
1351
+ registerSustainNote(cancelFn) {
1352
+ this.#notesWaitingForSustain.add(cancelFn);
1353
+ }
1354
+ setPitchBend(value) {
1355
+ this.#pitchBendValue = value;
1356
+ const normalized = value - 8192;
1357
+ const semitones = normalized >= 0 ? normalized / 8191 * this.#bendRange : normalized / 8192 * this.#bendRange;
1358
+ const now = this.#audioCtx.currentTime;
1359
+ this.#envelopes.forEach((e) => {
1360
+ if (e.audioBufferSourceNode && e.when + e.duration > now) {
1361
+ const originalPitchCents = e.pitch * 100;
1362
+ const baseDetuneCents = e.baseDetune;
1363
+ const bendCents = semitones * 100;
1364
+ const totalCents = originalPitchCents - baseDetuneCents + bendCents;
1365
+ const newRate = Math.pow(2, totalCents / 1200);
1366
+ e.audioBufferSourceNode.playbackRate.cancelScheduledValues(now);
1367
+ e.audioBufferSourceNode.playbackRate.setTargetAtTime(newRate, now, 0.015);
1368
+ }
1369
+ });
1370
+ }
1371
+ setController(number, value) {
1372
+ const now = this.#audioCtx.currentTime;
1373
+ const normalizedValue = Math.max(0, Math.min(127, value)) / 127;
1374
+ switch (number) {
1375
+ case 7:
1376
+ this.#volumeValue = normalizedValue;
1377
+ this.#mainGain.gain.setTargetAtTime(this.#volumeValue, now, 0.05);
1378
+ break;
1379
+ case 11:
1380
+ this.#expressionValue = normalizedValue;
1381
+ this.#expressionGain.gain.setTargetAtTime(this.#expressionValue, now, 0.03);
1382
+ break;
1383
+ case 64:
1384
+ this.#sustain = value >= 64;
1385
+ if (!this.#sustain) {
1386
+ this.#notesWaitingForSustain.forEach((cancelFn) => cancelFn());
1387
+ this.#notesWaitingForSustain.clear();
1309
1388
  }
1310
- );
1389
+ break;
1390
+ }
1391
+ }
1392
+ async #adjustZone(zone) {
1393
+ if (zone.buffer) return zone;
1394
+ zone.delay = 0;
1395
+ if (zone.file) {
1396
+ const binary = atob(zone.file);
1397
+ const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
1398
+ try {
1399
+ zone.buffer = await this.#audioCtx.decodeAudioData(bytes.buffer);
1400
+ this.#applyZoneParameters(zone);
1401
+ } catch (error) {
1402
+ console.error("Audio decoding error:", error);
1403
+ console.warn(this.#preset);
1404
+ return false;
1405
+ }
1311
1406
  } else {
1312
1407
  this.#applyZoneParameters(zone);
1313
- return zone;
1314
1408
  }
1409
+ return zone;
1315
1410
  }
1316
1411
  #applyZoneParameters(zone) {
1317
1412
  zone.loopStart = this.#numValue(zone.loopStart, 0);
@@ -1319,10 +1414,9 @@
1319
1414
  zone.coarseTune = this.#numValue(zone.coarseTune, 0);
1320
1415
  zone.fineTune = this.#numValue(zone.fineTune, 0);
1321
1416
  zone.originalPitch = this.#numValue(zone.originalPitch, 6e3);
1322
- zone.sampleRate = this.#numValue(zone.sampleRate, 44100);
1323
1417
  }
1324
1418
  #setupEnvelope(envelope, zone, volume, when, sampleDuration, noteDuration) {
1325
- envelope.gain.setValueAtTime(this.#noZeroVolume(0), this.#audioCtx.currentTime);
1419
+ envelope.gain.setValueAtTime(this.#nearZero, this.#audioCtx.currentTime);
1326
1420
  const duration = Math.min(noteDuration, sampleDuration - this.#afterTime);
1327
1421
  const ahdsr = zone.ahdsr && zone.ahdsr.length > 0 ? zone.ahdsr : [
1328
1422
  { duration: 0, volume: 1 },
@@ -1330,7 +1424,7 @@
1330
1424
  ];
1331
1425
  envelope.gain.cancelScheduledValues(when);
1332
1426
  const initialVol = (ahdsr[0]?.volume ?? 1) * volume;
1333
- envelope.gain.setValueAtTime(this.#noZeroVolume(initialVol), when);
1427
+ envelope.gain.linearRampToValueAtTime(this.#noZeroVolume(initialVol), when + 2e-3);
1334
1428
  let lastTime = 0;
1335
1429
  let lastVolume = ahdsr[0]?.volume ?? 1;
1336
1430
  for (const stage of ahdsr) {
@@ -1340,7 +1434,7 @@
1340
1434
  if (stageDuration > remainingTime) {
1341
1435
  const ratio = remainingTime / stageDuration;
1342
1436
  const interpolatedVolume = lastVolume + ratio * (stageVolume - lastVolume);
1343
- envelope.gain.linearRampToValueAtTime(
1437
+ envelope.gain.exponentialRampToValueAtTime(
1344
1438
  this.#noZeroVolume(volume * interpolatedVolume),
1345
1439
  when + duration
1346
1440
  );
@@ -1348,15 +1442,24 @@
1348
1442
  }
1349
1443
  lastTime += stageDuration;
1350
1444
  lastVolume = stageVolume;
1351
- envelope.gain.linearRampToValueAtTime(
1445
+ envelope.gain.exponentialRampToValueAtTime(
1352
1446
  this.#noZeroVolume(volume * lastVolume),
1353
1447
  when + lastTime
1354
1448
  );
1355
1449
  }
1356
- envelope.gain.linearRampToValueAtTime(this.#noZeroVolume(0), when + duration + this.#afterTime);
1450
+ envelope.gain.exponentialRampToValueAtTime(this.#nearZero, when + duration + this.#afterTime);
1357
1451
  }
1358
- #findEnvelope() {
1359
- let envelope = this.#envelopes.find((e) => e.target === this.#audioCtx.destination && this.#audioCtx.currentTime > e.when + e.duration + 1e-3);
1452
+ #findEnvelope(destinationNode) {
1453
+ const target = destinationNode || this.#mainGain;
1454
+ const now = this.#audioCtx.currentTime;
1455
+ let envelope = this.#envelopes.find((e) => e.target === target && now > e.when + e.duration + 0.05);
1456
+ if (!envelope && this.#envelopes.length >= 64) {
1457
+ const activeEnvelopes = this.#envelopes.filter((e) => e.target === target);
1458
+ if (activeEnvelopes.length > 0) {
1459
+ activeEnvelopes.sort((a, b) => a.when - b.when);
1460
+ envelope = activeEnvelopes[0];
1461
+ }
1462
+ }
1360
1463
  if (envelope) {
1361
1464
  if (envelope.audioBufferSourceNode) {
1362
1465
  try {
@@ -1366,27 +1469,36 @@
1366
1469
  }
1367
1470
  envelope.audioBufferSourceNode = null;
1368
1471
  }
1472
+ envelope.gain.cancelScheduledValues(0);
1473
+ envelope.gain.setValueAtTime(this.#nearZero, now);
1369
1474
  } else {
1370
1475
  envelope = this.#audioCtx.createGain();
1371
1476
  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
- };
1477
+ envelope.target = target;
1478
+ envelope.connect(target);
1382
1479
  this.#envelopes.push(envelope);
1383
1480
  }
1481
+ envelope.cancel = (force = false) => {
1482
+ const currentTime = this.#audioCtx.currentTime;
1483
+ if (force && envelope.audioBufferSourceNode) {
1484
+ try {
1485
+ envelope.audioBufferSourceNode.stop(0);
1486
+ envelope.audioBufferSourceNode.disconnect();
1487
+ } catch (e) {
1488
+ }
1489
+ envelope.audioBufferSourceNode = null;
1490
+ }
1491
+ if (envelope.when + envelope.duration > currentTime) {
1492
+ envelope.gain.cancelScheduledValues(0);
1493
+ envelope.gain.setTargetAtTime(this.#nearZero, currentTime, force ? 5e-3 : 0.02);
1494
+ envelope.when = currentTime + 1e-5;
1495
+ envelope.duration = 0;
1496
+ }
1497
+ };
1384
1498
  return envelope;
1385
1499
  }
1386
1500
  #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;
1501
+ return this.#preset.zones.findLast((z) => pitch >= z.keyRangeLow && pitch <= z.keyRangeHigh);
1390
1502
  }
1391
1503
  #limitVolume(v) {
1392
1504
  const requestedVolume = v ? 1 * v : 0.5;
@@ -1399,209 +1511,1597 @@
1399
1511
  return typeof a === "number" ? a : b;
1400
1512
  }
1401
1513
  };
1514
+ if (typeof window !== "undefined") window.WebAudioFontPlayer = WebAudioFontPlayer;
1515
+ var index_default = WebAudioFontPlayer;
1402
1516
 
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
- ]
1517
+ // src/libraries/audiocompressor.js
1518
+ var AudioCompressor = class _AudioCompressor {
1519
+ #input = null;
1520
+ #output = null;
1521
+ #audioCtx = null;
1522
+ #limiter = null;
1523
+ #analyser = null;
1524
+ #reverbNode = null;
1525
+ #reverbWet = null;
1526
+ #currentReverbLevel = 0;
1527
+ #eqBands = /* @__PURE__ */ new Map();
1528
+ static #EQ_FREQUENCIES = [32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384];
1529
+ static #EQ_Q = /* @__PURE__ */ new Map([
1530
+ [32, 0.7],
1531
+ [64, 0.8],
1532
+ [128, 0.9],
1533
+ [256, 1],
1534
+ [512, 1.1],
1535
+ [1024, 1.2],
1536
+ [2048, 1.4],
1537
+ [4096, 1.6],
1538
+ [8192, 1.8],
1539
+ [16384, 2]
1540
+ ]);
1541
+ constructor(audioCtx, volume, reverb) {
1542
+ this.#audioCtx = audioCtx;
1543
+ this.#input = this.#audioCtx.createGain();
1544
+ let lastNode = this.#input;
1545
+ const frequencies = [32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384];
1546
+ frequencies.forEach((freq) => {
1547
+ lastNode = this.#bandEqualizer(lastNode, freq);
1548
+ const label = freq < 1e3 ? freq : freq / 1024 + "k";
1549
+ this["band".concat(label)] = lastNode;
1550
+ });
1551
+ this.#currentReverbLevel = reverb;
1552
+ this.#reverbNode = this.#audioCtx.createConvolver();
1553
+ this.#reverbWet = this.#audioCtx.createGain();
1554
+ this.#reverbWet.gain.setValueAtTime(reverb, this.#audioCtx.currentTime);
1555
+ this.#generateImpulseResponse(1.5, 2);
1556
+ this.#limiter = this.#audioCtx.createDynamicsCompressor();
1557
+ this.#limiter.threshold.setValueAtTime(-10, this.#audioCtx.currentTime);
1558
+ this.#limiter.ratio.setValueAtTime(20, this.#audioCtx.currentTime);
1559
+ this.#limiter.attack.setValueAtTime(1e-3, this.#audioCtx.currentTime);
1560
+ this.#limiter.release.setValueAtTime(0.1, this.#audioCtx.currentTime);
1561
+ this.#limiter.knee.setValueAtTime(0, this.#audioCtx.currentTime);
1562
+ this.#analyser = this.#audioCtx.createAnalyser();
1563
+ this.#analyser.fftSize = 256;
1564
+ this.#analyser.smoothingTimeConstant = 0.6;
1565
+ this.#output = this.#audioCtx.createGain();
1566
+ this.#output.gain.setValueAtTime(volume, this.#audioCtx.currentTime);
1567
+ lastNode.connect(this.#output);
1568
+ this.#output.connect(this.#limiter);
1569
+ this.#output.connect(this.#reverbNode);
1570
+ this.#reverbNode.connect(this.#reverbWet);
1571
+ this.#limiter.connect(this.#analyser);
1572
+ this.#reverbWet.connect(this.#analyser);
1573
+ this.#analyser.connect(this.#audioCtx.destination);
1574
+ }
1575
+ get eqFrequencies() {
1576
+ return _AudioCompressor.#EQ_FREQUENCIES;
1577
+ }
1578
+ get analyser() {
1579
+ return this.#analyser || null;
1580
+ }
1581
+ get input() {
1582
+ return this.#input;
1583
+ }
1584
+ get reverb() {
1585
+ return this.#currentReverbLevel;
1586
+ }
1587
+ set reverb(value) {
1588
+ this.#currentReverbLevel = Math.max(0, Math.min(1, value));
1589
+ this.#reverbWet.gain.setTargetAtTime(this.#currentReverbLevel, this.#audioCtx.currentTime, 0.1);
1590
+ }
1591
+ get masterVolume() {
1592
+ return this.#output.gain.value;
1593
+ }
1594
+ set masterVolume(value) {
1595
+ const linearValue = Math.max(0, Math.min(1, value));
1596
+ const logVolume = Math.pow(linearValue, 2);
1597
+ this.#output.gain.setTargetAtTime(logVolume, this.#audioCtx.currentTime, 0.01);
1598
+ }
1599
+ killReverbTail() {
1600
+ const now = this.#audioCtx.currentTime;
1601
+ this.#reverbWet.gain.cancelScheduledValues(now);
1602
+ this.#reverbWet.gain.setValueAtTime(0, now);
1603
+ }
1604
+ restoreReverb() {
1605
+ this.reverb = this.#currentReverbLevel;
1606
+ }
1607
+ setEQ(gains, smoothTime = 0.04) {
1608
+ const now = this.#audioCtx.currentTime;
1609
+ const MAX_DB = 12;
1610
+ for (const [key, val] of Object.entries(gains)) {
1611
+ const freq = Number(key);
1612
+ const band = this.#eqBands.get(freq);
1613
+ if (!band) continue;
1614
+ const dbValue = Math.max(-MAX_DB, Math.min(MAX_DB, val));
1615
+ band.filter.gain.setTargetAtTime(dbValue, now, smoothTime);
1616
+ band.gain = dbValue;
1617
+ }
1618
+ }
1619
+ getEQ() {
1620
+ const result = {};
1621
+ for (const [freq, band] of this.#eqBands) result[freq] = band.gain;
1622
+ return result;
1623
+ }
1624
+ resetEQ(smoothTime = 0.04) {
1625
+ const flat = {};
1626
+ for (const freq of _AudioCompressor.#EQ_FREQUENCIES) flat[freq] = 0;
1627
+ this.setEQ(flat, smoothTime);
1628
+ }
1629
+ setEQPreset(name) {
1630
+ const presets = {
1631
+ flat: { 32: 0, 64: 0, 128: 0, 256: 0, 512: 0, 1024: 0, 2048: 0, 4096: 0, 8192: 0, 16384: 0 },
1632
+ bass: { 32: 7, 64: 6, 128: 4, 256: 2, 512: 0, 1024: -1, 2048: -1, 4096: 0, 8192: 0, 16384: 0 },
1633
+ treble: { 32: 0, 64: 0, 128: 0, 256: 0, 512: 0, 1024: 1, 2048: 3, 4096: 5, 8192: 7, 16384: 8 },
1634
+ vocal: { 32: -3, 64: -2, 128: 0, 256: 2, 512: 4, 1024: 5, 2048: 4, 4096: 2, 8192: 1, 16384: 0 },
1635
+ loudness: { 32: 6, 64: 4, 128: 1, 256: 0, 512: -1, 1024: -1, 2048: 0, 4096: 2, 8192: 4, 16384: 5 },
1636
+ classical: { 32: 4, 64: 3, 128: 2, 256: 0, 512: 0, 1024: 0, 2048: 0, 4096: 2, 8192: 3, 16384: 4 },
1637
+ jazz: { 32: 4, 64: 3, 128: 1, 256: 0, 512: -1, 1024: -1, 2048: 0, 4096: 1, 8192: 3, 16384: 4 },
1638
+ electronic: { 32: 6, 64: 5, 128: 2, 256: -1, 512: -2, 1024: -1, 2048: 2, 4096: 4, 8192: 5, 16384: 6 }
1639
+ };
1640
+ const preset = presets[name];
1641
+ if (!preset) throw new Error('Preset EQ unkown: "'.concat(name, '". Avaiables: ').concat(Object.keys(presets).join(", ")));
1642
+ this.setEQ(preset);
1643
+ }
1644
+ #bandEqualizer(from, frequency) {
1645
+ const filter = this.#audioCtx.createBiquadFilter();
1646
+ filter.type = "peaking";
1647
+ filter.frequency.setValueAtTime(frequency, this.#audioCtx.currentTime);
1648
+ filter.gain.setValueAtTime(0, this.#audioCtx.currentTime);
1649
+ const q = _AudioCompressor.#EQ_Q.get(frequency) ?? 1;
1650
+ filter.Q.setValueAtTime(q, this.#audioCtx.currentTime);
1651
+ this.#eqBands.set(frequency, { filter, gain: 0 });
1652
+ from.connect(filter);
1653
+ return filter;
1654
+ }
1655
+ #generateImpulseResponse(duration, decay) {
1656
+ const sampleRate = this.#audioCtx.sampleRate;
1657
+ const length = sampleRate * duration;
1658
+ const impulse = this.#audioCtx.createBuffer(2, length, sampleRate);
1659
+ const preDelayTime = 0.015;
1660
+ const preDelaySamples = Math.floor(preDelayTime * sampleRate);
1661
+ for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
1662
+ const data = impulse.getChannelData(channel);
1663
+ let lastValue = 0;
1664
+ const channelOffset = channel === 1 ? Math.floor(2e-3 * sampleRate) : 0;
1665
+ for (let i = 0; i < length; i++) {
1666
+ if (i < preDelaySamples) {
1667
+ data[i] = 0;
1668
+ continue;
1669
+ }
1670
+ const t = (i - preDelaySamples) / sampleRate;
1671
+ const envelope = Math.exp(-t * (decay / duration));
1672
+ const dampingFactor = Math.max(0.01, 0.2 * Math.exp(-t * 2.5));
1673
+ const whiteNoise = Math.random() * 2 - 1;
1674
+ lastValue = whiteNoise * dampingFactor + lastValue * (1 - dampingFactor);
1675
+ let sampleValue = lastValue * envelope;
1676
+ if (t < 0.04) {
1677
+ if (i % 123 === 0 || i % 234 === 0) {
1678
+ sampleValue += (Math.random() * 2 - 1) * 0.2 * (0.04 - t) / 0.04;
1679
+ }
1680
+ }
1681
+ if (i + channelOffset < length) data[i + channelOffset] = sampleValue;
1682
+ else data[i] = sampleValue;
1683
+ }
1684
+ }
1685
+ this.#reverbNode.buffer = impulse;
1686
+ }
1519
1687
  };
1520
1688
 
1689
+ // src/libraries/indexeddbstorage.js
1690
+ var DB_NAME = "MidiAudioPlayer";
1691
+ var STORE_NAME = "KeyValues";
1692
+ var DEFAULT_VERSION = 1;
1693
+ var dbInstance = null;
1694
+ var currentVersion = DEFAULT_VERSION;
1695
+ async function getDB(version = currentVersion) {
1696
+ if (dbInstance && version !== currentVersion) {
1697
+ dbInstance.close();
1698
+ dbInstance = null;
1699
+ currentVersion = version;
1700
+ }
1701
+ if (dbInstance) return dbInstance;
1702
+ return new Promise((resolve, reject) => {
1703
+ const request = indexedDB.open(DB_NAME, version);
1704
+ request.onupgradeneeded = (e) => {
1705
+ const db = e.target.result;
1706
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
1707
+ db.createObjectStore(STORE_NAME);
1708
+ }
1709
+ };
1710
+ request.onsuccess = (e) => {
1711
+ dbInstance = e.target.result;
1712
+ resolve(dbInstance);
1713
+ };
1714
+ request.onerror = (e) => reject(e.target.error);
1715
+ });
1716
+ }
1717
+ var indexedDbStorage = {
1718
+ async setVersion(version) {
1719
+ await getDB(version);
1720
+ },
1721
+ async setItem(key, value, compress = false) {
1722
+ const db = await getDB();
1723
+ let finalData = value;
1724
+ let isCompressed = false;
1725
+ if (compress) {
1726
+ const stringData = JSON.stringify(value);
1727
+ const stream = new Blob([stringData]).stream();
1728
+ const compressedStream = stream.pipeThrough(new CompressionStream("gzip"));
1729
+ const response = new Response(compressedStream);
1730
+ finalData = await response.arrayBuffer();
1731
+ isCompressed = true;
1732
+ }
1733
+ const record = { data: finalData, isCompressed };
1734
+ return new Promise((resolve, reject) => {
1735
+ const transaction = db.transaction(STORE_NAME, "readwrite");
1736
+ const store = transaction.objectStore(STORE_NAME);
1737
+ const request = store.put(record, key);
1738
+ request.onsuccess = () => resolve();
1739
+ request.onerror = () => reject(request.error);
1740
+ });
1741
+ },
1742
+ async getItem(key) {
1743
+ const db = await getDB();
1744
+ const record = await new Promise((resolve, reject) => {
1745
+ const transaction = db.transaction(STORE_NAME, "readonly");
1746
+ const store = transaction.objectStore(STORE_NAME);
1747
+ const request = store.get(key);
1748
+ request.onsuccess = () => resolve(request.result);
1749
+ request.onerror = () => reject(request.error);
1750
+ });
1751
+ if (!record) return null;
1752
+ if (record.isCompressed) {
1753
+ const stream = new Blob([record.data]).stream();
1754
+ const decompressedStream = stream.pipeThrough(new DecompressionStream("gzip"));
1755
+ const response = new Response(decompressedStream);
1756
+ const text = await response.text();
1757
+ return JSON.parse(text);
1758
+ }
1759
+ return record.data;
1760
+ },
1761
+ async removeItem(key) {
1762
+ const db = await getDB();
1763
+ return new Promise((resolve, reject) => {
1764
+ const transaction = db.transaction(STORE_NAME, "readwrite");
1765
+ const store = transaction.objectStore(STORE_NAME);
1766
+ const request = store.delete(key);
1767
+ request.onsuccess = () => resolve();
1768
+ request.onerror = () => reject(request.error);
1769
+ });
1770
+ },
1771
+ async clear() {
1772
+ const db = await getDB();
1773
+ return new Promise((resolve, reject) => {
1774
+ const transaction = db.transaction(STORE_NAME, "readwrite");
1775
+ const store = transaction.objectStore(STORE_NAME);
1776
+ const request = store.clear();
1777
+ request.onsuccess = () => resolve();
1778
+ request.onerror = () => reject(request.error);
1779
+ });
1780
+ }
1781
+ };
1782
+ var indexeddbstorage_default = indexedDbStorage;
1783
+
1521
1784
  // src/midiaudioplayer.js
1522
- var MidiAudioPlayer = class extends index.Player {
1785
+ var clamp = (num, min, max) => Math.min(Math.max(num, min), max);
1786
+ var MidiAudioPlayer = class _MidiAudioPlayer extends index.Player {
1787
+ static ENDPOINT = "https://webaudiofonts.com/presets/";
1788
+ static DEFAULT_PRESET = -1;
1789
+ static REFERENCE_GAIN = 0.15;
1790
+ static KARAOKE_CHANNEL = 0;
1791
+ #catalog = null;
1523
1792
  #audioCtx = null;
1524
- #audioPlayer = null;
1525
- #activeNotes = null;
1793
+ #compressor = null;
1794
+ #vocalChannel = null;
1795
+ #activeNotes = {};
1796
+ #channelStates = {};
1797
+ #instruments = {};
1798
+ #players = {};
1799
+ #channels = {};
1800
+ #channelVolumes = {};
1801
+ #presetMap = {};
1802
+ #bufferHash = null;
1803
+ #presetTimer = null;
1804
+ #presetMapThread = null;
1805
+ #lyrics = null;
1806
+ #haveLyrics = false;
1807
+ #title = "";
1526
1808
  #opts = {
1527
- preset: defaultpreset_default,
1528
- volume: 0.012,
1529
- onEndFile: null
1809
+ endpoint: _MidiAudioPlayer.ENDPOINT,
1810
+ volume: 0.6,
1811
+ reverb: 0.3,
1812
+ onEndFile: null,
1813
+ localCache: true,
1814
+ presetRandom: false,
1815
+ karaoke: false,
1816
+ karaokeDelay: 0,
1817
+ muteExpression: false,
1818
+ maxCharPerLine: 48,
1819
+ eqPreset: "flat",
1820
+ preferred: [],
1821
+ presets: []
1530
1822
  };
1531
1823
  constructor(opts = {}) {
1532
- super((event) => this.#handleMidiPipeline(event));
1824
+ super();
1533
1825
  this.#opts = { ...this.#opts, ...opts };
1534
- this.#activeNotes = /* @__PURE__ */ new Map();
1826
+ this.#presetMapThread = this.#mapPresets();
1535
1827
  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();
1828
+ this.#compressor = new AudioCompressor(this.#audioCtx, this.#opts.volume, this.#opts.reverb);
1829
+ this.#compressor.setEQPreset(this.#opts.eqPreset);
1830
+ if (this.#opts.karaoke) this.#sendKaraokeFrame("intro");
1831
+ }
1832
+ get catalog() {
1833
+ return this.getCatalog();
1834
+ }
1835
+ get channels() {
1836
+ return this.#players;
1837
+ }
1838
+ get channelStates() {
1839
+ return this.#channelStates;
1840
+ }
1841
+ get volume() {
1842
+ return this.#opts.volume;
1843
+ }
1844
+ set volume(vol) {
1845
+ this.#opts.volume = clamp(vol, 0, 1);
1846
+ this.#compressor.masterVolume = this.#opts.volume;
1847
+ }
1848
+ get volumes() {
1849
+ return this.#channelVolumes;
1850
+ }
1851
+ get reverb() {
1852
+ return this.#compressor.reverb;
1853
+ }
1854
+ set reverb(rev) {
1855
+ this.#compressor.reverb = rev;
1856
+ }
1857
+ get muteExpression() {
1858
+ return this.#opts.muteExpression;
1859
+ }
1860
+ set muteExpression(val) {
1861
+ this.#opts.muteExpression = Boolean(val);
1862
+ }
1863
+ get eqFrequencies() {
1864
+ return this.#compressor.eqFrequencies;
1865
+ }
1866
+ get eq() {
1867
+ return this.#compressor.getEQ();
1868
+ }
1869
+ getEQ() {
1870
+ return this.#compressor.getEQ();
1871
+ }
1872
+ setEQ(gains) {
1873
+ this.#compressor.setEQ(gains);
1874
+ }
1875
+ setEQPreset(name) {
1876
+ this.#compressor.setEQPreset(name);
1877
+ }
1878
+ setChannelVolume(channel, volume) {
1879
+ this.#channelVolumes[channel] = volume;
1880
+ this.#setupChange();
1881
+ }
1882
+ async #mapPresets() {
1883
+ await Promise.all(this.#opts.presets.map(async (p) => {
1884
+ const preset = await this.findPreset(p);
1885
+ if (preset) this.#presetMap[preset.program] = preset;
1886
+ }));
1887
+ }
1888
+ async findPreset(id) {
1889
+ let preset = null;
1890
+ const categories = await this.getCategories();
1891
+ categories.some((c) => {
1892
+ c.instruments.some((i) => {
1893
+ preset = i.presets.find((p) => p.id == id);
1894
+ if (preset) {
1895
+ preset.category = c.name;
1896
+ preset.instrument = i.name;
1897
+ preset.program = i.program;
1898
+ return true;
1899
+ }
1900
+ });
1901
+ if (preset) return true;
1540
1902
  });
1903
+ return preset;
1904
+ }
1905
+ async close() {
1906
+ Object.keys(this.#players).forEach((id) => this.#players[id].close());
1907
+ await this.#audioCtx.close();
1908
+ }
1909
+ async getCatalog() {
1910
+ if (this.#catalog) return this.#catalog;
1911
+ const cachedata = this.#opts.localCache ? await sessionStorage.getItem("waf_catalog") : null;
1912
+ if (cachedata) this.#catalog = JSON.parse(cachedata);
1913
+ else {
1914
+ this.#log("Downloading catalog...");
1915
+ const response = await fetch("".concat(this.#opts.endpoint, "catalog.json"));
1916
+ if (!response.ok) throw new Error("Impossible to download catalog: ".concat(response.status));
1917
+ this.#catalog = await response.json();
1918
+ if (this.#opts.localCache) await sessionStorage.setItem("waf_catalog", JSON.stringify(this.#catalog));
1919
+ }
1920
+ const catalogDate = new Date(this.#catalog.updatedAt).getTime();
1921
+ const catalogVersion = await indexeddbstorage_default.getItem("waf_catalog_version") || 1;
1922
+ if (catalogVersion < catalogDate) {
1923
+ await indexeddbstorage_default.clear();
1924
+ indexeddbstorage_default.setItem("waf_catalog_version", catalogDate);
1925
+ }
1926
+ return this.#catalog;
1927
+ }
1928
+ async getCategories() {
1929
+ return (await this.getCatalog()).categories;
1930
+ }
1931
+ async getProgramInstruments(program) {
1932
+ const categories = await this.getCategories();
1933
+ let instruments = [];
1934
+ await Promise.all(categories.map(async (category) => category.instruments.filter((elm) => elm.program == program).forEach((elm) => {
1935
+ elm.presets.forEach((p) => {
1936
+ p.instrument = category.name + " / " + elm.name;
1937
+ instruments.push(p);
1938
+ });
1939
+ })));
1940
+ return instruments;
1941
+ }
1942
+ async getPreset(id) {
1943
+ try {
1944
+ if (typeof id === "object") return id;
1945
+ const cacheid = "waf_preset_".concat(id);
1946
+ const cachedata = this.#opts.localCache ? await indexeddbstorage_default.getItem(cacheid) : null;
1947
+ if (cachedata) return JSON.parse(cachedata);
1948
+ this.#log("Downloading preset ".concat(id, "..."));
1949
+ const response = await fetch("".concat(_MidiAudioPlayer.ENDPOINT).concat(id, ".json"));
1950
+ const preset = await response.json();
1951
+ if (preset.zones === void 0) {
1952
+ console.error("Invalid preset: ".concat($id));
1953
+ throw new Error("Invalid preset: ".concat($id));
1954
+ }
1955
+ if (this.#opts.localCache) await indexeddbstorage_default.setItem(cacheid, JSON.stringify(preset), true);
1956
+ return preset;
1957
+ } catch (e) {
1958
+ console.error("Invalid preset: ".concat(id));
1959
+ throw new Error("Invalid preset: ".concat(id));
1960
+ }
1961
+ }
1962
+ async loadPreset(presetId, channel) {
1963
+ const presetInfo = await this.findPreset(presetId);
1964
+ if (!presetInfo) throw new Error("Invalid preset: ".concat(presetId));
1965
+ this.#presetMap[presetInfo.program] = presetInfo;
1966
+ const preset = await this.getPreset(presetId);
1967
+ await this.#players[channel].setPreset(preset);
1968
+ this.#setupChange();
1541
1969
  }
1542
- async load(content) {
1970
+ async load(content, setup) {
1971
+ if (typeof content === "string") {
1972
+ this.#log("Downloading song...");
1973
+ const response = await fetch(content);
1974
+ content = await response.arrayBuffer();
1975
+ }
1976
+ if (typeof setup === "string") {
1977
+ this.#log("Downloading setup...");
1978
+ const response = await fetch(setup);
1979
+ setup = await response.json();
1980
+ }
1981
+ this.#bufferHash = await this.hashBuffer(content);
1982
+ await this.#presetMapThread;
1543
1983
  if (this.isPlaying()) this.stop();
1544
1984
  this.#clearActiveNotes();
1545
- await this.loadArrayBuffer(content);
1985
+ await Promise.all(Object.values(this.#players).map(async (player) => player.close()));
1986
+ this.#players = {};
1987
+ this.#instruments = {};
1988
+ this.#activeNotes = {};
1989
+ this.#title = "";
1990
+ this.#log("Loading buffer...");
1991
+ try {
1992
+ await this.loadArrayBuffer(content);
1993
+ } catch (e) {
1994
+ await this.loadArrayBuffer(await this.#repairMidi(content));
1995
+ }
1996
+ this.#log("Loading instruments...");
1997
+ this.#channels = await this.#getInstruments();
1998
+ this.#channelStates = Object.keys(this.#channels).reduce((acc, key) => ({ ...acc, [key]: false }), {});
1999
+ this.#channelVolumes = Object.keys(this.#channels).reduce((acc, key) => ({ ...acc, [key]: 1 }), {});
2000
+ if (setup?.volumes !== void 0) {
2001
+ await Promise.all(Object.keys(setup.volumes).map(async (channel) => {
2002
+ if (this.#channelVolumes[channel] === void 0) return;
2003
+ this.#channelVolumes[channel] = setup.volumes[channel];
2004
+ }));
2005
+ }
2006
+ const setupPrograms = /* @__PURE__ */ new Set();
2007
+ const setupPresets = {};
2008
+ if (setup?.presets !== void 0) {
2009
+ await Promise.all(Object.keys(setup.presets).map(async (channel) => {
2010
+ const presetInfo = await this.findPreset(setup.presets[channel]);
2011
+ if (!presetInfo) return;
2012
+ setupPresets[channel] = await this.getPreset(presetInfo.id);
2013
+ setupPrograms.add(presetInfo.program);
2014
+ }));
2015
+ }
2016
+ const uniqueInstruments = await this.#getUniqueInstruments();
2017
+ if (!Object.values(this.#channels).length) this.#log("Error: no instrument found");
2018
+ const presets = Promise.all([...uniqueInstruments].map(async (program) => {
2019
+ if (setupPrograms.has(program)) return;
2020
+ let preset = null;
2021
+ if (this.#presetMap[program] !== void 0) preset = await this.getPreset(this.#presetMap[program].id);
2022
+ else if (this.#opts.presetRandom) preset = await this.#getRandomPreset(program);
2023
+ else preset = await this.#getAutoPreset(program);
2024
+ this.#instruments[program] = preset;
2025
+ }));
2026
+ if (this.#opts.karaoke) {
2027
+ this.#log("Generating karaoke frames...");
2028
+ this.#lyrics = null;
2029
+ await this.#generateKaraokeFrames();
2030
+ if (this.#title) this.#sendKaraokeFrame("title", this.#title);
2031
+ }
2032
+ this.#log("Trim midi events...");
2033
+ this.#trimMidiEvents();
2034
+ queueMicrotask(() => this.triggerPlayerEvent("computed"));
2035
+ await presets;
2036
+ await Promise.all(Object.keys(this.#channels).map(async (channel) => {
2037
+ if (this.#players[channel]) this.#players[channel].close();
2038
+ if (setupPresets[channel] !== void 0) this.#players[channel] = await this.#createPlayer(setupPresets[channel]);
2039
+ else this.#players[channel] = await this.#createPlayer(this.#instruments[this.#channels[channel]]);
2040
+ }));
2041
+ this.#log("Initializing instrument states...");
2042
+ await this.#initInstrumentStates();
2043
+ await this.triggerPlayerEvent("presetsLoaded", this.#instruments);
2044
+ await this.#setupChange();
2045
+ this.#log("Player ready");
2046
+ }
2047
+ async getSongSetup() {
2048
+ let setup = { hash: this.#bufferHash, presets: {}, volumes: {} };
2049
+ Object.keys(this.#players).map(async (channel) => setup.presets[channel] = this.#players[channel].preset.id);
2050
+ setup.volumes = this.#channelVolumes;
2051
+ return setup;
2052
+ }
2053
+ async getTrainingPresets() {
2054
+ return await Promise.all(Object.values(this.#presetMap).map(async (preset) => preset.id));
1546
2055
  }
1547
2056
  async play(content = null) {
2057
+ if (this.#audioCtx.state === "suspended") {
2058
+ try {
2059
+ await this.#audioCtx.resume();
2060
+ } catch (e) {
2061
+ return false;
2062
+ }
2063
+ }
1548
2064
  if (content) await this.load(content);
1549
- await this.#audioCtx.resume();
1550
- await super.play();
2065
+ await Promise.all(Object.keys(this.#players).map(async (k) => await this.#players[k]?.cancelQueue()));
2066
+ this.#compressor.restoreReverb();
2067
+ if (!this.isPlaying()) {
2068
+ if (!this.startTime) this.startTime = (/* @__PURE__ */ new Date()).getTime();
2069
+ this.scheduledTime = Date.now();
2070
+ this.schedulePlayLoop(this.sampleRate);
2071
+ }
2072
+ return true;
1551
2073
  }
1552
2074
  async pause() {
1553
2075
  await super.pause();
2076
+ this.#compressor.killReverbTail();
1554
2077
  await this.#clearActiveNotes();
1555
- await this.#audioPlayer.cancelQueue();
2078
+ await Promise.all(Object.keys(this.#players).map(async (k) => await this.#players[k]?.cancelQueue()));
1556
2079
  }
1557
- async stop() {
2080
+ async stop(skipKill = false) {
1558
2081
  await super.stop();
2082
+ this.setTimeoutId = false;
2083
+ if (!skipKill) {
2084
+ this.#compressor.killReverbTail();
2085
+ await Promise.all(Object.keys(this.#players).map(async (k) => await this.#players[k]?.cancelQueue()));
2086
+ }
1559
2087
  await this.#clearActiveNotes();
1560
- await this.#audioPlayer.cancelQueue();
2088
+ if (this.#opts.karaoke) this.#sendKaraokeFrame("intro");
2089
+ return this;
2090
+ }
2091
+ getRealTimeVolume() {
2092
+ const analyser = this.#compressor.analyser;
2093
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
2094
+ analyser.getByteFrequencyData(dataArray);
2095
+ let values = 0;
2096
+ for (let i = 0; i < dataArray.length; i++) values += dataArray[i];
2097
+ return values / (dataArray.length * 100);
2098
+ }
2099
+ getSongTimeRemaining() {
2100
+ return this.ticksToSeconds(this.getCurrentTick(), this.totalTicks);
2101
+ }
2102
+ async skipToSeconds(seconds) {
2103
+ const songTime = this.getSongTime();
2104
+ if (seconds < 0 || seconds > songTime) throw seconds + " seconds not within song time of " + songTime;
2105
+ await this.skipToTick(this.secondsToTicks(seconds));
2106
+ return this;
2107
+ }
2108
+ async generateWaveformSVG(samples = 1e3) {
2109
+ if (!this.totalTicks || !this.events) return "";
2110
+ const waveform = new Array(samples).fill(0);
2111
+ const tickInterval = this.totalTicks / samples;
2112
+ const allEvents = this.events.flatMap(
2113
+ (track, trackIdx) => track.map((event) => ({
2114
+ ...event,
2115
+ computedChannel: event.channel !== void 0 ? event.channel : trackIdx
2116
+ }))
2117
+ ).filter(
2118
+ (event) => event.name === "Controller Change" || event.name === "Program Change" || event.name === "Note on" && event.velocity > 0
2119
+ ).sort((a, b) => a.tick - b.tick);
2120
+ const channelsVolume = /* @__PURE__ */ new Map();
2121
+ const channelsExpression = /* @__PURE__ */ new Map();
2122
+ allEvents.forEach((event) => {
2123
+ const idx = Math.floor(event.tick / tickInterval);
2124
+ if (idx >= samples) return;
2125
+ const chan = event.computedChannel;
2126
+ if (!channelsVolume.has(chan)) channelsVolume.set(chan, 100);
2127
+ if (!channelsExpression.has(chan)) channelsExpression.set(chan, 127);
2128
+ if (event.name === "Controller Change") {
2129
+ if (event.number === 7) channelsVolume.set(chan, event.value);
2130
+ else if (event.number === 11) channelsExpression.set(chan, event.value);
2131
+ } else if (event.name === "Note on") {
2132
+ const volFactor = channelsVolume.get(chan) / 127;
2133
+ const expFactor = channelsExpression.get(chan) / 127;
2134
+ const modulatedVelocity = event.velocity * volFactor * expFactor;
2135
+ waveform[idx] += modulatedVelocity;
2136
+ }
2137
+ });
2138
+ const maxAmp = waveform.reduce((max, val) => {
2139
+ if (isNaN(val)) return max;
2140
+ return val > max ? val : max;
2141
+ }, 0);
2142
+ const normalized = maxAmp > 0 ? waveform.map((v) => isNaN(v) ? 0 : v / maxAmp) : waveform.fill(0);
2143
+ const width = samples;
2144
+ const height = width / 5;
2145
+ const points = normalized.map((val, i) => {
2146
+ const x = i;
2147
+ const y = Math.max(0, Math.min(height, height - val * height));
2148
+ return "".concat(x, ",").concat(y.toFixed(2));
2149
+ });
2150
+ const d = "M 0,".concat(height, " L ").concat(points.join(" L "), " L ").concat(width, ",").concat(height);
2151
+ return '<svg class="midiaudioplayer-waveform" viewBox="0 0 '.concat(width, " ").concat(height, '" preserveAspectRatio="none"><path d="').concat(d, '" fill="none" stroke-linecap="round" stroke-linejoin="round" /></svg>');
2152
+ }
2153
+ // ----------------------------------------------------------------------------------------------------------------------
2154
+ // ----------------------------------------------------------------------------------------------------------------------
2155
+ // ----------------------------------------------------------------------------------------------------------------------
2156
+ async hashBuffer(arrayBuffer, algorithm = "SHA-256") {
2157
+ const hashBuffer = await crypto.subtle.digest(algorithm, arrayBuffer);
2158
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2159
+ return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
2160
+ }
2161
+ async #setupChange() {
2162
+ if (this.#presetTimer) clearTimeout(this.#presetTimer);
2163
+ this.#presetTimer = setTimeout(async () => {
2164
+ const setup = await this.getSongSetup();
2165
+ queueMicrotask(() => this.triggerPlayerEvent("setupChange", setup));
2166
+ }, 1e3);
2167
+ }
2168
+ async triggerPlayerEvent(playerEvent, data) {
2169
+ if (playerEvent == "fileLoaded") return;
2170
+ else if (playerEvent == "computed") {
2171
+ this.#vocalChannel = await this.#detectKaraokeVocalChannel();
2172
+ super.triggerPlayerEvent(playerEvent, {
2173
+ title: this.#title,
2174
+ karaoke: this.#haveLyrics,
2175
+ vocalChannel: this.#vocalChannel,
2176
+ tempo: this.tempo,
2177
+ division: this.division,
2178
+ duration: this.getSongTime(),
2179
+ sampleRate: this.sampleRate,
2180
+ totalTicks: this.totalTicks,
2181
+ totalEvents: this.totalEvents,
2182
+ channels: await this.#channels
2183
+ });
2184
+ } else if (playerEvent == "endOfFile" && this.#opts.karaoke) {
2185
+ queueMicrotask(() => super.triggerPlayerEvent(playerEvent, data));
2186
+ } else super.triggerPlayerEvent(playerEvent, data);
2187
+ }
2188
+ async playLoop(dryRun) {
2189
+ if (this.inLoop) return;
2190
+ if (!dryRun && this.endOfFile() && this.tick > 0) {
2191
+ await this.stop(true);
2192
+ this.tick = 0;
2193
+ this.triggerPlayerEvent("endOfFile");
2194
+ return;
2195
+ }
2196
+ this.inLoop = true;
2197
+ this.tick = this.getCurrentTick();
2198
+ const tracksLen = this.tracks.length;
2199
+ for (let i = 0; i < tracksLen; i++) {
2200
+ const result = this.tracks[i].handleEvent(this.tick, dryRun);
2201
+ if (!result) continue;
2202
+ const isArray = result.constructor === Array;
2203
+ const eventsLen = isArray ? result.length : 1;
2204
+ for (let j = 0; j < eventsLen; j++) {
2205
+ const event = isArray ? result[j] : result;
2206
+ const { name, data, value } = event;
2207
+ if (name === "Set Tempo") this.setTempo(data);
2208
+ if (dryRun) {
2209
+ if (name === "Program Change" && !this.instruments.includes(value)) this.instruments.push(value);
2210
+ } else {
2211
+ this.emitEvent(event);
2212
+ }
2213
+ }
2214
+ }
2215
+ if (!dryRun && this.isPlaying()) this.triggerPlayerEvent("playing", { tick: this.tick });
2216
+ this.inLoop = false;
2217
+ }
2218
+ schedulePlayLoop(delay) {
2219
+ this.setTimeoutId = setTimeout(() => {
2220
+ if (this.setTimeoutId === false) return;
2221
+ this.playLoop();
2222
+ const currentAudioTime = this.#audioCtx.currentTime;
2223
+ if (!this._lastAudioTime) this._lastAudioTime = currentAudioTime;
2224
+ const elapsed = currentAudioTime - this._lastAudioTime;
2225
+ this._lastAudioTime = currentAudioTime;
2226
+ const sampleRateSec = this.sampleRate / 1e3;
2227
+ const drift = elapsed - sampleRateSec;
2228
+ const nextDelay = Math.max(0, this.sampleRate - drift * 1e3);
2229
+ this.schedulePlayLoop(nextDelay);
2230
+ }, delay);
2231
+ }
2232
+ emitEvent(event) {
2233
+ this.#handleMidiPipeline(event);
2234
+ }
2235
+ ticksToSeconds(startTick, endTick) {
2236
+ if (endTick === void 0) {
2237
+ endTick = startTick;
2238
+ startTick = 0;
2239
+ }
2240
+ if (startTick >= endTick) return 0;
2241
+ let seconds = 0;
2242
+ const len = this.tempoMap.length;
2243
+ const timeFactor = 60 / this.division;
2244
+ let low = 0;
2245
+ let high = len - 1;
2246
+ let startIndex = 0;
2247
+ while (low <= high) {
2248
+ const mid = low + high >> 1;
2249
+ if (this.tempoMap[mid].tick <= startTick) {
2250
+ startIndex = mid;
2251
+ low = mid + 1;
2252
+ } else {
2253
+ high = mid - 1;
2254
+ }
2255
+ }
2256
+ let currentTick = startTick;
2257
+ for (let i = startIndex; i < len; i++) {
2258
+ const entry = this.tempoMap[i];
2259
+ const nextTick = i + 1 < len ? this.tempoMap[i + 1].tick : endTick;
2260
+ if (nextTick <= startTick) continue;
2261
+ const segStart = Math.max(entry.tick, startTick);
2262
+ const segEnd = Math.min(nextTick, endTick);
2263
+ if (segStart >= endTick) break;
2264
+ seconds += (segEnd - segStart) / entry.tempo * timeFactor;
2265
+ currentTick = segEnd;
2266
+ }
2267
+ if (currentTick < endTick) {
2268
+ const lastEntry = this.tempoMap[len - 1];
2269
+ seconds += (endTick - currentTick) / lastEntry.tempo * timeFactor;
2270
+ }
2271
+ return seconds;
2272
+ }
2273
+ secondsToTicks(seconds) {
2274
+ let remainingSeconds = seconds;
2275
+ const len = this.tempoMap.length;
2276
+ const factor = 60 / this.division;
2277
+ for (let i = 0; i < len; i++) {
2278
+ const entry = this.tempoMap[i];
2279
+ const nextTick = i + 1 < len ? this.tempoMap[i + 1].tick : Infinity;
2280
+ const segmentTicks = nextTick - entry.tick;
2281
+ const segmentSeconds = segmentTicks / entry.tempo * factor;
2282
+ if (remainingSeconds <= segmentSeconds) {
2283
+ return entry.tick + Math.round(remainingSeconds * entry.tempo / factor);
2284
+ }
2285
+ remainingSeconds -= segmentSeconds;
2286
+ }
2287
+ return this.totalTicks;
1561
2288
  }
1562
- async #endOfFile() {
1563
- if (typeof this.#opts.onEndFile == "function") await this.#opts.onEndFile();
2289
+ getTickBeforeSeconds(targetTick, seconds) {
2290
+ if (targetTick <= 0) return 0;
2291
+ const targetTime = this.ticksToSeconds(0, targetTick);
2292
+ const desiredTime = Math.max(0, targetTime - seconds);
2293
+ return this.secondsToTicks(desiredTime);
2294
+ }
2295
+ async skipToTick(tick) {
2296
+ const safeTick = Math.max(0, Math.min(tick, this.totalTicks || 0));
2297
+ const wasPlaying = this.isPlaying();
2298
+ this.#clearActiveNotes();
2299
+ Object.keys(this.channels).forEach((k) => this.channels[k]?.cancelQueue?.());
2300
+ if (wasPlaying) super.pause();
2301
+ this.startTick = safeTick;
2302
+ this.tick = safeTick;
2303
+ if (this.tempoMap && this.tempoMap.length > 0) {
2304
+ for (let i = this.tempoMap.length - 1; i >= 0; i--) {
2305
+ if (this.tempoMap[i].tick <= safeTick) {
2306
+ this.setTempo(this.tempoMap[i].tempo);
2307
+ break;
2308
+ }
2309
+ }
2310
+ }
2311
+ try {
2312
+ const controllerChange = [];
2313
+ const programChange = [];
2314
+ const pitchBend = [];
2315
+ const karaokeEvent = [];
2316
+ this.#collectStateAtTick(safeTick).forEach((event) => {
2317
+ const channel = event.channel;
2318
+ if ((channel === void 0 || !this.channels[channel]) && event.name !== "Karaoke Event") return;
2319
+ switch (event.name) {
2320
+ case "Controller Change":
2321
+ controllerChange[event.channel] = event;
2322
+ break;
2323
+ case "Program Change":
2324
+ programChange[event.channel] = event;
2325
+ break;
2326
+ case "Pitch Bend":
2327
+ pitchBend[event.channel] = event;
2328
+ break;
2329
+ case "Karaoke Event":
2330
+ karaokeEvent[event.channel] = event;
2331
+ break;
2332
+ }
2333
+ });
2334
+ controllerChange.forEach((evt) => this.emitEvent(evt));
2335
+ programChange.forEach((evt) => this.emitEvent(evt));
2336
+ pitchBend.forEach((evt) => this.emitEvent(evt));
2337
+ karaokeEvent.forEach((evt) => this.triggerPlayerEvent("karaoke", { type: evt.type, tick: evt.tick, html: evt.text }));
2338
+ } catch (e) {
2339
+ console.warn("Chase MIDI Error:", e);
2340
+ this.#log("Chase MIDI Error:", e);
2341
+ }
2342
+ if (this.tracks && this.tracks.length > 0) {
2343
+ this.tracks.forEach((track, index2) => {
2344
+ const trackEvents = this.events[index2];
2345
+ if (trackEvents && trackEvents.length > 0) {
2346
+ let low = 0;
2347
+ let high = trackEvents.length - 1;
2348
+ let pointer = trackEvents.length;
2349
+ while (low <= high) {
2350
+ const mid = low + high >> 1;
2351
+ if (trackEvents[mid].tick >= safeTick) {
2352
+ pointer = mid;
2353
+ high = mid - 1;
2354
+ } else {
2355
+ low = mid + 1;
2356
+ }
2357
+ }
2358
+ track.eventIndex = pointer;
2359
+ } else if (typeof track.setEventIndexByTick === "function") {
2360
+ track.setEventIndexByTick(safeTick);
2361
+ }
2362
+ });
2363
+ }
2364
+ if (wasPlaying) this.play();
2365
+ else this.triggerPlayerEvent("playing", { tick: safeTick });
2366
+ return this;
2367
+ }
2368
+ #collectStateAtTick(tick) {
2369
+ const dominated = {};
2370
+ if (!this.events) return [];
2371
+ for (let t = 0; t < this.events.length; t++) {
2372
+ const trackEvents = this.events[t];
2373
+ if (!trackEvents || trackEvents.length === 0) continue;
2374
+ let low = 0;
2375
+ let high = trackEvents.length - 1;
2376
+ let endIdx = trackEvents.length;
2377
+ while (low <= high) {
2378
+ const mid = low + high >> 1;
2379
+ if (trackEvents[mid].tick >= tick) {
2380
+ endIdx = mid;
2381
+ high = mid - 1;
2382
+ } else {
2383
+ low = mid + 1;
2384
+ }
2385
+ }
2386
+ for (let i = 0; i < endIdx; i++) {
2387
+ const event = trackEvents[i];
2388
+ let key;
2389
+ if (event.name === "Program Change") {
2390
+ key = "pc:" + event.channel;
2391
+ } else if (event.name === "Controller Change") {
2392
+ key = "cc:" + event.channel + ":" + event.number;
2393
+ } else if (event.name === "Pitch Bend") {
2394
+ key = "pb:" + event.channel;
2395
+ } else if (event.name === "Karaoke Event") {
2396
+ key = "ke:" + event.channel;
2397
+ }
2398
+ if (key) {
2399
+ dominated[key] = event;
2400
+ }
2401
+ }
2402
+ }
2403
+ return Object.keys(dominated).map((key) => dominated[key]);
2404
+ }
2405
+ async #initInstrumentStates() {
2406
+ if (this.events) {
2407
+ this.#collectStateAtTick(1).forEach((event) => {
2408
+ const channel = event.channel;
2409
+ if (!this.#players[channel]) return;
2410
+ switch (event.name) {
2411
+ case "Controller Change":
2412
+ this.#players[channel].setController(event.number, event.value);
2413
+ break;
2414
+ case "Pitch Bend":
2415
+ this.#players[channel].setPitchBend?.(event.value);
2416
+ break;
2417
+ case "Program Change":
2418
+ if (event.value >= 0 && event.value <= 127 && this.#instruments[event.value + 1] !== void 0 && event.channel != 10) {
2419
+ if (this.#players[channel].preset?.program !== event.value + 1) {
2420
+ this.#players[channel].setPreset(this.#instruments[event.value + 1]);
2421
+ }
2422
+ }
2423
+ break;
2424
+ }
2425
+ });
2426
+ }
2427
+ }
2428
+ async #getInstruments() {
2429
+ const instrumentMap = {};
2430
+ const channelUsed = /* @__PURE__ */ new Set();
2431
+ this.events.forEach((track) => {
2432
+ track.forEach((event) => {
2433
+ if (event.name === "Program Change" && event.value >= 0 && event.value <= 127) {
2434
+ if (instrumentMap[event.channel]) return;
2435
+ else if (event.channel == 10) instrumentMap[event.channel] = -1;
2436
+ else instrumentMap[event.channel] = event.value + 1;
2437
+ } else if (event.name === "Note on" && event.channel == 10) {
2438
+ instrumentMap[event.channel] = -1;
2439
+ channelUsed.add(10);
2440
+ } else if (event.name === "Note on") {
2441
+ channelUsed.add(event.channel);
2442
+ }
2443
+ });
2444
+ });
2445
+ Object.keys(instrumentMap).forEach((channel) => {
2446
+ if (!channelUsed.has(Number(channel))) delete instrumentMap[channel];
2447
+ });
2448
+ return instrumentMap;
2449
+ }
2450
+ async #getUniqueInstruments() {
2451
+ const instrumentMap = /* @__PURE__ */ new Set();
2452
+ this.events.forEach((track) => {
2453
+ track.forEach((event) => {
2454
+ if (event.name === "Program Change" && event.value >= 0 && event.value <= 127) {
2455
+ instrumentMap.add(event.channel == 10 ? -1 : event.value + 1);
2456
+ } else if (event.name === "Note on" && event.channel == 10) instrumentMap.add(-1);
2457
+ });
2458
+ });
2459
+ return instrumentMap;
2460
+ }
2461
+ async #getRandomPreset(program) {
2462
+ const instruments = await this.getProgramInstruments(program);
2463
+ if (!instruments.length) return null;
2464
+ let preset = null;
2465
+ this.#opts.preferred.some((bank) => {
2466
+ const regex = new RegExp("_".concat(bank, "$"), "i");
2467
+ const group = instruments.filter((i) => regex.test(i.id));
2468
+ if (group.length) {
2469
+ preset = group[Math.floor(Math.random() * group.length)];
2470
+ return true;
2471
+ }
2472
+ });
2473
+ if (!preset) preset = instruments[Math.floor(Math.random() * instruments.length)];
2474
+ this.#presetMap[program] = preset;
2475
+ return await this.getPreset(preset.id);
2476
+ }
2477
+ async #getAutoPreset(program) {
2478
+ const instruments = await this.getProgramInstruments(program);
2479
+ if (!instruments.length) return null;
2480
+ let preset = null;
2481
+ this.#opts.preferred.some((bank) => {
2482
+ const regex = new RegExp("_".concat(bank, "$"), "i");
2483
+ preset = instruments.find((i) => regex.test(i.id));
2484
+ if (preset) return true;
2485
+ });
2486
+ if (!preset) preset = instruments[0];
2487
+ this.#presetMap[program] = preset;
2488
+ return await this.getPreset(preset.id);
2489
+ }
2490
+ async #createPlayer(preset) {
2491
+ return new index_default(preset, this.#audioCtx, this.#compressor);
1564
2492
  }
1565
2493
  async #handleMidiPipeline(event) {
1566
- if (event.name !== "Note on" && event.name !== "Note off") return;
1567
2494
  if (!this.isPlaying()) return;
1568
- if (event.noteNumber === void 0) return;
1569
- const now = this.#audioCtx.currentTime;
1570
2495
  switch (event.name) {
1571
2496
  case "Note on":
2497
+ if (event.tick < this.tick - 100) return;
2498
+ if (event.noteNumber === void 0) return;
2499
+ if (event.channel == this.#vocalChannel && this.#opts.muteExpression) return;
1572
2500
  if (event.velocity > 0 && event.velocity <= 127) {
1573
- this.#stopNotePipe(event.noteNumber);
1574
- const vol = event.velocity / 127 * this.#opts.volume;
1575
- const envelope = this.#audioPlayer.queueWaveTable(0, event.noteNumber, 2, vol);
1576
- this.#activeNotes.set(event.noteNumber, envelope);
2501
+ this.#stopNote(event.channel, event.noteNumber);
2502
+ if (this.#channelVolumes[event.channel] == 0) return;
2503
+ const noteVelocityRatio = event.velocity / 127;
2504
+ const finalVol = _MidiAudioPlayer.REFERENCE_GAIN * Math.pow(noteVelocityRatio, 2) * this.#channelVolumes[event.channel];
2505
+ const envelope = this.#players[event.channel]?.queueWaveTable(0, event.noteNumber, 2, finalVol);
2506
+ if (envelope) this.#addNote(event.channel, event.noteNumber, envelope);
1577
2507
  } else {
1578
- this.#stopNotePipe(event.noteNumber);
2508
+ this.#stopNote(event.channel, event.noteNumber);
1579
2509
  }
1580
2510
  break;
1581
2511
  case "Note off":
1582
- this.#stopNotePipe(event.noteNumber);
2512
+ if (event.noteNumber === void 0) return;
2513
+ this.#stopNote(event.channel, event.noteNumber);
2514
+ break;
2515
+ case "Controller Change":
2516
+ this.#players[event.channel]?.setController(event.number, event.value);
2517
+ break;
2518
+ case "Pitch Bend":
2519
+ this.#players[event.channel]?.setPitchBend?.(event.value);
2520
+ break;
2521
+ case "Program Change":
2522
+ return;
2523
+ if (!this.#players[event.channel]) return;
2524
+ if (event.channel == 10 || event.value > 127 || event.value < 0) break;
2525
+ if (this.#players[event.channel] !== void 0 && this.#players[event.channel].preset.program != event.value + 1)
2526
+ this.#players[event.channel].setPreset(this.#instruments[event.value + 1]);
2527
+ break;
2528
+ case "Karaoke Event":
2529
+ if (event.tick < this.tick - this.secondsToTicks(10)) return;
2530
+ this.triggerPlayerEvent("karaoke", { type: event.type, tick: event.tick, html: event.text });
1583
2531
  break;
1584
2532
  }
1585
2533
  }
1586
- #stopNotePipe(noteNumber) {
1587
- const envelope = this.#activeNotes.get(noteNumber);
2534
+ #addNote(channel, note, envelope) {
2535
+ if (!this.#activeNotes[channel]) this.#activeNotes[channel] = /* @__PURE__ */ new Map();
2536
+ this.#activeNotes[channel].set(note, envelope);
2537
+ this.#updateChannelStates();
2538
+ const realDurationMs = (envelope.duration || 0) * 1e3;
2539
+ envelope.cleanupTimer = setTimeout(() => {
2540
+ if (this.#activeNotes[channel]?.get(note) === envelope) {
2541
+ this.#activeNotes[channel].delete(note);
2542
+ this.#updateChannelStates();
2543
+ }
2544
+ }, realDurationMs + 50);
2545
+ }
2546
+ #stopNote(channel, noteNumber) {
2547
+ const player = this.#players[channel];
2548
+ const envelope = this.#activeNotes[channel]?.get(noteNumber);
1588
2549
  if (envelope) {
1589
- envelope.cancel();
1590
- this.#activeNotes.delete(noteNumber);
2550
+ if (envelope.cleanupTimer) clearTimeout(envelope.cleanupTimer);
2551
+ const removeNoteFromRegistry = () => {
2552
+ this.#activeNotes[channel]?.delete(noteNumber);
2553
+ this.#updateChannelStates();
2554
+ };
2555
+ if (player && player.isSustainActive()) {
2556
+ player.registerSustainNote(() => envelope.cancel(false));
2557
+ } else {
2558
+ envelope.cancel(false);
2559
+ }
2560
+ removeNoteFromRegistry();
1591
2561
  }
1592
2562
  }
1593
2563
  #clearActiveNotes() {
1594
- if (this.#activeNotes) {
1595
- this.#activeNotes.forEach((envelope, note) => {
1596
- if (envelope && envelope.cancel) envelope.cancel();
2564
+ Object.keys(this.#activeNotes).forEach((channel) => {
2565
+ this.#activeNotes[channel].forEach((envelope, note) => {
2566
+ if (envelope) {
2567
+ if (envelope.cleanupTimer) clearTimeout(envelope.cleanupTimer);
2568
+ if (envelope.cancel) envelope.cancel(true);
2569
+ }
2570
+ this.#activeNotes[channel]?.delete(note);
2571
+ });
2572
+ });
2573
+ this.#updateChannelStates();
2574
+ }
2575
+ async #updateChannelStates() {
2576
+ let hasChanged = false;
2577
+ const nextStates = {};
2578
+ Object.keys(this.#players).forEach((channel) => {
2579
+ const isActive = Boolean(this.#activeNotes[channel]?.size && this.#activeNotes[channel].size > 0);
2580
+ nextStates[channel] = isActive;
2581
+ if (this.#channelStates[channel] !== isActive) hasChanged = true;
2582
+ });
2583
+ if (hasChanged) {
2584
+ this.#channelStates = nextStates;
2585
+ this.triggerPlayerEvent("channelState", this.#channelStates);
2586
+ }
2587
+ }
2588
+ async #repairMidi(buffer) {
2589
+ const src = new Uint8Array(buffer);
2590
+ const view = new DataView(buffer);
2591
+ const magic = String.fromCharCode(...src.slice(0, 4));
2592
+ if (magic !== "MThd") throw new Error("Invalid MIDI file (MThd missing)");
2593
+ const headerLen = view.getUint32(4);
2594
+ const format = view.getUint16(8);
2595
+ const ntrks = view.getUint16(10);
2596
+ const division = view.getUint16(12);
2597
+ const EOT = [255, 47, 0];
2598
+ const chunks = [];
2599
+ let pos = 8 + headerLen;
2600
+ while (pos < src.length) {
2601
+ if (pos + 8 > src.length) {
2602
+ break;
2603
+ }
2604
+ const tag = String.fromCharCode(...src.slice(pos, pos + 4));
2605
+ const declaredLen = view.getUint32(pos + 4);
2606
+ const dataStart = pos + 8;
2607
+ const dataEnd = dataStart + declaredLen;
2608
+ if (tag !== "MTrk") {
2609
+ const end = Math.min(dataEnd, src.length);
2610
+ chunks.push({ tag, data: src.slice(pos, end), repaired: false });
2611
+ pos = dataEnd;
2612
+ continue;
2613
+ }
2614
+ const trackNum = chunks.filter((c) => c.tag === "MTrk").length + 1;
2615
+ const available = Math.min(declaredLen, src.length - dataStart);
2616
+ const trackData = src.slice(dataStart, dataStart + available);
2617
+ const last3 = trackData.slice(-3);
2618
+ const hasEOT = last3[0] === 255 && last3[1] === 47 && last3[2] === 0;
2619
+ if (hasEOT && available === declaredLen) {
2620
+ chunks.push({ tag, data: trackData, repaired: false });
2621
+ } else {
2622
+ let repairedData;
2623
+ if (available < declaredLen) {
2624
+ const missing = declaredLen - available;
2625
+ const last2 = trackData.slice(-2);
2626
+ if (last2[0] === 255 && last2[1] === 47) {
2627
+ repairedData = new Uint8Array(trackData.length + 1);
2628
+ repairedData.set(trackData);
2629
+ repairedData[trackData.length] = 0;
2630
+ } else {
2631
+ repairedData = new Uint8Array(trackData.length + 3);
2632
+ repairedData.set(trackData);
2633
+ repairedData.set(EOT, trackData.length);
2634
+ }
2635
+ } else {
2636
+ repairedData = new Uint8Array(trackData.length + 3);
2637
+ repairedData.set(trackData);
2638
+ repairedData.set(EOT, trackData.length);
2639
+ }
2640
+ chunks.push({ tag, data: repairedData, repaired: true });
2641
+ }
2642
+ pos = dataEnd;
2643
+ }
2644
+ const fixedNtrks = chunks.filter((c) => c.tag === "MTrk").length;
2645
+ const totalSize = 14 + chunks.reduce((acc, c) => acc + 8 + c.data.length, 0);
2646
+ const out = new Uint8Array(totalSize);
2647
+ const outView = new DataView(out.buffer);
2648
+ out.set([77, 84, 104, 100], 0);
2649
+ outView.setUint32(4, 6);
2650
+ outView.setUint16(8, format);
2651
+ outView.setUint16(10, fixedNtrks);
2652
+ outView.setUint16(12, division);
2653
+ let outPos = 14;
2654
+ for (const chunk of chunks) {
2655
+ const tagBytes = chunk.tag.split("").map((c) => c.charCodeAt(0));
2656
+ out.set(tagBytes, outPos);
2657
+ outView.setUint32(outPos + 4, chunk.data.length);
2658
+ out.set(chunk.data, outPos + 8);
2659
+ outPos += 8 + chunk.data.length;
2660
+ }
2661
+ const repairedCount = chunks.filter((c) => c.repaired).length;
2662
+ return out.buffer;
2663
+ }
2664
+ #trimMidiEvents() {
2665
+ if (!this.events || this.events.length === 0) return;
2666
+ let firstNoteTick = Infinity;
2667
+ let lastNoteTick = 0;
2668
+ this.events.forEach((track) => {
2669
+ track.forEach((event) => {
2670
+ if (event.name === "Note on" || event.name === "Note off") {
2671
+ if (event.tick < firstNoteTick) firstNoteTick = event.tick;
2672
+ if (event.tick > lastNoteTick) lastNoteTick = event.tick;
2673
+ }
2674
+ });
2675
+ });
2676
+ if (firstNoteTick === Infinity) return;
2677
+ const allSetupEventsBeforeFirstNote = [];
2678
+ this.events.forEach((track, trackIdx) => {
2679
+ track.forEach((event) => {
2680
+ const isSetupEvent = event.name === "Program Change" || event.name === "Controller Change" || event.name === "Pitch Bend" || event.name === "Set Tempo";
2681
+ if (event.tick < firstNoteTick && isSetupEvent) {
2682
+ allSetupEventsBeforeFirstNote.push({ event, trackIdx });
2683
+ }
2684
+ });
2685
+ });
2686
+ const uniqueSetupByTrack = Object.fromEntries(this.events.map((_, idx) => [idx, []]));
2687
+ const globalUniqueKeys = /* @__PURE__ */ new Set();
2688
+ for (let i = allSetupEventsBeforeFirstNote.length - 1; i >= 0; i--) {
2689
+ const { event, trackIdx } = allSetupEventsBeforeFirstNote[i];
2690
+ const channel = event.channel !== void 0 ? event.channel : "track-".concat(trackIdx);
2691
+ let key = null;
2692
+ if (event.name === "Program Change") {
2693
+ key = "pc:".concat(channel);
2694
+ } else if (event.name === "Controller Change") {
2695
+ key = "cc:".concat(channel, ":").concat(event.number);
2696
+ } else if (event.name === "Pitch Bend") {
2697
+ key = "pb:".concat(channel);
2698
+ } else if (event.name === "Set Tempo") {
2699
+ key = "tempo";
2700
+ }
2701
+ if (key) {
2702
+ if (!globalUniqueKeys.has(key)) {
2703
+ globalUniqueKeys.add(key);
2704
+ const clonedEvent = { ...event, tick: 0 };
2705
+ uniqueSetupByTrack[trackIdx].push(clonedEvent);
2706
+ }
2707
+ }
2708
+ }
2709
+ const trimmedEvents = this.events.map((track, trackIdx) => {
2710
+ const newTrack = [];
2711
+ track.forEach((event) => {
2712
+ const isSetupEvent = event.name === "Program Change" || event.name === "Controller Change" || event.name === "Pitch Bend" || event.name === "Set Tempo";
2713
+ const isTextOrKaraoke = event.name === "Text Event" || event.name === "Lyric Event" || event.name === "Track Name" || event.name === "Karaoke Event";
2714
+ if (event.tick < firstNoteTick) {
2715
+ if (!isSetupEvent && (isTextOrKaraoke || trackIdx === 0)) {
2716
+ event.tick = 0;
2717
+ newTrack.push(event);
2718
+ }
2719
+ } else {
2720
+ event.tick = event.tick - firstNoteTick;
2721
+ const maxAllowedTick = lastNoteTick - firstNoteTick;
2722
+ if (event.tick > maxAllowedTick) {
2723
+ event.tick = maxAllowedTick;
2724
+ }
2725
+ newTrack.push(event);
2726
+ }
1597
2727
  });
1598
- this.#activeNotes.clear();
2728
+ const filteredTrackSetup = uniqueSetupByTrack[trackIdx] || [];
2729
+ return [...filteredTrackSetup, ...newTrack].sort((a, b) => a.tick - b.tick);
2730
+ });
2731
+ this.events = trimmedEvents;
2732
+ this.totalTicks = lastNoteTick - firstNoteTick;
2733
+ if (typeof this.computeTempoMap === "function") this.computeTempoMap();
2734
+ }
2735
+ async #extractLyrics() {
2736
+ if (this.#lyrics) return this.#lyrics;
2737
+ const structure = { language: "", title: "", paragraphs: [] };
2738
+ let bestTrack = null;
2739
+ let maxTextEventsCount = 0;
2740
+ this.events.forEach((track) => {
2741
+ const textEventsInTrack = track.filter(
2742
+ (e) => e.name === "Text Event" || e.name === "Lyric Event" || e.name === "Cue Point" || e.name === "Marker" || e.name === "Track Name"
2743
+ );
2744
+ const realLyricsCount = textEventsInTrack.filter((e) => {
2745
+ const textStr = e.string || e.text || "";
2746
+ return textStr && !textStr.startsWith("@");
2747
+ }).length;
2748
+ if (realLyricsCount > maxTextEventsCount) {
2749
+ maxTextEventsCount = realLyricsCount;
2750
+ bestTrack = textEventsInTrack;
2751
+ }
2752
+ });
2753
+ if (!bestTrack || bestTrack.length === 0) return structure;
2754
+ const allTextEvents = bestTrack.sort((a, b) => a.tick - b.tick);
2755
+ let paragraphs = [];
2756
+ let currentParaLines = [];
2757
+ let currentLineBlocks = [];
2758
+ let lastBlockTick = 0;
2759
+ allTextEvents.forEach((event) => {
2760
+ let text = this.#decodeKaraokeString(event.string || "");
2761
+ if (!text) return;
2762
+ if (/^Track-/i.test(text.trim()) || /^Piste/i.test(text.trim()) || text.trim() === "" || event.tick === 0 && text.length > 20) {
2763
+ return;
2764
+ }
2765
+ if (text.startsWith("@L")) {
2766
+ structure.language = text.substring(2).trim();
2767
+ return;
2768
+ }
2769
+ if (text.startsWith("@T")) {
2770
+ structure.title += (structure.title ? " / " : "") + text.substring(2).trim();
2771
+ return;
2772
+ }
2773
+ if (text.startsWith("@") || text.startsWith("(") || text.startsWith("PART") || /^\d+\s+\d+/.test(text.trim())) {
2774
+ return;
2775
+ }
2776
+ if (/^(Verse|Chorus|Bridge|Break|Intro|End\.)/i.test(text.trim())) {
2777
+ const isExplicitCut = text.startsWith("\\") || text.startsWith("/");
2778
+ const isNaturalTransition = currentLineBlocks.length === 0 || event.tick - lastBlockTick > 500;
2779
+ if (isExplicitCut || isNaturalTransition) {
2780
+ if (currentLineBlocks.length > 0) {
2781
+ currentParaLines.push({ tick: currentLineBlocks[0].tick, blocks: currentLineBlocks });
2782
+ currentLineBlocks = [];
2783
+ }
2784
+ if (currentParaLines.length > 0) {
2785
+ while (currentParaLines.length > 4) {
2786
+ const linesToPush = currentParaLines.splice(0, 4);
2787
+ paragraphs.push({ tick: linesToPush[0].tick, lines: linesToPush });
2788
+ }
2789
+ if (currentParaLines.length > 0) {
2790
+ paragraphs.push({ tick: currentParaLines[0].tick, lines: currentParaLines });
2791
+ currentParaLines = [];
2792
+ }
2793
+ }
2794
+ }
2795
+ return;
2796
+ }
2797
+ let forceNewLine = false;
2798
+ if (currentLineBlocks.length > 0) {
2799
+ const prevBlock = currentLineBlocks[currentLineBlocks.length - 1];
2800
+ const prevText = prevBlock.text;
2801
+ const currentTrimmed = text.trimLeft();
2802
+ if (currentTrimmed.length > 0) {
2803
+ const isCapitalized = /^[A-Z]/.test(currentTrimmed) || /^'[A-Z]/.test(currentTrimmed);
2804
+ const prevIsCapitalized = /^[A-Z]/.test(prevText.trim()) || /^'[A-Z]/.test(prevText.trim());
2805
+ if (isCapitalized && !prevText.endsWith(" ") && prevText.trim() != "o" && !prevIsCapitalized) {
2806
+ if (event.tick > lastBlockTick) {
2807
+ forceNewLine = true;
2808
+ }
2809
+ }
2810
+ if (text.startsWith('"') && prevText.endsWith('"')) {
2811
+ forceNewLine = true;
2812
+ }
2813
+ if (text.startsWith('"') && (prevText.endsWith(")") || prevText.endsWith(')"'))) {
2814
+ forceNewLine = true;
2815
+ }
2816
+ if (currentTrimmed.startsWith('"') && prevText.trimRight().endsWith(")")) {
2817
+ forceNewLine = true;
2818
+ }
2819
+ }
2820
+ }
2821
+ const isNewParagraphMarker = text.startsWith("\\");
2822
+ const isNewLineMarker = text.startsWith("/") || forceNewLine;
2823
+ if (isNewParagraphMarker || isNewLineMarker) {
2824
+ if (text.startsWith("\\") || text.startsWith("/")) {
2825
+ text = text.substring(1);
2826
+ }
2827
+ }
2828
+ text = text.replace(/[\r\n]/g, "");
2829
+ let isTimeGapTrigger = false;
2830
+ if (lastBlockTick > 0 && event.tick > lastBlockTick) {
2831
+ const secondsSilence = this.ticksToSeconds(lastBlockTick, event.tick);
2832
+ if (secondsSilence > 2.5) {
2833
+ isTimeGapTrigger = true;
2834
+ }
2835
+ }
2836
+ const currentLineChars = currentLineBlocks.reduce((sum, b) => sum + b.text.length, 0);
2837
+ const isWordLimitTrigger = currentLineChars + text.length > this.#opts.maxCharPerLine;
2838
+ if (isNewLineMarker || isNewParagraphMarker || isTimeGapTrigger || isWordLimitTrigger) {
2839
+ if (currentLineBlocks.length > 0) {
2840
+ currentParaLines.push({ tick: currentLineBlocks[0].tick, blocks: currentLineBlocks });
2841
+ currentLineBlocks = [];
2842
+ }
2843
+ if (currentParaLines.length > 0) {
2844
+ if (isNewParagraphMarker || isTimeGapTrigger) {
2845
+ while (currentParaLines.length > 4) {
2846
+ const linesToPush = currentParaLines.splice(0, 4);
2847
+ paragraphs.push({ tick: linesToPush[0].tick, lines: linesToPush });
2848
+ }
2849
+ if (currentParaLines.length > 0) {
2850
+ paragraphs.push({ tick: currentParaLines[0].tick, lines: currentParaLines });
2851
+ currentParaLines = [];
2852
+ }
2853
+ } else if (currentParaLines.length >= 6) {
2854
+ const linesToPush = currentParaLines.splice(0, 4);
2855
+ paragraphs.push({ tick: linesToPush[0].tick, lines: linesToPush });
2856
+ }
2857
+ }
2858
+ }
2859
+ if (text.length > 0) {
2860
+ currentLineBlocks.push({ text, tick: event.tick });
2861
+ lastBlockTick = event.tick;
2862
+ }
2863
+ });
2864
+ if (currentLineBlocks.length > 0) {
2865
+ currentParaLines.push({
2866
+ tick: currentLineBlocks[0].tick,
2867
+ blocks: currentLineBlocks
2868
+ });
2869
+ }
2870
+ while (currentParaLines.length > 4) {
2871
+ const linesToPush = currentParaLines.splice(0, 4);
2872
+ paragraphs.push({ tick: linesToPush[0].tick, lines: linesToPush });
2873
+ }
2874
+ if (currentParaLines.length > 0) {
2875
+ paragraphs.push({ tick: currentParaLines[0].tick, lines: currentParaLines });
2876
+ }
2877
+ paragraphs = paragraphs.filter((p) => {
2878
+ return !(p.lines.length == 1 && p.lines[0].blocks.length == 1 && ["intro", "outro", "sfx", "solo", "chorus", "verse", "bridge", "break", "end"].includes(p.lines[0].blocks[0].text.toLowerCase().trim()));
2879
+ });
2880
+ if (paragraphs.length <= 2) paragraphs = [];
2881
+ structure.paragraphs = paragraphs;
2882
+ this.#lyrics = structure;
2883
+ return structure;
2884
+ }
2885
+ async #generateKaraokeFrames() {
2886
+ const lyrics = await this.#extractLyrics();
2887
+ if (!lyrics.paragraphs.length) {
2888
+ this.#haveLyrics = false;
2889
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
2890
+ text: '<span class="karaoke-intro"></span>',
2891
+ name: "Karaoke Event",
2892
+ type: "intro",
2893
+ tick: 0,
2894
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
2895
+ });
2896
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL] = this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].sort((a, b) => a.tick - b.tick);
2897
+ return;
2898
+ }
2899
+ this.#haveLyrics = true;
2900
+ this.#title = lyrics.title;
2901
+ let lastFrameEnd = 0;
2902
+ const delayTicks = this.secondsToTicks(this.#opts.karaokeDelay);
2903
+ const threeSecondsInTicks = this.secondsToTicks(3);
2904
+ const fiveSecondsInTicks = this.secondsToTicks(5);
2905
+ const sevenSecondsInTicks = this.secondsToTicks(7);
2906
+ const tenSecondsInTicks = this.secondsToTicks(10);
2907
+ const allBlocksInSong = [];
2908
+ lyrics.paragraphs.forEach((p, pIdx) => {
2909
+ p.lines.forEach((l, lIdx) => {
2910
+ l.blocks.forEach((b) => {
2911
+ allBlocksInSong.push({
2912
+ block: b,
2913
+ lineIdx: lIdx,
2914
+ paraIdx: pIdx,
2915
+ paragraph: p,
2916
+ fastLinesText: p.lines.map((li) => li.blocks.map((bl) => bl.text).join(""))
2917
+ });
2918
+ });
2919
+ });
2920
+ });
2921
+ const paragraphDisplayTicks = [];
2922
+ lyrics.paragraphs.forEach((p, pIdx) => {
2923
+ let paragraphDisplayTick = this.getTickBeforeSeconds(p.tick, 5);
2924
+ if (paragraphDisplayTick < lastFrameEnd)
2925
+ paragraphDisplayTick = lastFrameEnd + (p.tick - lastFrameEnd) / 2;
2926
+ if (pIdx === 0 && paragraphDisplayTick < 20)
2927
+ paragraphDisplayTick = 20;
2928
+ paragraphDisplayTicks[pIdx] = paragraphDisplayTick;
2929
+ const fastLinesText = p.lines.map((li) => li.blocks.map((b) => b.text).join(""));
2930
+ const initialHTML = fastLinesText.map((lineText) => '<span class="karaoke-coming">'.concat(lineText, "</span>")).join("<br/>");
2931
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
2932
+ text: initialHTML,
2933
+ name: "Karaoke Event",
2934
+ type: "lyric",
2935
+ tick: paragraphDisplayTick,
2936
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
2937
+ });
2938
+ if (p.lines.length > 0) {
2939
+ const lastLine = p.lines[p.lines.length - 1];
2940
+ if (lastLine.blocks.length > 0)
2941
+ lastFrameEnd = lastLine.blocks[lastLine.blocks.length - 1].tick;
2942
+ }
2943
+ });
2944
+ const firstParaDisplayTick = paragraphDisplayTicks[0] || 0;
2945
+ if (firstParaDisplayTick > 25) {
2946
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
2947
+ text: '<span class="karaoke-clear"></span>',
2948
+ name: "Karaoke Event",
2949
+ type: "clear",
2950
+ tick: 5,
2951
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
2952
+ });
2953
+ }
2954
+ allBlocksInSong.forEach((current, index2) => {
2955
+ const currentBlock = current.block;
2956
+ const currentLineIdx = current.lineIdx;
2957
+ const currentParaIdx = current.paraIdx;
2958
+ const p = current.paragraph;
2959
+ const fastLinesText = current.fastLinesText;
2960
+ const generateHTML = (forceAllPlayedOnActiveLine = false) => {
2961
+ return p.lines.map((li, liIdx) => {
2962
+ if (liIdx < currentLineIdx)
2963
+ return '<span class="karaoke-played">'.concat(fastLinesText[liIdx], "</span>");
2964
+ if (liIdx > currentLineIdx)
2965
+ return '<span class="karaoke-coming">'.concat(fastLinesText[liIdx], "</span>");
2966
+ let lineHTML = "";
2967
+ li.blocks.forEach((block) => {
2968
+ let className = "coming";
2969
+ if (forceAllPlayedOnActiveLine || block.tick < currentBlock.tick)
2970
+ className = "played";
2971
+ else if (block.tick === currentBlock.tick)
2972
+ className = "playing";
2973
+ lineHTML += '<span class="karaoke-'.concat(className, '">').concat(block.text, "</span>");
2974
+ });
2975
+ return lineHTML;
2976
+ }).join("<br>");
2977
+ };
2978
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
2979
+ text: generateHTML(false),
2980
+ name: "Karaoke Event",
2981
+ type: "lyric",
2982
+ tick: currentBlock.tick - delayTicks,
2983
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
2984
+ });
2985
+ const next = allBlocksInSong[index2 + 1];
2986
+ if (next) {
2987
+ const tickDifference = next.block.tick - currentBlock.tick;
2988
+ if (tickDifference > threeSecondsInTicks) {
2989
+ let targetCleanupTick = currentBlock.tick + threeSecondsInTicks;
2990
+ let targetClearTick = currentBlock.tick + sevenSecondsInTicks;
2991
+ let shouldAddClear = tickDifference > tenSecondsInTicks && currentParaIdx > 0;
2992
+ if (next.paraIdx !== currentParaIdx) {
2993
+ const nextParaDisplayTick = paragraphDisplayTicks[next.paraIdx];
2994
+ if (targetCleanupTick >= nextParaDisplayTick)
2995
+ targetCleanupTick = nextParaDisplayTick - 1;
2996
+ if (shouldAddClear) {
2997
+ if (targetClearTick >= nextParaDisplayTick || nextParaDisplayTick - targetClearTick < threeSecondsInTicks)
2998
+ shouldAddClear = false;
2999
+ }
3000
+ }
3001
+ if (targetCleanupTick > currentBlock.tick) {
3002
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
3003
+ text: generateHTML(true),
3004
+ name: "Karaoke Event",
3005
+ type: "lyric",
3006
+ tick: targetCleanupTick - delayTicks,
3007
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
3008
+ });
3009
+ }
3010
+ if (shouldAddClear && targetClearTick > targetCleanupTick) {
3011
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
3012
+ text: '<span class="karaoke-clear"></span>',
3013
+ name: "Karaoke Event",
3014
+ type: "clear",
3015
+ tick: targetClearTick - delayTicks,
3016
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
3017
+ });
3018
+ }
3019
+ }
3020
+ }
3021
+ lastFrameEnd = currentBlock.tick;
3022
+ });
3023
+ if (this.totalTicks - lastFrameEnd > this.secondsToTicks(5)) {
3024
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
3025
+ text: '<span class="karaoke-clear"></span>',
3026
+ name: "Karaoke Event",
3027
+ type: "clear",
3028
+ tick: lastFrameEnd + this.secondsToTicks(5),
3029
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
3030
+ });
3031
+ } else {
3032
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].push({
3033
+ text: '<span class="karaoke-clear"></span>',
3034
+ name: "Karaoke Event",
3035
+ type: "clear",
3036
+ tick: this.totalTicks - 1,
3037
+ channel: _MidiAudioPlayer.KARAOKE_CHANNEL
3038
+ });
3039
+ }
3040
+ this.events[_MidiAudioPlayer.KARAOKE_CHANNEL] = this.events[_MidiAudioPlayer.KARAOKE_CHANNEL].sort((a, b) => a.tick - b.tick);
3041
+ }
3042
+ async #detectKaraokeVocalChannel() {
3043
+ const lyrics = await this.#extractLyrics();
3044
+ if (!lyrics?.paragraphs?.length) return null;
3045
+ const textTicks = lyrics.paragraphs.flatMap((p) => p.lines.flatMap((l) => l.blocks.map((b) => b.tick)));
3046
+ if (textTicks.length === 0) return null;
3047
+ const tickTolerance = this.division ? this.division / 2 : 48;
3048
+ const VOCAL_MIN = 48;
3049
+ const VOCAL_MAX = 84;
3050
+ const channelsToScan = Object.keys(this.#channels).map(Number).filter((chan) => chan !== 10);
3051
+ let bestChannel = null;
3052
+ let bestScore = -Infinity;
3053
+ for (const channel of channelsToScan) {
3054
+ const notes = this.events.flatMap(
3055
+ (track) => track.filter(
3056
+ (e) => e.name === "Note on" && e.velocity > 0 && e.channel === channel
3057
+ )
3058
+ );
3059
+ if (notes.length === 0) continue;
3060
+ const aligned = textTicks.filter(
3061
+ (t) => notes.some((n) => Math.abs(n.tick - t) <= tickTolerance)
3062
+ ).length;
3063
+ const alignmentScore = aligned / textTicks.length;
3064
+ const notesInRange = notes.filter((n) => n.noteNumber >= VOCAL_MIN && n.noteNumber <= VOCAL_MAX);
3065
+ const rangeScore = notesInRange.length / notes.length;
3066
+ if (rangeScore < 0.3) continue;
3067
+ const sorted = [...notes].sort((a, b) => a.tick - b.tick);
3068
+ const minGap = this.division / 8 || 6;
3069
+ const poly = sorted.filter((n, i) => i > 0 && Math.abs(n.tick - sorted[i - 1].tick) < minGap).length;
3070
+ const monophonyScore = 1 - poly / Math.max(notes.length - 1, 1);
3071
+ const densityRatio = notes.length / Math.max(textTicks.length, 1);
3072
+ const densityScore = densityRatio < 0.3 ? densityRatio / 0.3 : densityRatio > 5 ? Math.max(0, 1 - (densityRatio - 5) / 10) : 1;
3073
+ const score = alignmentScore * 0.45 + rangeScore * 0.35 + monophonyScore * 0.15 + densityScore * 0.05;
3074
+ if (score > bestScore) {
3075
+ bestScore = score;
3076
+ bestChannel = channel;
3077
+ }
1599
3078
  }
3079
+ return bestScore >= 0.4 ? bestChannel : null;
3080
+ }
3081
+ #decodeKaraokeString(str) {
3082
+ if (!str) return "";
3083
+ const bytes = new Uint8Array(str.length);
3084
+ for (let i = 0; i < str.length; i++) bytes[i] = str.charCodeAt(i) & 255;
3085
+ const decoder = new TextDecoder("windows-1252");
3086
+ let decoded = decoder.decode(bytes);
3087
+ decoded = decoded.replace(/ÿ/g, "");
3088
+ decoded = decoded.replace(/’/g, "'");
3089
+ decoded = decoded.replace(/`/g, "'");
3090
+ return decoded;
3091
+ }
3092
+ #sendKaraokeFrame(type = "clear", text = "") {
3093
+ const html = '<span class="karaoke-'.concat(type, '">').concat(text.replace(/\s\/\s/g, "<br>"), "</span>");
3094
+ if (this.#opts.karaoke) {
3095
+ if (type == "title") queueMicrotask(() => this.triggerPlayerEvent("karaoke", { type, title: text, html }));
3096
+ else queueMicrotask(() => this.triggerPlayerEvent("karaoke", { type, html }));
3097
+ }
3098
+ }
3099
+ #log(str, err = false) {
3100
+ queueMicrotask(() => this.triggerPlayerEvent("logs", str));
1600
3101
  }
1601
3102
  };
1602
3103
 
1603
3104
  // index.js
1604
3105
  if (typeof window !== "undefined") window.MidiAudioPlayer = MidiAudioPlayer;
1605
- var index_default = MidiAudioPlayer;
3106
+ var index_default2 = MidiAudioPlayer;
1606
3107
  })();
1607
- //# sourceMappingURL=midi-audio-player.js.map