@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-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
- calcDetune(channel, note) {
928
+ calcChannelDetune(channel) {
911
929
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
912
930
  const channelTuning = channel.coarseTuning + channel.fineTuning;
913
- const scaleOctaveTuning = channel.scaleOctaveTuningTable[note.noteNumber % 12];
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
- return tuning + pitch;
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.state.portamentoTime;
945
- note.volumeNode.gain
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 attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
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.volumeNode.gain
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 baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1002
- softPedalFactor;
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.state.portamentoTime;
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.volumeNode.gain);
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.volumeNode);
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.volumeNode.connect(channel.gainL);
1140
- note.volumeNode.connect(channel.gainR);
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.volumeNode.gain
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 + state.portamentoTime;
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, pressure) {
1303
- const now = this.audioContext.currentTime;
1341
+ handleChannelPressure(channelNumber, value) {
1304
1342
  const channel = this.channels[channelNumber];
1305
- pressure /= 64;
1306
- channel.channelPressure = pressure;
1307
- const activeNotes = this.getActiveNotes(channel, now);
1308
- if (channel.channelPressure.amplitudeControl !== 1) {
1309
- activeNotes.forEach((activeNote) => {
1310
- const gain = activeNote.volumeNode.gain.value;
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 modLfoToPitch = note.voiceParams.modLfoToPitch;
1335
- const modulationDepth = Math.abs(modLfoToPitch) +
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 modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1401
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1338
1402
  note.modulationDepth.gain
1339
1403
  .cancelScheduledValues(now)
1340
- .setValueAtTime(modulationDepth * modulationDepthSign, now);
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 modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
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 volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1363
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
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 * volumeDepthSign, now);
1434
+ .setValueAtTime(volumeDepth, now);
1367
1435
  }
1368
- setChorusEffectsSend(note, prevValue) {
1436
+ setReverbEffectsSend(channel, note, prevValue) {
1369
1437
  if (0 < prevValue) {
1370
- if (0 < note.voiceParams.chorusEffectsSend) {
1438
+ if (0 < note.voiceParams.reverbEffectsSend) {
1371
1439
  const now = this.audioContext.currentTime;
1372
- const value = note.voiceParams.chorusEffectsSend;
1373
- note.chorusEffectsSend.gain
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.chorusEffectsSend.disconnect();
1447
+ note.reverbEffectsSend.disconnect();
1379
1448
  }
1380
1449
  }
1381
1450
  else {
1382
- if (0 < note.voiceParams.chorusEffectsSend) {
1383
- if (!note.chorusEffectsSend) {
1384
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1385
- gain: note.voiceParams.chorusEffectsSend,
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.chorusEffectsSend);
1456
+ note.volumeNode.connect(note.reverbEffectsSend);
1388
1457
  }
1389
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1458
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1390
1459
  }
1391
1460
  }
1392
1461
  }
1393
- setReverbEffectsSend(note, prevValue) {
1462
+ setChorusEffectsSend(channel, note, prevValue) {
1394
1463
  if (0 < prevValue) {
1395
- if (0 < note.voiceParams.reverbEffectsSend) {
1464
+ if (0 < note.voiceParams.chorusEffectsSend) {
1396
1465
  const now = this.audioContext.currentTime;
1397
- const value = note.voiceParams.reverbEffectsSend;
1398
- note.reverbEffectsSend.gain
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.reverbEffectsSend.disconnect();
1473
+ note.chorusEffectsSend.disconnect();
1404
1474
  }
1405
1475
  }
1406
1476
  else {
1407
- if (0 < note.voiceParams.reverbEffectsSend) {
1408
- if (!note.reverbEffectsSend) {
1409
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1410
- gain: note.voiceParams.reverbEffectsSend,
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.reverbEffectsSend);
1482
+ note.volumeNode.connect(note.chorusEffectsSend);
1413
1483
  }
1414
- note.reverbEffectsSend.connect(this.reverbEffect.input);
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: (_channel, note, prevValue) => {
1455
- this.setChorusEffectsSend(note, prevValue);
1526
+ chorusEffectsSend: (channel, note, prevValue) => {
1527
+ this.setChorusEffectsSend(channel, note, prevValue);
1456
1528
  },
1457
- reverbEffectsSend: (_channel, note, prevValue) => {
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.handleScaleOctaveTuning1ByteFormat(data);
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
- // case 1:
1987
- // // TODO
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
- // case 1:
1999
- // // TODO
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,