@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/esm/midy.js
CHANGED
|
@@ -66,31 +66,37 @@ class Note {
|
|
|
66
66
|
writable: true,
|
|
67
67
|
value: void 0
|
|
68
68
|
});
|
|
69
|
+
Object.defineProperty(this, "filterDepth", {
|
|
70
|
+
enumerable: true,
|
|
71
|
+
configurable: true,
|
|
72
|
+
writable: true,
|
|
73
|
+
value: void 0
|
|
74
|
+
});
|
|
69
75
|
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
70
76
|
enumerable: true,
|
|
71
77
|
configurable: true,
|
|
72
78
|
writable: true,
|
|
73
79
|
value: void 0
|
|
74
80
|
});
|
|
75
|
-
Object.defineProperty(this, "
|
|
81
|
+
Object.defineProperty(this, "volumeDepth", {
|
|
76
82
|
enumerable: true,
|
|
77
83
|
configurable: true,
|
|
78
84
|
writable: true,
|
|
79
85
|
value: void 0
|
|
80
86
|
});
|
|
81
|
-
Object.defineProperty(this, "
|
|
87
|
+
Object.defineProperty(this, "volumeNode", {
|
|
82
88
|
enumerable: true,
|
|
83
89
|
configurable: true,
|
|
84
90
|
writable: true,
|
|
85
91
|
value: void 0
|
|
86
92
|
});
|
|
87
|
-
Object.defineProperty(this, "
|
|
93
|
+
Object.defineProperty(this, "gainL", {
|
|
88
94
|
enumerable: true,
|
|
89
95
|
configurable: true,
|
|
90
96
|
writable: true,
|
|
91
97
|
value: void 0
|
|
92
98
|
});
|
|
93
|
-
Object.defineProperty(this, "
|
|
99
|
+
Object.defineProperty(this, "gainR", {
|
|
94
100
|
enumerable: true,
|
|
95
101
|
configurable: true,
|
|
96
102
|
writable: true,
|
|
@@ -499,6 +505,10 @@ export class Midy {
|
|
|
499
505
|
...this.setChannelAudioNodes(audioContext),
|
|
500
506
|
scheduledNotes: new SparseMap(128),
|
|
501
507
|
sostenutoNotes: new SparseMap(128),
|
|
508
|
+
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
509
|
+
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
510
|
+
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
511
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
502
512
|
};
|
|
503
513
|
});
|
|
504
514
|
return channels;
|
|
@@ -565,10 +575,11 @@ export class Midy {
|
|
|
565
575
|
const event = this.timeline[queueIndex];
|
|
566
576
|
if (event.startTime > t + this.lookAhead)
|
|
567
577
|
break;
|
|
578
|
+
const startTime = event.startTime + this.startDelay - offset;
|
|
568
579
|
switch (event.type) {
|
|
569
580
|
case "noteOn":
|
|
570
581
|
if (event.velocity !== 0) {
|
|
571
|
-
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity,
|
|
582
|
+
await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
|
|
572
583
|
break;
|
|
573
584
|
}
|
|
574
585
|
/* falls through */
|
|
@@ -576,29 +587,29 @@ export class Midy {
|
|
|
576
587
|
const portamentoTarget = this.findPortamentoTarget(queueIndex);
|
|
577
588
|
if (portamentoTarget)
|
|
578
589
|
portamentoTarget.portamento = true;
|
|
579
|
-
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity,
|
|
590
|
+
const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
|
|
580
591
|
if (notePromise) {
|
|
581
592
|
this.notePromises.push(notePromise);
|
|
582
593
|
}
|
|
583
594
|
break;
|
|
584
595
|
}
|
|
585
596
|
case "noteAftertouch":
|
|
586
|
-
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
|
|
597
|
+
this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
|
|
587
598
|
break;
|
|
588
599
|
case "controller":
|
|
589
|
-
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
|
|
600
|
+
this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
|
|
590
601
|
break;
|
|
591
602
|
case "programChange":
|
|
592
|
-
this.handleProgramChange(event.channel, event.programNumber);
|
|
603
|
+
this.handleProgramChange(event.channel, event.programNumber, startTime);
|
|
593
604
|
break;
|
|
594
605
|
case "channelAftertouch":
|
|
595
|
-
this.handleChannelPressure(event.channel, event.amount);
|
|
606
|
+
this.handleChannelPressure(event.channel, event.amount, startTime);
|
|
596
607
|
break;
|
|
597
608
|
case "pitchBend":
|
|
598
|
-
this.setPitchBend(event.channel, event.value + 8192);
|
|
609
|
+
this.setPitchBend(event.channel, event.value + 8192, startTime);
|
|
599
610
|
break;
|
|
600
611
|
case "sysEx":
|
|
601
|
-
this.handleSysEx(event.data);
|
|
612
|
+
this.handleSysEx(event.data, startTime);
|
|
602
613
|
}
|
|
603
614
|
queueIndex++;
|
|
604
615
|
}
|
|
@@ -845,6 +856,18 @@ export class Midy {
|
|
|
845
856
|
const now = this.audioContext.currentTime;
|
|
846
857
|
return this.resumeTime + now - this.startTime - this.startDelay;
|
|
847
858
|
}
|
|
859
|
+
processScheduledNotes(channel, scheduleTime, callback) {
|
|
860
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
861
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
862
|
+
const note = noteList[i];
|
|
863
|
+
if (!note)
|
|
864
|
+
continue;
|
|
865
|
+
if (scheduleTime < note.startTime)
|
|
866
|
+
continue;
|
|
867
|
+
callback(note);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
}
|
|
848
871
|
getActiveNotes(channel, time) {
|
|
849
872
|
const activeNotes = new SparseMap(128);
|
|
850
873
|
channel.scheduledNotes.forEach((noteList) => {
|
|
@@ -1026,14 +1049,15 @@ export class Midy {
|
|
|
1026
1049
|
const note = noteList[i];
|
|
1027
1050
|
if (!note)
|
|
1028
1051
|
continue;
|
|
1029
|
-
this.updateDetune(channel, note
|
|
1052
|
+
this.updateDetune(channel, note);
|
|
1030
1053
|
}
|
|
1031
1054
|
});
|
|
1032
1055
|
}
|
|
1033
|
-
updateDetune(channel, note
|
|
1056
|
+
updateDetune(channel, note) {
|
|
1034
1057
|
const now = this.audioContext.currentTime;
|
|
1035
1058
|
const noteDetune = this.calcNoteDetune(channel, note);
|
|
1036
|
-
const
|
|
1059
|
+
const pitchControl = this.getPitchControl(channel, note);
|
|
1060
|
+
const detune = channel.detune + noteDetune + pitchControl;
|
|
1037
1061
|
note.bufferSource.detune
|
|
1038
1062
|
.cancelScheduledValues(now)
|
|
1039
1063
|
.setValueAtTime(detune, now);
|
|
@@ -1055,12 +1079,12 @@ export class Midy {
|
|
|
1055
1079
|
.setValueAtTime(0, volDelay)
|
|
1056
1080
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
1057
1081
|
}
|
|
1058
|
-
setVolumeEnvelope(channel, note
|
|
1082
|
+
setVolumeEnvelope(channel, note) {
|
|
1059
1083
|
const now = this.audioContext.currentTime;
|
|
1060
1084
|
const state = channel.state;
|
|
1061
1085
|
const { voiceParams, startTime } = note;
|
|
1062
1086
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
1063
|
-
(1 +
|
|
1087
|
+
(1 + this.getAmplitudeControl(channel, note));
|
|
1064
1088
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
1065
1089
|
const volDelay = startTime + voiceParams.volDelay;
|
|
1066
1090
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
@@ -1074,20 +1098,20 @@ export class Midy {
|
|
|
1074
1098
|
.setValueAtTime(attackVolume, volHold)
|
|
1075
1099
|
.linearRampToValueAtTime(sustainVolume, volDecay);
|
|
1076
1100
|
}
|
|
1077
|
-
setPitchEnvelope(note) {
|
|
1078
|
-
|
|
1101
|
+
setPitchEnvelope(note, scheduleTime) {
|
|
1102
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1079
1103
|
const { voiceParams } = note;
|
|
1080
1104
|
const baseRate = voiceParams.playbackRate;
|
|
1081
1105
|
note.bufferSource.playbackRate
|
|
1082
|
-
.cancelScheduledValues(
|
|
1083
|
-
.setValueAtTime(baseRate,
|
|
1106
|
+
.cancelScheduledValues(scheduleTime)
|
|
1107
|
+
.setValueAtTime(baseRate, scheduleTime);
|
|
1084
1108
|
const modEnvToPitch = voiceParams.modEnvToPitch;
|
|
1085
1109
|
if (modEnvToPitch === 0)
|
|
1086
1110
|
return;
|
|
1087
1111
|
const basePitch = this.rateToCent(baseRate);
|
|
1088
1112
|
const peekPitch = basePitch + modEnvToPitch;
|
|
1089
1113
|
const peekRate = this.centToRate(peekPitch);
|
|
1090
|
-
const modDelay = startTime + voiceParams.modDelay;
|
|
1114
|
+
const modDelay = note.startTime + voiceParams.modDelay;
|
|
1091
1115
|
const modAttack = modDelay + voiceParams.modAttack;
|
|
1092
1116
|
const modHold = modAttack + voiceParams.modHold;
|
|
1093
1117
|
const modDecay = modHold + voiceParams.modDecay;
|
|
@@ -1124,13 +1148,14 @@ export class Midy {
|
|
|
1124
1148
|
.setValueAtTime(adjustedBaseFreq, modDelay)
|
|
1125
1149
|
.linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
|
|
1126
1150
|
}
|
|
1127
|
-
setFilterEnvelope(channel, note
|
|
1151
|
+
setFilterEnvelope(channel, note) {
|
|
1128
1152
|
const now = this.audioContext.currentTime;
|
|
1129
1153
|
const state = channel.state;
|
|
1130
1154
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1131
1155
|
const softPedalFactor = 1 -
|
|
1132
1156
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1133
|
-
const baseCent = voiceParams.initialFilterFc +
|
|
1157
|
+
const baseCent = voiceParams.initialFilterFc +
|
|
1158
|
+
this.getFilterCutoffControl(channel, note);
|
|
1134
1159
|
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1135
1160
|
state.brightness * 2;
|
|
1136
1161
|
const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
|
|
@@ -1161,9 +1186,9 @@ export class Midy {
|
|
|
1161
1186
|
gain: voiceParams.modLfoToFilterFc,
|
|
1162
1187
|
});
|
|
1163
1188
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1164
|
-
this.setModLfoToPitch(channel, note
|
|
1189
|
+
this.setModLfoToPitch(channel, note);
|
|
1165
1190
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1166
|
-
this.setModLfoToVolume(
|
|
1191
|
+
this.setModLfoToVolume(channel, note);
|
|
1167
1192
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1168
1193
|
note.modulationLFO.connect(note.filterDepth);
|
|
1169
1194
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
@@ -1224,8 +1249,8 @@ export class Midy {
|
|
|
1224
1249
|
}
|
|
1225
1250
|
else {
|
|
1226
1251
|
note.portamento = false;
|
|
1227
|
-
this.setVolumeEnvelope(channel, note
|
|
1228
|
-
this.setFilterEnvelope(channel, note
|
|
1252
|
+
this.setVolumeEnvelope(channel, note);
|
|
1253
|
+
this.setFilterEnvelope(channel, note);
|
|
1229
1254
|
}
|
|
1230
1255
|
if (0 < state.vibratoDepth) {
|
|
1231
1256
|
this.startVibrato(channel, note, startTime);
|
|
@@ -1437,15 +1462,16 @@ export class Midy {
|
|
|
1437
1462
|
console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
|
|
1438
1463
|
}
|
|
1439
1464
|
}
|
|
1440
|
-
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
|
|
1441
|
-
|
|
1465
|
+
handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
|
|
1466
|
+
if (!startTime)
|
|
1467
|
+
startTime = this.audioContext.currentTime;
|
|
1442
1468
|
const channel = this.channels[channelNumber];
|
|
1443
1469
|
channel.state.polyphonicKeyPressure = pressure / 127;
|
|
1444
1470
|
const table = channel.polyphonicKeyPressureTable;
|
|
1445
|
-
const activeNotes = this.getActiveNotes(channel,
|
|
1471
|
+
const activeNotes = this.getActiveNotes(channel, startTime);
|
|
1446
1472
|
if (activeNotes.has(noteNumber)) {
|
|
1447
1473
|
const note = activeNotes.get(noteNumber);
|
|
1448
|
-
this.
|
|
1474
|
+
this.setControllerParameters(channel, note, table);
|
|
1449
1475
|
}
|
|
1450
1476
|
// this.applyVoiceParams(channel, 10);
|
|
1451
1477
|
}
|
|
@@ -1454,8 +1480,9 @@ export class Midy {
|
|
|
1454
1480
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1455
1481
|
channel.program = program;
|
|
1456
1482
|
}
|
|
1457
|
-
handleChannelPressure(channelNumber, value) {
|
|
1458
|
-
|
|
1483
|
+
handleChannelPressure(channelNumber, value, startTime) {
|
|
1484
|
+
if (!startTime)
|
|
1485
|
+
startTime = this.audioContext.currentTime;
|
|
1459
1486
|
const channel = this.channels[channelNumber];
|
|
1460
1487
|
const prev = channel.state.channelPressure;
|
|
1461
1488
|
const next = value / 127;
|
|
@@ -1465,8 +1492,8 @@ export class Midy {
|
|
|
1465
1492
|
channel.detune += pressureDepth * (next - prev);
|
|
1466
1493
|
}
|
|
1467
1494
|
const table = channel.channelPressureTable;
|
|
1468
|
-
this.getActiveNotes(channel,
|
|
1469
|
-
this.
|
|
1495
|
+
this.getActiveNotes(channel, startTime).forEach((note) => {
|
|
1496
|
+
this.setControllerParameters(channel, note, table);
|
|
1470
1497
|
});
|
|
1471
1498
|
// this.applyVoiceParams(channel, 13);
|
|
1472
1499
|
}
|
|
@@ -1484,9 +1511,10 @@ export class Midy {
|
|
|
1484
1511
|
this.updateChannelDetune(channel);
|
|
1485
1512
|
this.applyVoiceParams(channel, 14);
|
|
1486
1513
|
}
|
|
1487
|
-
setModLfoToPitch(channel, note
|
|
1514
|
+
setModLfoToPitch(channel, note) {
|
|
1488
1515
|
const now = this.audioContext.currentTime;
|
|
1489
|
-
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1516
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch +
|
|
1517
|
+
this.getLFOPitchDepth(channel, note);
|
|
1490
1518
|
const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
|
|
1491
1519
|
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1492
1520
|
note.modulationDepth.gain
|
|
@@ -1503,18 +1531,20 @@ export class Midy {
|
|
|
1503
1531
|
.cancelScheduledValues(now)
|
|
1504
1532
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1505
1533
|
}
|
|
1506
|
-
setModLfoToFilterFc(
|
|
1534
|
+
setModLfoToFilterFc(channel, note) {
|
|
1507
1535
|
const now = this.audioContext.currentTime;
|
|
1508
|
-
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1536
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
|
|
1537
|
+
this.getLFOFilterDepth(channel, note);
|
|
1509
1538
|
note.filterDepth.gain
|
|
1510
1539
|
.cancelScheduledValues(now)
|
|
1511
1540
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1512
1541
|
}
|
|
1513
|
-
setModLfoToVolume(
|
|
1542
|
+
setModLfoToVolume(channel, note) {
|
|
1514
1543
|
const now = this.audioContext.currentTime;
|
|
1515
1544
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1516
1545
|
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1517
|
-
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1546
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
|
|
1547
|
+
(1 + this.getLFOAmplitudeDepth(channel, note));
|
|
1518
1548
|
note.volumeDepth.gain
|
|
1519
1549
|
.cancelScheduledValues(now)
|
|
1520
1550
|
.setValueAtTime(volumeDepth, now);
|
|
@@ -1598,7 +1628,7 @@ export class Midy {
|
|
|
1598
1628
|
return {
|
|
1599
1629
|
modLfoToPitch: (channel, note, _prevValue) => {
|
|
1600
1630
|
if (0 < channel.state.modulationDepth) {
|
|
1601
|
-
this.setModLfoToPitch(channel, note
|
|
1631
|
+
this.setModLfoToPitch(channel, note);
|
|
1602
1632
|
}
|
|
1603
1633
|
},
|
|
1604
1634
|
vibLfoToPitch: (channel, note, _prevValue) => {
|
|
@@ -1608,12 +1638,12 @@ export class Midy {
|
|
|
1608
1638
|
},
|
|
1609
1639
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1610
1640
|
if (0 < channel.state.modulationDepth) {
|
|
1611
|
-
this.setModLfoToFilterFc(
|
|
1641
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1612
1642
|
}
|
|
1613
1643
|
},
|
|
1614
1644
|
modLfoToVolume: (channel, note, _prevValue) => {
|
|
1615
1645
|
if (0 < channel.state.modulationDepth) {
|
|
1616
|
-
this.setModLfoToVolume(
|
|
1646
|
+
this.setModLfoToVolume(channel, note);
|
|
1617
1647
|
}
|
|
1618
1648
|
},
|
|
1619
1649
|
chorusEffectsSend: (channel, note, prevValue) => {
|
|
@@ -1683,7 +1713,7 @@ export class Midy {
|
|
|
1683
1713
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1684
1714
|
}
|
|
1685
1715
|
else {
|
|
1686
|
-
this.setFilterEnvelope(channel, note
|
|
1716
|
+
this.setFilterEnvelope(channel, note);
|
|
1687
1717
|
}
|
|
1688
1718
|
this.setPitchEnvelope(note);
|
|
1689
1719
|
}
|
|
@@ -1697,7 +1727,7 @@ export class Midy {
|
|
|
1697
1727
|
if (key in voiceParams)
|
|
1698
1728
|
noteVoiceParams[key] = voiceParams[key];
|
|
1699
1729
|
}
|
|
1700
|
-
this.setVolumeEnvelope(channel, note
|
|
1730
|
+
this.setVolumeEnvelope(channel, note);
|
|
1701
1731
|
}
|
|
1702
1732
|
}
|
|
1703
1733
|
}
|
|
@@ -1741,10 +1771,10 @@ export class Midy {
|
|
|
1741
1771
|
127: this.polyOn,
|
|
1742
1772
|
};
|
|
1743
1773
|
}
|
|
1744
|
-
handleControlChange(channelNumber, controllerType, value) {
|
|
1774
|
+
handleControlChange(channelNumber, controllerType, value, startTime) {
|
|
1745
1775
|
const handler = this.controlChangeHandlers[controllerType];
|
|
1746
1776
|
if (handler) {
|
|
1747
|
-
handler.call(this, channelNumber, value);
|
|
1777
|
+
handler.call(this, channelNumber, value, startTime);
|
|
1748
1778
|
const channel = this.channels[channelNumber];
|
|
1749
1779
|
this.applyVoiceParams(channel, controllerType + 128);
|
|
1750
1780
|
this.applyControlTable(channel, controllerType);
|
|
@@ -1756,55 +1786,45 @@ export class Midy {
|
|
|
1756
1786
|
setBankMSB(channelNumber, msb) {
|
|
1757
1787
|
this.channels[channelNumber].bankMSB = msb;
|
|
1758
1788
|
}
|
|
1759
|
-
updateModulation(channel) {
|
|
1760
|
-
|
|
1789
|
+
updateModulation(channel, scheduleTime) {
|
|
1790
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1761
1791
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}
|
|
1770
|
-
else {
|
|
1771
|
-
this.setPitchEnvelope(note);
|
|
1772
|
-
this.startModulation(channel, note, now);
|
|
1773
|
-
}
|
|
1792
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1793
|
+
if (note.modulationDepth) {
|
|
1794
|
+
note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
this.setPitchEnvelope(note, scheduleTime);
|
|
1798
|
+
this.startModulation(channel, note, scheduleTime);
|
|
1774
1799
|
}
|
|
1775
1800
|
});
|
|
1776
1801
|
}
|
|
1777
|
-
setModulationDepth(channelNumber, modulation) {
|
|
1802
|
+
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1778
1803
|
const channel = this.channels[channelNumber];
|
|
1779
1804
|
channel.state.modulationDepth = modulation / 127;
|
|
1780
|
-
this.updateModulation(channel);
|
|
1805
|
+
this.updateModulation(channel, scheduleTime);
|
|
1781
1806
|
}
|
|
1782
1807
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1783
1808
|
const channel = this.channels[channelNumber];
|
|
1784
1809
|
const factor = 5 * Math.log(10) / 127;
|
|
1785
1810
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1786
1811
|
}
|
|
1787
|
-
setKeyBasedVolume(channel) {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (!note)
|
|
1793
|
-
continue;
|
|
1794
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1795
|
-
if (keyBasedValue === 0)
|
|
1796
|
-
continue;
|
|
1812
|
+
setKeyBasedVolume(channel, scheduleTime) {
|
|
1813
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1814
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1815
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1816
|
+
if (keyBasedValue !== 0) {
|
|
1797
1817
|
note.volumeNode.gain
|
|
1798
|
-
.cancelScheduledValues(
|
|
1799
|
-
.setValueAtTime(1 + keyBasedValue,
|
|
1818
|
+
.cancelScheduledValues(scheduleTime)
|
|
1819
|
+
.setValueAtTime(1 + keyBasedValue, scheduleTime);
|
|
1800
1820
|
}
|
|
1801
1821
|
});
|
|
1802
1822
|
}
|
|
1803
|
-
setVolume(channelNumber, volume) {
|
|
1823
|
+
setVolume(channelNumber, volume, scheduleTime) {
|
|
1804
1824
|
const channel = this.channels[channelNumber];
|
|
1805
1825
|
channel.state.volume = volume / 127;
|
|
1806
|
-
this.updateChannelVolume(channel);
|
|
1807
|
-
this.setKeyBasedVolume(channel);
|
|
1826
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1827
|
+
this.setKeyBasedVolume(channel, scheduleTime);
|
|
1808
1828
|
}
|
|
1809
1829
|
panToGain(pan) {
|
|
1810
1830
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1813,36 +1833,31 @@ export class Midy {
|
|
|
1813
1833
|
gainRight: Math.sin(theta),
|
|
1814
1834
|
};
|
|
1815
1835
|
}
|
|
1816
|
-
setKeyBasedPan(channel) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
if (!note)
|
|
1822
|
-
continue;
|
|
1823
|
-
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1824
|
-
if (keyBasedValue === 0)
|
|
1825
|
-
continue;
|
|
1836
|
+
setKeyBasedPan(channel, scheduleTime) {
|
|
1837
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1838
|
+
this.processScheduledNotes(channel, scheduleTime, (note) => {
|
|
1839
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1840
|
+
if (keyBasedValue !== 0) {
|
|
1826
1841
|
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1827
1842
|
note.gainL.gain
|
|
1828
|
-
.cancelScheduledValues(
|
|
1829
|
-
.setValueAtTime(gainLeft,
|
|
1843
|
+
.cancelScheduledValues(scheduleTime)
|
|
1844
|
+
.setValueAtTime(gainLeft, scheduleTime);
|
|
1830
1845
|
note.gainR.gain
|
|
1831
|
-
.cancelScheduledValues(
|
|
1832
|
-
.setValueAtTime(gainRight,
|
|
1846
|
+
.cancelScheduledValues(scheduleTime)
|
|
1847
|
+
.setValueAtTime(gainRight, scheduleTime);
|
|
1833
1848
|
}
|
|
1834
1849
|
});
|
|
1835
1850
|
}
|
|
1836
|
-
setPan(channelNumber, pan) {
|
|
1851
|
+
setPan(channelNumber, pan, scheduleTime) {
|
|
1837
1852
|
const channel = this.channels[channelNumber];
|
|
1838
1853
|
channel.state.pan = pan / 127;
|
|
1839
|
-
this.updateChannelVolume(channel);
|
|
1840
|
-
this.setKeyBasedPan(channel);
|
|
1854
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1855
|
+
this.setKeyBasedPan(channel, scheduleTime);
|
|
1841
1856
|
}
|
|
1842
|
-
setExpression(channelNumber, expression) {
|
|
1857
|
+
setExpression(channelNumber, expression, scheduleTime) {
|
|
1843
1858
|
const channel = this.channels[channelNumber];
|
|
1844
1859
|
channel.state.expression = expression / 127;
|
|
1845
|
-
this.updateChannelVolume(channel);
|
|
1860
|
+
this.updateChannelVolume(channel, scheduleTime);
|
|
1846
1861
|
}
|
|
1847
1862
|
setBankLSB(channelNumber, lsb) {
|
|
1848
1863
|
this.channels[channelNumber].bankLSB = lsb;
|
|
@@ -1917,7 +1932,7 @@ export class Midy {
|
|
|
1917
1932
|
continue;
|
|
1918
1933
|
if (note.startTime < now)
|
|
1919
1934
|
continue;
|
|
1920
|
-
this.setVolumeEnvelope(channel, note
|
|
1935
|
+
this.setVolumeEnvelope(channel, note);
|
|
1921
1936
|
}
|
|
1922
1937
|
});
|
|
1923
1938
|
}
|
|
@@ -1933,7 +1948,7 @@ export class Midy {
|
|
|
1933
1948
|
this.setPortamentoStartFilterEnvelope(channel, note);
|
|
1934
1949
|
}
|
|
1935
1950
|
else {
|
|
1936
|
-
this.setFilterEnvelope(channel, note
|
|
1951
|
+
this.setFilterEnvelope(channel, note);
|
|
1937
1952
|
}
|
|
1938
1953
|
}
|
|
1939
1954
|
});
|
|
@@ -1946,7 +1961,7 @@ export class Midy {
|
|
|
1946
1961
|
const note = noteList[i];
|
|
1947
1962
|
if (!note)
|
|
1948
1963
|
continue;
|
|
1949
|
-
this.setVolumeEnvelope(channel, note
|
|
1964
|
+
this.setVolumeEnvelope(channel, note);
|
|
1950
1965
|
}
|
|
1951
1966
|
});
|
|
1952
1967
|
}
|
|
@@ -2636,61 +2651,61 @@ export class Midy {
|
|
|
2636
2651
|
this.updateChannelDetune(channel);
|
|
2637
2652
|
}
|
|
2638
2653
|
}
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2654
|
+
getPitchControl(channel, note) {
|
|
2655
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
|
|
2656
|
+
note.pressure;
|
|
2657
|
+
return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
|
|
2658
|
+
}
|
|
2659
|
+
getFilterCutoffControl(channel, note) {
|
|
2660
|
+
const channelPressure = (channel.channelPressureTable[1] - 64) *
|
|
2661
|
+
channel.state.channelPressure;
|
|
2662
|
+
const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
|
|
2663
|
+
note.pressure;
|
|
2664
|
+
return (channelPressure + polyphonicKeyPressure) * 15;
|
|
2665
|
+
}
|
|
2666
|
+
getAmplitudeControl(channel, note) {
|
|
2667
|
+
const channelPressure = channel.channelPressureTable[2] *
|
|
2668
|
+
channel.state.channelPressure;
|
|
2669
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
|
|
2670
|
+
note.pressure;
|
|
2671
|
+
return (channelPressure + polyphonicKeyPressure) / 128;
|
|
2672
|
+
}
|
|
2673
|
+
getLFOPitchDepth(channel, note) {
|
|
2674
|
+
const channelPressure = channel.channelPressureTable[3] *
|
|
2675
|
+
channel.state.channelPressure;
|
|
2676
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
|
|
2677
|
+
note.pressure;
|
|
2678
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2679
|
+
}
|
|
2680
|
+
getLFOFilterDepth(channel, note) {
|
|
2681
|
+
const channelPressure = channel.channelPressureTable[4] *
|
|
2682
|
+
channel.state.channelPressure;
|
|
2683
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
|
|
2684
|
+
note.pressure;
|
|
2685
|
+
return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2686
|
+
}
|
|
2687
|
+
getLFOAmplitudeDepth(channel, note) {
|
|
2688
|
+
const channelPressure = channel.channelPressureTable[5] *
|
|
2689
|
+
channel.state.channelPressure;
|
|
2690
|
+
const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
|
|
2691
|
+
note.pressure;
|
|
2692
|
+
return (channelPressure + polyphonicKeyPressure) / 254;
|
|
2693
|
+
}
|
|
2694
|
+
setControllerParameters(channel, note, table) {
|
|
2695
|
+
if (table[0] !== 64)
|
|
2696
|
+
this.updateDetune(channel, note);
|
|
2647
2697
|
if (!note.portamento) {
|
|
2648
|
-
if (table[1] !== 64)
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
channel.state.channelPressure;
|
|
2660
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2661
|
-
? channel.polyphonicKeyPressureTable[2] * note.pressure
|
|
2662
|
-
: 0;
|
|
2663
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 128;
|
|
2664
|
-
this.setVolumeEnvelope(channel, note, pressure);
|
|
2665
|
-
}
|
|
2666
|
-
}
|
|
2667
|
-
if (table[3] !== 0) {
|
|
2668
|
-
const channelPressure = channel.channelPressureTable[3] *
|
|
2669
|
-
channel.state.channelPressure;
|
|
2670
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2671
|
-
? channel.polyphonicKeyPressureTable[3] * note.pressure
|
|
2672
|
-
: 0;
|
|
2673
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
|
|
2674
|
-
this.setModLfoToPitch(channel, note, pressure);
|
|
2675
|
-
}
|
|
2676
|
-
if (table[4] !== 0) {
|
|
2677
|
-
const channelPressure = channel.channelPressureTable[4] *
|
|
2678
|
-
channel.state.channelPressure;
|
|
2679
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2680
|
-
? channel.polyphonicKeyPressureTable[4] * note.pressure
|
|
2681
|
-
: 0;
|
|
2682
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
|
|
2683
|
-
this.setModLfoToFilterFc(note, pressure);
|
|
2684
|
-
}
|
|
2685
|
-
if (table[5] !== 0) {
|
|
2686
|
-
const channelPressure = channel.channelPressureTable[5] *
|
|
2687
|
-
channel.state.channelPressure;
|
|
2688
|
-
const polyphonicKeyPressure = (0 < note.pressure)
|
|
2689
|
-
? channel.polyphonicKeyPressureTable[5] * note.pressure
|
|
2690
|
-
: 0;
|
|
2691
|
-
const pressure = (channelPressure + polyphonicKeyPressure) / 254;
|
|
2692
|
-
this.setModLfoToVolume(note, pressure);
|
|
2693
|
-
}
|
|
2698
|
+
if (table[1] !== 64)
|
|
2699
|
+
this.setFilterEnvelope(channel, note);
|
|
2700
|
+
if (table[2] !== 64)
|
|
2701
|
+
this.setVolumeEnvelope(channel, note);
|
|
2702
|
+
}
|
|
2703
|
+
if (table[3] !== 0)
|
|
2704
|
+
this.setModLfoToPitch(channel, note);
|
|
2705
|
+
if (table[4] !== 0)
|
|
2706
|
+
this.setModLfoToFilterFc(channel, note);
|
|
2707
|
+
if (table[5] !== 0)
|
|
2708
|
+
this.setModLfoToVolume(channel, note);
|
|
2694
2709
|
}
|
|
2695
2710
|
handleChannelPressureSysEx(data, tableName) {
|
|
2696
2711
|
const channelNumber = data[4];
|
|
@@ -2721,7 +2736,7 @@ export class Midy {
|
|
|
2721
2736
|
const note = noteList[i];
|
|
2722
2737
|
if (!note)
|
|
2723
2738
|
continue;
|
|
2724
|
-
this.
|
|
2739
|
+
this.setControllerParameters(channel, note, table);
|
|
2725
2740
|
}
|
|
2726
2741
|
});
|
|
2727
2742
|
}
|
|
@@ -2784,10 +2799,6 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2784
2799
|
value: {
|
|
2785
2800
|
currentBufferSource: null,
|
|
2786
2801
|
detune: 0,
|
|
2787
|
-
scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
|
|
2788
|
-
channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2789
|
-
polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2790
|
-
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2791
2802
|
program: 0,
|
|
2792
2803
|
bank: 121 * 128,
|
|
2793
2804
|
bankMSB: 121,
|