@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/esm/midy-GM2.js
CHANGED
|
@@ -14,12 +14,30 @@ class Note {
|
|
|
14
14
|
writable: true,
|
|
15
15
|
value: void 0
|
|
16
16
|
});
|
|
17
|
+
Object.defineProperty(this, "volumeEnvelopeNode", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
17
23
|
Object.defineProperty(this, "volumeNode", {
|
|
18
24
|
enumerable: true,
|
|
19
25
|
configurable: true,
|
|
20
26
|
writable: true,
|
|
21
27
|
value: void 0
|
|
22
28
|
});
|
|
29
|
+
Object.defineProperty(this, "gainL", {
|
|
30
|
+
enumerable: true,
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true,
|
|
33
|
+
value: void 0
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(this, "gainR", {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
writable: true,
|
|
39
|
+
value: void 0
|
|
40
|
+
});
|
|
23
41
|
Object.defineProperty(this, "volumeDepth", {
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
@@ -907,15 +925,16 @@ export class MidyGM2 {
|
|
|
907
925
|
centToHz(cent) {
|
|
908
926
|
return 8.176 * this.centToRate(cent);
|
|
909
927
|
}
|
|
910
|
-
|
|
928
|
+
calcChannelDetune(channel) {
|
|
911
929
|
const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
|
|
912
930
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
913
|
-
const
|
|
914
|
-
const tuning = masterTuning + channelTuning + scaleOctaveTuning;
|
|
931
|
+
const tuning = masterTuning + channelTuning;
|
|
915
932
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
916
933
|
const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
|
|
917
934
|
const pitch = pitchWheel * pitchWheelSensitivity;
|
|
918
|
-
|
|
935
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
936
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
937
|
+
return tuning + pitch + pressure;
|
|
919
938
|
}
|
|
920
939
|
calcNoteDetune(channel, note) {
|
|
921
940
|
return channel.scaleOctaveTuningTable[note.noteNumber % 12];
|
|
@@ -935,28 +954,37 @@ export class MidyGM2 {
|
|
|
935
954
|
}
|
|
936
955
|
});
|
|
937
956
|
}
|
|
957
|
+
getPortamentoTime(channel) {
|
|
958
|
+
const factor = 5 * Math.log(10) / 127;
|
|
959
|
+
const time = channel.state.portamentoTime;
|
|
960
|
+
return Math.log(time) / factor;
|
|
961
|
+
}
|
|
938
962
|
setPortamentoStartVolumeEnvelope(channel, note) {
|
|
939
963
|
const now = this.audioContext.currentTime;
|
|
940
964
|
const { voiceParams, startTime } = note;
|
|
941
965
|
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
|
|
942
966
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
943
967
|
const volDelay = startTime + voiceParams.volDelay;
|
|
944
|
-
const portamentoTime = volDelay + channel
|
|
945
|
-
note.
|
|
968
|
+
const portamentoTime = volDelay + this.getPortamentoTime(channel);
|
|
969
|
+
note.volumeEnvelopeNode.gain
|
|
946
970
|
.cancelScheduledValues(now)
|
|
947
971
|
.setValueAtTime(0, volDelay)
|
|
948
972
|
.linearRampToValueAtTime(sustainVolume, portamentoTime);
|
|
949
973
|
}
|
|
950
|
-
setVolumeEnvelope(note) {
|
|
974
|
+
setVolumeEnvelope(channel, note) {
|
|
951
975
|
const now = this.audioContext.currentTime;
|
|
976
|
+
const state = channel.state;
|
|
952
977
|
const { voiceParams, startTime } = note;
|
|
953
|
-
const
|
|
978
|
+
const pressureDepth = channel.pressureTable[2] / 64;
|
|
979
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
980
|
+
const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
|
|
981
|
+
pressure;
|
|
954
982
|
const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
|
|
955
983
|
const volDelay = startTime + voiceParams.volDelay;
|
|
956
984
|
const volAttack = volDelay + voiceParams.volAttack;
|
|
957
985
|
const volHold = volAttack + voiceParams.volHold;
|
|
958
986
|
const volDecay = volHold + voiceParams.volDecay;
|
|
959
|
-
note.
|
|
987
|
+
note.volumeEnvelopeNode.gain
|
|
960
988
|
.cancelScheduledValues(now)
|
|
961
989
|
.setValueAtTime(0, startTime)
|
|
962
990
|
.setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
|
|
@@ -998,14 +1026,16 @@ export class MidyGM2 {
|
|
|
998
1026
|
const { voiceParams, noteNumber, startTime } = note;
|
|
999
1027
|
const softPedalFactor = 1 -
|
|
1000
1028
|
(0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1029
|
+
const pressureDepth = (channel.pressureTable[1] - 64) * 15;
|
|
1030
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1031
|
+
const baseCent = voiceParams.initialFilterFc + pressure;
|
|
1032
|
+
const baseFreq = this.centToHz(baseCent) * softPedalFactor;
|
|
1003
1033
|
const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor;
|
|
1004
1034
|
const sustainFreq = baseFreq +
|
|
1005
1035
|
(peekFreq - baseFreq) * (1 - voiceParams.modSustain);
|
|
1006
1036
|
const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
|
|
1007
1037
|
const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
|
|
1008
|
-
const portamentoTime = startTime + channel
|
|
1038
|
+
const portamentoTime = startTime + this.getPortamentoTime(channel);
|
|
1009
1039
|
const modDelay = startTime + voiceParams.modDelay;
|
|
1010
1040
|
note.filterNode.frequency
|
|
1011
1041
|
.cancelScheduledValues(now)
|
|
@@ -1050,14 +1080,14 @@ export class MidyGM2 {
|
|
|
1050
1080
|
note.modulationDepth = new GainNode(this.audioContext);
|
|
1051
1081
|
this.setModLfoToPitch(channel, note);
|
|
1052
1082
|
note.volumeDepth = new GainNode(this.audioContext);
|
|
1053
|
-
this.setModLfoToVolume(note);
|
|
1083
|
+
this.setModLfoToVolume(channel, note);
|
|
1054
1084
|
note.modulationLFO.start(startTime + voiceParams.delayModLFO);
|
|
1055
1085
|
note.modulationLFO.connect(note.filterDepth);
|
|
1056
1086
|
note.filterDepth.connect(note.filterNode.frequency);
|
|
1057
1087
|
note.modulationLFO.connect(note.modulationDepth);
|
|
1058
1088
|
note.modulationDepth.connect(note.bufferSource.detune);
|
|
1059
1089
|
note.modulationLFO.connect(note.volumeDepth);
|
|
1060
|
-
note.volumeDepth.connect(note.
|
|
1090
|
+
note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
|
|
1061
1091
|
}
|
|
1062
1092
|
startVibrato(channel, note, startTime) {
|
|
1063
1093
|
const { voiceParams } = note;
|
|
@@ -1079,6 +1109,9 @@ export class MidyGM2 {
|
|
|
1079
1109
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1080
1110
|
note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
|
|
1081
1111
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1112
|
+
note.gainL = new GainNode(this.audioContext);
|
|
1113
|
+
note.gainR = new GainNode(this.audioContext);
|
|
1114
|
+
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
1082
1115
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
1083
1116
|
type: "lowpass",
|
|
1084
1117
|
Q: voiceParams.initialFilterQ / 10, // dB
|
|
@@ -1105,7 +1138,10 @@ export class MidyGM2 {
|
|
|
1105
1138
|
channel.currentBufferSource = note.bufferSource;
|
|
1106
1139
|
}
|
|
1107
1140
|
note.bufferSource.connect(note.filterNode);
|
|
1108
|
-
note.filterNode.connect(note.
|
|
1141
|
+
note.filterNode.connect(note.volumeEnvelopeNode);
|
|
1142
|
+
note.volumeEnvelopeNode.connect(note.volumeNode);
|
|
1143
|
+
note.volumeNode.connect(note.gainL);
|
|
1144
|
+
note.volumeNode.connect(note.gainR);
|
|
1109
1145
|
if (0 < channel.chorusSendLevel) {
|
|
1110
1146
|
this.setChorusEffectsSend(channel, note, 0);
|
|
1111
1147
|
}
|
|
@@ -1136,8 +1172,8 @@ export class MidyGM2 {
|
|
|
1136
1172
|
if (!voice)
|
|
1137
1173
|
return;
|
|
1138
1174
|
const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
|
|
1139
|
-
note.
|
|
1140
|
-
note.
|
|
1175
|
+
note.gainL.connect(channel.gainL);
|
|
1176
|
+
note.gainR.connect(channel.gainR);
|
|
1141
1177
|
if (channel.state.sostenutoPedal) {
|
|
1142
1178
|
channel.sostenutoNotes.set(noteNumber, note);
|
|
1143
1179
|
}
|
|
@@ -1168,7 +1204,7 @@ export class MidyGM2 {
|
|
|
1168
1204
|
}
|
|
1169
1205
|
stopNote(endTime, stopTime, scheduledNotes, index) {
|
|
1170
1206
|
const note = scheduledNotes[index];
|
|
1171
|
-
note.
|
|
1207
|
+
note.volumeEnvelopeNode.gain
|
|
1172
1208
|
.cancelScheduledValues(endTime)
|
|
1173
1209
|
.linearRampToValueAtTime(0, stopTime);
|
|
1174
1210
|
note.ending = true;
|
|
@@ -1179,8 +1215,11 @@ export class MidyGM2 {
|
|
|
1179
1215
|
note.bufferSource.onended = () => {
|
|
1180
1216
|
scheduledNotes[index] = null;
|
|
1181
1217
|
note.bufferSource.disconnect();
|
|
1182
|
-
note.volumeNode.disconnect();
|
|
1183
1218
|
note.filterNode.disconnect();
|
|
1219
|
+
note.volumeEnvelopeNode.disconnect();
|
|
1220
|
+
note.volumeNode.disconnect();
|
|
1221
|
+
note.gainL.disconnect();
|
|
1222
|
+
note.gainR.disconnect();
|
|
1184
1223
|
if (note.modulationDepth) {
|
|
1185
1224
|
note.volumeDepth.disconnect();
|
|
1186
1225
|
note.modulationDepth.disconnect();
|
|
@@ -1229,7 +1268,7 @@ export class MidyGM2 {
|
|
|
1229
1268
|
return this.stopNote(endTime, stopTime, scheduledNotes, i);
|
|
1230
1269
|
}
|
|
1231
1270
|
else {
|
|
1232
|
-
const portamentoTime = endTime +
|
|
1271
|
+
const portamentoTime = endTime + this.getPortamentoTime(channel);
|
|
1233
1272
|
const deltaNote = portamentoNoteNumber - noteNumber;
|
|
1234
1273
|
const baseRate = note.voiceParams.playbackRate;
|
|
1235
1274
|
const targetRate = baseRate * Math.pow(2, deltaNote / 12);
|
|
@@ -1299,22 +1338,45 @@ export class MidyGM2 {
|
|
|
1299
1338
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1300
1339
|
channel.program = program;
|
|
1301
1340
|
}
|
|
1302
|
-
handleChannelPressure(channelNumber,
|
|
1303
|
-
const now = this.audioContext.currentTime;
|
|
1341
|
+
handleChannelPressure(channelNumber, value) {
|
|
1304
1342
|
const channel = this.channels[channelNumber];
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
if (channel.
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
activeNote.volumeNode.gain
|
|
1312
|
-
.cancelScheduledValues(now)
|
|
1313
|
-
.setValueAtTime(gain * pressure, now);
|
|
1314
|
-
});
|
|
1343
|
+
const prev = channel.state.channelPressure;
|
|
1344
|
+
const next = value / 127;
|
|
1345
|
+
channel.state.channelPressure = next;
|
|
1346
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1347
|
+
const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
|
|
1348
|
+
channel.detune += pressureDepth * (next - prev);
|
|
1315
1349
|
}
|
|
1350
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1351
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1352
|
+
const note = noteList[i];
|
|
1353
|
+
if (!note)
|
|
1354
|
+
continue;
|
|
1355
|
+
this.setChannelPressure(channel, note);
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1316
1358
|
// this.applyVoiceParams(channel, 13);
|
|
1317
1359
|
}
|
|
1360
|
+
setChannelPressure(channel, note) {
|
|
1361
|
+
if (channel.pressureTable[0] !== 64) {
|
|
1362
|
+
this.updateDetune(channel);
|
|
1363
|
+
}
|
|
1364
|
+
if (channel.pressureTable[1] !== 64 && !note.portamento) {
|
|
1365
|
+
this.setFilterEnvelope(channel, note);
|
|
1366
|
+
}
|
|
1367
|
+
if (channel.pressureTable[2] !== 64 && !note.portamento) {
|
|
1368
|
+
this.setVolumeEnvelope(channel, note);
|
|
1369
|
+
}
|
|
1370
|
+
if (channel.pressureTable[3] !== 0) {
|
|
1371
|
+
this.setModLfoToPitch(channel, note);
|
|
1372
|
+
}
|
|
1373
|
+
if (channel.pressureTable[4] !== 0) {
|
|
1374
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1375
|
+
}
|
|
1376
|
+
if (channel.pressureTable[5] !== 0) {
|
|
1377
|
+
this.setModLfoToVolume(channel, note);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1318
1380
|
handlePitchBendMessage(channelNumber, lsb, msb) {
|
|
1319
1381
|
const pitchBend = msb * 128 + lsb;
|
|
1320
1382
|
this.setPitchBend(channelNumber, pitchBend);
|
|
@@ -1331,13 +1393,15 @@ export class MidyGM2 {
|
|
|
1331
1393
|
}
|
|
1332
1394
|
setModLfoToPitch(channel, note) {
|
|
1333
1395
|
const now = this.audioContext.currentTime;
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1396
|
+
const pressureDepth = channel.pressureTable[3] / 127 * 600;
|
|
1397
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1398
|
+
const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
|
|
1399
|
+
const baseDepth = Math.abs(modLfoToPitch) +
|
|
1336
1400
|
channel.state.modulationDepth;
|
|
1337
|
-
const
|
|
1401
|
+
const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
|
|
1338
1402
|
note.modulationDepth.gain
|
|
1339
1403
|
.cancelScheduledValues(now)
|
|
1340
|
-
.setValueAtTime(modulationDepth
|
|
1404
|
+
.setValueAtTime(modulationDepth, now);
|
|
1341
1405
|
}
|
|
1342
1406
|
setVibLfoToPitch(channel, note) {
|
|
1343
1407
|
const now = this.audioContext.currentTime;
|
|
@@ -1349,69 +1413,75 @@ export class MidyGM2 {
|
|
|
1349
1413
|
.cancelScheduledValues(now)
|
|
1350
1414
|
.setValueAtTime(vibratoDepth * vibratoDepthSign, now);
|
|
1351
1415
|
}
|
|
1352
|
-
setModLfoToFilterFc(note) {
|
|
1416
|
+
setModLfoToFilterFc(channel, note) {
|
|
1353
1417
|
const now = this.audioContext.currentTime;
|
|
1354
|
-
const
|
|
1418
|
+
const pressureDepth = channel.pressureTable[4] / 127 * 2400;
|
|
1419
|
+
const pressure = pressureDepth * channel.state.channelPressure;
|
|
1420
|
+
const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
|
|
1355
1421
|
note.filterDepth.gain
|
|
1356
1422
|
.cancelScheduledValues(now)
|
|
1357
1423
|
.setValueAtTime(modLfoToFilterFc, now);
|
|
1358
1424
|
}
|
|
1359
|
-
setModLfoToVolume(note) {
|
|
1425
|
+
setModLfoToVolume(channel, note) {
|
|
1360
1426
|
const now = this.audioContext.currentTime;
|
|
1361
1427
|
const modLfoToVolume = note.voiceParams.modLfoToVolume;
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1428
|
+
const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
|
|
1429
|
+
const pressureDepth = channel.pressureTable[5] / 127;
|
|
1430
|
+
const pressure = 1 + pressureDepth * channel.state.channelPressure;
|
|
1431
|
+
const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
|
|
1364
1432
|
note.volumeDepth.gain
|
|
1365
1433
|
.cancelScheduledValues(now)
|
|
1366
|
-
.setValueAtTime(volumeDepth
|
|
1434
|
+
.setValueAtTime(volumeDepth, now);
|
|
1367
1435
|
}
|
|
1368
|
-
|
|
1436
|
+
setReverbEffectsSend(channel, note, prevValue) {
|
|
1369
1437
|
if (0 < prevValue) {
|
|
1370
|
-
if (0 < note.voiceParams.
|
|
1438
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1371
1439
|
const now = this.audioContext.currentTime;
|
|
1372
|
-
const
|
|
1373
|
-
note.
|
|
1440
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
|
|
1441
|
+
const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
|
|
1442
|
+
note.reverbEffectsSend.gain
|
|
1374
1443
|
.cancelScheduledValues(now)
|
|
1375
1444
|
.setValueAtTime(value, now);
|
|
1376
1445
|
}
|
|
1377
1446
|
else {
|
|
1378
|
-
note.
|
|
1447
|
+
note.reverbEffectsSend.disconnect();
|
|
1379
1448
|
}
|
|
1380
1449
|
}
|
|
1381
1450
|
else {
|
|
1382
|
-
if (0 < note.voiceParams.
|
|
1383
|
-
if (!note.
|
|
1384
|
-
note.
|
|
1385
|
-
gain: note.voiceParams.
|
|
1451
|
+
if (0 < note.voiceParams.reverbEffectsSend) {
|
|
1452
|
+
if (!note.reverbEffectsSend) {
|
|
1453
|
+
note.reverbEffectsSend = new GainNode(this.audioContext, {
|
|
1454
|
+
gain: note.voiceParams.reverbEffectsSend,
|
|
1386
1455
|
});
|
|
1387
|
-
note.volumeNode.connect(note.
|
|
1456
|
+
note.volumeNode.connect(note.reverbEffectsSend);
|
|
1388
1457
|
}
|
|
1389
|
-
note.
|
|
1458
|
+
note.reverbEffectsSend.connect(this.reverbEffect.input);
|
|
1390
1459
|
}
|
|
1391
1460
|
}
|
|
1392
1461
|
}
|
|
1393
|
-
|
|
1462
|
+
setChorusEffectsSend(channel, note, prevValue) {
|
|
1394
1463
|
if (0 < prevValue) {
|
|
1395
|
-
if (0 < note.voiceParams.
|
|
1464
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1396
1465
|
const now = this.audioContext.currentTime;
|
|
1397
|
-
const
|
|
1398
|
-
note.
|
|
1466
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
|
|
1467
|
+
const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
|
|
1468
|
+
note.chorusEffectsSend.gain
|
|
1399
1469
|
.cancelScheduledValues(now)
|
|
1400
1470
|
.setValueAtTime(value, now);
|
|
1401
1471
|
}
|
|
1402
1472
|
else {
|
|
1403
|
-
note.
|
|
1473
|
+
note.chorusEffectsSend.disconnect();
|
|
1404
1474
|
}
|
|
1405
1475
|
}
|
|
1406
1476
|
else {
|
|
1407
|
-
if (0 < note.voiceParams.
|
|
1408
|
-
if (!note.
|
|
1409
|
-
note.
|
|
1410
|
-
gain: note.voiceParams.
|
|
1477
|
+
if (0 < note.voiceParams.chorusEffectsSend) {
|
|
1478
|
+
if (!note.chorusEffectsSend) {
|
|
1479
|
+
note.chorusEffectsSend = new GainNode(this.audioContext, {
|
|
1480
|
+
gain: note.voiceParams.chorusEffectsSend,
|
|
1411
1481
|
});
|
|
1412
|
-
note.volumeNode.connect(note.
|
|
1482
|
+
note.volumeNode.connect(note.chorusEffectsSend);
|
|
1413
1483
|
}
|
|
1414
|
-
note.
|
|
1484
|
+
note.chorusEffectsSend.connect(this.chorusEffect.input);
|
|
1415
1485
|
}
|
|
1416
1486
|
}
|
|
1417
1487
|
}
|
|
@@ -1444,18 +1514,20 @@ export class MidyGM2 {
|
|
|
1444
1514
|
}
|
|
1445
1515
|
},
|
|
1446
1516
|
modLfoToFilterFc: (channel, note, _prevValue) => {
|
|
1447
|
-
if (0 < channel.state.modulationDepth)
|
|
1448
|
-
this.setModLfoToFilterFc(note);
|
|
1517
|
+
if (0 < channel.state.modulationDepth) {
|
|
1518
|
+
this.setModLfoToFilterFc(channel, note);
|
|
1519
|
+
}
|
|
1449
1520
|
},
|
|
1450
1521
|
modLfoToVolume: (channel, note) => {
|
|
1451
|
-
if (0 < channel.state.modulationDepth)
|
|
1452
|
-
this.setModLfoToVolume(note);
|
|
1522
|
+
if (0 < channel.state.modulationDepth) {
|
|
1523
|
+
this.setModLfoToVolume(channel, note);
|
|
1524
|
+
}
|
|
1453
1525
|
},
|
|
1454
|
-
chorusEffectsSend: (
|
|
1455
|
-
this.setChorusEffectsSend(note, prevValue);
|
|
1526
|
+
chorusEffectsSend: (channel, note, prevValue) => {
|
|
1527
|
+
this.setChorusEffectsSend(channel, note, prevValue);
|
|
1456
1528
|
},
|
|
1457
|
-
reverbEffectsSend: (
|
|
1458
|
-
this.setReverbEffectsSend(note, prevValue);
|
|
1529
|
+
reverbEffectsSend: (channel, note, prevValue) => {
|
|
1530
|
+
this.setReverbEffectsSend(channel, note, prevValue);
|
|
1459
1531
|
},
|
|
1460
1532
|
delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
|
|
1461
1533
|
freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
|
|
@@ -1612,10 +1684,27 @@ export class MidyGM2 {
|
|
|
1612
1684
|
const factor = 5 * Math.log(10) / 127;
|
|
1613
1685
|
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1614
1686
|
}
|
|
1687
|
+
setKeyBasedVolume(channel) {
|
|
1688
|
+
const now = this.audioContext.currentTime;
|
|
1689
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1690
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1691
|
+
const note = noteList[i];
|
|
1692
|
+
if (!note)
|
|
1693
|
+
continue;
|
|
1694
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
|
|
1695
|
+
if (keyBasedValue === 0)
|
|
1696
|
+
continue;
|
|
1697
|
+
note.volumeNode.gain
|
|
1698
|
+
.cancelScheduledValues(now)
|
|
1699
|
+
.setValueAtTime(1 + keyBasedValue, now);
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1615
1703
|
setVolume(channelNumber, volume) {
|
|
1616
1704
|
const channel = this.channels[channelNumber];
|
|
1617
1705
|
channel.state.volume = volume / 127;
|
|
1618
1706
|
this.updateChannelVolume(channel);
|
|
1707
|
+
this.setKeyBasedVolume(channel);
|
|
1619
1708
|
}
|
|
1620
1709
|
panToGain(pan) {
|
|
1621
1710
|
const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
|
|
@@ -1624,10 +1713,31 @@ export class MidyGM2 {
|
|
|
1624
1713
|
gainRight: Math.sin(theta),
|
|
1625
1714
|
};
|
|
1626
1715
|
}
|
|
1716
|
+
setKeyBasedPan(channel) {
|
|
1717
|
+
const now = this.audioContext.currentTime;
|
|
1718
|
+
channel.scheduledNotes.forEach((noteList) => {
|
|
1719
|
+
for (let i = 0; i < noteList.length; i++) {
|
|
1720
|
+
const note = noteList[i];
|
|
1721
|
+
if (!note)
|
|
1722
|
+
continue;
|
|
1723
|
+
const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
|
|
1724
|
+
if (keyBasedValue === 0)
|
|
1725
|
+
continue;
|
|
1726
|
+
const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
|
|
1727
|
+
note.gainL.gain
|
|
1728
|
+
.cancelScheduledValues(now)
|
|
1729
|
+
.setValueAtTime(gainLeft, now);
|
|
1730
|
+
note.gainR.gain
|
|
1731
|
+
.cancelScheduledValues(now)
|
|
1732
|
+
.setValueAtTime(gainRight, now);
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1627
1736
|
setPan(channelNumber, pan) {
|
|
1628
1737
|
const channel = this.channels[channelNumber];
|
|
1629
1738
|
channel.state.pan = pan / 127;
|
|
1630
1739
|
this.updateChannelVolume(channel);
|
|
1740
|
+
this.setKeyBasedPan(channel);
|
|
1631
1741
|
}
|
|
1632
1742
|
setExpression(channelNumber, expression) {
|
|
1633
1743
|
const channel = this.channels[channelNumber];
|
|
@@ -1662,6 +1772,22 @@ export class MidyGM2 {
|
|
|
1662
1772
|
setPortamento(channelNumber, value) {
|
|
1663
1773
|
this.channels[channelNumber].state.portamento = value / 127;
|
|
1664
1774
|
}
|
|
1775
|
+
setSostenutoPedal(channelNumber, value) {
|
|
1776
|
+
const channel = this.channels[channelNumber];
|
|
1777
|
+
channel.state.sostenutoPedal = value / 127;
|
|
1778
|
+
if (64 <= value) {
|
|
1779
|
+
const now = this.audioContext.currentTime;
|
|
1780
|
+
const activeNotes = this.getActiveNotes(channel, now);
|
|
1781
|
+
channel.sostenutoNotes = new Map(activeNotes);
|
|
1782
|
+
}
|
|
1783
|
+
else {
|
|
1784
|
+
this.releaseSostenutoPedal(channelNumber, value);
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
setSoftPedal(channelNumber, softPedal) {
|
|
1788
|
+
const channel = this.channels[channelNumber];
|
|
1789
|
+
channel.state.softPedal = softPedal / 127;
|
|
1790
|
+
}
|
|
1665
1791
|
setReverbSendLevel(channelNumber, reverbSendLevel) {
|
|
1666
1792
|
const channel = this.channels[channelNumber];
|
|
1667
1793
|
const state = channel.state;
|
|
@@ -1694,7 +1820,7 @@ export class MidyGM2 {
|
|
|
1694
1820
|
const note = noteList[i];
|
|
1695
1821
|
if (!note)
|
|
1696
1822
|
continue;
|
|
1697
|
-
this.setReverbEffectsSend(note, 0);
|
|
1823
|
+
this.setReverbEffectsSend(channel, note, 0);
|
|
1698
1824
|
}
|
|
1699
1825
|
});
|
|
1700
1826
|
state.reverbSendLevel = reverbSendLevel / 127;
|
|
@@ -1735,7 +1861,7 @@ export class MidyGM2 {
|
|
|
1735
1861
|
const note = noteList[i];
|
|
1736
1862
|
if (!note)
|
|
1737
1863
|
continue;
|
|
1738
|
-
this.setChorusEffectsSend(note, 0);
|
|
1864
|
+
this.setChorusEffectsSend(channel, note, 0);
|
|
1739
1865
|
}
|
|
1740
1866
|
});
|
|
1741
1867
|
state.chorusSendLevel = chorusSendLevel / 127;
|
|
@@ -1744,22 +1870,6 @@ export class MidyGM2 {
|
|
|
1744
1870
|
}
|
|
1745
1871
|
}
|
|
1746
1872
|
}
|
|
1747
|
-
setSostenutoPedal(channelNumber, value) {
|
|
1748
|
-
const channel = this.channels[channelNumber];
|
|
1749
|
-
channel.state.sostenutoPedal = value / 127;
|
|
1750
|
-
if (64 <= value) {
|
|
1751
|
-
const now = this.audioContext.currentTime;
|
|
1752
|
-
const activeNotes = this.getActiveNotes(channel, now);
|
|
1753
|
-
channel.sostenutoNotes = new Map(activeNotes);
|
|
1754
|
-
}
|
|
1755
|
-
else {
|
|
1756
|
-
this.releaseSostenutoPedal(channelNumber, value);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
setSoftPedal(channelNumber, softPedal) {
|
|
1760
|
-
const channel = this.channels[channelNumber];
|
|
1761
|
-
channel.state.softPedal = softPedal / 127;
|
|
1762
|
-
}
|
|
1763
1873
|
limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
|
|
1764
1874
|
if (maxLSB < channel.dataLSB) {
|
|
1765
1875
|
channel.dataMSB++;
|
|
@@ -1922,7 +2032,7 @@ export class MidyGM2 {
|
|
|
1922
2032
|
switch (data[3]) {
|
|
1923
2033
|
case 8:
|
|
1924
2034
|
// https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
|
|
1925
|
-
return this.
|
|
2035
|
+
return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
|
|
1926
2036
|
default:
|
|
1927
2037
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1928
2038
|
}
|
|
@@ -1975,7 +2085,7 @@ export class MidyGM2 {
|
|
|
1975
2085
|
return this.handleMasterFineTuningSysEx(data);
|
|
1976
2086
|
case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
|
|
1977
2087
|
return this.handleMasterCoarseTuningSysEx(data);
|
|
1978
|
-
case 5:
|
|
2088
|
+
case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
|
|
1979
2089
|
return this.handleGlobalParameterControlSysEx(data);
|
|
1980
2090
|
default:
|
|
1981
2091
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -1983,9 +2093,8 @@ export class MidyGM2 {
|
|
|
1983
2093
|
break;
|
|
1984
2094
|
case 9:
|
|
1985
2095
|
switch (data[3]) {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
// return this.setChannelPressure();
|
|
2096
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
|
|
2097
|
+
return this.handleChannelPressureSysEx(data);
|
|
1989
2098
|
// case 3:
|
|
1990
2099
|
// // TODO
|
|
1991
2100
|
// return this.setControlChange();
|
|
@@ -1995,9 +2104,8 @@ export class MidyGM2 {
|
|
|
1995
2104
|
break;
|
|
1996
2105
|
case 10:
|
|
1997
2106
|
switch (data[3]) {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
// return this.handleKeyBasedInstrumentControl();
|
|
2107
|
+
case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
|
|
2108
|
+
return this.handleKeyBasedInstrumentControlSysEx(data);
|
|
2001
2109
|
default:
|
|
2002
2110
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2003
2111
|
}
|
|
@@ -2042,40 +2150,6 @@ export class MidyGM2 {
|
|
|
2042
2150
|
channel.detune += next - prev;
|
|
2043
2151
|
this.updateDetune(channel);
|
|
2044
2152
|
}
|
|
2045
|
-
getChannelBitmap(data) {
|
|
2046
|
-
const bitmap = new Array(16).fill(false);
|
|
2047
|
-
const ff = data[4] & 0b11;
|
|
2048
|
-
const gg = data[5] & 0x7F;
|
|
2049
|
-
const hh = data[6] & 0x7F;
|
|
2050
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2051
|
-
if (hh & (1 << bit))
|
|
2052
|
-
bitmap[bit] = true;
|
|
2053
|
-
}
|
|
2054
|
-
for (let bit = 0; bit < 7; bit++) {
|
|
2055
|
-
if (gg & (1 << bit))
|
|
2056
|
-
bitmap[bit + 7] = true;
|
|
2057
|
-
}
|
|
2058
|
-
for (let bit = 0; bit < 2; bit++) {
|
|
2059
|
-
if (ff & (1 << bit))
|
|
2060
|
-
bitmap[bit + 14] = true;
|
|
2061
|
-
}
|
|
2062
|
-
return bitmap;
|
|
2063
|
-
}
|
|
2064
|
-
handleScaleOctaveTuning1ByteFormat(data) {
|
|
2065
|
-
if (data.length < 18) {
|
|
2066
|
-
console.error("Data length is too short");
|
|
2067
|
-
return;
|
|
2068
|
-
}
|
|
2069
|
-
const channelBitmap = this.getChannelBitmap(data);
|
|
2070
|
-
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2071
|
-
if (!channelBitmap[i])
|
|
2072
|
-
continue;
|
|
2073
|
-
for (let j = 0; j < 12; j++) {
|
|
2074
|
-
const value = data[j + 7] - 64; // cent
|
|
2075
|
-
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2076
|
-
}
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
2153
|
handleGlobalParameterControlSysEx(data) {
|
|
2080
2154
|
if (data[7] === 1) {
|
|
2081
2155
|
switch (data[8]) {
|
|
@@ -2262,6 +2336,66 @@ export class MidyGM2 {
|
|
|
2262
2336
|
getChorusSendToReverb(value) {
|
|
2263
2337
|
return value * 0.00787;
|
|
2264
2338
|
}
|
|
2339
|
+
getChannelBitmap(data) {
|
|
2340
|
+
const bitmap = new Array(16).fill(false);
|
|
2341
|
+
const ff = data[4] & 0b11;
|
|
2342
|
+
const gg = data[5] & 0x7F;
|
|
2343
|
+
const hh = data[6] & 0x7F;
|
|
2344
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2345
|
+
if (hh & (1 << bit))
|
|
2346
|
+
bitmap[bit] = true;
|
|
2347
|
+
}
|
|
2348
|
+
for (let bit = 0; bit < 7; bit++) {
|
|
2349
|
+
if (gg & (1 << bit))
|
|
2350
|
+
bitmap[bit + 7] = true;
|
|
2351
|
+
}
|
|
2352
|
+
for (let bit = 0; bit < 2; bit++) {
|
|
2353
|
+
if (ff & (1 << bit))
|
|
2354
|
+
bitmap[bit + 14] = true;
|
|
2355
|
+
}
|
|
2356
|
+
return bitmap;
|
|
2357
|
+
}
|
|
2358
|
+
handleScaleOctaveTuning1ByteFormatSysEx(data) {
|
|
2359
|
+
if (data.length < 18) {
|
|
2360
|
+
console.error("Data length is too short");
|
|
2361
|
+
return;
|
|
2362
|
+
}
|
|
2363
|
+
const channelBitmap = this.getChannelBitmap(data);
|
|
2364
|
+
for (let i = 0; i < channelBitmap.length; i++) {
|
|
2365
|
+
if (!channelBitmap[i])
|
|
2366
|
+
continue;
|
|
2367
|
+
for (let j = 0; j < 12; j++) {
|
|
2368
|
+
const value = data[j + 7] - 64; // cent
|
|
2369
|
+
this.channels[i].scaleOctaveTuningTable[j] = value;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
handleChannelPressureSysEx(data) {
|
|
2374
|
+
const channelNumber = data[4];
|
|
2375
|
+
const table = this.channels[channelNumber].pressureTable;
|
|
2376
|
+
for (let i = 5; i < data.length - 1; i += 2) {
|
|
2377
|
+
const pp = data[i];
|
|
2378
|
+
const rr = data[i + 1];
|
|
2379
|
+
table[pp] = rr;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
|
|
2383
|
+
const index = keyNumber * 128 + controllerType;
|
|
2384
|
+
const controlValue = channel.keyBasedInstrumentControlTable[index];
|
|
2385
|
+
return (controlValue + 64) / 64;
|
|
2386
|
+
}
|
|
2387
|
+
handleKeyBasedInstrumentControlSysEx(data) {
|
|
2388
|
+
const channelNumber = data[4];
|
|
2389
|
+
const keyNumber = data[5];
|
|
2390
|
+
const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
|
|
2391
|
+
for (let i = 6; i < data.length - 1; i += 2) {
|
|
2392
|
+
const controllerType = data[i];
|
|
2393
|
+
const value = data[i + 1];
|
|
2394
|
+
const index = keyNumber * 128 + controllerType;
|
|
2395
|
+
table[index] = value - 64;
|
|
2396
|
+
}
|
|
2397
|
+
this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
|
|
2398
|
+
}
|
|
2265
2399
|
handleExclusiveMessage(data) {
|
|
2266
2400
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2267
2401
|
}
|
|
@@ -2295,6 +2429,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2295
2429
|
currentBufferSource: null,
|
|
2296
2430
|
detune: 0,
|
|
2297
2431
|
scaleOctaveTuningTable: new Array(12).fill(0), // cent
|
|
2432
|
+
pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
|
|
2433
|
+
keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
|
|
2298
2434
|
program: 0,
|
|
2299
2435
|
bank: 121 * 128,
|
|
2300
2436
|
bankMSB: 121,
|