@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-GM2.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,
|
|
@@ -910,15 +928,16 @@ class MidyGM2 {
|
|
|
910
928
|
centToHz(cent) {
|
|
911
929
|
return 8.176 * this.centToRate(cent);
|
|
912
930
|
}
|
|
913
|
-
|
|
931
|
+
calcChannelDetune(channel) {
|
|
914
932
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
915
933
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
916
|
-
const
|
|
917
|
-
const tuning = masterTuning + channelTuning + scaleOctaveTuning;
|
|
934
|
+
const tuning = masterTuning + channelTuning;
|
|
918
935
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
919
936
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
920
937
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
921
|
-
|
|
938
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
939
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
940
|
+
return tuning + pitch + pressure;
|
|
922
941
|
}
|
|
923
942
|
calcNoteDetune(channel, note) {
|
|
924
943
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -938,28 +957,37 @@ class MidyGM2 {
|
|
|
938
957
|
}
|
|
939
958
|
});
|
|
940
959
|
}
|
|
960
|
+
getPortamentoTime(channel) {
|
|
961
|
+
const factor = 5 * Math.log(10) / 127;
|
|
962
|
+
const time = channel.state.portamentoTime;
|
|
963
|
+
return Math.log(time) / factor;
|
|
964
|
+
}
|
|
941
965
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
942
966
|
const now = this.audioContext.currentTime;
|
|
943
967
|
const { voiceParams, startTime } = note;
|
|
944
968
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
945
969
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
946
970
|
const volDelay = startTime + voiceParams.volDelay;
|
|
947
|
-
const portamentoTime = volDelay + channel
|
|
948
|
-
note.
|
|
971
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
972
|
+
note.volumeEnvelopeNode.gain
|
|
949
973
|
.cancelScheduledValues(now)
|
|
950
974
|
.setValueAtTime(0, volDelay)
|
|
951
975
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
952
976
|
}
|
|
953
|
-
setVolumeEnvelope(note) {
|
|
977
|
+
setVolumeEnvelope(channel, note) {
|
|
954
978
|
const now = this.audioContext.currentTime;
|
|
979
|
+
const state = channel.state;
|
|
955
980
|
const { voiceParams, startTime } = note;
|
|
956
|
-
const
|
|
981
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
982
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
983
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
984
|
+
pressure;
|
|
957
985
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
958
986
|
const volDelay = startTime + voiceParams.volDelay;
|
|
959
987
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
960
988
|
const volHold = volAttack + voiceParams.volHold;
|
|
961
989
|
const volDecay = volHold + voiceParams.volDecay;
|
|
962
|
-
note.
|
|
990
|
+
note.volumeEnvelopeNode.gain
|
|
963
991
|
.cancelScheduledValues(now)
|
|
964
992
|
.setValueAtTime(0, startTime)
|
|
965
993
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -1001,14 +1029,16 @@ class MidyGM2 {
|
|
|
1001
1029
|
const { voiceParams, noteNumber, startTime } = note;
|
|
1002
1030
|
const softPedalFactor = 1 -
|
|
1003
1031
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1004
|
-
const
|
|
1005
|
-
|
|
1032
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1033
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1034
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1035
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1006
1036
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1007
1037
|
const sustainFreq = baseFreq +
|
|
1008
1038
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1009
1039
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1010
1040
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1011
|
-
const portamentoTime = startTime + channel
|
|
1041
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1012
1042
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1013
1043
|
note.filterNode.frequency
|
|
1014
1044
|
.cancelScheduledValues(now)
|
|
@@ -1053,14 +1083,14 @@ class MidyGM2 {
|
|
|
1053
1083
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1054
1084
|
this.setModLfoToPitch(channel, note);
|
|
1055
1085
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1056
|
-
this.setModLfoToVolume(note);
|
|
1086
|
+
this.setModLfoToVolume(channel, note);
|
|
1057
1087
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1058
1088
|
note.modulationLFO.connect(note.filterDepth);
|
|
1059
1089
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1060
1090
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1061
1091
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1062
1092
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1063
|
-
note.volumeDepth.connect(note.
|
|
1093
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1064
1094
|
}
|
|
1065
1095
|
startVibrato(channel, note, startTime) {
|
|
1066
1096
|
const { voiceParams } = note;
|
|
@@ -1082,6 +1112,9 @@ class MidyGM2 {
|
|
|
1082
1112
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1083
1113
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1084
1114
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1115
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1116
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1117
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1085
1118
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1086
1119
|
type: "lowpass",
|
|
1087
1120
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
@@ -1108,7 +1141,10 @@ class MidyGM2 {
|
|
|
1108
1141
|
channel.currentBufferSource = note.bufferSource;
|
|
1109
1142
|
}
|
|
1110
1143
|
note.bufferSource.connect(note.filterNode);
|
|
1111
|
-
note.filterNode.connect(note.
|
|
1144
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1145
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1146
|
+
note.volumeNode.connect(note.gainL);
|
|
1147
|
+
note.volumeNode.connect(note.gainR);
|
|
1112
1148
|
if (0 < channel.chorusSendLevel) {
|
|
1113
1149
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1114
1150
|
}
|
|
@@ -1139,8 +1175,8 @@ class MidyGM2 {
|
|
|
1139
1175
|
if (!voice)
|
|
1140
1176
|
return;
|
|
1141
1177
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1142
|
-
note.
|
|
1143
|
-
note.
|
|
1178
|
+
note.gainL.connect(channel.gainL);
|
|
1179
|
+
note.gainR.connect(channel.gainR);
|
|
1144
1180
|
if (channel.state.sostenutoPedal) {
|
|
1145
1181
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1146
1182
|
}
|
|
@@ -1171,7 +1207,7 @@ class MidyGM2 {
|
|
|
1171
1207
|
}
|
|
1172
1208
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1173
1209
|
const note = scheduledNotes[index];
|
|
1174
|
-
note.
|
|
1210
|
+
note.volumeEnvelopeNode.gain
|
|
1175
1211
|
.cancelScheduledValues(endTime)
|
|
1176
1212
|
.linearRampToValueAtTime(0, stopTime);
|
|
1177
1213
|
note.ending = true;
|
|
@@ -1182,8 +1218,11 @@ class MidyGM2 {
|
|
|
1182
1218
|
note.bufferSource.onended = () => {
|
|
1183
1219
|
scheduledNotes[index] = null;
|
|
1184
1220
|
note.bufferSource.disconnect();
|
|
1185
|
-
note.volumeNode.disconnect();
|
|
1186
1221
|
note.filterNode.disconnect();
|
|
1222
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1223
|
+
note.volumeNode.disconnect();
|
|
1224
|
+
note.gainL.disconnect();
|
|
1225
|
+
note.gainR.disconnect();
|
|
1187
1226
|
if (note.modulationDepth) {
|
|
1188
1227
|
note.volumeDepth.disconnect();
|
|
1189
1228
|
note.modulationDepth.disconnect();
|
|
@@ -1232,7 +1271,7 @@ class MidyGM2 {
|
|
|
1232
1271
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1233
1272
|
}
|
|
1234
1273
|
else {
|
|
1235
|
-
const portamentoTime = endTime +
|
|
1274
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1236
1275
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1237
1276
|
const baseRate = note.voiceParams.playbackRate;
|
|
1238
1277
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1302,22 +1341,45 @@ class MidyGM2 {
|
|
|
1302
1341
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1303
1342
|
channel.program = program;
|
|
1304
1343
|
}
|
|
1305
|
-
handleChannelPressure(channelNumber,
|
|
1306
|
-
const now = this.audioContext.currentTime;
|
|
1344
|
+
handleChannelPressure(channelNumber, value) {
|
|
1307
1345
|
const channel = this.channels[channelNumber];
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (channel.
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
activeNote.volumeNode.gain
|
|
1315
|
-
.cancelScheduledValues(now)
|
|
1316
|
-
.setValueAtTime(gain * pressure, now);
|
|
1317
|
-
});
|
|
1346
|
+
const prev = channel.state.channelPressure;
|
|
1347
|
+
const next = value / 127;
|
|
1348
|
+
channel.state.channelPressure = next;
|
|
1349
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1350
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1351
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1318
1352
|
}
|
|
1353
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1354
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1355
|
+
const note = noteList[i];
|
|
1356
|
+
if (!note)
|
|
1357
|
+
continue;
|
|
1358
|
+
this.setChannelPressure(channel, note);
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1319
1361
|
// this.applyVoiceParams(channel, 13);
|
|
1320
1362
|
}
|
|
1363
|
+
setChannelPressure(channel, note) {
|
|
1364
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1365
|
+
this.updateDetune(channel);
|
|
1366
|
+
}
|
|
1367
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1368
|
+
this.setFilterEnvelope(channel, note);
|
|
1369
|
+
}
|
|
1370
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1371
|
+
this.setVolumeEnvelope(channel, note);
|
|
1372
|
+
}
|
|
1373
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1374
|
+
this.setModLfoToPitch(channel, note);
|
|
1375
|
+
}
|
|
1376
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1377
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1378
|
+
}
|
|
1379
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1380
|
+
this.setModLfoToVolume(channel, note);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1321
1383
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1322
1384
|
const pitchBend = msb * 128 + lsb;
|
|
1323
1385
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1334,13 +1396,15 @@ class MidyGM2 {
|
|
|
1334
1396
|
}
|
|
1335
1397
|
setModLfoToPitch(channel, note) {
|
|
1336
1398
|
const now = this.audioContext.currentTime;
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1399
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1400
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1401
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1402
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1339
1403
|
channel.state.modulationDepth;
|
|
1340
|
-
const
|
|
1404
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1341
1405
|
note.modulationDepth.gain
|
|
1342
1406
|
.cancelScheduledValues(now)
|
|
1343
|
-
.setValueAtTime(modulationDepth
|
|
1407
|
+
.setValueAtTime(modulationDepth, now);
|
|
1344
1408
|
}
|
|
1345
1409
|
setVibLfoToPitch(channel, note) {
|
|
1346
1410
|
const now = this.audioContext.currentTime;
|
|
@@ -1352,69 +1416,75 @@ class MidyGM2 {
|
|
|
1352
1416
|
.cancelScheduledValues(now)
|
|
1353
1417
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1354
1418
|
}
|
|
1355
|
-
setModLfoToFilterFc(note) {
|
|
1419
|
+
setModLfoToFilterFc(channel, note) {
|
|
1356
1420
|
const now = this.audioContext.currentTime;
|
|
1357
|
-
const
|
|
1421
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1422
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1423
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1358
1424
|
note.filterDepth.gain
|
|
1359
1425
|
.cancelScheduledValues(now)
|
|
1360
1426
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1361
1427
|
}
|
|
1362
|
-
setModLfoToVolume(note) {
|
|
1428
|
+
setModLfoToVolume(channel, note) {
|
|
1363
1429
|
const now = this.audioContext.currentTime;
|
|
1364
1430
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1431
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1432
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1433
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1434
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1367
1435
|
note.volumeDepth.gain
|
|
1368
1436
|
.cancelScheduledValues(now)
|
|
1369
|
-
.setValueAtTime(volumeDepth
|
|
1437
|
+
.setValueAtTime(volumeDepth, now);
|
|
1370
1438
|
}
|
|
1371
|
-
|
|
1439
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1372
1440
|
if (0 < prevValue) {
|
|
1373
|
-
if (0 < note.voiceParams.
|
|
1441
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1374
1442
|
const now = this.audioContext.currentTime;
|
|
1375
|
-
const
|
|
1376
|
-
note.
|
|
1443
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1444
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1445
|
+
note.reverbEffectsSend.gain
|
|
1377
1446
|
.cancelScheduledValues(now)
|
|
1378
1447
|
.setValueAtTime(value, now);
|
|
1379
1448
|
}
|
|
1380
1449
|
else {
|
|
1381
|
-
note.
|
|
1450
|
+
note.reverbEffectsSend.disconnect();
|
|
1382
1451
|
}
|
|
1383
1452
|
}
|
|
1384
1453
|
else {
|
|
1385
|
-
if (0 < note.voiceParams.
|
|
1386
|
-
if (!note.
|
|
1387
|
-
note.
|
|
1388
|
-
gain: note.voiceParams.
|
|
1454
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1455
|
+
if (!note.reverbEffectsSend) {
|
|
1456
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1457
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1389
1458
|
});
|
|
1390
|
-
note.volumeNode.connect(note.
|
|
1459
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1391
1460
|
}
|
|
1392
|
-
note.
|
|
1461
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1393
1462
|
}
|
|
1394
1463
|
}
|
|
1395
1464
|
}
|
|
1396
|
-
|
|
1465
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1397
1466
|
if (0 < prevValue) {
|
|
1398
|
-
if (0 < note.voiceParams.
|
|
1467
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1399
1468
|
const now = this.audioContext.currentTime;
|
|
1400
|
-
const
|
|
1401
|
-
note.
|
|
1469
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1470
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1471
|
+
note.chorusEffectsSend.gain
|
|
1402
1472
|
.cancelScheduledValues(now)
|
|
1403
1473
|
.setValueAtTime(value, now);
|
|
1404
1474
|
}
|
|
1405
1475
|
else {
|
|
1406
|
-
note.
|
|
1476
|
+
note.chorusEffectsSend.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.chorusEffectsSend) {
|
|
1481
|
+
if (!note.chorusEffectsSend) {
|
|
1482
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1483
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1414
1484
|
});
|
|
1415
|
-
note.volumeNode.connect(note.
|
|
1485
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1416
1486
|
}
|
|
1417
|
-
note.
|
|
1487
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1418
1488
|
}
|
|
1419
1489
|
}
|
|
1420
1490
|
}
|
|
@@ -1447,18 +1517,20 @@ class MidyGM2 {
|
|
|
1447
1517
|
}
|
|
1448
1518
|
},
|
|
1449
1519
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1450
|
-
if (0 < channel.state.modulationDepth)
|
|
1451
|
-
this.setModLfoToFilterFc(note);
|
|
1520
|
+
if (0 < channel.state.modulationDepth) {
|
|
1521
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1522
|
+
}
|
|
1452
1523
|
},
|
|
1453
1524
|
modLfoToVolume: (channel, note) => {
|
|
1454
|
-
if (0 < channel.state.modulationDepth)
|
|
1455
|
-
this.setModLfoToVolume(note);
|
|
1525
|
+
if (0 < channel.state.modulationDepth) {
|
|
1526
|
+
this.setModLfoToVolume(channel, note);
|
|
1527
|
+
}
|
|
1456
1528
|
},
|
|
1457
|
-
chorusEffectsSend: (
|
|
1458
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1529
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1530
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1459
1531
|
},
|
|
1460
|
-
reverbEffectsSend: (
|
|
1461
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1532
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1533
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1462
1534
|
},
|
|
1463
1535
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1464
1536
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1615,10 +1687,27 @@ class MidyGM2 {
|
|
|
1615
1687
|
const factor = 5 * Math.log(10) / 127;
|
|
1616
1688
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1617
1689
|
}
|
|
1690
|
+
setKeyBasedVolume(channel) {
|
|
1691
|
+
const now = this.audioContext.currentTime;
|
|
1692
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1693
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1694
|
+
const note = noteList[i];
|
|
1695
|
+
if (!note)
|
|
1696
|
+
continue;
|
|
1697
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1698
|
+
if (keyBasedValue === 0)
|
|
1699
|
+
continue;
|
|
1700
|
+
note.volumeNode.gain
|
|
1701
|
+
.cancelScheduledValues(now)
|
|
1702
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1618
1706
|
setVolume(channelNumber, volume) {
|
|
1619
1707
|
const channel = this.channels[channelNumber];
|
|
1620
1708
|
channel.state.volume = volume / 127;
|
|
1621
1709
|
this.updateChannelVolume(channel);
|
|
1710
|
+
this.setKeyBasedVolume(channel);
|
|
1622
1711
|
}
|
|
1623
1712
|
panToGain(pan) {
|
|
1624
1713
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1627,10 +1716,31 @@ class MidyGM2 {
|
|
|
1627
1716
|
gainRight: Math.sin(theta),
|
|
1628
1717
|
};
|
|
1629
1718
|
}
|
|
1719
|
+
setKeyBasedPan(channel) {
|
|
1720
|
+
const now = this.audioContext.currentTime;
|
|
1721
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1722
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1723
|
+
const note = noteList[i];
|
|
1724
|
+
if (!note)
|
|
1725
|
+
continue;
|
|
1726
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1727
|
+
if (keyBasedValue === 0)
|
|
1728
|
+
continue;
|
|
1729
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1730
|
+
note.gainL.gain
|
|
1731
|
+
.cancelScheduledValues(now)
|
|
1732
|
+
.setValueAtTime(gainLeft, now);
|
|
1733
|
+
note.gainR.gain
|
|
1734
|
+
.cancelScheduledValues(now)
|
|
1735
|
+
.setValueAtTime(gainRight, now);
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1630
1739
|
setPan(channelNumber, pan) {
|
|
1631
1740
|
const channel = this.channels[channelNumber];
|
|
1632
1741
|
channel.state.pan = pan / 127;
|
|
1633
1742
|
this.updateChannelVolume(channel);
|
|
1743
|
+
this.setKeyBasedPan(channel);
|
|
1634
1744
|
}
|
|
1635
1745
|
setExpression(channelNumber, expression) {
|
|
1636
1746
|
const channel = this.channels[channelNumber];
|
|
@@ -1665,6 +1775,22 @@ class MidyGM2 {
|
|
|
1665
1775
|
setPortamento(channelNumber, value) {
|
|
1666
1776
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1667
1777
|
}
|
|
1778
|
+
setSostenutoPedal(channelNumber, value) {
|
|
1779
|
+
const channel = this.channels[channelNumber];
|
|
1780
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1781
|
+
if (64 <= value) {
|
|
1782
|
+
const now = this.audioContext.currentTime;
|
|
1783
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1784
|
+
channel.sostenutoNotes = new Map(activeNotes);
|
|
1785
|
+
}
|
|
1786
|
+
else {
|
|
1787
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
setSoftPedal(channelNumber, softPedal) {
|
|
1791
|
+
const channel = this.channels[channelNumber];
|
|
1792
|
+
channel.state.softPedal = softPedal / 127;
|
|
1793
|
+
}
|
|
1668
1794
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1669
1795
|
const channel = this.channels[channelNumber];
|
|
1670
1796
|
const state = channel.state;
|
|
@@ -1697,7 +1823,7 @@ class MidyGM2 {
|
|
|
1697
1823
|
const note = noteList[i];
|
|
1698
1824
|
if (!note)
|
|
1699
1825
|
continue;
|
|
1700
|
-
this.setReverbEffectsSend(note, 0);
|
|
1826
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1701
1827
|
}
|
|
1702
1828
|
});
|
|
1703
1829
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1738,7 +1864,7 @@ class MidyGM2 {
|
|
|
1738
1864
|
const note = noteList[i];
|
|
1739
1865
|
if (!note)
|
|
1740
1866
|
continue;
|
|
1741
|
-
this.setChorusEffectsSend(note, 0);
|
|
1867
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1742
1868
|
}
|
|
1743
1869
|
});
|
|
1744
1870
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1747,22 +1873,6 @@ class MidyGM2 {
|
|
|
1747
1873
|
}
|
|
1748
1874
|
}
|
|
1749
1875
|
}
|
|
1750
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1751
|
-
const channel = this.channels[channelNumber];
|
|
1752
|
-
channel.state.sostenutoPedal = value / 127;
|
|
1753
|
-
if (64 <= value) {
|
|
1754
|
-
const now = this.audioContext.currentTime;
|
|
1755
|
-
const activeNotes = this.getActiveNotes(channel, now);
|
|
1756
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1757
|
-
}
|
|
1758
|
-
else {
|
|
1759
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1763
|
-
const channel = this.channels[channelNumber];
|
|
1764
|
-
channel.state.softPedal = softPedal / 127;
|
|
1765
|
-
}
|
|
1766
1876
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1767
1877
|
if (maxLSB < channel.dataLSB) {
|
|
1768
1878
|
channel.dataMSB++;
|
|
@@ -1925,7 +2035,7 @@ class MidyGM2 {
|
|
|
1925
2035
|
switch (data[3]) {
|
|
1926
2036
|
case 8:
|
|
1927
2037
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
1928
|
-
return this.
|
|
2038
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
1929
2039
|
default:
|
|
1930
2040
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1931
2041
|
}
|
|
@@ -1978,7 +2088,7 @@ class MidyGM2 {
|
|
|
1978
2088
|
return this.handleMasterFineTuningSysEx(data);
|
|
1979
2089
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1980
2090
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1981
|
-
case 5:
|
|
2091
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
1982
2092
|
return this.handleGlobalParameterControlSysEx(data);
|
|
1983
2093
|
default:
|
|
1984
2094
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -1986,9 +2096,8 @@ class MidyGM2 {
|
|
|
1986
2096
|
break;
|
|
1987
2097
|
case 9:
|
|
1988
2098
|
switch (data[3]) {
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
// return this.setChannelPressure();
|
|
2099
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2100
|
+
return this.handleChannelPressureSysEx(data);
|
|
1992
2101
|
// case 3:
|
|
1993
2102
|
// // TODO
|
|
1994
2103
|
// return this.setControlChange();
|
|
@@ -1998,9 +2107,8 @@ class MidyGM2 {
|
|
|
1998
2107
|
break;
|
|
1999
2108
|
case 10:
|
|
2000
2109
|
switch (data[3]) {
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2110
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2111
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2004
2112
|
default:
|
|
2005
2113
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2006
2114
|
}
|
|
@@ -2045,40 +2153,6 @@ class MidyGM2 {
|
|
|
2045
2153
|
channel.detune += next - prev;
|
|
2046
2154
|
this.updateDetune(channel);
|
|
2047
2155
|
}
|
|
2048
|
-
getChannelBitmap(data) {
|
|
2049
|
-
const bitmap = new Array(16).fill(false);
|
|
2050
|
-
const ff = data[4] & 0b11;
|
|
2051
|
-
const gg = data[5] & 0x7F;
|
|
2052
|
-
const hh = data[6] & 0x7F;
|
|
2053
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2054
|
-
if (hh & (1 << bit))
|
|
2055
|
-
bitmap[bit] = true;
|
|
2056
|
-
}
|
|
2057
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2058
|
-
if (gg & (1 << bit))
|
|
2059
|
-
bitmap[bit + 7] = true;
|
|
2060
|
-
}
|
|
2061
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2062
|
-
if (ff & (1 << bit))
|
|
2063
|
-
bitmap[bit + 14] = true;
|
|
2064
|
-
}
|
|
2065
|
-
return bitmap;
|
|
2066
|
-
}
|
|
2067
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2068
|
-
if (data.length < 18) {
|
|
2069
|
-
console.error("Data length is too short");
|
|
2070
|
-
return;
|
|
2071
|
-
}
|
|
2072
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2073
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2074
|
-
if (!channelBitmap[i])
|
|
2075
|
-
continue;
|
|
2076
|
-
for (let j = 0; j < 12; j++) {
|
|
2077
|
-
const value = data[j + 7] - 64; // cent
|
|
2078
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
2156
|
handleGlobalParameterControlSysEx(data) {
|
|
2083
2157
|
if (data[7] === 1) {
|
|
2084
2158
|
switch (data[8]) {
|
|
@@ -2265,6 +2339,66 @@ class MidyGM2 {
|
|
|
2265
2339
|
getChorusSendToReverb(value) {
|
|
2266
2340
|
return value * 0.00787;
|
|
2267
2341
|
}
|
|
2342
|
+
getChannelBitmap(data) {
|
|
2343
|
+
const bitmap = new Array(16).fill(false);
|
|
2344
|
+
const ff = data[4] & 0b11;
|
|
2345
|
+
const gg = data[5] & 0x7F;
|
|
2346
|
+
const hh = data[6] & 0x7F;
|
|
2347
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2348
|
+
if (hh & (1 << bit))
|
|
2349
|
+
bitmap[bit] = true;
|
|
2350
|
+
}
|
|
2351
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2352
|
+
if (gg & (1 << bit))
|
|
2353
|
+
bitmap[bit + 7] = true;
|
|
2354
|
+
}
|
|
2355
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2356
|
+
if (ff & (1 << bit))
|
|
2357
|
+
bitmap[bit + 14] = true;
|
|
2358
|
+
}
|
|
2359
|
+
return bitmap;
|
|
2360
|
+
}
|
|
2361
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2362
|
+
if (data.length < 18) {
|
|
2363
|
+
console.error("Data length is too short");
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2367
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2368
|
+
if (!channelBitmap[i])
|
|
2369
|
+
continue;
|
|
2370
|
+
for (let j = 0; j < 12; j++) {
|
|
2371
|
+
const value = data[j + 7] - 64; // cent
|
|
2372
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
handleChannelPressureSysEx(data) {
|
|
2377
|
+
const channelNumber = data[4];
|
|
2378
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2379
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2380
|
+
const pp = data[i];
|
|
2381
|
+
const rr = data[i + 1];
|
|
2382
|
+
table[pp] = rr;
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2386
|
+
const index = keyNumber * 128 + controllerType;
|
|
2387
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2388
|
+
return (controlValue + 64) / 64;
|
|
2389
|
+
}
|
|
2390
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2391
|
+
const channelNumber = data[4];
|
|
2392
|
+
const keyNumber = data[5];
|
|
2393
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2394
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2395
|
+
const controllerType = data[i];
|
|
2396
|
+
const value = data[i + 1];
|
|
2397
|
+
const index = keyNumber * 128 + controllerType;
|
|
2398
|
+
table[index] = value - 64;
|
|
2399
|
+
}
|
|
2400
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2401
|
+
}
|
|
2268
2402
|
handleExclusiveMessage(data) {
|
|
2269
2403
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2270
2404
|
}
|
|
@@ -2299,6 +2433,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2299
2433
|
currentBufferSource: null,
|
|
2300
2434
|
detune: 0,
|
|
2301
2435
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2436
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2437
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2302
2438
|
program: 0,
|
|
2303
2439
|
bank: 121 * 128,
|
|
2304
2440
|
bankMSB: 121,
|