@marmooo/midy 0.2.5 → 0.2.6
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 +24 -25
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +55 -91
- package/esm/midy-GM2.d.ts +33 -29
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +146 -133
- package/esm/midy-GMLite.d.ts +18 -16
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +53 -39
- package/esm/midy.d.ts +35 -31
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +172 -161
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +24 -25
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +55 -91
- package/script/midy-GM2.d.ts +33 -29
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +146 -133
- package/script/midy-GMLite.d.ts +18 -16
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +53 -39
- package/script/midy.d.ts +35 -31
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +172 -161
package/script/midy.js
CHANGED
|
@@ -69,31 +69,37 @@ class Note {
|
|
|
69
69
|
writable: true,
|
|
70
70
|
value: void 0
|
|
71
71
|
});
|
|
72
|
+
Object.defineProperty(this, "filterDepth", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
72
78
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
73
79
|
enumerable: true,
|
|
74
80
|
configurable: true,
|
|
75
81
|
writable: true,
|
|
76
82
|
value: void 0
|
|
77
83
|
});
|
|
78
|
-
Object.defineProperty(this, "
|
|
84
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
79
85
|
enumerable: true,
|
|
80
86
|
configurable: true,
|
|
81
87
|
writable: true,
|
|
82
88
|
value: void 0
|
|
83
89
|
});
|
|
84
|
-
Object.defineProperty(this, "
|
|
90
|
+
Object.defineProperty(this, "volumeNode", {
|
|
85
91
|
enumerable: true,
|
|
86
92
|
configurable: true,
|
|
87
93
|
writable: true,
|
|
88
94
|
value: void 0
|
|
89
95
|
});
|
|
90
|
-
Object.defineProperty(this, "
|
|
96
|
+
Object.defineProperty(this, "gainL", {
|
|
91
97
|
enumerable: true,
|
|
92
98
|
configurable: true,
|
|
93
99
|
writable: true,
|
|
94
100
|
value: void 0
|
|
95
101
|
});
|
|
96
|
-
Object.defineProperty(this, "
|
|
102
|
+
Object.defineProperty(this, "gainR", {
|
|
97
103
|
enumerable: true,
|
|
98
104
|
configurable: true,
|
|
99
105
|
writable: true,
|
|
@@ -502,6 +508,10 @@ class Midy {
|
|
|
502
508
|
...this.setChannelAudioNodes(audioContext),
|
|
503
509
|
scheduledNotes: new SparseMap(128),
|
|
504
510
|
sostenutoNotes: new SparseMap(128),
|
|
511
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
512
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
513
|
+
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
514
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
505
515
|
};
|
|
506
516
|
});
|
|
507
517
|
return channels;
|
|
@@ -568,10 +578,11 @@ class Midy {
|
|
|
568
578
|
const event = this.timeline[queueIndex];
|
|
569
579
|
if (event.startTime > t + this.lookAhead)
|
|
570
580
|
break;
|
|
581
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
571
582
|
switch (event.type) {
|
|
572
583
|
case "noteOn":
|
|
573
584
|
if (event.velocity !== 0) {
|
|
574
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
585
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
575
586
|
break;
|
|
576
587
|
}
|
|
577
588
|
/* falls through */
|
|
@@ -579,29 +590,29 @@ class Midy {
|
|
|
579
590
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
580
591
|
if (portamentoTarget)
|
|
581
592
|
portamentoTarget.portamento = true;
|
|
582
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
593
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
583
594
|
if (notePromise) {
|
|
584
595
|
this.notePromises.push(notePromise);
|
|
585
596
|
}
|
|
586
597
|
break;
|
|
587
598
|
}
|
|
588
599
|
case "noteAftertouch":
|
|
589
|
-
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
600
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
590
601
|
break;
|
|
591
602
|
case "controller":
|
|
592
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
603
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
593
604
|
break;
|
|
594
605
|
case "programChange":
|
|
595
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
606
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
596
607
|
break;
|
|
597
608
|
case "channelAftertouch":
|
|
598
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
609
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
599
610
|
break;
|
|
600
611
|
case "pitchBend":
|
|
601
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
612
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
602
613
|
break;
|
|
603
614
|
case "sysEx":
|
|
604
|
-
this.handleSysEx(event.data);
|
|
615
|
+
this.handleSysEx(event.data, startTime);
|
|
605
616
|
}
|
|
606
617
|
queueIndex++;
|
|
607
618
|
}
|
|
@@ -848,6 +859,18 @@ class Midy {
|
|
|
848
859
|
const now = this.audioContext.currentTime;
|
|
849
860
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
850
861
|
}
|
|
862
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
863
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
864
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
865
|
+
const note = noteList[i];
|
|
866
|
+
if (!note)
|
|
867
|
+
continue;
|
|
868
|
+
if (scheduleTime < note.startTime)
|
|
869
|
+
continue;
|
|
870
|
+
callback(note);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
}
|
|
851
874
|
getActiveNotes(channel, time) {
|
|
852
875
|
const activeNotes = new SparseMap(128);
|
|
853
876
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1029,14 +1052,15 @@ class Midy {
|
|
|
1029
1052
|
const note = noteList[i];
|
|
1030
1053
|
if (!note)
|
|
1031
1054
|
continue;
|
|
1032
|
-
this.updateDetune(channel, note
|
|
1055
|
+
this.updateDetune(channel, note);
|
|
1033
1056
|
}
|
|
1034
1057
|
});
|
|
1035
1058
|
}
|
|
1036
|
-
updateDetune(channel, note
|
|
1059
|
+
updateDetune(channel, note) {
|
|
1037
1060
|
const now = this.audioContext.currentTime;
|
|
1038
1061
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1039
|
-
const
|
|
1062
|
+
const pitchControl = this.getPitchControl(channel, note);
|
|
1063
|
+
const detune = channel.detune + noteDetune + pitchControl;
|
|
1040
1064
|
note.bufferSource.detune
|
|
1041
1065
|
.cancelScheduledValues(now)
|
|
1042
1066
|
.setValueAtTime(detune, now);
|
|
@@ -1058,12 +1082,12 @@ class Midy {
|
|
|
1058
1082
|
.setValueAtTime(0, volDelay)
|
|
1059
1083
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1060
1084
|
}
|
|
1061
|
-
setVolumeEnvelope(channel, note
|
|
1085
|
+
setVolumeEnvelope(channel, note) {
|
|
1062
1086
|
const now = this.audioContext.currentTime;
|
|
1063
1087
|
const state = channel.state;
|
|
1064
1088
|
const { voiceParams, startTime } = note;
|
|
1065
1089
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1066
|
-
(1 +
|
|
1090
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1067
1091
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1068
1092
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1069
1093
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
@@ -1077,20 +1101,20 @@ class Midy {
|
|
|
1077
1101
|
.setValueAtTime(attackVolume, volHold)
|
|
1078
1102
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1079
1103
|
}
|
|
1080
|
-
setPitchEnvelope(note) {
|
|
1081
|
-
|
|
1104
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1105
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1082
1106
|
const { voiceParams } = note;
|
|
1083
1107
|
const baseRate = voiceParams.playbackRate;
|
|
1084
1108
|
note.bufferSource.playbackRate
|
|
1085
|
-
.cancelScheduledValues(
|
|
1086
|
-
.setValueAtTime(baseRate,
|
|
1109
|
+
.cancelScheduledValues(scheduleTime)
|
|
1110
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1087
1111
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1088
1112
|
if (modEnvToPitch === 0)
|
|
1089
1113
|
return;
|
|
1090
1114
|
const basePitch = this.rateToCent(baseRate);
|
|
1091
1115
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1092
1116
|
const peekRate = this.centToRate(peekPitch);
|
|
1093
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1117
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1094
1118
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1095
1119
|
const modHold = modAttack + voiceParams.modHold;
|
|
1096
1120
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1127,13 +1151,14 @@ class Midy {
|
|
|
1127
1151
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1128
1152
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1129
1153
|
}
|
|
1130
|
-
setFilterEnvelope(channel, note
|
|
1154
|
+
setFilterEnvelope(channel, note) {
|
|
1131
1155
|
const now = this.audioContext.currentTime;
|
|
1132
1156
|
const state = channel.state;
|
|
1133
1157
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1134
1158
|
const softPedalFactor = 1 -
|
|
1135
1159
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1136
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1160
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1161
|
+
this.getFilterCutoffControl(channel, note);
|
|
1137
1162
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1138
1163
|
state.brightness * 2;
|
|
1139
1164
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
@@ -1164,9 +1189,9 @@ class Midy {
|
|
|
1164
1189
|
gain: voiceParams.modLfoToFilterFc,
|
|
1165
1190
|
});
|
|
1166
1191
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1167
|
-
this.setModLfoToPitch(channel, note
|
|
1192
|
+
this.setModLfoToPitch(channel, note);
|
|
1168
1193
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1169
|
-
this.setModLfoToVolume(
|
|
1194
|
+
this.setModLfoToVolume(channel, note);
|
|
1170
1195
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1171
1196
|
note.modulationLFO.connect(note.filterDepth);
|
|
1172
1197
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1227,8 +1252,8 @@ class Midy {
|
|
|
1227
1252
|
}
|
|
1228
1253
|
else {
|
|
1229
1254
|
note.portamento = false;
|
|
1230
|
-
this.setVolumeEnvelope(channel, note
|
|
1231
|
-
this.setFilterEnvelope(channel, note
|
|
1255
|
+
this.setVolumeEnvelope(channel, note);
|
|
1256
|
+
this.setFilterEnvelope(channel, note);
|
|
1232
1257
|
}
|
|
1233
1258
|
if (0 < state.vibratoDepth) {
|
|
1234
1259
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1440,15 +1465,16 @@ class Midy {
|
|
|
1440
1465
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1441
1466
|
}
|
|
1442
1467
|
}
|
|
1443
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
1444
|
-
|
|
1468
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
|
|
1469
|
+
if (!startTime)
|
|
1470
|
+
startTime = this.audioContext.currentTime;
|
|
1445
1471
|
const channel = this.channels[channelNumber];
|
|
1446
1472
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1447
1473
|
const table = channel.polyphonicKeyPressureTable;
|
|
1448
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1474
|
+
const activeNotes = this.getActiveNotes(channel, startTime);
|
|
1449
1475
|
if (activeNotes.has(noteNumber)) {
|
|
1450
1476
|
const note = activeNotes.get(noteNumber);
|
|
1451
|
-
this.
|
|
1477
|
+
this.setControllerParameters(channel, note, table);
|
|
1452
1478
|
}
|
|
1453
1479
|
// this.applyVoiceParams(channel, 10);
|
|
1454
1480
|
}
|
|
@@ -1457,8 +1483,9 @@ class Midy {
|
|
|
1457
1483
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1458
1484
|
channel.program = program;
|
|
1459
1485
|
}
|
|
1460
|
-
handleChannelPressure(channelNumber, value) {
|
|
1461
|
-
|
|
1486
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1487
|
+
if (!startTime)
|
|
1488
|
+
startTime = this.audioContext.currentTime;
|
|
1462
1489
|
const channel = this.channels[channelNumber];
|
|
1463
1490
|
const prev = channel.state.channelPressure;
|
|
1464
1491
|
const next = value / 127;
|
|
@@ -1468,8 +1495,8 @@ class Midy {
|
|
|
1468
1495
|
channel.detune += pressureDepth * (next - prev);
|
|
1469
1496
|
}
|
|
1470
1497
|
const table = channel.channelPressureTable;
|
|
1471
|
-
this.getActiveNotes(channel,
|
|
1472
|
-
this.
|
|
1498
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1499
|
+
this.setControllerParameters(channel, note, table);
|
|
1473
1500
|
});
|
|
1474
1501
|
// this.applyVoiceParams(channel, 13);
|
|
1475
1502
|
}
|
|
@@ -1487,9 +1514,10 @@ class Midy {
|
|
|
1487
1514
|
this.updateChannelDetune(channel);
|
|
1488
1515
|
this.applyVoiceParams(channel, 14);
|
|
1489
1516
|
}
|
|
1490
|
-
setModLfoToPitch(channel, note
|
|
1517
|
+
setModLfoToPitch(channel, note) {
|
|
1491
1518
|
const now = this.audioContext.currentTime;
|
|
1492
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1519
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1520
|
+
this.getLFOPitchDepth(channel, note);
|
|
1493
1521
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1494
1522
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1495
1523
|
note.modulationDepth.gain
|
|
@@ -1506,18 +1534,20 @@ class Midy {
|
|
|
1506
1534
|
.cancelScheduledValues(now)
|
|
1507
1535
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1508
1536
|
}
|
|
1509
|
-
setModLfoToFilterFc(
|
|
1537
|
+
setModLfoToFilterFc(channel, note) {
|
|
1510
1538
|
const now = this.audioContext.currentTime;
|
|
1511
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1539
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1540
|
+
this.getLFOFilterDepth(channel, note);
|
|
1512
1541
|
note.filterDepth.gain
|
|
1513
1542
|
.cancelScheduledValues(now)
|
|
1514
1543
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1515
1544
|
}
|
|
1516
|
-
setModLfoToVolume(
|
|
1545
|
+
setModLfoToVolume(channel, note) {
|
|
1517
1546
|
const now = this.audioContext.currentTime;
|
|
1518
1547
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1519
1548
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1520
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1549
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1550
|
+
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1521
1551
|
note.volumeDepth.gain
|
|
1522
1552
|
.cancelScheduledValues(now)
|
|
1523
1553
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1601,7 +1631,7 @@ class Midy {
|
|
|
1601
1631
|
return {
|
|
1602
1632
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1603
1633
|
if (0 < channel.state.modulationDepth) {
|
|
1604
|
-
this.setModLfoToPitch(channel, note
|
|
1634
|
+
this.setModLfoToPitch(channel, note);
|
|
1605
1635
|
}
|
|
1606
1636
|
},
|
|
1607
1637
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1611,12 +1641,12 @@ class Midy {
|
|
|
1611
1641
|
},
|
|
1612
1642
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1613
1643
|
if (0 < channel.state.modulationDepth) {
|
|
1614
|
-
this.setModLfoToFilterFc(
|
|
1644
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1615
1645
|
}
|
|
1616
1646
|
},
|
|
1617
1647
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1618
1648
|
if (0 < channel.state.modulationDepth) {
|
|
1619
|
-
this.setModLfoToVolume(
|
|
1649
|
+
this.setModLfoToVolume(channel, note);
|
|
1620
1650
|
}
|
|
1621
1651
|
},
|
|
1622
1652
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1686,7 +1716,7 @@ class Midy {
|
|
|
1686
1716
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1687
1717
|
}
|
|
1688
1718
|
else {
|
|
1689
|
-
this.setFilterEnvelope(channel, note
|
|
1719
|
+
this.setFilterEnvelope(channel, note);
|
|
1690
1720
|
}
|
|
1691
1721
|
this.setPitchEnvelope(note);
|
|
1692
1722
|
}
|
|
@@ -1700,7 +1730,7 @@ class Midy {
|
|
|
1700
1730
|
if (key in voiceParams)
|
|
1701
1731
|
noteVoiceParams[key] = voiceParams[key];
|
|
1702
1732
|
}
|
|
1703
|
-
this.setVolumeEnvelope(channel, note
|
|
1733
|
+
this.setVolumeEnvelope(channel, note);
|
|
1704
1734
|
}
|
|
1705
1735
|
}
|
|
1706
1736
|
}
|
|
@@ -1744,10 +1774,10 @@ class Midy {
|
|
|
1744
1774
|
127: this.polyOn,
|
|
1745
1775
|
};
|
|
1746
1776
|
}
|
|
1747
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1777
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1748
1778
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1749
1779
|
if (handler) {
|
|
1750
|
-
handler.call(this, channelNumber, value);
|
|
1780
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1751
1781
|
const channel = this.channels[channelNumber];
|
|
1752
1782
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1753
1783
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1759,55 +1789,45 @@ class Midy {
|
|
|
1759
1789
|
setBankMSB(channelNumber, msb) {
|
|
1760
1790
|
this.channels[channelNumber].bankMSB = msb;
|
|
1761
1791
|
}
|
|
1762
|
-
updateModulation(channel) {
|
|
1763
|
-
|
|
1792
|
+
updateModulation(channel, scheduleTime) {
|
|
1793
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1764
1794
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
}
|
|
1773
|
-
else {
|
|
1774
|
-
this.setPitchEnvelope(note);
|
|
1775
|
-
this.startModulation(channel, note, now);
|
|
1776
|
-
}
|
|
1795
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1796
|
+
if (note.modulationDepth) {
|
|
1797
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1801
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1777
1802
|
}
|
|
1778
1803
|
});
|
|
1779
1804
|
}
|
|
1780
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1805
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1781
1806
|
const channel = this.channels[channelNumber];
|
|
1782
1807
|
channel.state.modulationDepth = modulation / 127;
|
|
1783
|
-
this.updateModulation(channel);
|
|
1808
|
+
this.updateModulation(channel, scheduleTime);
|
|
1784
1809
|
}
|
|
1785
1810
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1786
1811
|
const channel = this.channels[channelNumber];
|
|
1787
1812
|
const factor = 5 * Math.log(10) / 127;
|
|
1788
1813
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1789
1814
|
}
|
|
1790
|
-
setKeyBasedVolume(channel) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
if (!note)
|
|
1796
|
-
continue;
|
|
1797
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1798
|
-
if (keyBasedValue === 0)
|
|
1799
|
-
continue;
|
|
1815
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1816
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1817
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1818
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1819
|
+
if (keyBasedValue !== 0) {
|
|
1800
1820
|
note.volumeNode.gain
|
|
1801
|
-
.cancelScheduledValues(
|
|
1802
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1821
|
+
.cancelScheduledValues(scheduleTime)
|
|
1822
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1803
1823
|
}
|
|
1804
1824
|
});
|
|
1805
1825
|
}
|
|
1806
|
-
setVolume(channelNumber, volume) {
|
|
1826
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1807
1827
|
const channel = this.channels[channelNumber];
|
|
1808
1828
|
channel.state.volume = volume / 127;
|
|
1809
|
-
this.updateChannelVolume(channel);
|
|
1810
|
-
this.setKeyBasedVolume(channel);
|
|
1829
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1830
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1811
1831
|
}
|
|
1812
1832
|
panToGain(pan) {
|
|
1813
1833
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1816,36 +1836,31 @@ class Midy {
|
|
|
1816
1836
|
gainRight: Math.sin(theta),
|
|
1817
1837
|
};
|
|
1818
1838
|
}
|
|
1819
|
-
setKeyBasedPan(channel) {
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
if (!note)
|
|
1825
|
-
continue;
|
|
1826
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1827
|
-
if (keyBasedValue === 0)
|
|
1828
|
-
continue;
|
|
1839
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1840
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1841
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1842
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1843
|
+
if (keyBasedValue !== 0) {
|
|
1829
1844
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1830
1845
|
note.gainL.gain
|
|
1831
|
-
.cancelScheduledValues(
|
|
1832
|
-
.setValueAtTime(gainLeft,
|
|
1846
|
+
.cancelScheduledValues(scheduleTime)
|
|
1847
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1833
1848
|
note.gainR.gain
|
|
1834
|
-
.cancelScheduledValues(
|
|
1835
|
-
.setValueAtTime(gainRight,
|
|
1849
|
+
.cancelScheduledValues(scheduleTime)
|
|
1850
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1836
1851
|
}
|
|
1837
1852
|
});
|
|
1838
1853
|
}
|
|
1839
|
-
setPan(channelNumber, pan) {
|
|
1854
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1840
1855
|
const channel = this.channels[channelNumber];
|
|
1841
1856
|
channel.state.pan = pan / 127;
|
|
1842
|
-
this.updateChannelVolume(channel);
|
|
1843
|
-
this.setKeyBasedPan(channel);
|
|
1857
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1858
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1844
1859
|
}
|
|
1845
|
-
setExpression(channelNumber, expression) {
|
|
1860
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1846
1861
|
const channel = this.channels[channelNumber];
|
|
1847
1862
|
channel.state.expression = expression / 127;
|
|
1848
|
-
this.updateChannelVolume(channel);
|
|
1863
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1849
1864
|
}
|
|
1850
1865
|
setBankLSB(channelNumber, lsb) {
|
|
1851
1866
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1920,7 +1935,7 @@ class Midy {
|
|
|
1920
1935
|
continue;
|
|
1921
1936
|
if (note.startTime < now)
|
|
1922
1937
|
continue;
|
|
1923
|
-
this.setVolumeEnvelope(channel, note
|
|
1938
|
+
this.setVolumeEnvelope(channel, note);
|
|
1924
1939
|
}
|
|
1925
1940
|
});
|
|
1926
1941
|
}
|
|
@@ -1936,7 +1951,7 @@ class Midy {
|
|
|
1936
1951
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1937
1952
|
}
|
|
1938
1953
|
else {
|
|
1939
|
-
this.setFilterEnvelope(channel, note
|
|
1954
|
+
this.setFilterEnvelope(channel, note);
|
|
1940
1955
|
}
|
|
1941
1956
|
}
|
|
1942
1957
|
});
|
|
@@ -1949,7 +1964,7 @@ class Midy {
|
|
|
1949
1964
|
const note = noteList[i];
|
|
1950
1965
|
if (!note)
|
|
1951
1966
|
continue;
|
|
1952
|
-
this.setVolumeEnvelope(channel, note
|
|
1967
|
+
this.setVolumeEnvelope(channel, note);
|
|
1953
1968
|
}
|
|
1954
1969
|
});
|
|
1955
1970
|
}
|
|
@@ -2639,61 +2654,61 @@ class Midy {
|
|
|
2639
2654
|
this.updateChannelDetune(channel);
|
|
2640
2655
|
}
|
|
2641
2656
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2657
|
+
getPitchControl(channel, note) {
|
|
2658
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
|
|
2659
|
+
note.pressure;
|
|
2660
|
+
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2661
|
+
}
|
|
2662
|
+
getFilterCutoffControl(channel, note) {
|
|
2663
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2664
|
+
channel.state.channelPressure;
|
|
2665
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
|
|
2666
|
+
note.pressure;
|
|
2667
|
+
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2668
|
+
}
|
|
2669
|
+
getAmplitudeControl(channel, note) {
|
|
2670
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2671
|
+
channel.state.channelPressure;
|
|
2672
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
|
|
2673
|
+
note.pressure;
|
|
2674
|
+
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2675
|
+
}
|
|
2676
|
+
getLFOPitchDepth(channel, note) {
|
|
2677
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2678
|
+
channel.state.channelPressure;
|
|
2679
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
|
|
2680
|
+
note.pressure;
|
|
2681
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2682
|
+
}
|
|
2683
|
+
getLFOFilterDepth(channel, note) {
|
|
2684
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2685
|
+
channel.state.channelPressure;
|
|
2686
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
|
|
2687
|
+
note.pressure;
|
|
2688
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2689
|
+
}
|
|
2690
|
+
getLFOAmplitudeDepth(channel, note) {
|
|
2691
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2692
|
+
channel.state.channelPressure;
|
|
2693
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
|
|
2694
|
+
note.pressure;
|
|
2695
|
+
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2696
|
+
}
|
|
2697
|
+
setControllerParameters(channel, note, table) {
|
|
2698
|
+
if (table[0] !== 64)
|
|
2699
|
+
this.updateDetune(channel, note);
|
|
2650
2700
|
if (!note.portamento) {
|
|
2651
|
-
if (table[1] !== 64)
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
channel.state.channelPressure;
|
|
2663
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2664
|
-
? channel.polyphonicKeyPressureTable[2] * note.pressure
|
|
2665
|
-
: 0;
|
|
2666
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 128;
|
|
2667
|
-
this.setVolumeEnvelope(channel, note, pressure);
|
|
2668
|
-
}
|
|
2669
|
-
}
|
|
2670
|
-
if (table[3] !== 0) {
|
|
2671
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2672
|
-
channel.state.channelPressure;
|
|
2673
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2674
|
-
? channel.polyphonicKeyPressureTable[3] * note.pressure
|
|
2675
|
-
: 0;
|
|
2676
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2677
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2678
|
-
}
|
|
2679
|
-
if (table[4] !== 0) {
|
|
2680
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2681
|
-
channel.state.channelPressure;
|
|
2682
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2683
|
-
? channel.polyphonicKeyPressureTable[4] * note.pressure
|
|
2684
|
-
: 0;
|
|
2685
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2686
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2687
|
-
}
|
|
2688
|
-
if (table[5] !== 0) {
|
|
2689
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2690
|
-
channel.state.channelPressure;
|
|
2691
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2692
|
-
? channel.polyphonicKeyPressureTable[5] * note.pressure
|
|
2693
|
-
: 0;
|
|
2694
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254;
|
|
2695
|
-
this.setModLfoToVolume(note, pressure);
|
|
2696
|
-
}
|
|
2701
|
+
if (table[1] !== 64)
|
|
2702
|
+
this.setFilterEnvelope(channel, note);
|
|
2703
|
+
if (table[2] !== 64)
|
|
2704
|
+
this.setVolumeEnvelope(channel, note);
|
|
2705
|
+
}
|
|
2706
|
+
if (table[3] !== 0)
|
|
2707
|
+
this.setModLfoToPitch(channel, note);
|
|
2708
|
+
if (table[4] !== 0)
|
|
2709
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2710
|
+
if (table[5] !== 0)
|
|
2711
|
+
this.setModLfoToVolume(channel, note);
|
|
2697
2712
|
}
|
|
2698
2713
|
handleChannelPressureSysEx(data, tableName) {
|
|
2699
2714
|
const channelNumber = data[4];
|
|
@@ -2724,7 +2739,7 @@ class Midy {
|
|
|
2724
2739
|
const note = noteList[i];
|
|
2725
2740
|
if (!note)
|
|
2726
2741
|
continue;
|
|
2727
|
-
this.
|
|
2742
|
+
this.setControllerParameters(channel, note, table);
|
|
2728
2743
|
}
|
|
2729
2744
|
});
|
|
2730
2745
|
}
|
|
@@ -2788,10 +2803,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2788
2803
|
value: {
|
|
2789
2804
|
currentBufferSource: null,
|
|
2790
2805
|
detune: 0,
|
|
2791
|
-
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
2792
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2793
|
-
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2794
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2795
2806
|
program: 0,
|
|
2796
2807
|
bank: 121 * 128,
|
|
2797
2808
|
bankMSB: 121,
|