@marmooo/midy 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/script/midy.js CHANGED
@@ -4,7 +4,19 @@ exports.Midy = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
6
  class Note {
7
- constructor(noteNumber, velocity, startTime, voice, voiceParams) {
7
+ constructor(noteNumber, velocity, startTime) {
8
+ Object.defineProperty(this, "voice", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ Object.defineProperty(this, "voiceParams", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
8
20
  Object.defineProperty(this, "index", {
9
21
  enumerable: true,
10
22
  configurable: true,
@@ -17,6 +29,12 @@ class Note {
17
29
  writable: true,
18
30
  value: false
19
31
  });
32
+ Object.defineProperty(this, "pending", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: true
37
+ });
20
38
  Object.defineProperty(this, "bufferSource", {
21
39
  enumerable: true,
22
40
  configurable: true,
@@ -98,8 +116,6 @@ class Note {
98
116
  this.noteNumber = noteNumber;
99
117
  this.velocity = velocity;
100
118
  this.startTime = startTime;
101
- this.voice = voice;
102
- this.voiceParams = voiceParams;
103
119
  }
104
120
  }
105
121
  const drumExclusiveClassesByKit = new Array(57);
@@ -356,6 +372,12 @@ class Midy {
356
372
  writable: true,
357
373
  value: new Map()
358
374
  });
375
+ Object.defineProperty(this, "realtimeVoiceCache", {
376
+ enumerable: true,
377
+ configurable: true,
378
+ writable: true,
379
+ value: new Map()
380
+ });
359
381
  Object.defineProperty(this, "isPlaying", {
360
382
  enumerable: true,
361
383
  configurable: true,
@@ -620,10 +642,10 @@ class Midy {
620
642
  const startTime = event.startTime + schedulingOffset;
621
643
  switch (event.type) {
622
644
  case "noteOn":
623
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
645
+ await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
624
646
  break;
625
647
  case "noteOff": {
626
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
648
+ const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
627
649
  if (notePromise)
628
650
  this.notePromises.push(notePromise);
629
651
  break;
@@ -662,6 +684,7 @@ class Midy {
662
684
  this.exclusiveClassNotes.fill(undefined);
663
685
  this.drumExclusiveClassNotes.fill(undefined);
664
686
  this.voiceCache.clear();
687
+ this.realtimeVoiceCache.clear();
665
688
  for (let i = 0; i < this.channels.length; i++) {
666
689
  this.channels[i].scheduledNotes = [];
667
690
  this.resetChannelStates(i);
@@ -706,7 +729,6 @@ class Midy {
706
729
  finished = true;
707
730
  break;
708
731
  }
709
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
710
732
  if (this.isPausing) {
711
733
  await this.stopNotes(0, true, now);
712
734
  await this.audioContext.suspend();
@@ -728,6 +750,7 @@ class Midy {
728
750
  this.isSeeking = false;
729
751
  continue;
730
752
  }
753
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
731
754
  const waitTime = now + this.noteCheckInterval;
732
755
  await this.scheduleTask(() => { }, waitTime);
733
756
  }
@@ -836,7 +859,7 @@ class Midy {
836
859
  const channel = this.channels[channelNumber];
837
860
  const promises = [];
838
861
  this.processActiveNotes(channel, scheduleTime, (note) => {
839
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
862
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
840
863
  this.notePromises.push(promise);
841
864
  promises.push(promise);
842
865
  });
@@ -846,7 +869,7 @@ class Midy {
846
869
  const channel = this.channels[channelNumber];
847
870
  const promises = [];
848
871
  this.processScheduledNotes(channel, (note) => {
849
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
872
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
850
873
  this.notePromises.push(promise);
851
874
  promises.push(promise);
852
875
  });
@@ -879,7 +902,7 @@ class Midy {
879
902
  if (!this.isPlaying || this.isPaused)
880
903
  return;
881
904
  const now = this.audioContext.currentTime;
882
- this.resumeTime += now - this.startTime - this.startDelay;
905
+ this.resumeTime = now - this.startTime - this.startDelay;
883
906
  this.isPausing = true;
884
907
  await this.playPromise;
885
908
  this.isPausing = false;
@@ -905,11 +928,13 @@ class Midy {
905
928
  if (totalTime < event.startTime)
906
929
  totalTime = event.startTime;
907
930
  }
908
- return totalTime;
931
+ return totalTime + this.startDelay;
909
932
  }
910
933
  currentTime() {
934
+ if (!this.isPlaying)
935
+ return this.resumeTime;
911
936
  const now = this.audioContext.currentTime;
912
- return this.resumeTime + now - this.startTime - this.startDelay;
937
+ return now + this.resumeTime - this.startTime;
913
938
  }
914
939
  processScheduledNotes(channel, callback) {
915
940
  const scheduledNotes = channel.scheduledNotes;
@@ -1341,31 +1366,42 @@ class Midy {
1341
1366
  note.vibratoLFO.connect(note.vibratoDepth);
1342
1367
  note.vibratoDepth.connect(note.bufferSource.detune);
1343
1368
  }
1344
- async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
1369
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
1345
1370
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
1346
- const cache = this.voiceCache.get(audioBufferId);
1347
- if (cache) {
1348
- cache.counter += 1;
1349
- if (cache.maxCount <= cache.counter) {
1350
- this.voiceCache.delete(audioBufferId);
1351
- }
1352
- return cache.audioBuffer;
1353
- }
1354
- else {
1355
- const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1371
+ if (realtime) {
1372
+ const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
1373
+ if (cachedAudioBuffer)
1374
+ return cachedAudioBuffer;
1356
1375
  const audioBuffer = await this.createAudioBuffer(voiceParams);
1357
- const cache = { audioBuffer, maxCount, counter: 1 };
1358
- this.voiceCache.set(audioBufferId, cache);
1376
+ this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
1359
1377
  return audioBuffer;
1360
1378
  }
1379
+ else {
1380
+ const cache = this.voiceCache.get(audioBufferId);
1381
+ if (cache) {
1382
+ cache.counter += 1;
1383
+ if (cache.maxCount <= cache.counter) {
1384
+ this.voiceCache.delete(audioBufferId);
1385
+ }
1386
+ return cache.audioBuffer;
1387
+ }
1388
+ else {
1389
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1390
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
1391
+ const cache = { audioBuffer, maxCount, counter: 1 };
1392
+ this.voiceCache.set(audioBufferId, cache);
1393
+ return audioBuffer;
1394
+ }
1395
+ }
1361
1396
  }
1362
- async createNote(channel, voice, noteNumber, velocity, startTime) {
1397
+ async setNoteAudioNode(channel, note, realtime) {
1363
1398
  const now = this.audioContext.currentTime;
1399
+ const { noteNumber, velocity, startTime } = note;
1364
1400
  const state = channel.state;
1365
1401
  const controllerState = this.getControllerState(channel, noteNumber, velocity, 0);
1366
- const voiceParams = voice.getAllParams(controllerState);
1367
- const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1368
- const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
1402
+ const voiceParams = note.voice.getAllParams(controllerState);
1403
+ note.voiceParams = voiceParams;
1404
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
1369
1405
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1370
1406
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1371
1407
  const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
@@ -1413,7 +1449,7 @@ class Midy {
1413
1449
  if (prev) {
1414
1450
  const [prevNote, prevChannelNumber] = prev;
1415
1451
  if (prevNote && !prevNote.ending) {
1416
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1452
+ this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1417
1453
  startTime, true);
1418
1454
  }
1419
1455
  }
@@ -1433,27 +1469,14 @@ class Midy {
1433
1469
  channelNumber;
1434
1470
  const prevNote = this.drumExclusiveClassNotes[index];
1435
1471
  if (prevNote && !prevNote.ending) {
1436
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1472
+ this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1437
1473
  startTime, true);
1438
1474
  }
1439
1475
  this.drumExclusiveClassNotes[index] = note;
1440
1476
  }
1441
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1477
+ setNoteRouting(channelNumber, note, startTime) {
1442
1478
  const channel = this.channels[channelNumber];
1443
- const programNumber = channel.programNumber;
1444
- const bankTable = this.soundFontTable[programNumber];
1445
- if (!bankTable)
1446
- return;
1447
- const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
1448
- const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
1449
- const soundFontIndex = bankTable[bank];
1450
- if (soundFontIndex === undefined)
1451
- return;
1452
- const soundFont = this.soundFonts[soundFontIndex];
1453
- const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1454
- if (!voice)
1455
- return;
1456
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1479
+ const { noteNumber, volumeEnvelopeNode } = note;
1457
1480
  if (channel.isDrum) {
1458
1481
  const { keyBasedGainLs, keyBasedGainRs } = channel;
1459
1482
  let gainL = keyBasedGainLs[noteNumber];
@@ -1463,25 +1486,48 @@ class Midy {
1463
1486
  gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
1464
1487
  gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
1465
1488
  }
1466
- note.volumeEnvelopeNode.connect(gainL);
1467
- note.volumeEnvelopeNode.connect(gainR);
1489
+ volumeEnvelopeNode.connect(gainL);
1490
+ volumeEnvelopeNode.connect(gainR);
1468
1491
  }
1469
1492
  else {
1470
- note.volumeEnvelopeNode.connect(channel.gainL);
1471
- note.volumeEnvelopeNode.connect(channel.gainR);
1493
+ volumeEnvelopeNode.connect(channel.gainL);
1494
+ volumeEnvelopeNode.connect(channel.gainR);
1472
1495
  }
1473
1496
  if (0.5 <= channel.state.sustainPedal) {
1474
1497
  channel.sustainNotes.push(note);
1475
1498
  }
1476
1499
  this.handleExclusiveClass(note, channelNumber, startTime);
1477
1500
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
1501
+ }
1502
+ async noteOn(channelNumber, noteNumber, velocity, startTime) {
1503
+ const channel = this.channels[channelNumber];
1504
+ const realtime = startTime === undefined;
1505
+ if (realtime)
1506
+ startTime = this.audioContext.currentTime;
1507
+ const note = new Note(noteNumber, velocity, startTime);
1478
1508
  const scheduledNotes = channel.scheduledNotes;
1479
1509
  note.index = scheduledNotes.length;
1480
1510
  scheduledNotes.push(note);
1481
- }
1482
- noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1483
- scheduleTime ??= this.audioContext.currentTime;
1484
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
1511
+ const programNumber = channel.programNumber;
1512
+ const bankTable = this.soundFontTable[programNumber];
1513
+ if (!bankTable)
1514
+ return;
1515
+ const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
1516
+ const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
1517
+ const soundFontIndex = bankTable[bank];
1518
+ if (soundFontIndex === undefined)
1519
+ return;
1520
+ const soundFont = this.soundFonts[soundFontIndex];
1521
+ note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1522
+ if (!note.voice)
1523
+ return;
1524
+ await this.setNoteAudioNode(channel, note, realtime);
1525
+ this.setNoteRouting(channelNumber, note, startTime);
1526
+ note.pending = false;
1527
+ const off = note.offEvent;
1528
+ if (off) {
1529
+ this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
1530
+ }
1485
1531
  }
1486
1532
  disconnectNote(note) {
1487
1533
  note.bufferSource.disconnect();
@@ -1504,6 +1550,7 @@ class Midy {
1504
1550
  }
1505
1551
  }
1506
1552
  releaseNote(channel, note, endTime) {
1553
+ endTime ??= this.audioContext.currentTime;
1507
1554
  const releaseTime = this.getRelativeKeyBasedValue(channel, note, 72) * 2;
1508
1555
  const volRelease = endTime + note.voiceParams.volRelease * releaseTime;
1509
1556
  const modRelease = endTime + note.voiceParams.modRelease;
@@ -1525,7 +1572,7 @@ class Midy {
1525
1572
  }, stopTime);
1526
1573
  });
1527
1574
  }
1528
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1575
+ noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1529
1576
  const channel = this.channels[channelNumber];
1530
1577
  const state = channel.state;
1531
1578
  if (!force) {
@@ -1544,6 +1591,10 @@ class Midy {
1544
1591
  if (index < 0)
1545
1592
  return;
1546
1593
  const note = channel.scheduledNotes[index];
1594
+ if (note.pending) {
1595
+ note.offEvent = { velocity, startTime: endTime };
1596
+ return;
1597
+ }
1547
1598
  note.ending = true;
1548
1599
  this.setNoteIndex(channel, index);
1549
1600
  this.releaseNote(channel, note, endTime);
@@ -1574,16 +1625,12 @@ class Midy {
1574
1625
  }
1575
1626
  return -1;
1576
1627
  }
1577
- noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1578
- scheduleTime ??= this.audioContext.currentTime;
1579
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1580
- }
1581
1628
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1582
1629
  const velocity = halfVelocity * 2;
1583
1630
  const channel = this.channels[channelNumber];
1584
1631
  const promises = [];
1585
1632
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1586
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1633
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1587
1634
  promises.push(promise);
1588
1635
  }
1589
1636
  channel.sustainNotes = [];
@@ -1597,7 +1644,7 @@ class Midy {
1597
1644
  channel.state.sostenutoPedal = 0;
1598
1645
  for (let i = 0; i < sostenutoNotes.length; i++) {
1599
1646
  const note = sostenutoNotes[i];
1600
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1647
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1601
1648
  promises.push(promise);
1602
1649
  }
1603
1650
  channel.sostenutoNotes = [];