@marmooo/midy 0.2.2 → 0.2.4

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
@@ -89,6 +89,12 @@ class Note {
89
89
  writable: true,
90
90
  value: void 0
91
91
  });
92
+ Object.defineProperty(this, "pressure", {
93
+ enumerable: true,
94
+ configurable: true,
95
+ writable: true,
96
+ value: 0
97
+ });
92
98
  this.noteNumber = noteNumber;
93
99
  this.velocity = velocity;
94
100
  this.startTime = startTime;
@@ -100,7 +106,7 @@ class Note {
100
106
  const defaultControllerState = {
101
107
  noteOnVelocity: { type: 2, defaultValue: 0 },
102
108
  noteOnKeyNumber: { type: 3, defaultValue: 0 },
103
- polyPressure: { type: 10, defaultValue: 0 },
109
+ polyphonicKeyPressure: { type: 10, defaultValue: 0 },
104
110
  channelPressure: { type: 13, defaultValue: 0 },
105
111
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
106
112
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
@@ -362,15 +368,15 @@ class Midy {
362
368
  });
363
369
  this.audioContext = audioContext;
364
370
  this.options = { ...this.defaultOptions, ...options };
365
- this.masterGain = new GainNode(audioContext);
371
+ this.masterVolume = new GainNode(audioContext);
366
372
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
367
373
  this.controlChangeHandlers = this.createControlChangeHandlers();
368
374
  this.channels = this.createChannels(audioContext);
369
375
  this.reverbEffect = this.options.reverbAlgorithm(audioContext);
370
376
  this.chorusEffect = this.createChorusEffect(audioContext);
371
- this.chorusEffect.output.connect(this.masterGain);
372
- this.reverbEffect.output.connect(this.masterGain);
373
- this.masterGain.connect(audioContext.destination);
377
+ this.chorusEffect.output.connect(this.masterVolume);
378
+ this.reverbEffect.output.connect(this.masterVolume);
379
+ this.masterVolume.connect(audioContext.destination);
374
380
  this.GM2SystemOn();
375
381
  }
376
382
  initSoundFontTable() {
@@ -416,7 +422,7 @@ class Midy {
416
422
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
417
423
  gainL.connect(merger, 0, 0);
418
424
  gainR.connect(merger, 0, 1);
419
- merger.connect(this.masterGain);
425
+ merger.connect(this.masterVolume);
420
426
  return {
421
427
  gainL,
422
428
  gainR,
@@ -428,15 +434,10 @@ class Midy {
428
434
  return {
429
435
  ...this.constructor.channelSettings,
430
436
  state: new ControllerState(),
437
+ controlTable: this.initControlTable(),
431
438
  ...this.setChannelAudioNodes(audioContext),
432
439
  scheduledNotes: new Map(),
433
440
  sostenutoNotes: new Map(),
434
- polyphonicKeyPressure: {
435
- ...this.constructor.controllerDestinationSettings,
436
- },
437
- channelPressure: {
438
- ...this.constructor.controllerDestinationSettings,
439
- },
440
441
  };
441
442
  });
442
443
  return channels;
@@ -941,28 +942,31 @@ class Midy {
941
942
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
942
943
  const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
943
944
  const pitch = pitchWheel * pitchWheelSensitivity;
944
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
945
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
945
946
  const pressure = pressureDepth * channel.state.channelPressure;
946
947
  return tuning + pitch + pressure;
947
948
  }
948
949
  calcNoteDetune(channel, note) {
949
950
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
950
951
  }
951
- updateDetune(channel) {
952
- const now = this.audioContext.currentTime;
952
+ updateChannelDetune(channel) {
953
953
  channel.scheduledNotes.forEach((noteList) => {
954
954
  for (let i = 0; i < noteList.length; i++) {
955
955
  const note = noteList[i];
956
956
  if (!note)
957
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);
958
+ this.updateDetune(channel, note, 0);
963
959
  }
964
960
  });
965
961
  }
962
+ updateDetune(channel, note, pressure) {
963
+ const now = this.audioContext.currentTime;
964
+ const noteDetune = this.calcNoteDetune(channel, note);
965
+ const detune = channel.detune + noteDetune + pressure;
966
+ note.bufferSource.detune
967
+ .cancelScheduledValues(now)
968
+ .setValueAtTime(detune, now);
969
+ }
966
970
  getPortamentoTime(channel) {
967
971
  const factor = 5 * Math.log(10) / 127;
968
972
  const time = channel.state.portamentoTime;
@@ -980,14 +984,12 @@ class Midy {
980
984
  .setValueAtTime(0, volDelay)
981
985
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
982
986
  }
983
- setVolumeEnvelope(channel, note) {
987
+ setVolumeEnvelope(channel, note, pressure) {
984
988
  const now = this.audioContext.currentTime;
985
989
  const state = channel.state;
986
990
  const { voiceParams, startTime } = note;
987
- const pressureDepth = channel.pressureTable[2] / 64;
988
- const pressure = 1 + pressureDepth * channel.state.channelPressure;
989
991
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
990
- pressure;
992
+ (1 + pressure);
991
993
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
992
994
  const volDelay = startTime + voiceParams.volDelay;
993
995
  const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
@@ -1035,10 +1037,8 @@ class Midy {
1035
1037
  const { voiceParams, noteNumber, startTime } = note;
1036
1038
  const softPedalFactor = 1 -
1037
1039
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
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 *
1040
+ const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1041
+ softPedalFactor *
1042
1042
  state.brightness * 2;
1043
1043
  const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
1044
1044
  const sustainFreq = baseFreq +
@@ -1053,15 +1053,17 @@ class Midy {
1053
1053
  .setValueAtTime(adjustedBaseFreq, modDelay)
1054
1054
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1055
1055
  }
1056
- setFilterEnvelope(channel, note) {
1056
+ setFilterEnvelope(channel, note, pressure) {
1057
1057
  const now = this.audioContext.currentTime;
1058
1058
  const state = channel.state;
1059
1059
  const { voiceParams, noteNumber, startTime } = note;
1060
1060
  const softPedalFactor = 1 -
1061
1061
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1062
- const baseFreq = this.centToHz(voiceParams.initialFilterFc) *
1062
+ const baseCent = voiceParams.initialFilterFc + pressure;
1063
+ const baseFreq = this.centToHz(baseCent) * softPedalFactor *
1064
+ state.brightness * 2;
1065
+ const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
1063
1066
  softPedalFactor * state.brightness * 2;
1064
- const peekFreq = this.centToHz(voiceParams.initialFilterFc + voiceParams.modEnvToFilterFc) * softPedalFactor * state.brightness * 2;
1065
1067
  const sustainFreq = baseFreq +
1066
1068
  (peekFreq - baseFreq) * (1 - voiceParams.modSustain);
1067
1069
  const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
@@ -1088,9 +1090,9 @@ class Midy {
1088
1090
  gain: voiceParams.modLfoToFilterFc,
1089
1091
  });
1090
1092
  note.modulationDepth = new GainNode(this.audioContext);
1091
- this.setModLfoToPitch(channel, note);
1093
+ this.setModLfoToPitch(channel, note, 0);
1092
1094
  note.volumeDepth = new GainNode(this.audioContext);
1093
- this.setModLfoToVolume(channel, note);
1095
+ this.setModLfoToVolume(note, 0);
1094
1096
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1095
1097
  note.modulationLFO.connect(note.filterDepth);
1096
1098
  note.filterDepth.connect(note.filterNode.frequency);
@@ -1103,8 +1105,7 @@ class Midy {
1103
1105
  const { voiceParams } = note;
1104
1106
  const state = channel.state;
1105
1107
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
1106
- frequency: this.centToHz(voiceParams.freqVibLFO) *
1107
- state.vibratoRate,
1108
+ frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
1108
1109
  });
1109
1110
  note.vibratoLFO.start(startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1110
1111
  note.vibratoDepth = new GainNode(this.audioContext);
@@ -1133,8 +1134,8 @@ class Midy {
1133
1134
  }
1134
1135
  else {
1135
1136
  note.portamento = false;
1136
- this.setVolumeEnvelope(channel, note);
1137
- this.setFilterEnvelope(channel, note);
1137
+ this.setVolumeEnvelope(channel, note, 0);
1138
+ this.setFilterEnvelope(channel, note, 0);
1138
1139
  }
1139
1140
  if (0 < state.vibratoDepth) {
1140
1141
  this.startVibrato(channel, note, startTime);
@@ -1349,16 +1350,12 @@ class Midy {
1349
1350
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
1350
1351
  const now = this.audioContext.currentTime;
1351
1352
  const channel = this.channels[channelNumber];
1352
- pressure /= 64;
1353
+ channel.state.polyphonicKeyPressure = pressure / 127;
1354
+ const table = channel.polyphonicKeyPressureTable;
1353
1355
  const activeNotes = this.getActiveNotes(channel, now);
1354
- if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
1355
- if (activeNotes.has(noteNumber)) {
1356
- const activeNote = activeNotes.get(noteNumber);
1357
- const gain = activeNote.gainL.gain.value;
1358
- activeNote.volumeNode.gain
1359
- .cancelScheduledValues(now)
1360
- .setValueAtTime(gain * pressure, now);
1361
- }
1356
+ if (activeNotes.has(noteNumber)) {
1357
+ const note = activeNotes.get(noteNumber);
1358
+ this.applyDestinationSettings(channel, note, table);
1362
1359
  }
1363
1360
  // this.applyVoiceParams(channel, 10);
1364
1361
  }
@@ -1372,40 +1369,21 @@ class Midy {
1372
1369
  const prev = channel.state.channelPressure;
1373
1370
  const next = value / 127;
1374
1371
  channel.state.channelPressure = next;
1375
- if (channel.pressureTable[0] !== 64) {
1376
- const pressureDepth = (channel.pressureTable[0] - 64) / 37.5; // 2400 / 64;
1372
+ if (channel.channelPressureTable[0] !== 64) {
1373
+ const pressureDepth = (channel.channelPressureTable[0] - 64) / 37.5; // 2400 / 64;
1377
1374
  channel.detune += pressureDepth * (next - prev);
1378
1375
  }
1376
+ const table = channel.channelPressureTable;
1379
1377
  channel.scheduledNotes.forEach((noteList) => {
1380
1378
  for (let i = 0; i < noteList.length; i++) {
1381
1379
  const note = noteList[i];
1382
1380
  if (!note)
1383
1381
  continue;
1384
- this.setChannelPressure(channel, note);
1382
+ this.applyDestinationSettings(channel, note, table);
1385
1383
  }
1386
1384
  });
1387
1385
  // this.applyVoiceParams(channel, 13);
1388
1386
  }
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
- }
1409
1387
  handlePitchBendMessage(channelNumber, lsb, msb) {
1410
1388
  const pitchBend = msb * 128 + lsb;
1411
1389
  this.setPitchBend(channelNumber, pitchBend);
@@ -1417,16 +1395,13 @@ class Midy {
1417
1395
  const next = (value - 8192) / 8192;
1418
1396
  state.pitchWheel = value / 16383;
1419
1397
  channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1420
- this.updateDetune(channel);
1398
+ this.updateChannelDetune(channel);
1421
1399
  this.applyVoiceParams(channel, 14);
1422
1400
  }
1423
- setModLfoToPitch(channel, note) {
1401
+ setModLfoToPitch(channel, note, pressure) {
1424
1402
  const now = this.audioContext.currentTime;
1425
- const pressureDepth = channel.pressureTable[3] / 127 * 600;
1426
- const pressure = pressureDepth * channel.state.channelPressure;
1427
1403
  const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1428
- const baseDepth = Math.abs(modLfoToPitch) +
1429
- channel.state.modulationDepth;
1404
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1430
1405
  const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1431
1406
  note.modulationDepth.gain
1432
1407
  .cancelScheduledValues(now)
@@ -1442,22 +1417,18 @@ class Midy {
1442
1417
  .cancelScheduledValues(now)
1443
1418
  .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1444
1419
  }
1445
- setModLfoToFilterFc(channel, note) {
1420
+ setModLfoToFilterFc(note, pressure) {
1446
1421
  const now = this.audioContext.currentTime;
1447
- const pressureDepth = channel.pressureTable[4] / 127 * 2400;
1448
- const pressure = pressureDepth * channel.state.channelPressure;
1449
1422
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1450
1423
  note.filterDepth.gain
1451
1424
  .cancelScheduledValues(now)
1452
1425
  .setValueAtTime(modLfoToFilterFc, now);
1453
1426
  }
1454
- setModLfoToVolume(channel, note) {
1427
+ setModLfoToVolume(note, pressure) {
1455
1428
  const now = this.audioContext.currentTime;
1456
1429
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1457
1430
  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;
1431
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
1461
1432
  note.volumeDepth.gain
1462
1433
  .cancelScheduledValues(now)
1463
1434
  .setValueAtTime(volumeDepth, now);
@@ -1530,11 +1501,18 @@ class Midy {
1530
1501
  .cancelScheduledValues(now)
1531
1502
  .setValueAtTime(freqModLFO, now);
1532
1503
  }
1504
+ setFreqVibLFO(channel, note) {
1505
+ const now = this.audioContext.currentTime;
1506
+ const freqVibLFO = note.voiceParams.freqVibLFO;
1507
+ note.vibratoLFO.frequency
1508
+ .cancelScheduledValues(now)
1509
+ .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, now);
1510
+ }
1533
1511
  createVoiceParamsHandlers() {
1534
1512
  return {
1535
1513
  modLfoToPitch: (channel, note, _prevValue) => {
1536
1514
  if (0 < channel.state.modulationDepth) {
1537
- this.setModLfoToPitch(channel, note);
1515
+ this.setModLfoToPitch(channel, note, 0);
1538
1516
  }
1539
1517
  },
1540
1518
  vibLfoToPitch: (channel, note, _prevValue) => {
@@ -1544,12 +1522,12 @@ class Midy {
1544
1522
  },
1545
1523
  modLfoToFilterFc: (channel, note, _prevValue) => {
1546
1524
  if (0 < channel.state.modulationDepth) {
1547
- this.setModLfoToFilterFc(channel, note);
1525
+ this.setModLfoToFilterFc(note, 0);
1548
1526
  }
1549
1527
  },
1550
- modLfoToVolume: (channel, note) => {
1528
+ modLfoToVolume: (channel, note, _prevValue) => {
1551
1529
  if (0 < channel.state.modulationDepth) {
1552
- this.setModLfoToVolume(channel, note);
1530
+ this.setModLfoToVolume(note, 0);
1553
1531
  }
1554
1532
  },
1555
1533
  chorusEffectsSend: (channel, note, prevValue) => {
@@ -1563,22 +1541,19 @@ class Midy {
1563
1541
  delayVibLFO: (channel, note, prevValue) => {
1564
1542
  if (0 < channel.state.vibratoDepth) {
1565
1543
  const now = this.audioContext.currentTime;
1566
- const prevStartTime = note.startTime +
1567
- prevValue * channel.state.vibratoDelay * 2;
1544
+ const vibratoDelay = channel.state.vibratoDelay * 2;
1545
+ const prevStartTime = note.startTime + prevValue * vibratoDelay;
1568
1546
  if (now < prevStartTime)
1569
1547
  return;
1570
- const startTime = note.startTime +
1571
- value * channel.state.vibratoDelay * 2;
1548
+ const value = note.voiceParams.delayVibLFO;
1549
+ const startTime = note.startTime + value * vibratoDelay;
1572
1550
  note.vibratoLFO.stop(now);
1573
1551
  note.vibratoLFO.start(startTime);
1574
1552
  }
1575
1553
  },
1576
1554
  freqVibLFO: (channel, note, _prevValue) => {
1577
1555
  if (0 < channel.state.vibratoDepth) {
1578
- const now = this.audioContext.currentTime;
1579
- note.vibratoLFO.frequency
1580
- .cancelScheduledValues(now)
1581
- .setValueAtTime(value * sate.vibratoRate, now);
1556
+ this.setFreqVibLFO(channel, note);
1582
1557
  }
1583
1558
  },
1584
1559
  };
@@ -1622,7 +1597,7 @@ class Midy {
1622
1597
  this.setPortamentoStartFilterEnvelope(channel, note);
1623
1598
  }
1624
1599
  else {
1625
- this.setFilterEnvelope(channel, note);
1600
+ this.setFilterEnvelope(channel, note, 0);
1626
1601
  }
1627
1602
  this.setPitchEnvelope(note);
1628
1603
  }
@@ -1636,7 +1611,7 @@ class Midy {
1636
1611
  if (key in voiceParams)
1637
1612
  noteVoiceParams[key] = voiceParams[key];
1638
1613
  }
1639
- this.setVolumeEnvelope(channel, note);
1614
+ this.setVolumeEnvelope(channel, note, 0);
1640
1615
  }
1641
1616
  }
1642
1617
  }
@@ -1685,8 +1660,8 @@ class Midy {
1685
1660
  if (handler) {
1686
1661
  handler.call(this, channelNumber, value);
1687
1662
  const channel = this.channels[channelNumber];
1688
- const controller = 128 + controllerType;
1689
- this.applyVoiceParams(channel, controller);
1663
+ this.applyVoiceParams(channel, controllerType + 128);
1664
+ this.applyControlTable(channel, controllerType);
1690
1665
  }
1691
1666
  else {
1692
1667
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1697,13 +1672,14 @@ class Midy {
1697
1672
  }
1698
1673
  updateModulation(channel) {
1699
1674
  const now = this.audioContext.currentTime;
1675
+ const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1700
1676
  channel.scheduledNotes.forEach((noteList) => {
1701
1677
  for (let i = 0; i < noteList.length; i++) {
1702
1678
  const note = noteList[i];
1703
1679
  if (!note)
1704
1680
  continue;
1705
1681
  if (note.modulationDepth) {
1706
- note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1682
+ note.modulationDepth.gain.setValueAtTime(depth, now);
1707
1683
  }
1708
1684
  else {
1709
1685
  this.setPitchEnvelope(note);
@@ -1714,8 +1690,7 @@ class Midy {
1714
1690
  }
1715
1691
  setModulationDepth(channelNumber, modulation) {
1716
1692
  const channel = this.channels[channelNumber];
1717
- channel.state.modulationDepth = (modulation / 127) *
1718
- channel.modulationDepthRange;
1693
+ channel.state.modulationDepth = modulation / 127;
1719
1694
  this.updateModulation(channel);
1720
1695
  }
1721
1696
  setPortamentoTime(channelNumber, portamentoTime) {
@@ -1857,7 +1832,7 @@ class Midy {
1857
1832
  continue;
1858
1833
  if (note.startTime < now)
1859
1834
  continue;
1860
- this.setVolumeEnvelope(channel, note);
1835
+ this.setVolumeEnvelope(channel, note, 0);
1861
1836
  }
1862
1837
  });
1863
1838
  }
@@ -1869,7 +1844,12 @@ class Midy {
1869
1844
  const note = noteList[i];
1870
1845
  if (!note)
1871
1846
  continue;
1872
- this.setFilterEnvelope(channel, note);
1847
+ if (note.portamento) {
1848
+ this.setPortamentoStartFilterEnvelope(channel, note);
1849
+ }
1850
+ else {
1851
+ this.setFilterEnvelope(channel, note, 0);
1852
+ }
1873
1853
  }
1874
1854
  });
1875
1855
  }
@@ -1881,7 +1861,7 @@ class Midy {
1881
1861
  const note = noteList[i];
1882
1862
  if (!note)
1883
1863
  continue;
1884
- this.setVolumeEnvelope(channel, note);
1864
+ this.setVolumeEnvelope(channel, note, 0);
1885
1865
  }
1886
1866
  });
1887
1867
  }
@@ -1890,21 +1870,53 @@ class Midy {
1890
1870
  channel.state.vibratoRate = vibratoRate / 64;
1891
1871
  if (channel.vibratoDepth <= 0)
1892
1872
  return;
1893
- const now = this.audioContext.currentTime;
1894
- const activeNotes = this.getActiveNotes(channel, now);
1895
- activeNotes.forEach((activeNote) => {
1896
- activeNote.vibratoLFO.frequency
1897
- .cancelScheduledValues(now)
1898
- .setValueAtTime(channel.state.vibratoRate, now);
1873
+ channel.scheduledNotes.forEach((noteList) => {
1874
+ for (let i = 0; i < noteList.length; i++) {
1875
+ const note = noteList[i];
1876
+ if (!note)
1877
+ continue;
1878
+ this.setVibLfoToPitch(channel, note);
1879
+ }
1899
1880
  });
1900
1881
  }
1901
1882
  setVibratoDepth(channelNumber, vibratoDepth) {
1902
1883
  const channel = this.channels[channelNumber];
1884
+ const prev = channel.state.vibratoDepth;
1903
1885
  channel.state.vibratoDepth = vibratoDepth / 64;
1886
+ if (0 < prev) {
1887
+ channel.scheduledNotes.forEach((noteList) => {
1888
+ for (let i = 0; i < noteList.length; i++) {
1889
+ const note = noteList[i];
1890
+ if (!note)
1891
+ continue;
1892
+ this.setFreqVibLFO(channel, note);
1893
+ }
1894
+ });
1895
+ }
1896
+ else {
1897
+ channel.scheduledNotes.forEach((noteList) => {
1898
+ for (let i = 0; i < noteList.length; i++) {
1899
+ const note = noteList[i];
1900
+ if (!note)
1901
+ continue;
1902
+ this.startVibrato(channel, note, note.startTime);
1903
+ }
1904
+ });
1905
+ }
1904
1906
  }
1905
1907
  setVibratoDelay(channelNumber, vibratoDelay) {
1906
1908
  const channel = this.channels[channelNumber];
1907
1909
  channel.state.vibratoDelay = vibratoDelay / 64;
1910
+ if (0 < channel.state.vibratoDepth) {
1911
+ channel.scheduledNotes.forEach((noteList) => {
1912
+ for (let i = 0; i < noteList.length; i++) {
1913
+ const note = noteList[i];
1914
+ if (!note)
1915
+ continue;
1916
+ this.startVibrato(channel, note, note.startTime);
1917
+ }
1918
+ });
1919
+ }
1908
1920
  }
1909
1921
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1910
1922
  const channel = this.channels[channelNumber];
@@ -2069,7 +2081,7 @@ class Midy {
2069
2081
  const next = value / 128;
2070
2082
  state.pitchWheelSensitivity = next;
2071
2083
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
2072
- this.updateDetune(channel);
2084
+ this.updateChannelDetune(channel);
2073
2085
  this.applyVoiceParams(channel, 16);
2074
2086
  }
2075
2087
  handleFineTuningRPN(channelNumber) {
@@ -2084,7 +2096,7 @@ class Midy {
2084
2096
  const next = (value - 8192) / 8.192; // cent
2085
2097
  channel.fineTuning = next;
2086
2098
  channel.detune += next - prev;
2087
- this.updateDetune(channel);
2099
+ this.updateChannelDetune(channel);
2088
2100
  }
2089
2101
  handleCoarseTuningRPN(channelNumber) {
2090
2102
  const channel = this.channels[channelNumber];
@@ -2098,7 +2110,7 @@ class Midy {
2098
2110
  const next = (value - 64) * 100; // cent
2099
2111
  channel.coarseTuning = next;
2100
2112
  channel.detune += next - prev;
2101
- this.updateDetune(channel);
2113
+ this.updateChannelDetune(channel);
2102
2114
  }
2103
2115
  handleModulationDepthRangeRPN(channelNumber) {
2104
2116
  const channel = this.channels[channelNumber];
@@ -2109,7 +2121,6 @@ class Midy {
2109
2121
  setModulationDepthRange(channelNumber, modulationDepthRange) {
2110
2122
  const channel = this.channels[channelNumber];
2111
2123
  channel.modulationDepthRange = modulationDepthRange;
2112
- channel.modulationDepth = (modulation / 127) * modulationDepthRange;
2113
2124
  this.updateModulation(channel);
2114
2125
  }
2115
2126
  allSoundOff(channelNumber) {
@@ -2130,7 +2141,7 @@ class Midy {
2130
2141
  const state = channel.state;
2131
2142
  for (let i = 0; i < stateTypes.length; i++) {
2132
2143
  const type = stateTypes[i];
2133
- state[type] = defaultControllerState[type];
2144
+ state[type] = defaultControllerState[type].defaultValue;
2134
2145
  }
2135
2146
  const settingTypes = [
2136
2147
  "rpnMSB",
@@ -2233,10 +2244,11 @@ class Midy {
2233
2244
  case 9:
2234
2245
  switch (data[3]) {
2235
2246
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2236
- return this.handleChannelPressureSysEx(data);
2237
- // case 3:
2238
- // // TODO
2239
- // return this.setControlChange();
2247
+ return this.handlePressureSysEx(data, "channelPressureTable");
2248
+ case 2: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2249
+ return this.handlePressureSysEx(data, "polyphonicKeyPressureTable");
2250
+ case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2251
+ return this.handleControlChangeSysEx(data);
2240
2252
  default:
2241
2253
  console.warn(`Unsupported Exclusive Message: ${data}`);
2242
2254
  }
@@ -2263,8 +2275,8 @@ class Midy {
2263
2275
  }
2264
2276
  else {
2265
2277
  const now = this.audioContext.currentTime;
2266
- this.masterGain.gain.cancelScheduledValues(now);
2267
- this.masterGain.gain.setValueAtTime(volume * volume, now);
2278
+ this.masterVolume.gain.cancelScheduledValues(now);
2279
+ this.masterVolume.gain.setValueAtTime(volume * volume, now);
2268
2280
  }
2269
2281
  }
2270
2282
  handleMasterFineTuningSysEx(data) {
@@ -2276,7 +2288,7 @@ class Midy {
2276
2288
  const next = (value - 8192) / 8.192; // cent
2277
2289
  this.masterFineTuning = next;
2278
2290
  channel.detune += next - prev;
2279
- this.updateDetune(channel);
2291
+ this.updateChannelDetune(channel);
2280
2292
  }
2281
2293
  handleMasterCoarseTuningSysEx(data) {
2282
2294
  const coarseTuning = data[4];
@@ -2287,7 +2299,7 @@ class Midy {
2287
2299
  const next = (value - 64) * 100; // cent
2288
2300
  this.masterCoarseTuning = next;
2289
2301
  channel.detune += next - prev;
2290
- this.updateDetune(channel);
2302
+ this.updateChannelDetune(channel);
2291
2303
  }
2292
2304
  handleGlobalParameterControlSysEx(data) {
2293
2305
  if (data[7] === 1) {
@@ -2509,15 +2521,105 @@ class Midy {
2509
2521
  }
2510
2522
  }
2511
2523
  }
2512
- handleChannelPressureSysEx(data) {
2524
+ applyDestinationSettings(channel, note, table) {
2525
+ if (table[0] !== 64) {
2526
+ const polyphonicKeyPressure = (0 < note.pressure)
2527
+ ? channel.polyphonicKeyPressureTable[0] * note.pressure
2528
+ : 0;
2529
+ const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
2530
+ this.updateDetune(channel, note, pressure);
2531
+ }
2532
+ if (!note.portamento) {
2533
+ if (table[1] !== 64) {
2534
+ const channelPressure = channel.channelPressureTable[1] *
2535
+ channel.state.channelPressure;
2536
+ const polyphonicKeyPressure = (0 < note.pressure)
2537
+ ? channel.polyphonicKeyPressureTable[1] * note.pressure
2538
+ : 0;
2539
+ const pressure = (channelPressure + polyphonicKeyPressure - 128) * 15;
2540
+ this.setFilterEnvelope(channel, note, pressure);
2541
+ }
2542
+ if (table[2] !== 64) {
2543
+ const channelPressure = channel.channelPressureTable[2] *
2544
+ channel.state.channelPressure;
2545
+ const polyphonicKeyPressure = (0 < note.pressure)
2546
+ ? channel.polyphonicKeyPressureTable[2] * note.pressure
2547
+ : 0;
2548
+ const pressure = (channelPressure + polyphonicKeyPressure) / 128;
2549
+ this.setVolumeEnvelope(channel, note, pressure);
2550
+ }
2551
+ }
2552
+ if (table[3] !== 0) {
2553
+ const channelPressure = channel.channelPressureTable[3] *
2554
+ channel.state.channelPressure;
2555
+ const polyphonicKeyPressure = (0 < note.pressure)
2556
+ ? channel.polyphonicKeyPressureTable[3] * note.pressure
2557
+ : 0;
2558
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
2559
+ this.setModLfoToPitch(channel, note, pressure);
2560
+ }
2561
+ if (table[4] !== 0) {
2562
+ const channelPressure = channel.channelPressureTable[4] *
2563
+ channel.state.channelPressure;
2564
+ const polyphonicKeyPressure = (0 < note.pressure)
2565
+ ? channel.polyphonicKeyPressureTable[4] * note.pressure
2566
+ : 0;
2567
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2568
+ this.setModLfoToFilterFc(note, pressure);
2569
+ }
2570
+ if (table[5] !== 0) {
2571
+ const channelPressure = channel.channelPressureTable[5] *
2572
+ channel.state.channelPressure;
2573
+ const polyphonicKeyPressure = (0 < note.pressure)
2574
+ ? channel.polyphonicKeyPressureTable[5] * note.pressure
2575
+ : 0;
2576
+ const pressure = (channelPressure + polyphonicKeyPressure) / 254;
2577
+ this.setModLfoToVolume(note, pressure);
2578
+ }
2579
+ }
2580
+ handleChannelPressureSysEx(data, tableName) {
2513
2581
  const channelNumber = data[4];
2514
- const table = this.channels[channelNumber].pressureTable;
2582
+ const table = this.channels[channelNumber][tableName];
2515
2583
  for (let i = 5; i < data.length - 1; i += 2) {
2516
2584
  const pp = data[i];
2517
2585
  const rr = data[i + 1];
2518
2586
  table[pp] = rr;
2519
2587
  }
2520
2588
  }
2589
+ initControlTable() {
2590
+ const channelCount = 128;
2591
+ const slotSize = 6;
2592
+ const defaultValues = [64, 64, 64, 0, 0, 0];
2593
+ const table = new Uint8Array(channelCount * slotSize);
2594
+ for (let ch = 0; ch < channelCount; ch++) {
2595
+ const offset = ch * slotSize;
2596
+ table.set(defaultValues, offset);
2597
+ }
2598
+ return table;
2599
+ }
2600
+ applyControlTable(channel, controllerType) {
2601
+ const slotSize = 6;
2602
+ const offset = controllerType * slotSize;
2603
+ const table = channel.controlTable.subarray(offset, offset + slotSize);
2604
+ channel.scheduledNotes.forEach((noteList) => {
2605
+ for (let i = 0; i < noteList.length; i++) {
2606
+ const note = noteList[i];
2607
+ if (!note)
2608
+ continue;
2609
+ this.applyDestinationSettings(channel, note, table);
2610
+ }
2611
+ });
2612
+ }
2613
+ handleControlChangeSysEx(data) {
2614
+ const channelNumber = data[4];
2615
+ const controllerType = data[5];
2616
+ const table = this.channels[channelNumber].controlTable[controllerType];
2617
+ for (let i = 6; i < data.length - 1; i += 2) {
2618
+ const pp = data[i];
2619
+ const rr = data[i + 1];
2620
+ table[pp] = rr;
2621
+ }
2622
+ }
2521
2623
  getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2522
2624
  const index = keyNumber * 128 + controllerType;
2523
2625
  const controlValue = channel.keyBasedInstrumentControlTable[index];
@@ -2569,7 +2671,8 @@ Object.defineProperty(Midy, "channelSettings", {
2569
2671
  currentBufferSource: null,
2570
2672
  detune: 0,
2571
2673
  scaleOctaveTuningTable: new Array(12).fill(0), // cent
2572
- pressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2674
+ channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2675
+ polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2573
2676
  keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2574
2677
  program: 0,
2575
2678
  bank: 121 * 128,
@@ -2584,16 +2687,3 @@ Object.defineProperty(Midy, "channelSettings", {
2584
2687
  modulationDepthRange: 50, // cent
2585
2688
  }
2586
2689
  });
2587
- Object.defineProperty(Midy, "controllerDestinationSettings", {
2588
- enumerable: true,
2589
- configurable: true,
2590
- writable: true,
2591
- value: {
2592
- pitchControl: 0,
2593
- filterCutoffControl: 0,
2594
- amplitudeControl: 1,
2595
- lfoPitchDepth: 0,
2596
- lfoFilterDepth: 0,
2597
- lfoAmplitudeDepth: 0,
2598
- }
2599
- });