@marmooo/midy 0.2.5 → 0.2.6

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
@@ -69,31 +69,37 @@ class Note {
69
69
  writable: true,
70
70
  value: void 0
71
71
  });
72
+ Object.defineProperty(this, "filterDepth", {
73
+ enumerable: true,
74
+ configurable: true,
75
+ writable: true,
76
+ value: void 0
77
+ });
72
78
  Object.defineProperty(this, "volumeEnvelopeNode", {
73
79
  enumerable: true,
74
80
  configurable: true,
75
81
  writable: true,
76
82
  value: void 0
77
83
  });
78
- Object.defineProperty(this, "volumeNode", {
84
+ Object.defineProperty(this, "volumeDepth", {
79
85
  enumerable: true,
80
86
  configurable: true,
81
87
  writable: true,
82
88
  value: void 0
83
89
  });
84
- Object.defineProperty(this, "gainL", {
90
+ Object.defineProperty(this, "volumeNode", {
85
91
  enumerable: true,
86
92
  configurable: true,
87
93
  writable: true,
88
94
  value: void 0
89
95
  });
90
- Object.defineProperty(this, "gainR", {
96
+ Object.defineProperty(this, "gainL", {
91
97
  enumerable: true,
92
98
  configurable: true,
93
99
  writable: true,
94
100
  value: void 0
95
101
  });
96
- Object.defineProperty(this, "volumeDepth", {
102
+ Object.defineProperty(this, "gainR", {
97
103
  enumerable: true,
98
104
  configurable: true,
99
105
  writable: true,
@@ -502,6 +508,10 @@ class Midy {
502
508
  ...this.setChannelAudioNodes(audioContext),
503
509
  scheduledNotes: new SparseMap(128),
504
510
  sostenutoNotes: new SparseMap(128),
511
+ scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
512
+ channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
513
+ polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
514
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
505
515
  };
506
516
  });
507
517
  return channels;
@@ -568,10 +578,11 @@ class Midy {
568
578
  const event = this.timeline[queueIndex];
569
579
  if (event.startTime > t + this.lookAhead)
570
580
  break;
581
+ const startTime = event.startTime + this.startDelay - offset;
571
582
  switch (event.type) {
572
583
  case "noteOn":
573
584
  if (event.velocity !== 0) {
574
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, event.portamento);
585
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
575
586
  break;
576
587
  }
577
588
  /* falls through */
@@ -579,29 +590,29 @@ class Midy {
579
590
  const portamentoTarget = this.findPortamentoTarget(queueIndex);
580
591
  if (portamentoTarget)
581
592
  portamentoTarget.portamento = true;
582
- const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, portamentoTarget?.noteNumber, false);
593
+ const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
583
594
  if (notePromise) {
584
595
  this.notePromises.push(notePromise);
585
596
  }
586
597
  break;
587
598
  }
588
599
  case "noteAftertouch":
589
- this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
600
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
590
601
  break;
591
602
  case "controller":
592
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
603
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
593
604
  break;
594
605
  case "programChange":
595
- this.handleProgramChange(event.channel, event.programNumber);
606
+ this.handleProgramChange(event.channel, event.programNumber, startTime);
596
607
  break;
597
608
  case "channelAftertouch":
598
- this.handleChannelPressure(event.channel, event.amount);
609
+ this.handleChannelPressure(event.channel, event.amount, startTime);
599
610
  break;
600
611
  case "pitchBend":
601
- this.setPitchBend(event.channel, event.value + 8192);
612
+ this.setPitchBend(event.channel, event.value + 8192, startTime);
602
613
  break;
603
614
  case "sysEx":
604
- this.handleSysEx(event.data);
615
+ this.handleSysEx(event.data, startTime);
605
616
  }
606
617
  queueIndex++;
607
618
  }
@@ -848,6 +859,18 @@ class Midy {
848
859
  const now = this.audioContext.currentTime;
849
860
  return this.resumeTime + now - this.startTime - this.startDelay;
850
861
  }
862
+ processScheduledNotes(channel, scheduleTime, callback) {
863
+ channel.scheduledNotes.forEach((noteList) => {
864
+ for (let i = 0; i < noteList.length; i++) {
865
+ const note = noteList[i];
866
+ if (!note)
867
+ continue;
868
+ if (scheduleTime < note.startTime)
869
+ continue;
870
+ callback(note);
871
+ }
872
+ });
873
+ }
851
874
  getActiveNotes(channel, time) {
852
875
  const activeNotes = new SparseMap(128);
853
876
  channel.scheduledNotes.forEach((noteList) => {
@@ -1029,14 +1052,15 @@ class Midy {
1029
1052
  const note = noteList[i];
1030
1053
  if (!note)
1031
1054
  continue;
1032
- this.updateDetune(channel, note, 0);
1055
+ this.updateDetune(channel, note);
1033
1056
  }
1034
1057
  });
1035
1058
  }
1036
- updateDetune(channel, note, pressure) {
1059
+ updateDetune(channel, note) {
1037
1060
  const now = this.audioContext.currentTime;
1038
1061
  const noteDetune = this.calcNoteDetune(channel, note);
1039
- const detune = channel.detune + noteDetune + pressure;
1062
+ const pitchControl = this.getPitchControl(channel, note);
1063
+ const detune = channel.detune + noteDetune + pitchControl;
1040
1064
  note.bufferSource.detune
1041
1065
  .cancelScheduledValues(now)
1042
1066
  .setValueAtTime(detune, now);
@@ -1058,12 +1082,12 @@ class Midy {
1058
1082
  .setValueAtTime(0, volDelay)
1059
1083
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
1060
1084
  }
1061
- setVolumeEnvelope(channel, note, pressure) {
1085
+ setVolumeEnvelope(channel, note) {
1062
1086
  const now = this.audioContext.currentTime;
1063
1087
  const state = channel.state;
1064
1088
  const { voiceParams, startTime } = note;
1065
1089
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1066
- (1 + pressure);
1090
+ (1 + this.getAmplitudeControl(channel, note));
1067
1091
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1068
1092
  const volDelay = startTime + voiceParams.volDelay;
1069
1093
  const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
@@ -1077,20 +1101,20 @@ class Midy {
1077
1101
  .setValueAtTime(attackVolume, volHold)
1078
1102
  .linearRampToValueAtTime(sustainVolume, volDecay);
1079
1103
  }
1080
- setPitchEnvelope(note) {
1081
- const now = this.audioContext.currentTime;
1104
+ setPitchEnvelope(note, scheduleTime) {
1105
+ scheduleTime ??= this.audioContext.currentTime;
1082
1106
  const { voiceParams } = note;
1083
1107
  const baseRate = voiceParams.playbackRate;
1084
1108
  note.bufferSource.playbackRate
1085
- .cancelScheduledValues(now)
1086
- .setValueAtTime(baseRate, now);
1109
+ .cancelScheduledValues(scheduleTime)
1110
+ .setValueAtTime(baseRate, scheduleTime);
1087
1111
  const modEnvToPitch = voiceParams.modEnvToPitch;
1088
1112
  if (modEnvToPitch === 0)
1089
1113
  return;
1090
1114
  const basePitch = this.rateToCent(baseRate);
1091
1115
  const peekPitch = basePitch + modEnvToPitch;
1092
1116
  const peekRate = this.centToRate(peekPitch);
1093
- const modDelay = startTime + voiceParams.modDelay;
1117
+ const modDelay = note.startTime + voiceParams.modDelay;
1094
1118
  const modAttack = modDelay + voiceParams.modAttack;
1095
1119
  const modHold = modAttack + voiceParams.modHold;
1096
1120
  const modDecay = modHold + voiceParams.modDecay;
@@ -1127,13 +1151,14 @@ class Midy {
1127
1151
  .setValueAtTime(adjustedBaseFreq, modDelay)
1128
1152
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1129
1153
  }
1130
- setFilterEnvelope(channel, note, pressure) {
1154
+ setFilterEnvelope(channel, note) {
1131
1155
  const now = this.audioContext.currentTime;
1132
1156
  const state = channel.state;
1133
1157
  const { voiceParams, noteNumber, startTime } = note;
1134
1158
  const softPedalFactor = 1 -
1135
1159
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1136
- const baseCent = voiceParams.initialFilterFc + pressure;
1160
+ const baseCent = voiceParams.initialFilterFc +
1161
+ this.getFilterCutoffControl(channel, note);
1137
1162
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
1138
1163
  state.brightness * 2;
1139
1164
  const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
@@ -1164,9 +1189,9 @@ class Midy {
1164
1189
  gain: voiceParams.modLfoToFilterFc,
1165
1190
  });
1166
1191
  note.modulationDepth = new GainNode(this.audioContext);
1167
- this.setModLfoToPitch(channel, note, 0);
1192
+ this.setModLfoToPitch(channel, note);
1168
1193
  note.volumeDepth = new GainNode(this.audioContext);
1169
- this.setModLfoToVolume(note, 0);
1194
+ this.setModLfoToVolume(channel, note);
1170
1195
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1171
1196
  note.modulationLFO.connect(note.filterDepth);
1172
1197
  note.filterDepth.connect(note.filterNode.frequency);
@@ -1227,8 +1252,8 @@ class Midy {
1227
1252
  }
1228
1253
  else {
1229
1254
  note.portamento = false;
1230
- this.setVolumeEnvelope(channel, note, 0);
1231
- this.setFilterEnvelope(channel, note, 0);
1255
+ this.setVolumeEnvelope(channel, note);
1256
+ this.setFilterEnvelope(channel, note);
1232
1257
  }
1233
1258
  if (0 < state.vibratoDepth) {
1234
1259
  this.startVibrato(channel, note, startTime);
@@ -1440,15 +1465,16 @@ class Midy {
1440
1465
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1441
1466
  }
1442
1467
  }
1443
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
1444
- const now = this.audioContext.currentTime;
1468
+ handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
1469
+ if (!startTime)
1470
+ startTime = this.audioContext.currentTime;
1445
1471
  const channel = this.channels[channelNumber];
1446
1472
  channel.state.polyphonicKeyPressure = pressure / 127;
1447
1473
  const table = channel.polyphonicKeyPressureTable;
1448
- const activeNotes = this.getActiveNotes(channel, now);
1474
+ const activeNotes = this.getActiveNotes(channel, startTime);
1449
1475
  if (activeNotes.has(noteNumber)) {
1450
1476
  const note = activeNotes.get(noteNumber);
1451
- this.applyDestinationSettings(channel, note, table);
1477
+ this.setControllerParameters(channel, note, table);
1452
1478
  }
1453
1479
  // this.applyVoiceParams(channel, 10);
1454
1480
  }
@@ -1457,8 +1483,9 @@ class Midy {
1457
1483
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1458
1484
  channel.program = program;
1459
1485
  }
1460
- handleChannelPressure(channelNumber, value) {
1461
- const now = this.audioContext.currentTime;
1486
+ handleChannelPressure(channelNumber, value, startTime) {
1487
+ if (!startTime)
1488
+ startTime = this.audioContext.currentTime;
1462
1489
  const channel = this.channels[channelNumber];
1463
1490
  const prev = channel.state.channelPressure;
1464
1491
  const next = value / 127;
@@ -1468,8 +1495,8 @@ class Midy {
1468
1495
  channel.detune += pressureDepth * (next - prev);
1469
1496
  }
1470
1497
  const table = channel.channelPressureTable;
1471
- this.getActiveNotes(channel, now).forEach((note) => {
1472
- this.applyDestinationSettings(channel, note, table);
1498
+ this.getActiveNotes(channel, startTime).forEach((note) => {
1499
+ this.setControllerParameters(channel, note, table);
1473
1500
  });
1474
1501
  // this.applyVoiceParams(channel, 13);
1475
1502
  }
@@ -1487,9 +1514,10 @@ class Midy {
1487
1514
  this.updateChannelDetune(channel);
1488
1515
  this.applyVoiceParams(channel, 14);
1489
1516
  }
1490
- setModLfoToPitch(channel, note, pressure) {
1517
+ setModLfoToPitch(channel, note) {
1491
1518
  const now = this.audioContext.currentTime;
1492
- const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1519
+ const modLfoToPitch = note.voiceParams.modLfoToPitch +
1520
+ this.getLFOPitchDepth(channel, note);
1493
1521
  const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1494
1522
  const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1495
1523
  note.modulationDepth.gain
@@ -1506,18 +1534,20 @@ class Midy {
1506
1534
  .cancelScheduledValues(now)
1507
1535
  .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1508
1536
  }
1509
- setModLfoToFilterFc(note, pressure) {
1537
+ setModLfoToFilterFc(channel, note) {
1510
1538
  const now = this.audioContext.currentTime;
1511
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1539
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
1540
+ this.getLFOFilterDepth(channel, note);
1512
1541
  note.filterDepth.gain
1513
1542
  .cancelScheduledValues(now)
1514
1543
  .setValueAtTime(modLfoToFilterFc, now);
1515
1544
  }
1516
- setModLfoToVolume(note, pressure) {
1545
+ setModLfoToVolume(channel, note) {
1517
1546
  const now = this.audioContext.currentTime;
1518
1547
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1519
1548
  const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1520
- const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
1549
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
1550
+ (1 + this.getLFOAmplitudeDepth(channel, note));
1521
1551
  note.volumeDepth.gain
1522
1552
  .cancelScheduledValues(now)
1523
1553
  .setValueAtTime(volumeDepth, now);
@@ -1601,7 +1631,7 @@ class Midy {
1601
1631
  return {
1602
1632
  modLfoToPitch: (channel, note, _prevValue) => {
1603
1633
  if (0 < channel.state.modulationDepth) {
1604
- this.setModLfoToPitch(channel, note, 0);
1634
+ this.setModLfoToPitch(channel, note);
1605
1635
  }
1606
1636
  },
1607
1637
  vibLfoToPitch: (channel, note, _prevValue) => {
@@ -1611,12 +1641,12 @@ class Midy {
1611
1641
  },
1612
1642
  modLfoToFilterFc: (channel, note, _prevValue) => {
1613
1643
  if (0 < channel.state.modulationDepth) {
1614
- this.setModLfoToFilterFc(note, 0);
1644
+ this.setModLfoToFilterFc(channel, note);
1615
1645
  }
1616
1646
  },
1617
1647
  modLfoToVolume: (channel, note, _prevValue) => {
1618
1648
  if (0 < channel.state.modulationDepth) {
1619
- this.setModLfoToVolume(note, 0);
1649
+ this.setModLfoToVolume(channel, note);
1620
1650
  }
1621
1651
  },
1622
1652
  chorusEffectsSend: (channel, note, prevValue) => {
@@ -1686,7 +1716,7 @@ class Midy {
1686
1716
  this.setPortamentoStartFilterEnvelope(channel, note);
1687
1717
  }
1688
1718
  else {
1689
- this.setFilterEnvelope(channel, note, 0);
1719
+ this.setFilterEnvelope(channel, note);
1690
1720
  }
1691
1721
  this.setPitchEnvelope(note);
1692
1722
  }
@@ -1700,7 +1730,7 @@ class Midy {
1700
1730
  if (key in voiceParams)
1701
1731
  noteVoiceParams[key] = voiceParams[key];
1702
1732
  }
1703
- this.setVolumeEnvelope(channel, note, 0);
1733
+ this.setVolumeEnvelope(channel, note);
1704
1734
  }
1705
1735
  }
1706
1736
  }
@@ -1744,10 +1774,10 @@ class Midy {
1744
1774
  127: this.polyOn,
1745
1775
  };
1746
1776
  }
1747
- handleControlChange(channelNumber, controllerType, value) {
1777
+ handleControlChange(channelNumber, controllerType, value, startTime) {
1748
1778
  const handler = this.controlChangeHandlers[controllerType];
1749
1779
  if (handler) {
1750
- handler.call(this, channelNumber, value);
1780
+ handler.call(this, channelNumber, value, startTime);
1751
1781
  const channel = this.channels[channelNumber];
1752
1782
  this.applyVoiceParams(channel, controllerType + 128);
1753
1783
  this.applyControlTable(channel, controllerType);
@@ -1759,55 +1789,45 @@ class Midy {
1759
1789
  setBankMSB(channelNumber, msb) {
1760
1790
  this.channels[channelNumber].bankMSB = msb;
1761
1791
  }
1762
- updateModulation(channel) {
1763
- const now = this.audioContext.currentTime;
1792
+ updateModulation(channel, scheduleTime) {
1793
+ scheduleTime ??= this.audioContext.currentTime;
1764
1794
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1765
- channel.scheduledNotes.forEach((noteList) => {
1766
- for (let i = 0; i < noteList.length; i++) {
1767
- const note = noteList[i];
1768
- if (!note)
1769
- continue;
1770
- if (note.modulationDepth) {
1771
- note.modulationDepth.gain.setValueAtTime(depth, now);
1772
- }
1773
- else {
1774
- this.setPitchEnvelope(note);
1775
- this.startModulation(channel, note, now);
1776
- }
1795
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1796
+ if (note.modulationDepth) {
1797
+ note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1798
+ }
1799
+ else {
1800
+ this.setPitchEnvelope(note, scheduleTime);
1801
+ this.startModulation(channel, note, scheduleTime);
1777
1802
  }
1778
1803
  });
1779
1804
  }
1780
- setModulationDepth(channelNumber, modulation) {
1805
+ setModulationDepth(channelNumber, modulation, scheduleTime) {
1781
1806
  const channel = this.channels[channelNumber];
1782
1807
  channel.state.modulationDepth = modulation / 127;
1783
- this.updateModulation(channel);
1808
+ this.updateModulation(channel, scheduleTime);
1784
1809
  }
1785
1810
  setPortamentoTime(channelNumber, portamentoTime) {
1786
1811
  const channel = this.channels[channelNumber];
1787
1812
  const factor = 5 * Math.log(10) / 127;
1788
1813
  channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1789
1814
  }
1790
- setKeyBasedVolume(channel) {
1791
- const now = this.audioContext.currentTime;
1792
- channel.scheduledNotes.forEach((noteList) => {
1793
- for (let i = 0; i < noteList.length; i++) {
1794
- const note = noteList[i];
1795
- if (!note)
1796
- continue;
1797
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1798
- if (keyBasedValue === 0)
1799
- continue;
1815
+ setKeyBasedVolume(channel, scheduleTime) {
1816
+ scheduleTime ??= this.audioContext.currentTime;
1817
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1818
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1819
+ if (keyBasedValue !== 0) {
1800
1820
  note.volumeNode.gain
1801
- .cancelScheduledValues(now)
1802
- .setValueAtTime(1 + keyBasedValue, now);
1821
+ .cancelScheduledValues(scheduleTime)
1822
+ .setValueAtTime(1 + keyBasedValue, scheduleTime);
1803
1823
  }
1804
1824
  });
1805
1825
  }
1806
- setVolume(channelNumber, volume) {
1826
+ setVolume(channelNumber, volume, scheduleTime) {
1807
1827
  const channel = this.channels[channelNumber];
1808
1828
  channel.state.volume = volume / 127;
1809
- this.updateChannelVolume(channel);
1810
- this.setKeyBasedVolume(channel);
1829
+ this.updateChannelVolume(channel, scheduleTime);
1830
+ this.setKeyBasedVolume(channel, scheduleTime);
1811
1831
  }
1812
1832
  panToGain(pan) {
1813
1833
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1816,36 +1836,31 @@ class Midy {
1816
1836
  gainRight: Math.sin(theta),
1817
1837
  };
1818
1838
  }
1819
- setKeyBasedPan(channel) {
1820
- const now = this.audioContext.currentTime;
1821
- channel.scheduledNotes.forEach((noteList) => {
1822
- for (let i = 0; i < noteList.length; i++) {
1823
- const note = noteList[i];
1824
- if (!note)
1825
- continue;
1826
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1827
- if (keyBasedValue === 0)
1828
- continue;
1839
+ setKeyBasedPan(channel, scheduleTime) {
1840
+ scheduleTime ??= this.audioContext.currentTime;
1841
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1842
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1843
+ if (keyBasedValue !== 0) {
1829
1844
  const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
1830
1845
  note.gainL.gain
1831
- .cancelScheduledValues(now)
1832
- .setValueAtTime(gainLeft, now);
1846
+ .cancelScheduledValues(scheduleTime)
1847
+ .setValueAtTime(gainLeft, scheduleTime);
1833
1848
  note.gainR.gain
1834
- .cancelScheduledValues(now)
1835
- .setValueAtTime(gainRight, now);
1849
+ .cancelScheduledValues(scheduleTime)
1850
+ .setValueAtTime(gainRight, scheduleTime);
1836
1851
  }
1837
1852
  });
1838
1853
  }
1839
- setPan(channelNumber, pan) {
1854
+ setPan(channelNumber, pan, scheduleTime) {
1840
1855
  const channel = this.channels[channelNumber];
1841
1856
  channel.state.pan = pan / 127;
1842
- this.updateChannelVolume(channel);
1843
- this.setKeyBasedPan(channel);
1857
+ this.updateChannelVolume(channel, scheduleTime);
1858
+ this.setKeyBasedPan(channel, scheduleTime);
1844
1859
  }
1845
- setExpression(channelNumber, expression) {
1860
+ setExpression(channelNumber, expression, scheduleTime) {
1846
1861
  const channel = this.channels[channelNumber];
1847
1862
  channel.state.expression = expression / 127;
1848
- this.updateChannelVolume(channel);
1863
+ this.updateChannelVolume(channel, scheduleTime);
1849
1864
  }
1850
1865
  setBankLSB(channelNumber, lsb) {
1851
1866
  this.channels[channelNumber].bankLSB = lsb;
@@ -1920,7 +1935,7 @@ class Midy {
1920
1935
  continue;
1921
1936
  if (note.startTime < now)
1922
1937
  continue;
1923
- this.setVolumeEnvelope(channel, note, 0);
1938
+ this.setVolumeEnvelope(channel, note);
1924
1939
  }
1925
1940
  });
1926
1941
  }
@@ -1936,7 +1951,7 @@ class Midy {
1936
1951
  this.setPortamentoStartFilterEnvelope(channel, note);
1937
1952
  }
1938
1953
  else {
1939
- this.setFilterEnvelope(channel, note, 0);
1954
+ this.setFilterEnvelope(channel, note);
1940
1955
  }
1941
1956
  }
1942
1957
  });
@@ -1949,7 +1964,7 @@ class Midy {
1949
1964
  const note = noteList[i];
1950
1965
  if (!note)
1951
1966
  continue;
1952
- this.setVolumeEnvelope(channel, note, 0);
1967
+ this.setVolumeEnvelope(channel, note);
1953
1968
  }
1954
1969
  });
1955
1970
  }
@@ -2639,61 +2654,61 @@ class Midy {
2639
2654
  this.updateChannelDetune(channel);
2640
2655
  }
2641
2656
  }
2642
- applyDestinationSettings(channel, note, table) {
2643
- if (table[0] !== 64) {
2644
- const polyphonicKeyPressure = (0 < note.pressure)
2645
- ? channel.polyphonicKeyPressureTable[0] * note.pressure
2646
- : 0;
2647
- const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
2648
- this.updateDetune(channel, note, pressure);
2649
- }
2657
+ getPitchControl(channel, note) {
2658
+ const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
2659
+ note.pressure;
2660
+ return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
2661
+ }
2662
+ getFilterCutoffControl(channel, note) {
2663
+ const channelPressure = (channel.channelPressureTable[1] - 64) *
2664
+ channel.state.channelPressure;
2665
+ const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
2666
+ note.pressure;
2667
+ return (channelPressure + polyphonicKeyPressure) * 15;
2668
+ }
2669
+ getAmplitudeControl(channel, note) {
2670
+ const channelPressure = channel.channelPressureTable[2] *
2671
+ channel.state.channelPressure;
2672
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
2673
+ note.pressure;
2674
+ return (channelPressure + polyphonicKeyPressure) / 128;
2675
+ }
2676
+ getLFOPitchDepth(channel, note) {
2677
+ const channelPressure = channel.channelPressureTable[3] *
2678
+ channel.state.channelPressure;
2679
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
2680
+ note.pressure;
2681
+ return (channelPressure + polyphonicKeyPressure) / 254 * 600;
2682
+ }
2683
+ getLFOFilterDepth(channel, note) {
2684
+ const channelPressure = channel.channelPressureTable[4] *
2685
+ channel.state.channelPressure;
2686
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
2687
+ note.pressure;
2688
+ return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2689
+ }
2690
+ getLFOAmplitudeDepth(channel, note) {
2691
+ const channelPressure = channel.channelPressureTable[5] *
2692
+ channel.state.channelPressure;
2693
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
2694
+ note.pressure;
2695
+ return (channelPressure + polyphonicKeyPressure) / 254;
2696
+ }
2697
+ setControllerParameters(channel, note, table) {
2698
+ if (table[0] !== 64)
2699
+ this.updateDetune(channel, note);
2650
2700
  if (!note.portamento) {
2651
- if (table[1] !== 64) {
2652
- const channelPressure = channel.channelPressureTable[1] *
2653
- channel.state.channelPressure;
2654
- const polyphonicKeyPressure = (0 < note.pressure)
2655
- ? channel.polyphonicKeyPressureTable[1] * note.pressure
2656
- : 0;
2657
- const pressure = (channelPressure + polyphonicKeyPressure - 128) * 15;
2658
- this.setFilterEnvelope(channel, note, pressure);
2659
- }
2660
- if (table[2] !== 64) {
2661
- const channelPressure = channel.channelPressureTable[2] *
2662
- channel.state.channelPressure;
2663
- const polyphonicKeyPressure = (0 < note.pressure)
2664
- ? channel.polyphonicKeyPressureTable[2] * note.pressure
2665
- : 0;
2666
- const pressure = (channelPressure + polyphonicKeyPressure) / 128;
2667
- this.setVolumeEnvelope(channel, note, pressure);
2668
- }
2669
- }
2670
- if (table[3] !== 0) {
2671
- const channelPressure = channel.channelPressureTable[3] *
2672
- channel.state.channelPressure;
2673
- const polyphonicKeyPressure = (0 < note.pressure)
2674
- ? channel.polyphonicKeyPressureTable[3] * note.pressure
2675
- : 0;
2676
- const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
2677
- this.setModLfoToPitch(channel, note, pressure);
2678
- }
2679
- if (table[4] !== 0) {
2680
- const channelPressure = channel.channelPressureTable[4] *
2681
- channel.state.channelPressure;
2682
- const polyphonicKeyPressure = (0 < note.pressure)
2683
- ? channel.polyphonicKeyPressureTable[4] * note.pressure
2684
- : 0;
2685
- const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2686
- this.setModLfoToFilterFc(note, pressure);
2687
- }
2688
- if (table[5] !== 0) {
2689
- const channelPressure = channel.channelPressureTable[5] *
2690
- channel.state.channelPressure;
2691
- const polyphonicKeyPressure = (0 < note.pressure)
2692
- ? channel.polyphonicKeyPressureTable[5] * note.pressure
2693
- : 0;
2694
- const pressure = (channelPressure + polyphonicKeyPressure) / 254;
2695
- this.setModLfoToVolume(note, pressure);
2696
- }
2701
+ if (table[1] !== 64)
2702
+ this.setFilterEnvelope(channel, note);
2703
+ if (table[2] !== 64)
2704
+ this.setVolumeEnvelope(channel, note);
2705
+ }
2706
+ if (table[3] !== 0)
2707
+ this.setModLfoToPitch(channel, note);
2708
+ if (table[4] !== 0)
2709
+ this.setModLfoToFilterFc(channel, note);
2710
+ if (table[5] !== 0)
2711
+ this.setModLfoToVolume(channel, note);
2697
2712
  }
2698
2713
  handleChannelPressureSysEx(data, tableName) {
2699
2714
  const channelNumber = data[4];
@@ -2724,7 +2739,7 @@ class Midy {
2724
2739
  const note = noteList[i];
2725
2740
  if (!note)
2726
2741
  continue;
2727
- this.applyDestinationSettings(channel, note, table);
2742
+ this.setControllerParameters(channel, note, table);
2728
2743
  }
2729
2744
  });
2730
2745
  }
@@ -2788,10 +2803,6 @@ Object.defineProperty(Midy, "channelSettings", {
2788
2803
  value: {
2789
2804
  currentBufferSource: null,
2790
2805
  detune: 0,
2791
- scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
2792
- channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2793
- polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2794
- keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2795
2806
  program: 0,
2796
2807
  bank: 121 * 128,
2797
2808
  bankMSB: 121,