@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/esm/midy-GM1.d.ts +6 -24
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +98 -51
- package/esm/midy-GM2.d.ts +6 -29
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +107 -60
- package/esm/midy-GMLite.d.ts +6 -24
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +100 -53
- package/esm/midy.d.ts +6 -30
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +107 -60
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +6 -24
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +98 -51
- package/script/midy-GM2.d.ts +6 -29
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +107 -60
- package/script/midy-GMLite.d.ts +6 -24
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +100 -53
- package/script/midy.d.ts +6 -30
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +107 -60
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
|
|
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.
|
|
645
|
+
await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
|
|
624
646
|
break;
|
|
625
647
|
case "noteOff": {
|
|
626
|
-
const notePromise = this.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1472
|
+
this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1437
1473
|
startTime, true);
|
|
1438
1474
|
}
|
|
1439
1475
|
this.drumExclusiveClassNotes[index] = note;
|
|
1440
1476
|
}
|
|
1441
|
-
|
|
1477
|
+
setNoteRouting(channelNumber, note, startTime) {
|
|
1442
1478
|
const channel = this.channels[channelNumber];
|
|
1443
|
-
const
|
|
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
|
-
|
|
1467
|
-
|
|
1489
|
+
volumeEnvelopeNode.connect(gainL);
|
|
1490
|
+
volumeEnvelopeNode.connect(gainR);
|
|
1468
1491
|
}
|
|
1469
1492
|
else {
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1647
|
+
const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
|
|
1601
1648
|
promises.push(promise);
|
|
1602
1649
|
}
|
|
1603
1650
|
channel.sostenutoNotes = [];
|