@marmooo/midy 0.2.0 → 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/script/midy.js CHANGED
@@ -17,12 +17,30 @@ class Note {
17
17
  writable: true,
18
18
  value: void 0
19
19
  });
20
+ Object.defineProperty(this, "volumeEnvelopeNode", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
20
26
  Object.defineProperty(this, "volumeNode", {
21
27
  enumerable: true,
22
28
  configurable: true,
23
29
  writable: true,
24
30
  value: void 0
25
31
  });
32
+ Object.defineProperty(this, "gainL", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "gainR", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: void 0
43
+ });
26
44
  Object.defineProperty(this, "volumeDepth", {
27
45
  enumerable: true,
28
46
  configurable: true,
@@ -907,16 +925,48 @@ class Midy {
907
925
  cbToRatio(cb) {
908
926
  return Math.pow(10, cb / 200);
909
927
  }
928
+ rateToCent(rate) {
929
+ return 1200 * Math.log2(rate);
930
+ }
931
+ centToRate(cent) {
932
+ return Math.pow(2, cent / 1200);
933
+ }
910
934
  centToHz(cent) {
911
- return 8.176 * Math.pow(2, cent / 1200);
935
+ return 8.176 * this.centToRate(cent);
912
936
  }
913
- calcSemitoneOffset(channel) {
937
+ calcChannelDetune(channel) {
914
938
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
915
939
  const channelTuning = channel.coarseTuning + channel.fineTuning;
940
+ const tuning = masterTuning + channelTuning;
916
941
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
917
- const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
942
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
918
943
  const pitch = pitchWheel * pitchWheelSensitivity;
919
- return masterTuning + channelTuning + pitch;
944
+ const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
945
+ const pressure = pressureDepth * channel.state.channelPressure;
946
+ return tuning + pitch + pressure;
947
+ }
948
+ calcNoteDetune(channel, note) {
949
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
950
+ }
951
+ updateDetune(channel) {
952
+ const now = this.audioContext.currentTime;
953
+ channel.scheduledNotes.forEach((noteList) => {
954
+ for (let i = 0; i < noteList.length; i++) {
955
+ const note = noteList[i];
956
+ if (!note)
957
+ continue;
958
+ const noteDetune = this.calcNoteDetune(channel, note);
959
+ const detune = channel.detune + noteDetune;
960
+ note.bufferSource.detune
961
+ .cancelScheduledValues(now)
962
+ .setValueAtTime(detune, now);
963
+ }
964
+ });
965
+ }
966
+ getPortamentoTime(channel) {
967
+ const factor = 5 * Math.log(10) / 127;
968
+ const time = channel.state.portamentoTime;
969
+ return Math.log(time) / factor;
920
970
  }
921
971
  setPortamentoStartVolumeEnvelope(channel, note) {
922
972
  const now = this.audioContext.currentTime;
@@ -924,8 +974,8 @@ class Midy {
924
974
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
925
975
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
926
976
  const volDelay = startTime + voiceParams.volDelay;
927
- const portamentoTime = volDelay + channel.state.portamentoTime;
928
- note.volumeNode.gain
977
+ const portamentoTime = volDelay + this.getPortamentoTime(channel);
978
+ note.volumeEnvelopeNode.gain
929
979
  .cancelScheduledValues(now)
930
980
  .setValueAtTime(0, volDelay)
931
981
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
@@ -934,13 +984,16 @@ class Midy {
934
984
  const now = this.audioContext.currentTime;
935
985
  const state = channel.state;
936
986
  const { voiceParams, startTime } = note;
937
- const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation);
987
+ const pressureDepth = channel.pressureTable[2] / 64;
988
+ const pressure = 1 + pressureDepth * channel.state.channelPressure;
989
+ const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
990
+ pressure;
938
991
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
939
992
  const volDelay = startTime + voiceParams.volDelay;
940
993
  const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
941
994
  const volHold = volAttack + voiceParams.volHold;
942
995
  const volDecay = volHold + voiceParams.volDecay * state.decayTime * 2;
943
- note.volumeNode.gain
996
+ note.volumeEnvelopeNode.gain
944
997
  .cancelScheduledValues(now)
945
998
  .setValueAtTime(0, startTime)
946
999
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
@@ -948,32 +1001,28 @@ class Midy {
948
1001
  .setValueAtTime(attackVolume, volHold)
949
1002
  .linearRampToValueAtTime(sustainVolume, volDecay);
950
1003
  }
951
- setPlaybackRate(note) {
1004
+ setPitchEnvelope(note) {
952
1005
  const now = this.audioContext.currentTime;
1006
+ const { voiceParams } = note;
1007
+ const baseRate = voiceParams.playbackRate;
953
1008
  note.bufferSource.playbackRate
954
1009
  .cancelScheduledValues(now)
955
- .setValueAtTime(note.voiceParams.playbackRate, now);
956
- }
957
- setPitch(channel, note) {
958
- const now = this.audioContext.currentTime;
959
- const { startTime } = note;
960
- const basePitch = this.calcSemitoneOffset(channel) * 100;
961
- note.bufferSource.detune
962
- .cancelScheduledValues(now)
963
- .setValueAtTime(basePitch, startTime);
964
- const modEnvToPitch = note.voiceParams.modEnvToPitch;
1010
+ .setValueAtTime(baseRate, now);
1011
+ const modEnvToPitch = voiceParams.modEnvToPitch;
965
1012
  if (modEnvToPitch === 0)
966
1013
  return;
1014
+ const basePitch = this.rateToCent(baseRate);
967
1015
  const peekPitch = basePitch + modEnvToPitch;
1016
+ const peekRate = this.centToRate(peekPitch);
968
1017
  const modDelay = startTime + voiceParams.modDelay;
969
1018
  const modAttack = modDelay + voiceParams.modAttack;
970
1019
  const modHold = modAttack + voiceParams.modHold;
971
1020
  const modDecay = modHold + voiceParams.modDecay;
972
- note.bufferSource.detune
973
- .setValueAtTime(basePitch, modDelay)
974
- .exponentialRampToValueAtTime(peekPitch, modAttack)
975
- .setValueAtTime(peekPitch, modHold)
976
- .linearRampToValueAtTime(basePitch, modDecay);
1021
+ note.bufferSource.playbackRate
1022
+ .setValueAtTime(baseRate, modDelay)
1023
+ .exponentialRampToValueAtTime(peekRate, modAttack)
1024
+ .setValueAtTime(peekRate, modHold)
1025
+ .linearRampToValueAtTime(baseRate, modDecay);
977
1026
  }
978
1027
  clampCutoffFrequency(frequency) {
979
1028
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -986,14 +1035,17 @@ class Midy {
986
1035
  const { voiceParams, noteNumber, startTime } = note;
987
1036
  const softPedalFactor = 1 -
988
1037
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
989
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
990
- softPedalFactor * state.brightness * 2;
1038
+ const pressureDepth = (channel.pressureTable[1] - 64) * 15;
1039
+ const pressure = pressureDepth * channel.state.channelPressure;
1040
+ const baseCent = voiceParams.initialFilterFc + pressure;
1041
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor *
1042
+ state.brightness * 2;
991
1043
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
992
1044
  const sustainFreq = baseFreq +
993
1045
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
994
1046
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
995
1047
  const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
996
- const portamentoTime = startTime + channel.state.portamentoTime;
1048
+ const portamentoTime = startTime + this.getPortamentoTime(channel);
997
1049
  const modDelay = startTime + voiceParams.modDelay;
998
1050
  note.filterNode.frequency
999
1051
  .cancelScheduledValues(now)
@@ -1038,14 +1090,14 @@ class Midy {
1038
1090
  note.modulationDepth = new GainNode(this.audioContext);
1039
1091
  this.setModLfoToPitch(channel, note);
1040
1092
  note.volumeDepth = new GainNode(this.audioContext);
1041
- this.setModLfoToVolume(note);
1093
+ this.setModLfoToVolume(channel, note);
1042
1094
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1043
1095
  note.modulationLFO.connect(note.filterDepth);
1044
1096
  note.filterDepth.connect(note.filterNode.frequency);
1045
1097
  note.modulationLFO.connect(note.modulationDepth);
1046
1098
  note.modulationDepth.connect(note.bufferSource.detune);
1047
1099
  note.modulationLFO.connect(note.volumeDepth);
1048
- note.volumeDepth.connect(note.volumeNode.gain);
1100
+ note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
1049
1101
  }
1050
1102
  startVibrato(channel, note, startTime) {
1051
1103
  const { voiceParams } = note;
@@ -1067,6 +1119,9 @@ class Midy {
1067
1119
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1068
1120
  note.bufferSource = await this.createNoteBufferNode(voiceParams, isSF3);
1069
1121
  note.volumeNode = new GainNode(this.audioContext);
1122
+ note.gainL = new GainNode(this.audioContext);
1123
+ note.gainR = new GainNode(this.audioContext);
1124
+ note.volumeEnvelopeNode = new GainNode(this.audioContext);
1070
1125
  note.filterNode = new BiquadFilterNode(this.audioContext, {
1071
1126
  type: "lowpass",
1072
1127
  Q: voiceParams.initialFilterQ / 5 * state.filterResonance, // dB
@@ -1084,9 +1139,8 @@ class Midy {
1084
1139
  if (0 < state.vibratoDepth) {
1085
1140
  this.startVibrato(channel, note, startTime);
1086
1141
  }
1087
- this.setPlaybackRate(note);
1142
+ this.setPitchEnvelope(note);
1088
1143
  if (0 < state.modulationDepth) {
1089
- this.setPitch(channel, note);
1090
1144
  this.startModulation(channel, note, startTime);
1091
1145
  }
1092
1146
  if (this.mono && channel.currentBufferSource) {
@@ -1094,7 +1148,10 @@ class Midy {
1094
1148
  channel.currentBufferSource = note.bufferSource;
1095
1149
  }
1096
1150
  note.bufferSource.connect(note.filterNode);
1097
- note.filterNode.connect(note.volumeNode);
1151
+ note.filterNode.connect(note.volumeEnvelopeNode);
1152
+ note.volumeEnvelopeNode.connect(note.volumeNode);
1153
+ note.volumeNode.connect(note.gainL);
1154
+ note.volumeNode.connect(note.gainR);
1098
1155
  if (0 < channel.chorusSendLevel) {
1099
1156
  this.setChorusEffectsSend(channel, note, 0);
1100
1157
  }
@@ -1125,8 +1182,8 @@ class Midy {
1125
1182
  if (!voice)
1126
1183
  return;
1127
1184
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1128
- note.volumeNode.connect(channel.gainL);
1129
- note.volumeNode.connect(channel.gainR);
1185
+ note.gainL.connect(channel.gainL);
1186
+ note.gainR.connect(channel.gainR);
1130
1187
  if (channel.state.sostenutoPedal) {
1131
1188
  channel.sostenutoNotes.set(noteNumber, note);
1132
1189
  }
@@ -1157,7 +1214,7 @@ class Midy {
1157
1214
  }
1158
1215
  stopNote(endTime, stopTime, scheduledNotes, index) {
1159
1216
  const note = scheduledNotes[index];
1160
- note.volumeNode.gain
1217
+ note.volumeEnvelopeNode.gain
1161
1218
  .cancelScheduledValues(endTime)
1162
1219
  .linearRampToValueAtTime(0, stopTime);
1163
1220
  note.ending = true;
@@ -1168,8 +1225,11 @@ class Midy {
1168
1225
  note.bufferSource.onended = () => {
1169
1226
  scheduledNotes[index] = null;
1170
1227
  note.bufferSource.disconnect();
1171
- note.volumeNode.disconnect();
1172
1228
  note.filterNode.disconnect();
1229
+ note.volumeEnvelopeNode.disconnect();
1230
+ note.volumeNode.disconnect();
1231
+ note.gainL.disconnect();
1232
+ note.gainR.disconnect();
1173
1233
  if (note.modulationDepth) {
1174
1234
  note.volumeDepth.disconnect();
1175
1235
  note.modulationDepth.disconnect();
@@ -1219,12 +1279,13 @@ class Midy {
1219
1279
  return this.stopNote(endTime, stopTime, scheduledNotes, i);
1220
1280
  }
1221
1281
  else {
1222
- const portamentoTime = endTime + state.portamentoTime;
1223
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1224
- const detune = note.bufferSource.detune.value + detuneChange;
1225
- note.bufferSource.detune
1282
+ const portamentoTime = endTime + this.getPortamentoTime(channel);
1283
+ const deltaNote = portamentoNoteNumber - noteNumber;
1284
+ const baseRate = note.voiceParams.playbackRate;
1285
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1286
+ note.bufferSource.playbackRate
1226
1287
  .cancelScheduledValues(endTime)
1227
- .linearRampToValueAtTime(detune, portamentoTime);
1288
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1228
1289
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1229
1290
  }
1230
1291
  }
@@ -1293,7 +1354,7 @@ class Midy {
1293
1354
  if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
1294
1355
  if (activeNotes.has(noteNumber)) {
1295
1356
  const activeNote = activeNotes.get(noteNumber);
1296
- const gain = activeNote.volumeNode.gain.value;
1357
+ const gain = activeNote.gainL.gain.value;
1297
1358
  activeNote.volumeNode.gain
1298
1359
  .cancelScheduledValues(now)
1299
1360
  .setValueAtTime(gain * pressure, now);
@@ -1306,22 +1367,45 @@ class Midy {
1306
1367
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1307
1368
  channel.program = program;
1308
1369
  }
1309
- handleChannelPressure(channelNumber, pressure) {
1310
- const now = this.audioContext.currentTime;
1370
+ handleChannelPressure(channelNumber, value) {
1311
1371
  const channel = this.channels[channelNumber];
1312
- pressure /= 64;
1313
- channel.channelPressure = pressure;
1314
- const activeNotes = this.getActiveNotes(channel, now);
1315
- if (channel.channelPressure.amplitudeControl !== 1) {
1316
- activeNotes.forEach((activeNote) => {
1317
- const gain = activeNote.volumeNode.gain.value;
1318
- activeNote.volumeNode.gain
1319
- .cancelScheduledValues(now)
1320
- .setValueAtTime(gain * pressure, now);
1321
- });
1372
+ const prev = channel.state.channelPressure;
1373
+ const next = value / 127;
1374
+ channel.state.channelPressure = next;
1375
+ if (channel.pressureTable[0] !== 64) {
1376
+ const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
1377
+ channel.detune += pressureDepth * (next - prev);
1322
1378
  }
1379
+ channel.scheduledNotes.forEach((noteList) => {
1380
+ for (let i = 0; i < noteList.length; i++) {
1381
+ const note = noteList[i];
1382
+ if (!note)
1383
+ continue;
1384
+ this.setChannelPressure(channel, note);
1385
+ }
1386
+ });
1323
1387
  // this.applyVoiceParams(channel, 13);
1324
1388
  }
1389
+ setChannelPressure(channel, note) {
1390
+ if (channel.pressureTable[0] !== 64) {
1391
+ this.updateDetune(channel);
1392
+ }
1393
+ if (channel.pressureTable[1] !== 64 && !note.portamento) {
1394
+ this.setFilterEnvelope(channel, note);
1395
+ }
1396
+ if (channel.pressureTable[2] !== 64 && !note.portamento) {
1397
+ this.setVolumeEnvelope(channel, note);
1398
+ }
1399
+ if (channel.pressureTable[3] !== 0) {
1400
+ this.setModLfoToPitch(channel, note);
1401
+ }
1402
+ if (channel.pressureTable[4] !== 0) {
1403
+ this.setModLfoToFilterFc(channel, note);
1404
+ }
1405
+ if (channel.pressureTable[5] !== 0) {
1406
+ this.setModLfoToVolume(channel, note);
1407
+ }
1408
+ }
1325
1409
  handlePitchBendMessage(channelNumber, lsb, msb) {
1326
1410
  const pitchBend = msb * 128 + lsb;
1327
1411
  this.setPitchBend(channelNumber, pitchBend);
@@ -1329,98 +1413,107 @@ class Midy {
1329
1413
  setPitchBend(channelNumber, value) {
1330
1414
  const channel = this.channels[channelNumber];
1331
1415
  const state = channel.state;
1416
+ const prev = state.pitchWheel * 2 - 1;
1417
+ const next = (value - 8192) / 8192;
1332
1418
  state.pitchWheel = value / 16383;
1333
- const pitchWheel = (value - 8192) / 8192;
1334
- const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1335
- this.updateDetune(channel, detuneChange);
1419
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1420
+ this.updateDetune(channel);
1336
1421
  this.applyVoiceParams(channel, 14);
1337
1422
  }
1338
1423
  setModLfoToPitch(channel, note) {
1339
1424
  const now = this.audioContext.currentTime;
1340
- const modLfoToPitch = note.voiceParams.modLfoToPitch;
1341
- const modulationDepth = Math.abs(modLfoToPitch) +
1425
+ const pressureDepth = channel.pressureTable[3] / 127 * 600;
1426
+ const pressure = pressureDepth * channel.state.channelPressure;
1427
+ const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1428
+ const baseDepth = Math.abs(modLfoToPitch) +
1342
1429
  channel.state.modulationDepth;
1343
- const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
1430
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1344
1431
  note.modulationDepth.gain
1345
1432
  .cancelScheduledValues(now)
1346
- .setValueAtTime(modulationDepth * modulationDepthSign, now);
1433
+ .setValueAtTime(modulationDepth, now);
1347
1434
  }
1348
- setModLfoToVolume(note) {
1435
+ setVibLfoToPitch(channel, note) {
1436
+ const now = this.audioContext.currentTime;
1437
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1438
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1439
+ 2;
1440
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1441
+ note.vibratoDepth.gain
1442
+ .cancelScheduledValues(now)
1443
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1444
+ }
1445
+ setModLfoToFilterFc(channel, note) {
1446
+ const now = this.audioContext.currentTime;
1447
+ const pressureDepth = channel.pressureTable[4] / 127 * 2400;
1448
+ const pressure = pressureDepth * channel.state.channelPressure;
1449
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1450
+ note.filterDepth.gain
1451
+ .cancelScheduledValues(now)
1452
+ .setValueAtTime(modLfoToFilterFc, now);
1453
+ }
1454
+ setModLfoToVolume(channel, note) {
1349
1455
  const now = this.audioContext.currentTime;
1350
1456
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1351
- const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1352
- const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
1457
+ const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1458
+ const pressureDepth = channel.pressureTable[5] / 127;
1459
+ const pressure = 1 + pressureDepth * channel.state.channelPressure;
1460
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * pressure;
1353
1461
  note.volumeDepth.gain
1354
1462
  .cancelScheduledValues(now)
1355
- .setValueAtTime(volumeDepth * volumeDepthSign, now);
1463
+ .setValueAtTime(volumeDepth, now);
1356
1464
  }
1357
- setChorusEffectsSend(note, prevValue) {
1465
+ setReverbEffectsSend(channel, note, prevValue) {
1358
1466
  if (0 < prevValue) {
1359
- if (0 < note.voiceParams.chorusEffectsSend) {
1467
+ if (0 < note.voiceParams.reverbEffectsSend) {
1360
1468
  const now = this.audioContext.currentTime;
1361
- const value = note.voiceParams.chorusEffectsSend;
1362
- note.chorusEffectsSend.gain
1469
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1470
+ const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
1471
+ note.reverbEffectsSend.gain
1363
1472
  .cancelScheduledValues(now)
1364
1473
  .setValueAtTime(value, now);
1365
1474
  }
1366
1475
  else {
1367
- note.chorusEffectsSend.disconnect();
1476
+ note.reverbEffectsSend.disconnect();
1368
1477
  }
1369
1478
  }
1370
1479
  else {
1371
- if (0 < note.voiceParams.chorusEffectsSend) {
1372
- if (!note.chorusEffectsSend) {
1373
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1374
- gain: note.voiceParams.chorusEffectsSend,
1480
+ if (0 < note.voiceParams.reverbEffectsSend) {
1481
+ if (!note.reverbEffectsSend) {
1482
+ note.reverbEffectsSend = new GainNode(this.audioContext, {
1483
+ gain: note.voiceParams.reverbEffectsSend,
1375
1484
  });
1376
- note.volumeNode.connect(note.chorusEffectsSend);
1485
+ note.volumeNode.connect(note.reverbEffectsSend);
1377
1486
  }
1378
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1487
+ note.reverbEffectsSend.connect(this.reverbEffect.input);
1379
1488
  }
1380
1489
  }
1381
1490
  }
1382
- setReverbEffectsSend(note, prevValue) {
1491
+ setChorusEffectsSend(channel, note, prevValue) {
1383
1492
  if (0 < prevValue) {
1384
- if (0 < note.voiceParams.reverbEffectsSend) {
1493
+ if (0 < note.voiceParams.chorusEffectsSend) {
1385
1494
  const now = this.audioContext.currentTime;
1386
- const value = note.voiceParams.reverbEffectsSend;
1387
- note.reverbEffectsSend.gain
1495
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1496
+ const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
1497
+ note.chorusEffectsSend.gain
1388
1498
  .cancelScheduledValues(now)
1389
1499
  .setValueAtTime(value, now);
1390
1500
  }
1391
1501
  else {
1392
- note.reverbEffectsSend.disconnect();
1502
+ note.chorusEffectsSend.disconnect();
1393
1503
  }
1394
1504
  }
1395
1505
  else {
1396
- if (0 < note.voiceParams.reverbEffectsSend) {
1397
- if (!note.reverbEffectsSend) {
1398
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1399
- gain: note.voiceParams.reverbEffectsSend,
1506
+ if (0 < note.voiceParams.chorusEffectsSend) {
1507
+ if (!note.chorusEffectsSend) {
1508
+ note.chorusEffectsSend = new GainNode(this.audioContext, {
1509
+ gain: note.voiceParams.chorusEffectsSend,
1400
1510
  });
1401
- note.volumeNode.connect(note.reverbEffectsSend);
1511
+ note.volumeNode.connect(note.chorusEffectsSend);
1402
1512
  }
1403
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1513
+ note.chorusEffectsSend.connect(this.chorusEffect.input);
1404
1514
  }
1405
1515
  }
1406
1516
  }
1407
- setVibLfoToPitch(channel, note) {
1408
- const now = this.audioContext.currentTime;
1409
- const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1410
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1411
- 2;
1412
- const vibratoDepthSign = 0 < vibLfoToPitch;
1413
- note.vibratoDepth.gain
1414
- .cancelScheduledValues(now)
1415
- .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1416
- }
1417
- setModLfoToFilterFc(note) {
1418
- const now = this.audioContext.currentTime;
1419
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1420
- note.filterDepth.gain
1421
- .cancelScheduledValues(now)
1422
- .setValueAtTime(modLfoToFilterFc, now);
1423
- }
1424
1517
  setDelayModLFO(note) {
1425
1518
  const now = this.audioContext.currentTime;
1426
1519
  const startTime = note.startTime;
@@ -1450,18 +1543,20 @@ class Midy {
1450
1543
  }
1451
1544
  },
1452
1545
  modLfoToFilterFc: (channel, note, _prevValue) => {
1453
- if (0 < channel.state.modulationDepth)
1454
- this.setModLfoToFilterFc(note);
1546
+ if (0 < channel.state.modulationDepth) {
1547
+ this.setModLfoToFilterFc(channel, note);
1548
+ }
1455
1549
  },
1456
1550
  modLfoToVolume: (channel, note) => {
1457
- if (0 < channel.state.modulationDepth)
1458
- this.setModLfoToVolume(note);
1551
+ if (0 < channel.state.modulationDepth) {
1552
+ this.setModLfoToVolume(channel, note);
1553
+ }
1459
1554
  },
1460
- chorusEffectsSend: (_channel, note, prevValue) => {
1461
- this.setChorusEffectsSend(note, prevValue);
1555
+ chorusEffectsSend: (channel, note, prevValue) => {
1556
+ this.setChorusEffectsSend(channel, note, prevValue);
1462
1557
  },
1463
- reverbEffectsSend: (_channel, note, prevValue) => {
1464
- this.setReverbEffectsSend(note, prevValue);
1558
+ reverbEffectsSend: (channel, note, prevValue) => {
1559
+ this.setReverbEffectsSend(channel, note, prevValue);
1465
1560
  },
1466
1561
  delayModLFO: (_channel, note, _prevValue) => this.setDelayModLFO(note),
1467
1562
  freqModLFO: (_channel, note, _prevValue) => this.setFreqModLFO(note),
@@ -1529,7 +1624,7 @@ class Midy {
1529
1624
  else {
1530
1625
  this.setFilterEnvelope(channel, note);
1531
1626
  }
1532
- this.setPitch(channel, note);
1627
+ this.setPitchEnvelope(note);
1533
1628
  }
1534
1629
  else if (volumeEnvelopeKeySet.has(key)) {
1535
1630
  if (appliedVolumeEnvelope)
@@ -1611,7 +1706,7 @@ class Midy {
1611
1706
  note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1612
1707
  }
1613
1708
  else {
1614
- this.setPitch(channel, note);
1709
+ this.setPitchEnvelope(note);
1615
1710
  this.startModulation(channel, note, now);
1616
1711
  }
1617
1712
  }
@@ -1628,10 +1723,27 @@ class Midy {
1628
1723
  const factor = 5 * Math.log(10) / 127;
1629
1724
  channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1630
1725
  }
1726
+ setKeyBasedVolume(channel) {
1727
+ const now = this.audioContext.currentTime;
1728
+ channel.scheduledNotes.forEach((noteList) => {
1729
+ for (let i = 0; i < noteList.length; i++) {
1730
+ const note = noteList[i];
1731
+ if (!note)
1732
+ continue;
1733
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1734
+ if (keyBasedValue === 0)
1735
+ continue;
1736
+ note.volumeNode.gain
1737
+ .cancelScheduledValues(now)
1738
+ .setValueAtTime(1 + keyBasedValue, now);
1739
+ }
1740
+ });
1741
+ }
1631
1742
  setVolume(channelNumber, volume) {
1632
1743
  const channel = this.channels[channelNumber];
1633
1744
  channel.state.volume = volume / 127;
1634
1745
  this.updateChannelVolume(channel);
1746
+ this.setKeyBasedVolume(channel);
1635
1747
  }
1636
1748
  panToGain(pan) {
1637
1749
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1640,10 +1752,31 @@ class Midy {
1640
1752
  gainRight: Math.sin(theta),
1641
1753
  };
1642
1754
  }
1755
+ setKeyBasedPan(channel) {
1756
+ const now = this.audioContext.currentTime;
1757
+ channel.scheduledNotes.forEach((noteList) => {
1758
+ for (let i = 0; i < noteList.length; i++) {
1759
+ const note = noteList[i];
1760
+ if (!note)
1761
+ continue;
1762
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1763
+ if (keyBasedValue === 0)
1764
+ continue;
1765
+ const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
1766
+ note.gainL.gain
1767
+ .cancelScheduledValues(now)
1768
+ .setValueAtTime(gainLeft, now);
1769
+ note.gainR.gain
1770
+ .cancelScheduledValues(now)
1771
+ .setValueAtTime(gainRight, now);
1772
+ }
1773
+ });
1774
+ }
1643
1775
  setPan(channelNumber, pan) {
1644
1776
  const channel = this.channels[channelNumber];
1645
1777
  channel.state.pan = pan / 127;
1646
1778
  this.updateChannelVolume(channel);
1779
+ this.setKeyBasedPan(channel);
1647
1780
  }
1648
1781
  setExpression(channelNumber, expression) {
1649
1782
  const channel = this.channels[channelNumber];
@@ -1678,88 +1811,6 @@ class Midy {
1678
1811
  setPortamento(channelNumber, value) {
1679
1812
  this.channels[channelNumber].state.portamento = value / 127;
1680
1813
  }
1681
- setReverbSendLevel(channelNumber, reverbSendLevel) {
1682
- const channel = this.channels[channelNumber];
1683
- const state = channel.state;
1684
- const reverbEffect = this.reverbEffect;
1685
- if (0 < state.reverbSendLevel) {
1686
- if (0 < reverbSendLevel) {
1687
- const now = this.audioContext.currentTime;
1688
- state.reverbSendLevel = reverbSendLevel / 127;
1689
- reverbEffect.input.gain.cancelScheduledValues(now);
1690
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1691
- }
1692
- else {
1693
- channel.scheduledNotes.forEach((noteList) => {
1694
- for (let i = 0; i < noteList.length; i++) {
1695
- const note = noteList[i];
1696
- if (!note)
1697
- continue;
1698
- if (note.voiceParams.reverbEffectsSend <= 0)
1699
- continue;
1700
- note.reverbEffectsSend.disconnect();
1701
- }
1702
- });
1703
- }
1704
- }
1705
- else {
1706
- if (0 < reverbSendLevel) {
1707
- const now = this.audioContext.currentTime;
1708
- channel.scheduledNotes.forEach((noteList) => {
1709
- for (let i = 0; i < noteList.length; i++) {
1710
- const note = noteList[i];
1711
- if (!note)
1712
- continue;
1713
- this.setReverbEffectsSend(note, 0);
1714
- }
1715
- });
1716
- state.reverbSendLevel = reverbSendLevel / 127;
1717
- reverbEffect.input.gain.cancelScheduledValues(now);
1718
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1719
- }
1720
- }
1721
- }
1722
- setChorusSendLevel(channelNumber, chorusSendLevel) {
1723
- const channel = this.channels[channelNumber];
1724
- const state = channel.state;
1725
- const chorusEffect = this.chorusEffect;
1726
- if (0 < state.chorusSendLevel) {
1727
- if (0 < chorusSendLevel) {
1728
- const now = this.audioContext.currentTime;
1729
- state.chorusSendLevel = chorusSendLevel / 127;
1730
- chorusEffect.input.gain.cancelScheduledValues(now);
1731
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1732
- }
1733
- else {
1734
- channel.scheduledNotes.forEach((noteList) => {
1735
- for (let i = 0; i < noteList.length; i++) {
1736
- const note = noteList[i];
1737
- if (!note)
1738
- continue;
1739
- if (note.voiceParams.chorusEffectsSend <= 0)
1740
- continue;
1741
- note.chorusEffectsSend.disconnect();
1742
- }
1743
- });
1744
- }
1745
- }
1746
- else {
1747
- if (0 < chorusSendLevel) {
1748
- const now = this.audioContext.currentTime;
1749
- channel.scheduledNotes.forEach((noteList) => {
1750
- for (let i = 0; i < noteList.length; i++) {
1751
- const note = noteList[i];
1752
- if (!note)
1753
- continue;
1754
- this.setChorusEffectsSend(note, 0);
1755
- }
1756
- });
1757
- state.chorusSendLevel = chorusSendLevel / 127;
1758
- chorusEffect.input.gain.cancelScheduledValues(now);
1759
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1760
- }
1761
- }
1762
- }
1763
1814
  setSostenutoPedal(channelNumber, value) {
1764
1815
  const channel = this.channels[channelNumber];
1765
1816
  channel.state.sostenutoPedal = value / 127;
@@ -1855,6 +1906,88 @@ class Midy {
1855
1906
  const channel = this.channels[channelNumber];
1856
1907
  channel.state.vibratoDelay = vibratoDelay / 64;
1857
1908
  }
1909
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1910
+ const channel = this.channels[channelNumber];
1911
+ const state = channel.state;
1912
+ const reverbEffect = this.reverbEffect;
1913
+ if (0 < state.reverbSendLevel) {
1914
+ if (0 < reverbSendLevel) {
1915
+ const now = this.audioContext.currentTime;
1916
+ state.reverbSendLevel = reverbSendLevel / 127;
1917
+ reverbEffect.input.gain.cancelScheduledValues(now);
1918
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1919
+ }
1920
+ else {
1921
+ channel.scheduledNotes.forEach((noteList) => {
1922
+ for (let i = 0; i < noteList.length; i++) {
1923
+ const note = noteList[i];
1924
+ if (!note)
1925
+ continue;
1926
+ if (note.voiceParams.reverbEffectsSend <= 0)
1927
+ continue;
1928
+ note.reverbEffectsSend.disconnect();
1929
+ }
1930
+ });
1931
+ }
1932
+ }
1933
+ else {
1934
+ if (0 < reverbSendLevel) {
1935
+ const now = this.audioContext.currentTime;
1936
+ channel.scheduledNotes.forEach((noteList) => {
1937
+ for (let i = 0; i < noteList.length; i++) {
1938
+ const note = noteList[i];
1939
+ if (!note)
1940
+ continue;
1941
+ this.setReverbEffectsSend(channel, note, 0);
1942
+ }
1943
+ });
1944
+ state.reverbSendLevel = reverbSendLevel / 127;
1945
+ reverbEffect.input.gain.cancelScheduledValues(now);
1946
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1947
+ }
1948
+ }
1949
+ }
1950
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1951
+ const channel = this.channels[channelNumber];
1952
+ const state = channel.state;
1953
+ const chorusEffect = this.chorusEffect;
1954
+ if (0 < state.chorusSendLevel) {
1955
+ if (0 < chorusSendLevel) {
1956
+ const now = this.audioContext.currentTime;
1957
+ state.chorusSendLevel = chorusSendLevel / 127;
1958
+ chorusEffect.input.gain.cancelScheduledValues(now);
1959
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1960
+ }
1961
+ else {
1962
+ channel.scheduledNotes.forEach((noteList) => {
1963
+ for (let i = 0; i < noteList.length; i++) {
1964
+ const note = noteList[i];
1965
+ if (!note)
1966
+ continue;
1967
+ if (note.voiceParams.chorusEffectsSend <= 0)
1968
+ continue;
1969
+ note.chorusEffectsSend.disconnect();
1970
+ }
1971
+ });
1972
+ }
1973
+ }
1974
+ else {
1975
+ if (0 < chorusSendLevel) {
1976
+ const now = this.audioContext.currentTime;
1977
+ channel.scheduledNotes.forEach((noteList) => {
1978
+ for (let i = 0; i < noteList.length; i++) {
1979
+ const note = noteList[i];
1980
+ if (!note)
1981
+ continue;
1982
+ this.setChorusEffectsSend(channel, note, 0);
1983
+ }
1984
+ });
1985
+ state.chorusSendLevel = chorusSendLevel / 127;
1986
+ chorusEffect.input.gain.cancelScheduledValues(now);
1987
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1988
+ }
1989
+ }
1990
+ }
1858
1991
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1859
1992
  if (maxLSB < channel.dataLSB) {
1860
1993
  channel.dataMSB++;
@@ -1923,59 +2056,49 @@ class Midy {
1923
2056
  this.channels[channelNumber].dataMSB = value;
1924
2057
  this.handleRPN(channelNumber, 0);
1925
2058
  }
1926
- updateDetune(channel, detune) {
1927
- const now = this.audioContext.currentTime;
1928
- channel.scheduledNotes.forEach((noteList) => {
1929
- for (let i = 0; i < noteList.length; i++) {
1930
- const note = noteList[i];
1931
- if (!note)
1932
- continue;
1933
- const { bufferSource } = note;
1934
- bufferSource.detune
1935
- .cancelScheduledValues(now)
1936
- .setValueAtTime(detune, now);
1937
- }
1938
- });
1939
- }
1940
2059
  handlePitchBendRangeRPN(channelNumber) {
1941
2060
  const channel = this.channels[channelNumber];
1942
2061
  this.limitData(channel, 0, 127, 0, 99);
1943
2062
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1944
2063
  this.setPitchBendRange(channelNumber, pitchBendRange);
1945
2064
  }
1946
- setPitchBendRange(channelNumber, pitchWheelSensitivity) {
2065
+ setPitchBendRange(channelNumber, value) {
1947
2066
  const channel = this.channels[channelNumber];
1948
2067
  const state = channel.state;
1949
- state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1950
- const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1951
- this.updateDetune(channel, detune);
2068
+ const prev = state.pitchWheelSensitivity;
2069
+ const next = value / 128;
2070
+ state.pitchWheelSensitivity = next;
2071
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
2072
+ this.updateDetune(channel);
1952
2073
  this.applyVoiceParams(channel, 16);
1953
2074
  }
1954
2075
  handleFineTuningRPN(channelNumber) {
1955
2076
  const channel = this.channels[channelNumber];
1956
2077
  this.limitData(channel, 0, 127, 0, 127);
1957
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
2078
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1958
2079
  this.setFineTuning(channelNumber, fineTuning);
1959
2080
  }
1960
- setFineTuning(channelNumber, fineTuning) {
2081
+ setFineTuning(channelNumber, value) {
1961
2082
  const channel = this.channels[channelNumber];
1962
- const prevFineTuning = channel.fineTuning;
1963
- channel.fineTuning = fineTuning;
1964
- const detuneChange = channel.fineTuning - prevFineTuning;
1965
- this.updateDetune(channel, detuneChange);
2083
+ const prev = channel.fineTuning;
2084
+ const next = (value - 8192) / 8.192; // cent
2085
+ channel.fineTuning = next;
2086
+ channel.detune += next - prev;
2087
+ this.updateDetune(channel);
1966
2088
  }
1967
2089
  handleCoarseTuningRPN(channelNumber) {
1968
2090
  const channel = this.channels[channelNumber];
1969
2091
  this.limitDataMSB(channel, 0, 127);
1970
- const coarseTuning = channel.dataMSB - 64;
1971
- this.setFineTuning(channelNumber, coarseTuning);
2092
+ const coarseTuning = channel.dataMSB;
2093
+ this.setCoarseTuning(channelNumber, coarseTuning);
1972
2094
  }
1973
- setCoarseTuning(channelNumber, coarseTuning) {
2095
+ setCoarseTuning(channelNumber, value) {
1974
2096
  const channel = this.channels[channelNumber];
1975
- const prevCoarseTuning = channel.coarseTuning;
1976
- channel.coarseTuning = coarseTuning;
1977
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1978
- this.updateDetune(channel, detuneChange);
2097
+ const prev = channel.coarseTuning;
2098
+ const next = (value - 64) * 100; // cent
2099
+ channel.coarseTuning = next;
2100
+ channel.detune += next - prev;
2101
+ this.updateDetune(channel);
1979
2102
  }
1980
2103
  handleModulationDepthRangeRPN(channelNumber) {
1981
2104
  const channel = this.channels[channelNumber];
@@ -2035,6 +2158,15 @@ class Midy {
2035
2158
  }
2036
2159
  handleUniversalNonRealTimeExclusiveMessage(data) {
2037
2160
  switch (data[2]) {
2161
+ case 8:
2162
+ switch (data[3]) {
2163
+ case 8:
2164
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2165
+ return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
2166
+ default:
2167
+ console.warn(`Unsupported Exclusive Message: ${data}`);
2168
+ }
2169
+ break;
2038
2170
  case 9:
2039
2171
  switch (data[3]) {
2040
2172
  case 1:
@@ -2083,7 +2215,7 @@ class Midy {
2083
2215
  return this.handleMasterFineTuningSysEx(data);
2084
2216
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
2085
2217
  return this.handleMasterCoarseTuningSysEx(data);
2086
- case 5:
2218
+ case 5: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca24.pdf
2087
2219
  return this.handleGlobalParameterControlSysEx(data);
2088
2220
  default:
2089
2221
  console.warn(`Unsupported Exclusive Message: ${data}`);
@@ -2091,18 +2223,17 @@ class Midy {
2091
2223
  break;
2092
2224
  case 8:
2093
2225
  switch (data[3]) {
2094
- // case 8:
2095
- // // TODO
2096
- // return this.handleScaleOctaveTuning1ByteFormat();
2226
+ case 8: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2227
+ // TODO: realtime
2228
+ return this.handleScaleOctaveTuning1ByteFormatSysEx(data);
2097
2229
  default:
2098
2230
  console.warn(`Unsupported Exclusive Message: ${data}`);
2099
2231
  }
2100
2232
  break;
2101
2233
  case 9:
2102
2234
  switch (data[3]) {
2103
- // case 1:
2104
- // // TODO
2105
- // return this.setChannelPressure();
2235
+ case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2236
+ return this.handleChannelPressureSysEx(data);
2106
2237
  // case 3:
2107
2238
  // // TODO
2108
2239
  // return this.setControlChange();
@@ -2112,9 +2243,8 @@ class Midy {
2112
2243
  break;
2113
2244
  case 10:
2114
2245
  switch (data[3]) {
2115
- // case 1:
2116
- // // TODO
2117
- // return this.handleKeyBasedInstrumentControl();
2246
+ case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca23.pdf
2247
+ return this.handleKeyBasedInstrumentControlSysEx(data);
2118
2248
  default:
2119
2249
  console.warn(`Unsupported Exclusive Message: ${data}`);
2120
2250
  }
@@ -2138,28 +2268,26 @@ class Midy {
2138
2268
  }
2139
2269
  }
2140
2270
  handleMasterFineTuningSysEx(data) {
2141
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2271
+ const fineTuning = data[5] * 128 + data[4];
2142
2272
  this.setMasterFineTuning(fineTuning);
2143
2273
  }
2144
- setMasterFineTuning(fineTuning) {
2145
- if (fineTuning < -1 && 1 < fineTuning) {
2146
- console.error("Master Fine Tuning value is out of range");
2147
- }
2148
- else {
2149
- this.masterFineTuning = fineTuning;
2150
- }
2274
+ setMasterFineTuning(value) {
2275
+ const prev = this.masterFineTuning;
2276
+ const next = (value - 8192) / 8.192; // cent
2277
+ this.masterFineTuning = next;
2278
+ channel.detune += next - prev;
2279
+ this.updateDetune(channel);
2151
2280
  }
2152
2281
  handleMasterCoarseTuningSysEx(data) {
2153
2282
  const coarseTuning = data[4];
2154
2283
  this.setMasterCoarseTuning(coarseTuning);
2155
2284
  }
2156
- setMasterCoarseTuning(coarseTuning) {
2157
- if (coarseTuning < 0 && 127 < coarseTuning) {
2158
- console.error("Master Coarse Tuning value is out of range");
2159
- }
2160
- else {
2161
- this.masterCoarseTuning = coarseTuning - 64;
2162
- }
2285
+ setMasterCoarseTuning(value) {
2286
+ const prev = this.masterCoarseTuning;
2287
+ const next = (value - 64) * 100; // cent
2288
+ this.masterCoarseTuning = next;
2289
+ channel.detune += next - prev;
2290
+ this.updateDetune(channel);
2163
2291
  }
2164
2292
  handleGlobalParameterControlSysEx(data) {
2165
2293
  if (data[7] === 1) {
@@ -2347,6 +2475,66 @@ class Midy {
2347
2475
  getChorusSendToReverb(value) {
2348
2476
  return value * 0.00787;
2349
2477
  }
2478
+ getChannelBitmap(data) {
2479
+ const bitmap = new Array(16).fill(false);
2480
+ const ff = data[4] & 0b11;
2481
+ const gg = data[5] & 0x7F;
2482
+ const hh = data[6] & 0x7F;
2483
+ for (let bit = 0; bit < 7; bit++) {
2484
+ if (hh & (1 << bit))
2485
+ bitmap[bit] = true;
2486
+ }
2487
+ for (let bit = 0; bit < 7; bit++) {
2488
+ if (gg & (1 << bit))
2489
+ bitmap[bit + 7] = true;
2490
+ }
2491
+ for (let bit = 0; bit < 2; bit++) {
2492
+ if (ff & (1 << bit))
2493
+ bitmap[bit + 14] = true;
2494
+ }
2495
+ return bitmap;
2496
+ }
2497
+ handleScaleOctaveTuning1ByteFormatSysEx(data) {
2498
+ if (data.length < 18) {
2499
+ console.error("Data length is too short");
2500
+ return;
2501
+ }
2502
+ const channelBitmap = this.getChannelBitmap(data);
2503
+ for (let i = 0; i < channelBitmap.length; i++) {
2504
+ if (!channelBitmap[i])
2505
+ continue;
2506
+ for (let j = 0; j < 12; j++) {
2507
+ const value = data[j + 7] - 64; // cent
2508
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2509
+ }
2510
+ }
2511
+ }
2512
+ handleChannelPressureSysEx(data) {
2513
+ const channelNumber = data[4];
2514
+ const table = this.channels[channelNumber].pressureTable;
2515
+ for (let i = 5; i < data.length - 1; i += 2) {
2516
+ const pp = data[i];
2517
+ const rr = data[i + 1];
2518
+ table[pp] = rr;
2519
+ }
2520
+ }
2521
+ getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2522
+ const index = keyNumber * 128 + controllerType;
2523
+ const controlValue = channel.keyBasedInstrumentControlTable[index];
2524
+ return (controlValue + 64) / 64;
2525
+ }
2526
+ handleKeyBasedInstrumentControlSysEx(data) {
2527
+ const channelNumber = data[4];
2528
+ const keyNumber = data[5];
2529
+ const table = this.channels[channelNumber].keyBasedInstrumentControlTable;
2530
+ for (let i = 6; i < data.length - 1; i += 2) {
2531
+ const controllerType = data[i];
2532
+ const value = data[i + 1];
2533
+ const index = keyNumber * 128 + controllerType;
2534
+ table[index] = value - 64;
2535
+ }
2536
+ this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127);
2537
+ }
2350
2538
  handleExclusiveMessage(data) {
2351
2539
  console.warn(`Unsupported Exclusive Message: ${data}`);
2352
2540
  }
@@ -2379,6 +2567,10 @@ Object.defineProperty(Midy, "channelSettings", {
2379
2567
  writable: true,
2380
2568
  value: {
2381
2569
  currentBufferSource: null,
2570
+ detune: 0,
2571
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2572
+ pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2573
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2382
2574
  program: 0,
2383
2575
  bank: 121 * 128,
2384
2576
  bankMSB: 121,