@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/esm/midy.js CHANGED
@@ -66,31 +66,37 @@ class Note {
66
66
  writable: true,
67
67
  value: void 0
68
68
  });
69
+ Object.defineProperty(this, "filterDepth", {
70
+ enumerable: true,
71
+ configurable: true,
72
+ writable: true,
73
+ value: void 0
74
+ });
69
75
  Object.defineProperty(this, "volumeEnvelopeNode", {
70
76
  enumerable: true,
71
77
  configurable: true,
72
78
  writable: true,
73
79
  value: void 0
74
80
  });
75
- Object.defineProperty(this, "volumeNode", {
81
+ Object.defineProperty(this, "volumeDepth", {
76
82
  enumerable: true,
77
83
  configurable: true,
78
84
  writable: true,
79
85
  value: void 0
80
86
  });
81
- Object.defineProperty(this, "gainL", {
87
+ Object.defineProperty(this, "volumeNode", {
82
88
  enumerable: true,
83
89
  configurable: true,
84
90
  writable: true,
85
91
  value: void 0
86
92
  });
87
- Object.defineProperty(this, "gainR", {
93
+ Object.defineProperty(this, "gainL", {
88
94
  enumerable: true,
89
95
  configurable: true,
90
96
  writable: true,
91
97
  value: void 0
92
98
  });
93
- Object.defineProperty(this, "volumeDepth", {
99
+ Object.defineProperty(this, "gainR", {
94
100
  enumerable: true,
95
101
  configurable: true,
96
102
  writable: true,
@@ -499,6 +505,10 @@ export class Midy {
499
505
  ...this.setChannelAudioNodes(audioContext),
500
506
  scheduledNotes: new SparseMap(128),
501
507
  sostenutoNotes: new SparseMap(128),
508
+ scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
509
+ channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
510
+ polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
511
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
502
512
  };
503
513
  });
504
514
  return channels;
@@ -565,10 +575,11 @@ export class Midy {
565
575
  const event = this.timeline[queueIndex];
566
576
  if (event.startTime > t + this.lookAhead)
567
577
  break;
578
+ const startTime = event.startTime + this.startDelay - offset;
568
579
  switch (event.type) {
569
580
  case "noteOn":
570
581
  if (event.velocity !== 0) {
571
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, event.portamento);
582
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, event.portamento);
572
583
  break;
573
584
  }
574
585
  /* falls through */
@@ -576,29 +587,29 @@ export class Midy {
576
587
  const portamentoTarget = this.findPortamentoTarget(queueIndex);
577
588
  if (portamentoTarget)
578
589
  portamentoTarget.portamento = true;
579
- const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset, portamentoTarget?.noteNumber, false);
590
+ const notePromise = this.scheduleNoteRelease(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, portamentoTarget?.noteNumber, false);
580
591
  if (notePromise) {
581
592
  this.notePromises.push(notePromise);
582
593
  }
583
594
  break;
584
595
  }
585
596
  case "noteAftertouch":
586
- this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
597
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
587
598
  break;
588
599
  case "controller":
589
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
600
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
590
601
  break;
591
602
  case "programChange":
592
- this.handleProgramChange(event.channel, event.programNumber);
603
+ this.handleProgramChange(event.channel, event.programNumber, startTime);
593
604
  break;
594
605
  case "channelAftertouch":
595
- this.handleChannelPressure(event.channel, event.amount);
606
+ this.handleChannelPressure(event.channel, event.amount, startTime);
596
607
  break;
597
608
  case "pitchBend":
598
- this.setPitchBend(event.channel, event.value + 8192);
609
+ this.setPitchBend(event.channel, event.value + 8192, startTime);
599
610
  break;
600
611
  case "sysEx":
601
- this.handleSysEx(event.data);
612
+ this.handleSysEx(event.data, startTime);
602
613
  }
603
614
  queueIndex++;
604
615
  }
@@ -845,6 +856,18 @@ export class Midy {
845
856
  const now = this.audioContext.currentTime;
846
857
  return this.resumeTime + now - this.startTime - this.startDelay;
847
858
  }
859
+ processScheduledNotes(channel, scheduleTime, callback) {
860
+ channel.scheduledNotes.forEach((noteList) => {
861
+ for (let i = 0; i < noteList.length; i++) {
862
+ const note = noteList[i];
863
+ if (!note)
864
+ continue;
865
+ if (scheduleTime < note.startTime)
866
+ continue;
867
+ callback(note);
868
+ }
869
+ });
870
+ }
848
871
  getActiveNotes(channel, time) {
849
872
  const activeNotes = new SparseMap(128);
850
873
  channel.scheduledNotes.forEach((noteList) => {
@@ -1026,14 +1049,15 @@ export class Midy {
1026
1049
  const note = noteList[i];
1027
1050
  if (!note)
1028
1051
  continue;
1029
- this.updateDetune(channel, note, 0);
1052
+ this.updateDetune(channel, note);
1030
1053
  }
1031
1054
  });
1032
1055
  }
1033
- updateDetune(channel, note, pressure) {
1056
+ updateDetune(channel, note) {
1034
1057
  const now = this.audioContext.currentTime;
1035
1058
  const noteDetune = this.calcNoteDetune(channel, note);
1036
- const detune = channel.detune + noteDetune + pressure;
1059
+ const pitchControl = this.getPitchControl(channel, note);
1060
+ const detune = channel.detune + noteDetune + pitchControl;
1037
1061
  note.bufferSource.detune
1038
1062
  .cancelScheduledValues(now)
1039
1063
  .setValueAtTime(detune, now);
@@ -1055,12 +1079,12 @@ export class Midy {
1055
1079
  .setValueAtTime(0, volDelay)
1056
1080
  .linearRampToValueAtTime(sustainVolume, portamentoTime);
1057
1081
  }
1058
- setVolumeEnvelope(channel, note, pressure) {
1082
+ setVolumeEnvelope(channel, note) {
1059
1083
  const now = this.audioContext.currentTime;
1060
1084
  const state = channel.state;
1061
1085
  const { voiceParams, startTime } = note;
1062
1086
  const attackVolume = this.cbToRatio(-voiceParams.initialAttenuation) *
1063
- (1 + pressure);
1087
+ (1 + this.getAmplitudeControl(channel, note));
1064
1088
  const sustainVolume = attackVolume * (1 - voiceParams.volSustain);
1065
1089
  const volDelay = startTime + voiceParams.volDelay;
1066
1090
  const volAttack = volDelay + voiceParams.volAttack * state.attackTime * 2;
@@ -1074,20 +1098,20 @@ export class Midy {
1074
1098
  .setValueAtTime(attackVolume, volHold)
1075
1099
  .linearRampToValueAtTime(sustainVolume, volDecay);
1076
1100
  }
1077
- setPitchEnvelope(note) {
1078
- const now = this.audioContext.currentTime;
1101
+ setPitchEnvelope(note, scheduleTime) {
1102
+ scheduleTime ??= this.audioContext.currentTime;
1079
1103
  const { voiceParams } = note;
1080
1104
  const baseRate = voiceParams.playbackRate;
1081
1105
  note.bufferSource.playbackRate
1082
- .cancelScheduledValues(now)
1083
- .setValueAtTime(baseRate, now);
1106
+ .cancelScheduledValues(scheduleTime)
1107
+ .setValueAtTime(baseRate, scheduleTime);
1084
1108
  const modEnvToPitch = voiceParams.modEnvToPitch;
1085
1109
  if (modEnvToPitch === 0)
1086
1110
  return;
1087
1111
  const basePitch = this.rateToCent(baseRate);
1088
1112
  const peekPitch = basePitch + modEnvToPitch;
1089
1113
  const peekRate = this.centToRate(peekPitch);
1090
- const modDelay = startTime + voiceParams.modDelay;
1114
+ const modDelay = note.startTime + voiceParams.modDelay;
1091
1115
  const modAttack = modDelay + voiceParams.modAttack;
1092
1116
  const modHold = modAttack + voiceParams.modHold;
1093
1117
  const modDecay = modHold + voiceParams.modDecay;
@@ -1124,13 +1148,14 @@ export class Midy {
1124
1148
  .setValueAtTime(adjustedBaseFreq, modDelay)
1125
1149
  .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1126
1150
  }
1127
- setFilterEnvelope(channel, note, pressure) {
1151
+ setFilterEnvelope(channel, note) {
1128
1152
  const now = this.audioContext.currentTime;
1129
1153
  const state = channel.state;
1130
1154
  const { voiceParams, noteNumber, startTime } = note;
1131
1155
  const softPedalFactor = 1 -
1132
1156
  (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1133
- const baseCent = voiceParams.initialFilterFc + pressure;
1157
+ const baseCent = voiceParams.initialFilterFc +
1158
+ this.getFilterCutoffControl(channel, note);
1134
1159
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
1135
1160
  state.brightness * 2;
1136
1161
  const peekFreq = this.centToHz(baseCent + voiceParams.modEnvToFilterFc) *
@@ -1161,9 +1186,9 @@ export class Midy {
1161
1186
  gain: voiceParams.modLfoToFilterFc,
1162
1187
  });
1163
1188
  note.modulationDepth = new GainNode(this.audioContext);
1164
- this.setModLfoToPitch(channel, note, 0);
1189
+ this.setModLfoToPitch(channel, note);
1165
1190
  note.volumeDepth = new GainNode(this.audioContext);
1166
- this.setModLfoToVolume(note, 0);
1191
+ this.setModLfoToVolume(channel, note);
1167
1192
  note.modulationLFO.start(startTime + voiceParams.delayModLFO);
1168
1193
  note.modulationLFO.connect(note.filterDepth);
1169
1194
  note.filterDepth.connect(note.filterNode.frequency);
@@ -1224,8 +1249,8 @@ export class Midy {
1224
1249
  }
1225
1250
  else {
1226
1251
  note.portamento = false;
1227
- this.setVolumeEnvelope(channel, note, 0);
1228
- this.setFilterEnvelope(channel, note, 0);
1252
+ this.setVolumeEnvelope(channel, note);
1253
+ this.setFilterEnvelope(channel, note);
1229
1254
  }
1230
1255
  if (0 < state.vibratoDepth) {
1231
1256
  this.startVibrato(channel, note, startTime);
@@ -1437,15 +1462,16 @@ export class Midy {
1437
1462
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1438
1463
  }
1439
1464
  }
1440
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
1441
- const now = this.audioContext.currentTime;
1465
+ handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure, startTime) {
1466
+ if (!startTime)
1467
+ startTime = this.audioContext.currentTime;
1442
1468
  const channel = this.channels[channelNumber];
1443
1469
  channel.state.polyphonicKeyPressure = pressure / 127;
1444
1470
  const table = channel.polyphonicKeyPressureTable;
1445
- const activeNotes = this.getActiveNotes(channel, now);
1471
+ const activeNotes = this.getActiveNotes(channel, startTime);
1446
1472
  if (activeNotes.has(noteNumber)) {
1447
1473
  const note = activeNotes.get(noteNumber);
1448
- this.applyDestinationSettings(channel, note, table);
1474
+ this.setControllerParameters(channel, note, table);
1449
1475
  }
1450
1476
  // this.applyVoiceParams(channel, 10);
1451
1477
  }
@@ -1454,8 +1480,9 @@ export class Midy {
1454
1480
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1455
1481
  channel.program = program;
1456
1482
  }
1457
- handleChannelPressure(channelNumber, value) {
1458
- const now = this.audioContext.currentTime;
1483
+ handleChannelPressure(channelNumber, value, startTime) {
1484
+ if (!startTime)
1485
+ startTime = this.audioContext.currentTime;
1459
1486
  const channel = this.channels[channelNumber];
1460
1487
  const prev = channel.state.channelPressure;
1461
1488
  const next = value / 127;
@@ -1465,8 +1492,8 @@ export class Midy {
1465
1492
  channel.detune += pressureDepth * (next - prev);
1466
1493
  }
1467
1494
  const table = channel.channelPressureTable;
1468
- this.getActiveNotes(channel, now).forEach((note) => {
1469
- this.applyDestinationSettings(channel, note, table);
1495
+ this.getActiveNotes(channel, startTime).forEach((note) => {
1496
+ this.setControllerParameters(channel, note, table);
1470
1497
  });
1471
1498
  // this.applyVoiceParams(channel, 13);
1472
1499
  }
@@ -1484,9 +1511,10 @@ export class Midy {
1484
1511
  this.updateChannelDetune(channel);
1485
1512
  this.applyVoiceParams(channel, 14);
1486
1513
  }
1487
- setModLfoToPitch(channel, note, pressure) {
1514
+ setModLfoToPitch(channel, note) {
1488
1515
  const now = this.audioContext.currentTime;
1489
- const modLfoToPitch = note.voiceParams.modLfoToPitch + pressure;
1516
+ const modLfoToPitch = note.voiceParams.modLfoToPitch +
1517
+ this.getLFOPitchDepth(channel, note);
1490
1518
  const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1491
1519
  const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1492
1520
  note.modulationDepth.gain
@@ -1503,18 +1531,20 @@ export class Midy {
1503
1531
  .cancelScheduledValues(now)
1504
1532
  .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1505
1533
  }
1506
- setModLfoToFilterFc(note, pressure) {
1534
+ setModLfoToFilterFc(channel, note) {
1507
1535
  const now = this.audioContext.currentTime;
1508
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc + pressure;
1536
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
1537
+ this.getLFOFilterDepth(channel, note);
1509
1538
  note.filterDepth.gain
1510
1539
  .cancelScheduledValues(now)
1511
1540
  .setValueAtTime(modLfoToFilterFc, now);
1512
1541
  }
1513
- setModLfoToVolume(note, pressure) {
1542
+ setModLfoToVolume(channel, note) {
1514
1543
  const now = this.audioContext.currentTime;
1515
1544
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
1516
1545
  const baseDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
1517
- const volumeDepth = baseDepth * Math.sign(modLfoToVolume) * (1 + pressure);
1546
+ const volumeDepth = baseDepth * Math.sign(modLfoToVolume) *
1547
+ (1 + this.getLFOAmplitudeDepth(channel, note));
1518
1548
  note.volumeDepth.gain
1519
1549
  .cancelScheduledValues(now)
1520
1550
  .setValueAtTime(volumeDepth, now);
@@ -1598,7 +1628,7 @@ export class Midy {
1598
1628
  return {
1599
1629
  modLfoToPitch: (channel, note, _prevValue) => {
1600
1630
  if (0 < channel.state.modulationDepth) {
1601
- this.setModLfoToPitch(channel, note, 0);
1631
+ this.setModLfoToPitch(channel, note);
1602
1632
  }
1603
1633
  },
1604
1634
  vibLfoToPitch: (channel, note, _prevValue) => {
@@ -1608,12 +1638,12 @@ export class Midy {
1608
1638
  },
1609
1639
  modLfoToFilterFc: (channel, note, _prevValue) => {
1610
1640
  if (0 < channel.state.modulationDepth) {
1611
- this.setModLfoToFilterFc(note, 0);
1641
+ this.setModLfoToFilterFc(channel, note);
1612
1642
  }
1613
1643
  },
1614
1644
  modLfoToVolume: (channel, note, _prevValue) => {
1615
1645
  if (0 < channel.state.modulationDepth) {
1616
- this.setModLfoToVolume(note, 0);
1646
+ this.setModLfoToVolume(channel, note);
1617
1647
  }
1618
1648
  },
1619
1649
  chorusEffectsSend: (channel, note, prevValue) => {
@@ -1683,7 +1713,7 @@ export class Midy {
1683
1713
  this.setPortamentoStartFilterEnvelope(channel, note);
1684
1714
  }
1685
1715
  else {
1686
- this.setFilterEnvelope(channel, note, 0);
1716
+ this.setFilterEnvelope(channel, note);
1687
1717
  }
1688
1718
  this.setPitchEnvelope(note);
1689
1719
  }
@@ -1697,7 +1727,7 @@ export class Midy {
1697
1727
  if (key in voiceParams)
1698
1728
  noteVoiceParams[key] = voiceParams[key];
1699
1729
  }
1700
- this.setVolumeEnvelope(channel, note, 0);
1730
+ this.setVolumeEnvelope(channel, note);
1701
1731
  }
1702
1732
  }
1703
1733
  }
@@ -1741,10 +1771,10 @@ export class Midy {
1741
1771
  127: this.polyOn,
1742
1772
  };
1743
1773
  }
1744
- handleControlChange(channelNumber, controllerType, value) {
1774
+ handleControlChange(channelNumber, controllerType, value, startTime) {
1745
1775
  const handler = this.controlChangeHandlers[controllerType];
1746
1776
  if (handler) {
1747
- handler.call(this, channelNumber, value);
1777
+ handler.call(this, channelNumber, value, startTime);
1748
1778
  const channel = this.channels[channelNumber];
1749
1779
  this.applyVoiceParams(channel, controllerType + 128);
1750
1780
  this.applyControlTable(channel, controllerType);
@@ -1756,55 +1786,45 @@ export class Midy {
1756
1786
  setBankMSB(channelNumber, msb) {
1757
1787
  this.channels[channelNumber].bankMSB = msb;
1758
1788
  }
1759
- updateModulation(channel) {
1760
- const now = this.audioContext.currentTime;
1789
+ updateModulation(channel, scheduleTime) {
1790
+ scheduleTime ??= this.audioContext.currentTime;
1761
1791
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1762
- channel.scheduledNotes.forEach((noteList) => {
1763
- for (let i = 0; i < noteList.length; i++) {
1764
- const note = noteList[i];
1765
- if (!note)
1766
- continue;
1767
- if (note.modulationDepth) {
1768
- note.modulationDepth.gain.setValueAtTime(depth, now);
1769
- }
1770
- else {
1771
- this.setPitchEnvelope(note);
1772
- this.startModulation(channel, note, now);
1773
- }
1792
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1793
+ if (note.modulationDepth) {
1794
+ note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1795
+ }
1796
+ else {
1797
+ this.setPitchEnvelope(note, scheduleTime);
1798
+ this.startModulation(channel, note, scheduleTime);
1774
1799
  }
1775
1800
  });
1776
1801
  }
1777
- setModulationDepth(channelNumber, modulation) {
1802
+ setModulationDepth(channelNumber, modulation, scheduleTime) {
1778
1803
  const channel = this.channels[channelNumber];
1779
1804
  channel.state.modulationDepth = modulation / 127;
1780
- this.updateModulation(channel);
1805
+ this.updateModulation(channel, scheduleTime);
1781
1806
  }
1782
1807
  setPortamentoTime(channelNumber, portamentoTime) {
1783
1808
  const channel = this.channels[channelNumber];
1784
1809
  const factor = 5 * Math.log(10) / 127;
1785
1810
  channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1786
1811
  }
1787
- setKeyBasedVolume(channel) {
1788
- const now = this.audioContext.currentTime;
1789
- channel.scheduledNotes.forEach((noteList) => {
1790
- for (let i = 0; i < noteList.length; i++) {
1791
- const note = noteList[i];
1792
- if (!note)
1793
- continue;
1794
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1795
- if (keyBasedValue === 0)
1796
- continue;
1812
+ setKeyBasedVolume(channel, scheduleTime) {
1813
+ scheduleTime ??= this.audioContext.currentTime;
1814
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1815
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1816
+ if (keyBasedValue !== 0) {
1797
1817
  note.volumeNode.gain
1798
- .cancelScheduledValues(now)
1799
- .setValueAtTime(1 + keyBasedValue, now);
1818
+ .cancelScheduledValues(scheduleTime)
1819
+ .setValueAtTime(1 + keyBasedValue, scheduleTime);
1800
1820
  }
1801
1821
  });
1802
1822
  }
1803
- setVolume(channelNumber, volume) {
1823
+ setVolume(channelNumber, volume, scheduleTime) {
1804
1824
  const channel = this.channels[channelNumber];
1805
1825
  channel.state.volume = volume / 127;
1806
- this.updateChannelVolume(channel);
1807
- this.setKeyBasedVolume(channel);
1826
+ this.updateChannelVolume(channel, scheduleTime);
1827
+ this.setKeyBasedVolume(channel, scheduleTime);
1808
1828
  }
1809
1829
  panToGain(pan) {
1810
1830
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1813,36 +1833,31 @@ export class Midy {
1813
1833
  gainRight: Math.sin(theta),
1814
1834
  };
1815
1835
  }
1816
- setKeyBasedPan(channel) {
1817
- const now = this.audioContext.currentTime;
1818
- channel.scheduledNotes.forEach((noteList) => {
1819
- for (let i = 0; i < noteList.length; i++) {
1820
- const note = noteList[i];
1821
- if (!note)
1822
- continue;
1823
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1824
- if (keyBasedValue === 0)
1825
- continue;
1836
+ setKeyBasedPan(channel, scheduleTime) {
1837
+ scheduleTime ??= this.audioContext.currentTime;
1838
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1839
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1840
+ if (keyBasedValue !== 0) {
1826
1841
  const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
1827
1842
  note.gainL.gain
1828
- .cancelScheduledValues(now)
1829
- .setValueAtTime(gainLeft, now);
1843
+ .cancelScheduledValues(scheduleTime)
1844
+ .setValueAtTime(gainLeft, scheduleTime);
1830
1845
  note.gainR.gain
1831
- .cancelScheduledValues(now)
1832
- .setValueAtTime(gainRight, now);
1846
+ .cancelScheduledValues(scheduleTime)
1847
+ .setValueAtTime(gainRight, scheduleTime);
1833
1848
  }
1834
1849
  });
1835
1850
  }
1836
- setPan(channelNumber, pan) {
1851
+ setPan(channelNumber, pan, scheduleTime) {
1837
1852
  const channel = this.channels[channelNumber];
1838
1853
  channel.state.pan = pan / 127;
1839
- this.updateChannelVolume(channel);
1840
- this.setKeyBasedPan(channel);
1854
+ this.updateChannelVolume(channel, scheduleTime);
1855
+ this.setKeyBasedPan(channel, scheduleTime);
1841
1856
  }
1842
- setExpression(channelNumber, expression) {
1857
+ setExpression(channelNumber, expression, scheduleTime) {
1843
1858
  const channel = this.channels[channelNumber];
1844
1859
  channel.state.expression = expression / 127;
1845
- this.updateChannelVolume(channel);
1860
+ this.updateChannelVolume(channel, scheduleTime);
1846
1861
  }
1847
1862
  setBankLSB(channelNumber, lsb) {
1848
1863
  this.channels[channelNumber].bankLSB = lsb;
@@ -1917,7 +1932,7 @@ export class Midy {
1917
1932
  continue;
1918
1933
  if (note.startTime < now)
1919
1934
  continue;
1920
- this.setVolumeEnvelope(channel, note, 0);
1935
+ this.setVolumeEnvelope(channel, note);
1921
1936
  }
1922
1937
  });
1923
1938
  }
@@ -1933,7 +1948,7 @@ export class Midy {
1933
1948
  this.setPortamentoStartFilterEnvelope(channel, note);
1934
1949
  }
1935
1950
  else {
1936
- this.setFilterEnvelope(channel, note, 0);
1951
+ this.setFilterEnvelope(channel, note);
1937
1952
  }
1938
1953
  }
1939
1954
  });
@@ -1946,7 +1961,7 @@ export class Midy {
1946
1961
  const note = noteList[i];
1947
1962
  if (!note)
1948
1963
  continue;
1949
- this.setVolumeEnvelope(channel, note, 0);
1964
+ this.setVolumeEnvelope(channel, note);
1950
1965
  }
1951
1966
  });
1952
1967
  }
@@ -2636,61 +2651,61 @@ export class Midy {
2636
2651
  this.updateChannelDetune(channel);
2637
2652
  }
2638
2653
  }
2639
- applyDestinationSettings(channel, note, table) {
2640
- if (table[0] !== 64) {
2641
- const polyphonicKeyPressure = (0 < note.pressure)
2642
- ? channel.polyphonicKeyPressureTable[0] * note.pressure
2643
- : 0;
2644
- const pressure = (polyphonicKeyPressure - 64) / 37.5; // 2400 / 64;
2645
- this.updateDetune(channel, note, pressure);
2646
- }
2654
+ getPitchControl(channel, note) {
2655
+ const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[0] - 64) *
2656
+ note.pressure;
2657
+ return polyphonicKeyPressure * note.pressure / 37.5; // 2400 / 64;
2658
+ }
2659
+ getFilterCutoffControl(channel, note) {
2660
+ const channelPressure = (channel.channelPressureTable[1] - 64) *
2661
+ channel.state.channelPressure;
2662
+ const polyphonicKeyPressure = (channel.polyphonicKeyPressureTable[1] - 64) *
2663
+ note.pressure;
2664
+ return (channelPressure + polyphonicKeyPressure) * 15;
2665
+ }
2666
+ getAmplitudeControl(channel, note) {
2667
+ const channelPressure = channel.channelPressureTable[2] *
2668
+ channel.state.channelPressure;
2669
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[2] *
2670
+ note.pressure;
2671
+ return (channelPressure + polyphonicKeyPressure) / 128;
2672
+ }
2673
+ getLFOPitchDepth(channel, note) {
2674
+ const channelPressure = channel.channelPressureTable[3] *
2675
+ channel.state.channelPressure;
2676
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[3] *
2677
+ note.pressure;
2678
+ return (channelPressure + polyphonicKeyPressure) / 254 * 600;
2679
+ }
2680
+ getLFOFilterDepth(channel, note) {
2681
+ const channelPressure = channel.channelPressureTable[4] *
2682
+ channel.state.channelPressure;
2683
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[4] *
2684
+ note.pressure;
2685
+ return (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2686
+ }
2687
+ getLFOAmplitudeDepth(channel, note) {
2688
+ const channelPressure = channel.channelPressureTable[5] *
2689
+ channel.state.channelPressure;
2690
+ const polyphonicKeyPressure = channel.polyphonicKeyPressureTable[5] *
2691
+ note.pressure;
2692
+ return (channelPressure + polyphonicKeyPressure) / 254;
2693
+ }
2694
+ setControllerParameters(channel, note, table) {
2695
+ if (table[0] !== 64)
2696
+ this.updateDetune(channel, note);
2647
2697
  if (!note.portamento) {
2648
- if (table[1] !== 64) {
2649
- const channelPressure = channel.channelPressureTable[1] *
2650
- channel.state.channelPressure;
2651
- const polyphonicKeyPressure = (0 < note.pressure)
2652
- ? channel.polyphonicKeyPressureTable[1] * note.pressure
2653
- : 0;
2654
- const pressure = (channelPressure + polyphonicKeyPressure - 128) * 15;
2655
- this.setFilterEnvelope(channel, note, pressure);
2656
- }
2657
- if (table[2] !== 64) {
2658
- const channelPressure = channel.channelPressureTable[2] *
2659
- channel.state.channelPressure;
2660
- const polyphonicKeyPressure = (0 < note.pressure)
2661
- ? channel.polyphonicKeyPressureTable[2] * note.pressure
2662
- : 0;
2663
- const pressure = (channelPressure + polyphonicKeyPressure) / 128;
2664
- this.setVolumeEnvelope(channel, note, pressure);
2665
- }
2666
- }
2667
- if (table[3] !== 0) {
2668
- const channelPressure = channel.channelPressureTable[3] *
2669
- channel.state.channelPressure;
2670
- const polyphonicKeyPressure = (0 < note.pressure)
2671
- ? channel.polyphonicKeyPressureTable[3] * note.pressure
2672
- : 0;
2673
- const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 600;
2674
- this.setModLfoToPitch(channel, note, pressure);
2675
- }
2676
- if (table[4] !== 0) {
2677
- const channelPressure = channel.channelPressureTable[4] *
2678
- channel.state.channelPressure;
2679
- const polyphonicKeyPressure = (0 < note.pressure)
2680
- ? channel.polyphonicKeyPressureTable[4] * note.pressure
2681
- : 0;
2682
- const pressure = (channelPressure + polyphonicKeyPressure) / 254 * 2400;
2683
- this.setModLfoToFilterFc(note, pressure);
2684
- }
2685
- if (table[5] !== 0) {
2686
- const channelPressure = channel.channelPressureTable[5] *
2687
- channel.state.channelPressure;
2688
- const polyphonicKeyPressure = (0 < note.pressure)
2689
- ? channel.polyphonicKeyPressureTable[5] * note.pressure
2690
- : 0;
2691
- const pressure = (channelPressure + polyphonicKeyPressure) / 254;
2692
- this.setModLfoToVolume(note, pressure);
2693
- }
2698
+ if (table[1] !== 64)
2699
+ this.setFilterEnvelope(channel, note);
2700
+ if (table[2] !== 64)
2701
+ this.setVolumeEnvelope(channel, note);
2702
+ }
2703
+ if (table[3] !== 0)
2704
+ this.setModLfoToPitch(channel, note);
2705
+ if (table[4] !== 0)
2706
+ this.setModLfoToFilterFc(channel, note);
2707
+ if (table[5] !== 0)
2708
+ this.setModLfoToVolume(channel, note);
2694
2709
  }
2695
2710
  handleChannelPressureSysEx(data, tableName) {
2696
2711
  const channelNumber = data[4];
@@ -2721,7 +2736,7 @@ export class Midy {
2721
2736
  const note = noteList[i];
2722
2737
  if (!note)
2723
2738
  continue;
2724
- this.applyDestinationSettings(channel, note, table);
2739
+ this.setControllerParameters(channel, note, table);
2725
2740
  }
2726
2741
  });
2727
2742
  }
@@ -2784,10 +2799,6 @@ Object.defineProperty(Midy, "channelSettings", {
2784
2799
  value: {
2785
2800
  currentBufferSource: null,
2786
2801
  detune: 0,
2787
- scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
2788
- channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2789
- polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
2790
- keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
2791
2802
  program: 0,
2792
2803
  bank: 121 * 128,
2793
2804
  bankMSB: 121,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",