@marmooo/midy 0.2.1 → 0.2.2
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 +2 -2
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +15 -15
- package/esm/midy-GM2.d.ts +28 -16
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +268 -132
- package/esm/midy-GMLite.d.ts +2 -2
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +15 -15
- package/esm/midy.d.ts +24 -12
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +252 -116
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +2 -2
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +15 -15
- package/script/midy-GM2.d.ts +28 -16
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +268 -132
- package/script/midy-GMLite.d.ts +2 -2
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +15 -15
- package/script/midy.d.ts +24 -12
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +252 -116
package/script/midy.js
CHANGED
|
@@ -17,12 +17,30 @@ class Note {
|
|
|
17
17
|
writable: true,
|
|
18
18
|
value: void 0
|
|
19
19
|
});
|
|
20
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
20
26
|
Object.defineProperty(this, "volumeNode", {
|
|
21
27
|
enumerable: true,
|
|
22
28
|
configurable: true,
|
|
23
29
|
writable: true,
|
|
24
30
|
value: void 0
|
|
25
31
|
});
|
|
32
|
+
Object.defineProperty(this, "gainL", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: void 0
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "gainR", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: void 0
|
|
43
|
+
});
|
|
26
44
|
Object.defineProperty(this, "volumeDepth", {
|
|
27
45
|
enumerable: true,
|
|
28
46
|
configurable: true,
|
|
@@ -923,7 +941,9 @@ class Midy {
|
|
|
923
941
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
924
942
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
925
943
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
926
|
-
|
|
944
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
945
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
946
|
+
return tuning + pitch + pressure;
|
|
927
947
|
}
|
|
928
948
|
calcNoteDetune(channel, note) {
|
|
929
949
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -943,14 +963,19 @@ class Midy {
|
|
|
943
963
|
}
|
|
944
964
|
});
|
|
945
965
|
}
|
|
966
|
+
getPortamentoTime(channel) {
|
|
967
|
+
const factor = 5 * Math.log(10) / 127;
|
|
968
|
+
const time = channel.state.portamentoTime;
|
|
969
|
+
return Math.log(time) / factor;
|
|
970
|
+
}
|
|
946
971
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
947
972
|
const now = this.audioContext.currentTime;
|
|
948
973
|
const { voiceParams, startTime } = note;
|
|
949
974
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
950
975
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
951
976
|
const volDelay = startTime + voiceParams.volDelay;
|
|
952
|
-
const portamentoTime = volDelay + channel
|
|
953
|
-
note.
|
|
977
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
978
|
+
note.volumeEnvelopeNode.gain
|
|
954
979
|
.cancelScheduledValues(now)
|
|
955
980
|
.setValueAtTime(0, volDelay)
|
|
956
981
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
@@ -959,13 +984,16 @@ class Midy {
|
|
|
959
984
|
const now = this.audioContext.currentTime;
|
|
960
985
|
const state = channel.state;
|
|
961
986
|
const { voiceParams, startTime } = note;
|
|
962
|
-
const
|
|
987
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
988
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
989
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
990
|
+
pressure;
|
|
963
991
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
964
992
|
const volDelay = startTime + voiceParams.volDelay;
|
|
965
993
|
const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
|
|
966
994
|
const volHold = volAttack + voiceParams.volHold;
|
|
967
995
|
const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
|
|
968
|
-
note.
|
|
996
|
+
note.volumeEnvelopeNode.gain
|
|
969
997
|
.cancelScheduledValues(now)
|
|
970
998
|
.setValueAtTime(0, startTime)
|
|
971
999
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -1007,14 +1035,17 @@ class Midy {
|
|
|
1007
1035
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1008
1036
|
const softPedalFactor = 1 -
|
|
1009
1037
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1038
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1039
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1040
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1041
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor *
|
|
1042
|
+
state.brightness * 2;
|
|
1012
1043
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
|
|
1013
1044
|
const sustainFreq = baseFreq +
|
|
1014
1045
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1015
1046
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1016
1047
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1017
|
-
const portamentoTime = startTime + channel
|
|
1048
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1018
1049
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1019
1050
|
note.filterNode.frequency
|
|
1020
1051
|
.cancelScheduledValues(now)
|
|
@@ -1059,14 +1090,14 @@ class Midy {
|
|
|
1059
1090
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1060
1091
|
this.setModLfoToPitch(channel, note);
|
|
1061
1092
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1062
|
-
this.setModLfoToVolume(note);
|
|
1093
|
+
this.setModLfoToVolume(channel, note);
|
|
1063
1094
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1064
1095
|
note.modulationLFO.connect(note.filterDepth);
|
|
1065
1096
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1066
1097
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1067
1098
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1068
1099
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1069
|
-
note.volumeDepth.connect(note.
|
|
1100
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1070
1101
|
}
|
|
1071
1102
|
startVibrato(channel, note, startTime) {
|
|
1072
1103
|
const { voiceParams } = note;
|
|
@@ -1088,6 +1119,9 @@ class Midy {
|
|
|
1088
1119
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1089
1120
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1090
1121
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1122
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1123
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1124
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1091
1125
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1092
1126
|
type: "lowpass",
|
|
1093
1127
|
Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
|
|
@@ -1114,7 +1148,10 @@ class Midy {
|
|
|
1114
1148
|
channel.currentBufferSource = note.bufferSource;
|
|
1115
1149
|
}
|
|
1116
1150
|
note.bufferSource.connect(note.filterNode);
|
|
1117
|
-
note.filterNode.connect(note.
|
|
1151
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1152
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1153
|
+
note.volumeNode.connect(note.gainL);
|
|
1154
|
+
note.volumeNode.connect(note.gainR);
|
|
1118
1155
|
if (0 < channel.chorusSendLevel) {
|
|
1119
1156
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1120
1157
|
}
|
|
@@ -1145,8 +1182,8 @@ class Midy {
|
|
|
1145
1182
|
if (!voice)
|
|
1146
1183
|
return;
|
|
1147
1184
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1148
|
-
note.
|
|
1149
|
-
note.
|
|
1185
|
+
note.gainL.connect(channel.gainL);
|
|
1186
|
+
note.gainR.connect(channel.gainR);
|
|
1150
1187
|
if (channel.state.sostenutoPedal) {
|
|
1151
1188
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1152
1189
|
}
|
|
@@ -1177,7 +1214,7 @@ class Midy {
|
|
|
1177
1214
|
}
|
|
1178
1215
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1179
1216
|
const note = scheduledNotes[index];
|
|
1180
|
-
note.
|
|
1217
|
+
note.volumeEnvelopeNode.gain
|
|
1181
1218
|
.cancelScheduledValues(endTime)
|
|
1182
1219
|
.linearRampToValueAtTime(0, stopTime);
|
|
1183
1220
|
note.ending = true;
|
|
@@ -1188,8 +1225,11 @@ class Midy {
|
|
|
1188
1225
|
note.bufferSource.onended = () => {
|
|
1189
1226
|
scheduledNotes[index] = null;
|
|
1190
1227
|
note.bufferSource.disconnect();
|
|
1191
|
-
note.volumeNode.disconnect();
|
|
1192
1228
|
note.filterNode.disconnect();
|
|
1229
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1230
|
+
note.volumeNode.disconnect();
|
|
1231
|
+
note.gainL.disconnect();
|
|
1232
|
+
note.gainR.disconnect();
|
|
1193
1233
|
if (note.modulationDepth) {
|
|
1194
1234
|
note.volumeDepth.disconnect();
|
|
1195
1235
|
note.modulationDepth.disconnect();
|
|
@@ -1239,7 +1279,7 @@ class Midy {
|
|
|
1239
1279
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1240
1280
|
}
|
|
1241
1281
|
else {
|
|
1242
|
-
const portamentoTime = endTime +
|
|
1282
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1243
1283
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1244
1284
|
const baseRate = note.voiceParams.playbackRate;
|
|
1245
1285
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1314,7 +1354,7 @@ class Midy {
|
|
|
1314
1354
|
if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
|
|
1315
1355
|
if (activeNotes.has(noteNumber)) {
|
|
1316
1356
|
const activeNote = activeNotes.get(noteNumber);
|
|
1317
|
-
const gain = activeNote.
|
|
1357
|
+
const gain = activeNote.gainL.gain.value;
|
|
1318
1358
|
activeNote.volumeNode.gain
|
|
1319
1359
|
.cancelScheduledValues(now)
|
|
1320
1360
|
.setValueAtTime(gain * pressure, now);
|
|
@@ -1327,22 +1367,45 @@ class Midy {
|
|
|
1327
1367
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1328
1368
|
channel.program = program;
|
|
1329
1369
|
}
|
|
1330
|
-
handleChannelPressure(channelNumber,
|
|
1331
|
-
const now = this.audioContext.currentTime;
|
|
1370
|
+
handleChannelPressure(channelNumber, value) {
|
|
1332
1371
|
const channel = this.channels[channelNumber];
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (channel.
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
activeNote.volumeNode.gain
|
|
1340
|
-
.cancelScheduledValues(now)
|
|
1341
|
-
.setValueAtTime(gain * pressure, now);
|
|
1342
|
-
});
|
|
1372
|
+
const prev = channel.state.channelPressure;
|
|
1373
|
+
const next = value / 127;
|
|
1374
|
+
channel.state.channelPressure = next;
|
|
1375
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1376
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1377
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1343
1378
|
}
|
|
1379
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1380
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1381
|
+
const note = noteList[i];
|
|
1382
|
+
if (!note)
|
|
1383
|
+
continue;
|
|
1384
|
+
this.setChannelPressure(channel, note);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1344
1387
|
// this.applyVoiceParams(channel, 13);
|
|
1345
1388
|
}
|
|
1389
|
+
setChannelPressure(channel, note) {
|
|
1390
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1391
|
+
this.updateDetune(channel);
|
|
1392
|
+
}
|
|
1393
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1394
|
+
this.setFilterEnvelope(channel, note);
|
|
1395
|
+
}
|
|
1396
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1397
|
+
this.setVolumeEnvelope(channel, note);
|
|
1398
|
+
}
|
|
1399
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1400
|
+
this.setModLfoToPitch(channel, note);
|
|
1401
|
+
}
|
|
1402
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1403
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1404
|
+
}
|
|
1405
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1406
|
+
this.setModLfoToVolume(channel, note);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1346
1409
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1347
1410
|
const pitchBend = msb * 128 + lsb;
|
|
1348
1411
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1359,13 +1422,15 @@ class Midy {
|
|
|
1359
1422
|
}
|
|
1360
1423
|
setModLfoToPitch(channel, note) {
|
|
1361
1424
|
const now = this.audioContext.currentTime;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1425
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1426
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1427
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1428
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1364
1429
|
channel.state.modulationDepth;
|
|
1365
|
-
const
|
|
1430
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1366
1431
|
note.modulationDepth.gain
|
|
1367
1432
|
.cancelScheduledValues(now)
|
|
1368
|
-
.setValueAtTime(modulationDepth
|
|
1433
|
+
.setValueAtTime(modulationDepth, now);
|
|
1369
1434
|
}
|
|
1370
1435
|
setVibLfoToPitch(channel, note) {
|
|
1371
1436
|
const now = this.audioContext.currentTime;
|
|
@@ -1377,69 +1442,75 @@ class Midy {
|
|
|
1377
1442
|
.cancelScheduledValues(now)
|
|
1378
1443
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1379
1444
|
}
|
|
1380
|
-
setModLfoToFilterFc(note) {
|
|
1445
|
+
setModLfoToFilterFc(channel, note) {
|
|
1381
1446
|
const now = this.audioContext.currentTime;
|
|
1382
|
-
const
|
|
1447
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1448
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1449
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1383
1450
|
note.filterDepth.gain
|
|
1384
1451
|
.cancelScheduledValues(now)
|
|
1385
1452
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1386
1453
|
}
|
|
1387
|
-
setModLfoToVolume(note) {
|
|
1454
|
+
setModLfoToVolume(channel, note) {
|
|
1388
1455
|
const now = this.audioContext.currentTime;
|
|
1389
1456
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1390
|
-
const
|
|
1391
|
-
const
|
|
1457
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1458
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1459
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1460
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1392
1461
|
note.volumeDepth.gain
|
|
1393
1462
|
.cancelScheduledValues(now)
|
|
1394
|
-
.setValueAtTime(volumeDepth
|
|
1463
|
+
.setValueAtTime(volumeDepth, now);
|
|
1395
1464
|
}
|
|
1396
|
-
|
|
1465
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1397
1466
|
if (0 < prevValue) {
|
|
1398
|
-
if (0 < note.voiceParams.
|
|
1467
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1399
1468
|
const now = this.audioContext.currentTime;
|
|
1400
|
-
const
|
|
1401
|
-
note.
|
|
1469
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1470
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1471
|
+
note.reverbEffectsSend.gain
|
|
1402
1472
|
.cancelScheduledValues(now)
|
|
1403
1473
|
.setValueAtTime(value, now);
|
|
1404
1474
|
}
|
|
1405
1475
|
else {
|
|
1406
|
-
note.
|
|
1476
|
+
note.reverbEffectsSend.disconnect();
|
|
1407
1477
|
}
|
|
1408
1478
|
}
|
|
1409
1479
|
else {
|
|
1410
|
-
if (0 < note.voiceParams.
|
|
1411
|
-
if (!note.
|
|
1412
|
-
note.
|
|
1413
|
-
gain: note.voiceParams.
|
|
1480
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1481
|
+
if (!note.reverbEffectsSend) {
|
|
1482
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1483
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1414
1484
|
});
|
|
1415
|
-
note.volumeNode.connect(note.
|
|
1485
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1416
1486
|
}
|
|
1417
|
-
note.
|
|
1487
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1418
1488
|
}
|
|
1419
1489
|
}
|
|
1420
1490
|
}
|
|
1421
|
-
|
|
1491
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1422
1492
|
if (0 < prevValue) {
|
|
1423
|
-
if (0 < note.voiceParams.
|
|
1493
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1424
1494
|
const now = this.audioContext.currentTime;
|
|
1425
|
-
const
|
|
1426
|
-
note.
|
|
1495
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1496
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1497
|
+
note.chorusEffectsSend.gain
|
|
1427
1498
|
.cancelScheduledValues(now)
|
|
1428
1499
|
.setValueAtTime(value, now);
|
|
1429
1500
|
}
|
|
1430
1501
|
else {
|
|
1431
|
-
note.
|
|
1502
|
+
note.chorusEffectsSend.disconnect();
|
|
1432
1503
|
}
|
|
1433
1504
|
}
|
|
1434
1505
|
else {
|
|
1435
|
-
if (0 < note.voiceParams.
|
|
1436
|
-
if (!note.
|
|
1437
|
-
note.
|
|
1438
|
-
gain: note.voiceParams.
|
|
1506
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1507
|
+
if (!note.chorusEffectsSend) {
|
|
1508
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1509
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1439
1510
|
});
|
|
1440
|
-
note.volumeNode.connect(note.
|
|
1511
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1441
1512
|
}
|
|
1442
|
-
note.
|
|
1513
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1443
1514
|
}
|
|
1444
1515
|
}
|
|
1445
1516
|
}
|
|
@@ -1472,18 +1543,20 @@ class Midy {
|
|
|
1472
1543
|
}
|
|
1473
1544
|
},
|
|
1474
1545
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1475
|
-
if (0 < channel.state.modulationDepth)
|
|
1476
|
-
this.setModLfoToFilterFc(note);
|
|
1546
|
+
if (0 < channel.state.modulationDepth) {
|
|
1547
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1548
|
+
}
|
|
1477
1549
|
},
|
|
1478
1550
|
modLfoToVolume: (channel, note) => {
|
|
1479
|
-
if (0 < channel.state.modulationDepth)
|
|
1480
|
-
this.setModLfoToVolume(note);
|
|
1551
|
+
if (0 < channel.state.modulationDepth) {
|
|
1552
|
+
this.setModLfoToVolume(channel, note);
|
|
1553
|
+
}
|
|
1481
1554
|
},
|
|
1482
|
-
chorusEffectsSend: (
|
|
1483
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1555
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1556
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1484
1557
|
},
|
|
1485
|
-
reverbEffectsSend: (
|
|
1486
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1558
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1559
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1487
1560
|
},
|
|
1488
1561
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1489
1562
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1650,10 +1723,27 @@ class Midy {
|
|
|
1650
1723
|
const factor = 5 * Math.log(10) / 127;
|
|
1651
1724
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1652
1725
|
}
|
|
1726
|
+
setKeyBasedVolume(channel) {
|
|
1727
|
+
const now = this.audioContext.currentTime;
|
|
1728
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1729
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1730
|
+
const note = noteList[i];
|
|
1731
|
+
if (!note)
|
|
1732
|
+
continue;
|
|
1733
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1734
|
+
if (keyBasedValue === 0)
|
|
1735
|
+
continue;
|
|
1736
|
+
note.volumeNode.gain
|
|
1737
|
+
.cancelScheduledValues(now)
|
|
1738
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1739
|
+
}
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1653
1742
|
setVolume(channelNumber, volume) {
|
|
1654
1743
|
const channel = this.channels[channelNumber];
|
|
1655
1744
|
channel.state.volume = volume / 127;
|
|
1656
1745
|
this.updateChannelVolume(channel);
|
|
1746
|
+
this.setKeyBasedVolume(channel);
|
|
1657
1747
|
}
|
|
1658
1748
|
panToGain(pan) {
|
|
1659
1749
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1662,10 +1752,31 @@ class Midy {
|
|
|
1662
1752
|
gainRight: Math.sin(theta),
|
|
1663
1753
|
};
|
|
1664
1754
|
}
|
|
1755
|
+
setKeyBasedPan(channel) {
|
|
1756
|
+
const now = this.audioContext.currentTime;
|
|
1757
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1758
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1759
|
+
const note = noteList[i];
|
|
1760
|
+
if (!note)
|
|
1761
|
+
continue;
|
|
1762
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1763
|
+
if (keyBasedValue === 0)
|
|
1764
|
+
continue;
|
|
1765
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1766
|
+
note.gainL.gain
|
|
1767
|
+
.cancelScheduledValues(now)
|
|
1768
|
+
.setValueAtTime(gainLeft, now);
|
|
1769
|
+
note.gainR.gain
|
|
1770
|
+
.cancelScheduledValues(now)
|
|
1771
|
+
.setValueAtTime(gainRight, now);
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1665
1775
|
setPan(channelNumber, pan) {
|
|
1666
1776
|
const channel = this.channels[channelNumber];
|
|
1667
1777
|
channel.state.pan = pan / 127;
|
|
1668
1778
|
this.updateChannelVolume(channel);
|
|
1779
|
+
this.setKeyBasedPan(channel);
|
|
1669
1780
|
}
|
|
1670
1781
|
setExpression(channelNumber, expression) {
|
|
1671
1782
|
const channel = this.channels[channelNumber];
|
|
@@ -1827,7 +1938,7 @@ class Midy {
|
|
|
1827
1938
|
const note = noteList[i];
|
|
1828
1939
|
if (!note)
|
|
1829
1940
|
continue;
|
|
1830
|
-
this.setReverbEffectsSend(note, 0);
|
|
1941
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1831
1942
|
}
|
|
1832
1943
|
});
|
|
1833
1944
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1868,7 +1979,7 @@ class Midy {
|
|
|
1868
1979
|
const note = noteList[i];
|
|
1869
1980
|
if (!note)
|
|
1870
1981
|
continue;
|
|
1871
|
-
this.setChorusEffectsSend(note, 0);
|
|
1982
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1872
1983
|
}
|
|
1873
1984
|
});
|
|
1874
1985
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -2051,7 +2162,7 @@ class Midy {
|
|
|
2051
2162
|
switch (data[3]) {
|
|
2052
2163
|
case 8:
|
|
2053
2164
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2054
|
-
return this.
|
|
2165
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2055
2166
|
default:
|
|
2056
2167
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2057
2168
|
}
|
|
@@ -2104,7 +2215,7 @@ class Midy {
|
|
|
2104
2215
|
return this.handleMasterFineTuningSysEx(data);
|
|
2105
2216
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
2106
2217
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
2107
|
-
case 5:
|
|
2218
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
2108
2219
|
return this.handleGlobalParameterControlSysEx(data);
|
|
2109
2220
|
default:
|
|
2110
2221
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2112,19 +2223,17 @@ class Midy {
|
|
|
2112
2223
|
break;
|
|
2113
2224
|
case 8:
|
|
2114
2225
|
switch (data[3]) {
|
|
2115
|
-
case 8:
|
|
2116
|
-
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2226
|
+
case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
2117
2227
|
// TODO: realtime
|
|
2118
|
-
return this.
|
|
2228
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
2119
2229
|
default:
|
|
2120
2230
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2121
2231
|
}
|
|
2122
2232
|
break;
|
|
2123
2233
|
case 9:
|
|
2124
2234
|
switch (data[3]) {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
// return this.setChannelPressure();
|
|
2235
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2236
|
+
return this.handleChannelPressureSysEx(data);
|
|
2128
2237
|
// case 3:
|
|
2129
2238
|
// // TODO
|
|
2130
2239
|
// return this.setControlChange();
|
|
@@ -2134,9 +2243,8 @@ class Midy {
|
|
|
2134
2243
|
break;
|
|
2135
2244
|
case 10:
|
|
2136
2245
|
switch (data[3]) {
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2246
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2247
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2140
2248
|
default:
|
|
2141
2249
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2142
2250
|
}
|
|
@@ -2181,40 +2289,6 @@ class Midy {
|
|
|
2181
2289
|
channel.detune += next - prev;
|
|
2182
2290
|
this.updateDetune(channel);
|
|
2183
2291
|
}
|
|
2184
|
-
getChannelBitmap(data) {
|
|
2185
|
-
const bitmap = new Array(16).fill(false);
|
|
2186
|
-
const ff = data[4] & 0b11;
|
|
2187
|
-
const gg = data[5] & 0x7F;
|
|
2188
|
-
const hh = data[6] & 0x7F;
|
|
2189
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2190
|
-
if (hh & (1 << bit))
|
|
2191
|
-
bitmap[bit] = true;
|
|
2192
|
-
}
|
|
2193
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2194
|
-
if (gg & (1 << bit))
|
|
2195
|
-
bitmap[bit + 7] = true;
|
|
2196
|
-
}
|
|
2197
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2198
|
-
if (ff & (1 << bit))
|
|
2199
|
-
bitmap[bit + 14] = true;
|
|
2200
|
-
}
|
|
2201
|
-
return bitmap;
|
|
2202
|
-
}
|
|
2203
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2204
|
-
if (data.length < 18) {
|
|
2205
|
-
console.error("Data length is too short");
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2209
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2210
|
-
if (!channelBitmap[i])
|
|
2211
|
-
continue;
|
|
2212
|
-
for (let j = 0; j < 12; j++) {
|
|
2213
|
-
const value = data[j + 7] - 64; // cent
|
|
2214
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
2292
|
handleGlobalParameterControlSysEx(data) {
|
|
2219
2293
|
if (data[7] === 1) {
|
|
2220
2294
|
switch (data[8]) {
|
|
@@ -2401,6 +2475,66 @@ class Midy {
|
|
|
2401
2475
|
getChorusSendToReverb(value) {
|
|
2402
2476
|
return value * 0.00787;
|
|
2403
2477
|
}
|
|
2478
|
+
getChannelBitmap(data) {
|
|
2479
|
+
const bitmap = new Array(16).fill(false);
|
|
2480
|
+
const ff = data[4] & 0b11;
|
|
2481
|
+
const gg = data[5] & 0x7F;
|
|
2482
|
+
const hh = data[6] & 0x7F;
|
|
2483
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2484
|
+
if (hh & (1 << bit))
|
|
2485
|
+
bitmap[bit] = true;
|
|
2486
|
+
}
|
|
2487
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2488
|
+
if (gg & (1 << bit))
|
|
2489
|
+
bitmap[bit + 7] = true;
|
|
2490
|
+
}
|
|
2491
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2492
|
+
if (ff & (1 << bit))
|
|
2493
|
+
bitmap[bit + 14] = true;
|
|
2494
|
+
}
|
|
2495
|
+
return bitmap;
|
|
2496
|
+
}
|
|
2497
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2498
|
+
if (data.length < 18) {
|
|
2499
|
+
console.error("Data length is too short");
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2503
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2504
|
+
if (!channelBitmap[i])
|
|
2505
|
+
continue;
|
|
2506
|
+
for (let j = 0; j < 12; j++) {
|
|
2507
|
+
const value = data[j + 7] - 64; // cent
|
|
2508
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
handleChannelPressureSysEx(data) {
|
|
2513
|
+
const channelNumber = data[4];
|
|
2514
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2515
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2516
|
+
const pp = data[i];
|
|
2517
|
+
const rr = data[i + 1];
|
|
2518
|
+
table[pp] = rr;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2522
|
+
const index = keyNumber * 128 + controllerType;
|
|
2523
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2524
|
+
return (controlValue + 64) / 64;
|
|
2525
|
+
}
|
|
2526
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2527
|
+
const channelNumber = data[4];
|
|
2528
|
+
const keyNumber = data[5];
|
|
2529
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2530
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2531
|
+
const controllerType = data[i];
|
|
2532
|
+
const value = data[i + 1];
|
|
2533
|
+
const index = keyNumber * 128 + controllerType;
|
|
2534
|
+
table[index] = value - 64;
|
|
2535
|
+
}
|
|
2536
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2537
|
+
}
|
|
2404
2538
|
handleExclusiveMessage(data) {
|
|
2405
2539
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2406
2540
|
}
|
|
@@ -2435,6 +2569,8 @@ Object.defineProperty(Midy, "channelSettings", {
|
|
|
2435
2569
|
currentBufferSource: null,
|
|
2436
2570
|
detune: 0,
|
|
2437
2571
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2572
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2573
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2438
2574
|
program: 0,
|
|
2439
2575
|
bank: 121 * 128,
|
|
2440
2576
|
bankMSB: 121,
|