@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.
@@ -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
- calcDetune(channel, note) {
931
+ calcChannelDetune(channel) {
914
932
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
915
933
  const channelTuning = channel.coarseTuning + channel.fineTuning;
916
- const scaleOctaveTuning = channel.scaleOctaveTuningTable[note.noteNumber % 12];
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
- return tuning + pitch;
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.state.portamentoTime;
948
- note.volumeNode.gain
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 attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
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.volumeNode.gain
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 baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1005
- softPedalFactor;
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.state.portamentoTime;
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.volumeNode.gain);
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.volumeNode);
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.volumeNode.connect(channel.gainL);
1143
- note.volumeNode.connect(channel.gainR);
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.volumeNode.gain
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 + state.portamentoTime;
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, pressure) {
1306
- const now = this.audioContext.currentTime;
1344
+ handleChannelPressure(channelNumber, value) {
1307
1345
  const channel = this.channels[channelNumber];
1308
- pressure /= 64;
1309
- channel.channelPressure = pressure;
1310
- const activeNotes = this.getActiveNotes(channel, now);
1311
- if (channel.channelPressure.amplitudeControl !== 1) {
1312
- activeNotes.forEach((activeNote) => {
1313
- const gain = activeNote.volumeNode.gain.value;
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 modLfoToPitch = note.voiceParams.modLfoToPitch;
1338
- const modulationDepth = Math.abs(modLfoToPitch) +
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 modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1404
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1341
1405
  note.modulationDepth.gain
1342
1406
  .cancelScheduledValues(now)
1343
- .setValueAtTime(modulationDepth * modulationDepthSign, now);
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 modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
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 volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1366
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
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 * volumeDepthSign, now);
1437
+ .setValueAtTime(volumeDepth, now);
1370
1438
  }
1371
- setChorusEffectsSend(note, prevValue) {
1439
+ setReverbEffectsSend(channel, note, prevValue) {
1372
1440
  if (0 < prevValue) {
1373
- if (0 < note.voiceParams.chorusEffectsSend) {
1441
+ if (0 < note.voiceParams.reverbEffectsSend) {
1374
1442
  const now = this.audioContext.currentTime;
1375
- const value = note.voiceParams.chorusEffectsSend;
1376
- note.chorusEffectsSend.gain
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.chorusEffectsSend.disconnect();
1450
+ note.reverbEffectsSend.disconnect();
1382
1451
  }
1383
1452
  }
1384
1453
  else {
1385
- if (0 < note.voiceParams.chorusEffectsSend) {
1386
- if (!note.chorusEffectsSend) {
1387
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1388
- gain: note.voiceParams.chorusEffectsSend,
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.chorusEffectsSend);
1459
+ note.volumeNode.connect(note.reverbEffectsSend);
1391
1460
  }
1392
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1461
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1393
1462
  }
1394
1463
  }
1395
1464
  }
1396
- setReverbEffectsSend(note, prevValue) {
1465
+ setChorusEffectsSend(channel, note, prevValue) {
1397
1466
  if (0 < prevValue) {
1398
- if (0 < note.voiceParams.reverbEffectsSend) {
1467
+ if (0 < note.voiceParams.chorusEffectsSend) {
1399
1468
  const now = this.audioContext.currentTime;
1400
- const value = note.voiceParams.reverbEffectsSend;
1401
- note.reverbEffectsSend.gain
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.reverbEffectsSend.disconnect();
1476
+ note.chorusEffectsSend.disconnect();
1407
1477
  }
1408
1478
  }
1409
1479
  else {
1410
- if (0 < note.voiceParams.reverbEffectsSend) {
1411
- if (!note.reverbEffectsSend) {
1412
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1413
- gain: note.voiceParams.reverbEffectsSend,
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.reverbEffectsSend);
1485
+ note.volumeNode.connect(note.chorusEffectsSend);
1416
1486
  }
1417
- note.reverbEffectsSend.connect(this.reverbEffect.input);
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: (_channel, note, prevValue) => {
1458
- this.setChorusEffectsSend(note, prevValue);
1529
+ chorusEffectsSend: (channel, note, prevValue) => {
1530
+ this.setChorusEffectsSend(channel, note, prevValue);
1459
1531
  },
1460
- reverbEffectsSend: (_channel, note, prevValue) => {
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.handleScaleOctaveTuning1ByteFormat(data);
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
- // case 1:
1990
- // // TODO
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
- // case 1:
2002
- // // TODO
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,