@marmooo/midy 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/midy-GM2.js CHANGED
@@ -68,13 +68,13 @@ class Note {
68
68
  writable: true,
69
69
  value: void 0
70
70
  });
71
- Object.defineProperty(this, "reverbEffectsSend", {
71
+ Object.defineProperty(this, "reverbSend", {
72
72
  enumerable: true,
73
73
  configurable: true,
74
74
  writable: true,
75
75
  value: void 0
76
76
  });
77
- Object.defineProperty(this, "chorusEffectsSend", {
77
+ Object.defineProperty(this, "chorusSend", {
78
78
  enumerable: true,
79
79
  configurable: true,
80
80
  writable: true,
@@ -225,13 +225,13 @@ export class MidyGM2 {
225
225
  configurable: true,
226
226
  writable: true,
227
227
  value: 0
228
- }); // cb
228
+ }); // cent
229
229
  Object.defineProperty(this, "masterCoarseTuning", {
230
230
  enumerable: true,
231
231
  configurable: true,
232
232
  writable: true,
233
233
  value: 0
234
- }); // cb
234
+ }); // cent
235
235
  Object.defineProperty(this, "reverb", {
236
236
  enumerable: true,
237
237
  configurable: true,
@@ -356,13 +356,13 @@ export class MidyGM2 {
356
356
  writable: true,
357
357
  value: false
358
358
  });
359
- Object.defineProperty(this, "timeline", {
359
+ Object.defineProperty(this, "playPromise", {
360
360
  enumerable: true,
361
361
  configurable: true,
362
362
  writable: true,
363
- value: []
363
+ value: void 0
364
364
  });
365
- Object.defineProperty(this, "instruments", {
365
+ Object.defineProperty(this, "timeline", {
366
366
  enumerable: true,
367
367
  configurable: true,
368
368
  writable: true,
@@ -374,6 +374,12 @@ export class MidyGM2 {
374
374
  writable: true,
375
375
  value: []
376
376
  });
377
+ Object.defineProperty(this, "instruments", {
378
+ enumerable: true,
379
+ configurable: true,
380
+ writable: true,
381
+ value: new Set()
382
+ });
377
383
  Object.defineProperty(this, "exclusiveClassNotes", {
378
384
  enumerable: true,
379
385
  configurable: true,
@@ -504,7 +510,7 @@ export class MidyGM2 {
504
510
  const soundFont = this.soundFonts[soundFontIndex];
505
511
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
506
512
  const { instrument, sampleID } = voice.generators;
507
- return `${soundFontIndex}:${instrument}:${sampleID}`;
513
+ return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
508
514
  }
509
515
  createChannelAudioNodes(audioContext) {
510
516
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
@@ -524,7 +530,7 @@ export class MidyGM2 {
524
530
  channel.controlTable.fill(-1);
525
531
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
526
532
  channel.channelPressureTable.fill(-1);
527
- channel.keyBasedInstrumentControlTable.fill(-1);
533
+ channel.keyBasedTable.fill(-1);
528
534
  }
529
535
  createChannels(audioContext) {
530
536
  const channels = Array.from({ length: this.numChannels }, () => {
@@ -540,7 +546,7 @@ export class MidyGM2 {
540
546
  controlTable: this.initControlTable(),
541
547
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
542
548
  channelPressureTable: new Int8Array(6).fill(-1),
543
- keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
549
+ keyBasedTable: new Int8Array(128 * 128).fill(-1),
544
550
  keyBasedGainLs: new Array(128),
545
551
  keyBasedGainRs: new Array(128),
546
552
  };
@@ -615,72 +621,80 @@ export class MidyGM2 {
615
621
  }
616
622
  return 0;
617
623
  }
618
- playNotes() {
619
- return new Promise((resolve) => {
620
- this.isPlaying = true;
621
- this.isPaused = false;
622
- this.startTime = this.audioContext.currentTime;
623
- let queueIndex = this.getQueueIndex(this.resumeTime);
624
- let resumeTime = this.resumeTime - this.startTime;
624
+ resetAllStates() {
625
+ this.exclusiveClassNotes.fill(undefined);
626
+ this.drumExclusiveClassNotes.fill(undefined);
627
+ this.voiceCache.clear();
628
+ for (let i = 0; i < this.channels.length; i++) {
629
+ this.channels[i].scheduledNotes = [];
630
+ this.resetChannelStates(i);
631
+ }
632
+ }
633
+ updateStates(queueIndex, nextQueueIndex) {
634
+ if (nextQueueIndex < queueIndex)
635
+ queueIndex = 0;
636
+ for (let i = queueIndex; i < nextQueueIndex; i++) {
637
+ const event = this.timeline[i];
638
+ switch (event.type) {
639
+ case "controller":
640
+ this.setControlChange(event.channel, event.controllerType, event.value, 0);
641
+ break;
642
+ case "programChange":
643
+ this.setProgramChange(event.channel, event.programNumber, 0);
644
+ break;
645
+ case "pitchBend":
646
+ this.setPitchBend(event.channel, event.value + 8192, 0);
647
+ break;
648
+ case "sysEx":
649
+ this.handleSysEx(event.data, 0);
650
+ }
651
+ }
652
+ }
653
+ async playNotes() {
654
+ if (this.audioContext.state === "suspended") {
655
+ await this.audioContext.resume();
656
+ }
657
+ this.isPlaying = true;
658
+ this.isPaused = false;
659
+ this.startTime = this.audioContext.currentTime;
660
+ let queueIndex = this.getQueueIndex(this.resumeTime);
661
+ let resumeTime = this.resumeTime - this.startTime;
662
+ let finished = false;
663
+ this.notePromises = [];
664
+ while (queueIndex < this.timeline.length) {
665
+ const now = this.audioContext.currentTime;
666
+ const t = now + resumeTime;
667
+ queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
668
+ if (this.isPausing) {
669
+ await this.stopNotes(0, true, now);
670
+ await this.audioContext.suspend();
671
+ this.notePromises = [];
672
+ break;
673
+ }
674
+ else if (this.isStopping) {
675
+ await this.stopNotes(0, true, now);
676
+ await this.audioContext.suspend();
677
+ finished = true;
678
+ break;
679
+ }
680
+ else if (this.isSeeking) {
681
+ await this.stopNotes(0, true, now);
682
+ this.startTime = this.audioContext.currentTime;
683
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
684
+ this.updateStates(queueIndex, nextQueueIndex);
685
+ queueIndex = nextQueueIndex;
686
+ resumeTime = this.resumeTime - this.startTime;
687
+ this.isSeeking = false;
688
+ continue;
689
+ }
690
+ const waitTime = now + this.noteCheckInterval;
691
+ await this.scheduleTask(() => { }, waitTime);
692
+ }
693
+ if (finished) {
625
694
  this.notePromises = [];
626
- const schedulePlayback = async () => {
627
- if (queueIndex >= this.timeline.length) {
628
- await Promise.all(this.notePromises);
629
- this.notePromises = [];
630
- this.exclusiveClassNotes.fill(undefined);
631
- this.drumExclusiveClassNotes.fill(undefined);
632
- this.voiceCache.clear();
633
- for (let i = 0; i < this.channels.length; i++) {
634
- this.channels[i].scheduledNotes = [];
635
- this.resetAllStates(i);
636
- }
637
- resolve();
638
- return;
639
- }
640
- const now = this.audioContext.currentTime;
641
- const t = now + resumeTime;
642
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
643
- if (this.isPausing) {
644
- await this.stopNotes(0, true, now);
645
- this.notePromises = [];
646
- this.isPausing = false;
647
- this.isPaused = true;
648
- resolve();
649
- return;
650
- }
651
- else if (this.isStopping) {
652
- await this.stopNotes(0, true, now);
653
- this.notePromises = [];
654
- this.exclusiveClassNotes.fill(undefined);
655
- this.drumExclusiveClassNotes.fill(undefined);
656
- this.voiceCache.clear();
657
- for (let i = 0; i < this.channels.length; i++) {
658
- this.channels[i].scheduledNotes = [];
659
- this.resetAllStates(i);
660
- }
661
- this.isStopping = false;
662
- this.isPaused = false;
663
- resolve();
664
- return;
665
- }
666
- else if (this.isSeeking) {
667
- this.stopNotes(0, true, now);
668
- this.exclusiveClassNotes.fill(undefined);
669
- this.drumExclusiveClassNotes.fill(undefined);
670
- this.startTime = this.audioContext.currentTime;
671
- queueIndex = this.getQueueIndex(this.resumeTime);
672
- resumeTime = this.resumeTime - this.startTime;
673
- this.isSeeking = false;
674
- await schedulePlayback();
675
- }
676
- else {
677
- const waitTime = now + this.noteCheckInterval;
678
- await this.scheduleTask(() => { }, waitTime);
679
- await schedulePlayback();
680
- }
681
- };
682
- schedulePlayback();
683
- });
695
+ this.resetAllStates();
696
+ }
697
+ this.isPlaying = false;
684
698
  }
685
699
  ticksToSecond(ticks, secondsPerBeat) {
686
700
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -817,26 +831,32 @@ export class MidyGM2 {
817
831
  this.resumeTime = 0;
818
832
  if (this.voiceCounter.size === 0)
819
833
  this.cacheVoiceIds();
820
- await this.playNotes();
821
- this.isPlaying = false;
834
+ this.playPromise = this.playNotes();
835
+ await this.playPromise;
822
836
  }
823
- stop() {
837
+ async stop() {
824
838
  if (!this.isPlaying)
825
839
  return;
826
840
  this.isStopping = true;
841
+ await this.playPromise;
842
+ this.isStopping = false;
827
843
  }
828
- pause() {
844
+ async pause() {
829
845
  if (!this.isPlaying || this.isPaused)
830
846
  return;
831
847
  const now = this.audioContext.currentTime;
832
848
  this.resumeTime += now - this.startTime - this.startDelay;
833
849
  this.isPausing = true;
850
+ await this.playPromise;
851
+ this.isPausing = false;
852
+ this.isPaused = true;
834
853
  }
835
854
  async resume() {
836
855
  if (!this.isPaused)
837
856
  return;
838
- await this.playNotes();
839
- this.isPlaying = false;
857
+ this.playPromise = this.playNotes();
858
+ await this.playPromise;
859
+ this.isPaused = false;
840
860
  }
841
861
  seekTo(second) {
842
862
  this.resumeTime = second;
@@ -904,13 +924,11 @@ export class MidyGM2 {
904
924
  return impulse;
905
925
  }
906
926
  createConvolutionReverb(audioContext, impulse) {
907
- const input = new GainNode(audioContext);
908
927
  const convolverNode = new ConvolverNode(audioContext, {
909
928
  buffer: impulse,
910
929
  });
911
- input.connect(convolverNode);
912
930
  return {
913
- input,
931
+ input: convolverNode,
914
932
  output: convolverNode,
915
933
  convolverNode,
916
934
  };
@@ -1273,10 +1291,12 @@ export class MidyGM2 {
1273
1291
  startVibrato(channel, note, scheduleTime) {
1274
1292
  const { voiceParams } = note;
1275
1293
  const state = channel.state;
1294
+ const vibratoRate = state.vibratoRate * 2;
1295
+ const vibratoDelay = state.vibratoDelay * 2;
1276
1296
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
1277
- frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
1297
+ frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
1278
1298
  });
1279
- note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1299
+ note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
1280
1300
  note.vibratoDepth = new GainNode(this.audioContext);
1281
1301
  this.setVibLfoToPitch(channel, note, scheduleTime);
1282
1302
  note.vibratoLFO.connect(note.vibratoDepth);
@@ -1340,12 +1360,8 @@ export class MidyGM2 {
1340
1360
  }
1341
1361
  note.bufferSource.connect(note.filterNode);
1342
1362
  note.filterNode.connect(note.volumeEnvelopeNode);
1343
- if (0 < state.chorusSendLevel) {
1344
- this.setChorusEffectsSend(channel, note, 0, now);
1345
- }
1346
- if (0 < state.reverbSendLevel) {
1347
- this.setReverbEffectsSend(channel, note, 0, now);
1348
- }
1363
+ this.setChorusSend(channel, note, now);
1364
+ this.setReverbSend(channel, note, now);
1349
1365
  note.bufferSource.start(startTime);
1350
1366
  return note;
1351
1367
  }
@@ -1411,10 +1427,14 @@ export class MidyGM2 {
1411
1427
  return;
1412
1428
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1413
1429
  if (channel.isDrum) {
1414
- const audioContext = this.audioContext;
1415
- const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1416
- channel.keyBasedGainLs[noteNumber] = gainL;
1417
- channel.keyBasedGainRs[noteNumber] = gainR;
1430
+ const { keyBasedGainLs, keyBasedGainRs } = channel;
1431
+ let gainL = keyBasedGainLs[noteNumber];
1432
+ let gainR = keyBasedGainRs[noteNumber];
1433
+ if (!gainL) {
1434
+ const audioNodes = this.createChannelAudioNodes(this.audioContext);
1435
+ gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
1436
+ gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
1437
+ }
1418
1438
  note.volumeEnvelopeNode.connect(gainL);
1419
1439
  note.volumeEnvelopeNode.connect(gainR);
1420
1440
  }
@@ -1448,11 +1468,11 @@ export class MidyGM2 {
1448
1468
  note.vibratoDepth.disconnect();
1449
1469
  note.vibratoLFO.stop();
1450
1470
  }
1451
- if (note.reverbEffectsSend) {
1452
- note.reverbEffectsSend.disconnect();
1471
+ if (note.reverbSend) {
1472
+ note.reverbSend.disconnect();
1453
1473
  }
1454
- if (note.chorusEffectsSend) {
1455
- note.chorusEffectsSend.disconnect();
1474
+ if (note.chorusSend) {
1475
+ note.chorusSend.disconnect();
1456
1476
  }
1457
1477
  }
1458
1478
  releaseNote(channel, note, endTime) {
@@ -1588,6 +1608,7 @@ export class MidyGM2 {
1588
1608
  break;
1589
1609
  }
1590
1610
  }
1611
+ channel.keyBasedTable.fill(-1);
1591
1612
  }
1592
1613
  setChannelPressure(channelNumber, value, scheduleTime) {
1593
1614
  const channel = this.channels[channelNumber];
@@ -1603,7 +1624,7 @@ export class MidyGM2 {
1603
1624
  }
1604
1625
  const table = channel.channelPressureTable;
1605
1626
  this.processActiveNotes(channel, scheduleTime, (note) => {
1606
- this.setControllerParameters(channel, note, table);
1627
+ this.setEffects(channel, note, table, scheduleTime);
1607
1628
  });
1608
1629
  this.applyVoiceParams(channel, 13);
1609
1630
  }
@@ -1625,22 +1646,32 @@ export class MidyGM2 {
1625
1646
  this.applyVoiceParams(channel, 14, scheduleTime);
1626
1647
  }
1627
1648
  setModLfoToPitch(channel, note, scheduleTime) {
1628
- const modLfoToPitch = note.voiceParams.modLfoToPitch +
1629
- this.getLFOPitchDepth(channel);
1630
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1631
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1632
- note.modulationDepth.gain
1633
- .cancelScheduledValues(scheduleTime)
1634
- .setValueAtTime(modulationDepth, scheduleTime);
1649
+ if (note.modulationDepth) {
1650
+ const modLfoToPitch = note.voiceParams.modLfoToPitch +
1651
+ this.getLFOPitchDepth(channel, note);
1652
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1653
+ const depth = baseDepth * Math.sign(modLfoToPitch);
1654
+ note.modulationDepth.gain
1655
+ .cancelScheduledValues(scheduleTime)
1656
+ .setValueAtTime(depth, scheduleTime);
1657
+ }
1658
+ else {
1659
+ this.startModulation(channel, note, scheduleTime);
1660
+ }
1635
1661
  }
1636
1662
  setVibLfoToPitch(channel, note, scheduleTime) {
1637
- const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1638
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1639
- 2;
1640
- const vibratoDepthSign = 0 < vibLfoToPitch;
1641
- note.vibratoDepth.gain
1642
- .cancelScheduledValues(scheduleTime)
1643
- .setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
1663
+ if (note.vibratoDepth) {
1664
+ const vibratoDepth = channel.state.vibratoDepth * 2;
1665
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1666
+ const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
1667
+ const depth = baseDepth * Math.sign(vibLfoToPitch);
1668
+ note.vibratoDepth.gain
1669
+ .cancelScheduledValues(scheduleTime)
1670
+ .setValueAtTime(depth, scheduleTime);
1671
+ }
1672
+ else {
1673
+ this.startVibrato(channel, note, scheduleTime);
1674
+ }
1644
1675
  }
1645
1676
  setModLfoToFilterFc(channel, note, scheduleTime) {
1646
1677
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
@@ -1658,73 +1689,72 @@ export class MidyGM2 {
1658
1689
  .cancelScheduledValues(scheduleTime)
1659
1690
  .setValueAtTime(volumeDepth, scheduleTime);
1660
1691
  }
1661
- setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1662
- let value = note.voiceParams.reverbEffectsSend;
1692
+ setReverbSend(channel, note, scheduleTime) {
1693
+ let value = note.voiceParams.reverbEffectsSend *
1694
+ channel.state.reverbSendLevel;
1663
1695
  if (channel.isDrum) {
1664
1696
  const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1665
- if (0 <= keyBasedValue) {
1666
- value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1667
- }
1697
+ if (0 <= keyBasedValue)
1698
+ value = keyBasedValue / 127;
1668
1699
  }
1669
- if (0 < prevValue) {
1700
+ if (!note.reverbSend) {
1670
1701
  if (0 < value) {
1671
- note.reverbEffectsSend.gain
1672
- .cancelScheduledValues(scheduleTime)
1673
- .setValueAtTime(value, scheduleTime);
1674
- }
1675
- else {
1676
- note.reverbEffectsSend.disconnect();
1702
+ note.reverbSend = new GainNode(this.audioContext, { gain: value });
1703
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1704
+ note.reverbSend.connect(this.reverbEffect.input);
1677
1705
  }
1678
1706
  }
1679
1707
  else {
1708
+ note.reverbSend.gain
1709
+ .cancelScheduledValues(scheduleTime)
1710
+ .setValueAtTime(value, scheduleTime);
1680
1711
  if (0 < value) {
1681
- if (!note.reverbEffectsSend) {
1682
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1683
- gain: value,
1684
- });
1685
- note.volumeNode.connect(note.reverbEffectsSend);
1712
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1713
+ }
1714
+ else {
1715
+ try {
1716
+ note.volumeEnvelopeNode.disconnect(note.reverbSend);
1686
1717
  }
1687
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1718
+ catch { /* empty */ }
1688
1719
  }
1689
1720
  }
1690
1721
  }
1691
- setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1692
- let value = note.voiceParams.chorusEffectsSend;
1722
+ setChorusSend(channel, note, scheduleTime) {
1723
+ let value = note.voiceParams.chorusEffectsSend *
1724
+ channel.state.chorusSendLevel;
1693
1725
  if (channel.isDrum) {
1694
1726
  const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1695
- if (0 <= keyBasedValue) {
1696
- value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1697
- }
1727
+ if (0 <= keyBasedValue)
1728
+ value = keyBasedValue / 127;
1698
1729
  }
1699
- if (0 < prevValue) {
1730
+ if (!note.chorusSend) {
1700
1731
  if (0 < value) {
1701
- note.chorusEffectsSend.gain
1702
- .cancelScheduledValues(scheduleTime)
1703
- .setValueAtTime(value, scheduleTime);
1704
- }
1705
- else {
1706
- note.chorusEffectsSend.disconnect();
1732
+ note.chorusSend = new GainNode(this.audioContext, { gain: value });
1733
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1734
+ note.chorusSend.connect(this.chorusEffect.input);
1707
1735
  }
1708
1736
  }
1709
1737
  else {
1738
+ note.chorusSend.gain
1739
+ .cancelScheduledValues(scheduleTime)
1740
+ .setValueAtTime(value, scheduleTime);
1710
1741
  if (0 < value) {
1711
- if (!note.chorusEffectsSend) {
1712
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1713
- gain: value,
1714
- });
1715
- note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1742
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1743
+ }
1744
+ else {
1745
+ try {
1746
+ note.volumeEnvelopeNode.disconnect(note.chorusSend);
1716
1747
  }
1717
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1748
+ catch { /* empty */ }
1718
1749
  }
1719
1750
  }
1720
1751
  }
1721
- setDelayModLFO(note, scheduleTime) {
1722
- const startTime = note.startTime;
1723
- if (startTime < scheduleTime)
1724
- return;
1725
- note.modulationLFO.stop(scheduleTime);
1726
- note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1727
- note.modulationLFO.connect(note.filterDepth);
1752
+ setDelayModLFO(note) {
1753
+ const startTime = note.startTime + note.voiceParams.delayModLFO;
1754
+ try {
1755
+ note.modulationLFO.start(startTime);
1756
+ }
1757
+ catch { /* empty */ }
1728
1758
  }
1729
1759
  setFreqModLFO(note, scheduleTime) {
1730
1760
  const freqModLFO = note.voiceParams.freqModLFO;
@@ -1733,54 +1763,65 @@ export class MidyGM2 {
1733
1763
  .setValueAtTime(freqModLFO, scheduleTime);
1734
1764
  }
1735
1765
  setFreqVibLFO(channel, note, scheduleTime) {
1766
+ const vibratoRate = channel.state.vibratoRate * 2;
1736
1767
  const freqVibLFO = note.voiceParams.freqVibLFO;
1737
1768
  note.vibratoLFO.frequency
1738
1769
  .cancelScheduledValues(scheduleTime)
1739
- .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
1770
+ .setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
1771
+ }
1772
+ setDelayVibLFO(channel, note) {
1773
+ const vibratoDelay = channel.state.vibratoDelay * 2;
1774
+ const value = note.voiceParams.delayVibLFO;
1775
+ const startTime = note.startTime + value * vibratoDelay;
1776
+ try {
1777
+ note.vibratoLFO.start(startTime);
1778
+ }
1779
+ catch { /* empty */ }
1740
1780
  }
1741
1781
  createVoiceParamsHandlers() {
1742
1782
  return {
1743
- modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1783
+ modLfoToPitch: (channel, note, scheduleTime) => {
1744
1784
  if (0 < channel.state.modulationDepth) {
1745
1785
  this.setModLfoToPitch(channel, note, scheduleTime);
1746
1786
  }
1747
1787
  },
1748
- vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1788
+ vibLfoToPitch: (channel, note, scheduleTime) => {
1749
1789
  if (0 < channel.state.vibratoDepth) {
1750
1790
  this.setVibLfoToPitch(channel, note, scheduleTime);
1751
1791
  }
1752
1792
  },
1753
- modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
1793
+ modLfoToFilterFc: (channel, note, scheduleTime) => {
1754
1794
  if (0 < channel.state.modulationDepth) {
1755
1795
  this.setModLfoToFilterFc(channel, note, scheduleTime);
1756
1796
  }
1757
1797
  },
1758
- modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
1798
+ modLfoToVolume: (channel, note, scheduleTime) => {
1759
1799
  if (0 < channel.state.modulationDepth) {
1760
1800
  this.setModLfoToVolume(channel, note, scheduleTime);
1761
1801
  }
1762
1802
  },
1763
- chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
1764
- this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
1803
+ chorusEffectsSend: (channel, note, scheduleTime) => {
1804
+ this.setChorusSend(channel, note, scheduleTime);
1805
+ },
1806
+ reverbEffectsSend: (channel, note, scheduleTime) => {
1807
+ this.setReverbSend(channel, note, scheduleTime);
1808
+ },
1809
+ delayModLFO: (_channel, note, _scheduleTime) => {
1810
+ if (0 < channel.state.modulationDepth) {
1811
+ this.setDelayModLFO(note);
1812
+ }
1765
1813
  },
1766
- reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
1767
- this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
1814
+ freqModLFO: (_channel, note, scheduleTime) => {
1815
+ if (0 < channel.state.modulationDepth) {
1816
+ this.setFreqModLFO(note, scheduleTime);
1817
+ }
1768
1818
  },
1769
- delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
1770
- freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
1771
- delayVibLFO: (channel, note, prevValue, scheduleTime) => {
1819
+ delayVibLFO: (channel, note, _scheduleTime) => {
1772
1820
  if (0 < channel.state.vibratoDepth) {
1773
- const vibratoDelay = channel.state.vibratoDelay * 2;
1774
- const prevStartTime = note.startTime + prevValue * vibratoDelay;
1775
- if (scheduleTime < prevStartTime)
1776
- return;
1777
- const value = note.voiceParams.delayVibLFO;
1778
- const startTime = note.startTime + value * vibratoDelay;
1779
- note.vibratoLFO.stop(scheduleTime);
1780
- note.vibratoLFO.start(startTime);
1821
+ setDelayVibLFO(channel, note);
1781
1822
  }
1782
1823
  },
1783
- freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
1824
+ freqVibLFO: (channel, note, scheduleTime) => {
1784
1825
  if (0 < channel.state.vibratoDepth) {
1785
1826
  this.setFreqVibLFO(channel, note, scheduleTime);
1786
1827
  }
@@ -1808,7 +1849,7 @@ export class MidyGM2 {
1808
1849
  continue;
1809
1850
  note.voiceParams[key] = value;
1810
1851
  if (key in this.voiceParamsHandlers) {
1811
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1852
+ this.voiceParamsHandlers[key](channel, note, scheduleTime);
1812
1853
  }
1813
1854
  else {
1814
1855
  if (volumeEnvelopeKeySet.has(key))
@@ -1863,7 +1904,7 @@ export class MidyGM2 {
1863
1904
  handler.call(this, channelNumber, value, scheduleTime);
1864
1905
  const channel = this.channels[channelNumber];
1865
1906
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1866
- this.applyControlTable(channel, controllerType, scheduleTime);
1907
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
1867
1908
  }
1868
1909
  else {
1869
1910
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1879,7 +1920,6 @@ export class MidyGM2 {
1879
1920
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1880
1921
  }
1881
1922
  else {
1882
- this.setPitchEnvelope(note, scheduleTime);
1883
1923
  this.startModulation(channel, note, scheduleTime);
1884
1924
  }
1885
1925
  });
@@ -1924,8 +1964,14 @@ export class MidyGM2 {
1924
1964
  scheduleTime ??= this.audioContext.currentTime;
1925
1965
  const channel = this.channels[channelNumber];
1926
1966
  channel.state.volume = volume / 127;
1927
- this.updateChannelVolume(channel, scheduleTime);
1928
- this.updateKeyBasedVolume(channel, scheduleTime);
1967
+ if (channel.isDrum) {
1968
+ for (let i = 0; i < 128; i++) {
1969
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1970
+ }
1971
+ }
1972
+ else {
1973
+ this.updateChannelVolume(channel, scheduleTime);
1974
+ }
1929
1975
  }
1930
1976
  panToGain(pan) {
1931
1977
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1938,8 +1984,14 @@ export class MidyGM2 {
1938
1984
  scheduleTime ??= this.audioContext.currentTime;
1939
1985
  const channel = this.channels[channelNumber];
1940
1986
  channel.state.pan = pan / 127;
1941
- this.updateChannelVolume(channel, scheduleTime);
1942
- this.updateKeyBasedVolume(channel, scheduleTime);
1987
+ if (channel.isDrum) {
1988
+ for (let i = 0; i < 128; i++) {
1989
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1990
+ }
1991
+ }
1992
+ else {
1993
+ this.updateChannelVolume(channel, scheduleTime);
1994
+ }
1943
1995
  }
1944
1996
  setExpression(channelNumber, expression, scheduleTime) {
1945
1997
  scheduleTime ??= this.audioContext.currentTime;
@@ -1965,33 +2017,27 @@ export class MidyGM2 {
1965
2017
  .cancelScheduledValues(scheduleTime)
1966
2018
  .setValueAtTime(volume * gainRight, scheduleTime);
1967
2019
  }
1968
- updateKeyBasedVolume(channel, scheduleTime) {
1969
- if (!channel.isDrum)
2020
+ updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
2021
+ const gainL = channel.keyBasedGainLs[keyNumber];
2022
+ if (!gainL)
1970
2023
  return;
2024
+ const gainR = channel.keyBasedGainRs[keyNumber];
1971
2025
  const state = channel.state;
1972
2026
  const defaultVolume = state.volume * state.expression;
1973
2027
  const defaultPan = state.pan;
1974
- for (let i = 0; i < 128; i++) {
1975
- const gainL = channel.keyBasedGainLs[i];
1976
- const gainR = channel.keyBasedGainLs[i];
1977
- if (!gainL)
1978
- continue;
1979
- if (!gainR)
1980
- continue;
1981
- const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
1982
- const volume = (0 <= keyBasedVolume)
1983
- ? defaultVolume * keyBasedVolume / 64
1984
- : defaultVolume;
1985
- const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
1986
- const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1987
- const { gainLeft, gainRight } = this.panToGain(pan);
1988
- gainL.gain
1989
- .cancelScheduledValues(scheduleTime)
1990
- .setValueAtTime(volume * gainLeft, scheduleTime);
1991
- gainR.gain
1992
- .cancelScheduledValues(scheduleTime)
1993
- .setValueAtTime(volume * gainRight, scheduleTime);
1994
- }
2028
+ const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
2029
+ const volume = (0 <= keyBasedVolume)
2030
+ ? defaultVolume * keyBasedVolume / 64
2031
+ : defaultVolume;
2032
+ const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
2033
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
2034
+ const { gainLeft, gainRight } = this.panToGain(pan);
2035
+ gainL.gain
2036
+ .cancelScheduledValues(scheduleTime)
2037
+ .setValueAtTime(volume * gainLeft, scheduleTime);
2038
+ gainR.gain
2039
+ .cancelScheduledValues(scheduleTime)
2040
+ .setValueAtTime(volume * gainRight, scheduleTime);
1995
2041
  }
1996
2042
  setSustainPedal(channelNumber, value, scheduleTime) {
1997
2043
  const channel = this.channels[channelNumber];
@@ -2058,67 +2104,19 @@ export class MidyGM2 {
2058
2104
  scheduleTime ??= this.audioContext.currentTime;
2059
2105
  const channel = this.channels[channelNumber];
2060
2106
  const state = channel.state;
2061
- const reverbEffect = this.reverbEffect;
2062
- if (0 < state.reverbSendLevel) {
2063
- if (0 < reverbSendLevel) {
2064
- state.reverbSendLevel = reverbSendLevel / 127;
2065
- reverbEffect.input.gain
2066
- .cancelScheduledValues(scheduleTime)
2067
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2068
- }
2069
- else {
2070
- this.processScheduledNotes(channel, (note) => {
2071
- if (note.voiceParams.reverbEffectsSend <= 0)
2072
- return false;
2073
- if (note.reverbEffectsSend)
2074
- note.reverbEffectsSend.disconnect();
2075
- });
2076
- }
2077
- }
2078
- else {
2079
- if (0 < reverbSendLevel) {
2080
- this.processScheduledNotes(channel, (note) => {
2081
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2082
- });
2083
- state.reverbSendLevel = reverbSendLevel / 127;
2084
- reverbEffect.input.gain
2085
- .cancelScheduledValues(scheduleTime)
2086
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2087
- }
2088
- }
2107
+ state.reverbSendLevel = reverbSendLevel / 127;
2108
+ this.processScheduledNotes(channel, (note) => {
2109
+ this.setReverbSend(channel, note, scheduleTime);
2110
+ });
2089
2111
  }
2090
2112
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2091
2113
  scheduleTime ??= this.audioContext.currentTime;
2092
2114
  const channel = this.channels[channelNumber];
2093
2115
  const state = channel.state;
2094
- const chorusEffect = this.chorusEffect;
2095
- if (0 < state.chorusSendLevel) {
2096
- if (0 < chorusSendLevel) {
2097
- state.chorusSendLevel = chorusSendLevel / 127;
2098
- chorusEffect.input.gain
2099
- .cancelScheduledValues(scheduleTime)
2100
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2101
- }
2102
- else {
2103
- this.processScheduledNotes(channel, (note) => {
2104
- if (note.voiceParams.chorusEffectsSend <= 0)
2105
- return false;
2106
- if (note.chorusEffectsSend)
2107
- note.chorusEffectsSend.disconnect();
2108
- });
2109
- }
2110
- }
2111
- else {
2112
- if (0 < chorusSendLevel) {
2113
- this.processScheduledNotes(channel, (note) => {
2114
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2115
- });
2116
- state.chorusSendLevel = chorusSendLevel / 127;
2117
- chorusEffect.input.gain
2118
- .cancelScheduledValues(scheduleTime)
2119
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2120
- }
2121
- }
2116
+ state.chorusSendLevel = chorusSendLevel / 127;
2117
+ this.processScheduledNotes(channel, (note) => {
2118
+ this.setChorusSend(channel, note, scheduleTime);
2119
+ });
2122
2120
  }
2123
2121
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
2124
2122
  if (maxLSB < channel.dataLSB) {
@@ -2178,8 +2176,8 @@ export class MidyGM2 {
2178
2176
  }
2179
2177
  handlePitchBendRangeRPN(channelNumber, scheduleTime) {
2180
2178
  const channel = this.channels[channelNumber];
2181
- this.limitData(channel, 0, 127, 0, 99);
2182
- const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
2179
+ this.limitData(channel, 0, 127, 0, 127);
2180
+ const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
2183
2181
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
2184
2182
  }
2185
2183
  setPitchBendRange(channelNumber, value, scheduleTime) {
@@ -2189,7 +2187,7 @@ export class MidyGM2 {
2189
2187
  scheduleTime ??= this.audioContext.currentTime;
2190
2188
  const state = channel.state;
2191
2189
  const prev = state.pitchWheelSensitivity;
2192
- const next = value / 128;
2190
+ const next = value / 12800;
2193
2191
  state.pitchWheelSensitivity = next;
2194
2192
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
2195
2193
  this.updateChannelDetune(channel, scheduleTime);
@@ -2198,7 +2196,8 @@ export class MidyGM2 {
2198
2196
  handleFineTuningRPN(channelNumber, scheduleTime) {
2199
2197
  const channel = this.channels[channelNumber];
2200
2198
  this.limitData(channel, 0, 127, 0, 127);
2201
- const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
2199
+ const value = channel.dataMSB * 128 + channel.dataLSB;
2200
+ const fineTuning = (value - 8192) / 8192 * 100;
2202
2201
  this.setFineTuning(channelNumber, fineTuning, scheduleTime);
2203
2202
  }
2204
2203
  setFineTuning(channelNumber, value, scheduleTime) {
@@ -2207,7 +2206,7 @@ export class MidyGM2 {
2207
2206
  return;
2208
2207
  scheduleTime ??= this.audioContext.currentTime;
2209
2208
  const prev = channel.fineTuning;
2210
- const next = (value - 8192) / 8.192; // cent
2209
+ const next = value;
2211
2210
  channel.fineTuning = next;
2212
2211
  channel.detune += next - prev;
2213
2212
  this.updateChannelDetune(channel, scheduleTime);
@@ -2215,7 +2214,7 @@ export class MidyGM2 {
2215
2214
  handleCoarseTuningRPN(channelNumber, scheduleTime) {
2216
2215
  const channel = this.channels[channelNumber];
2217
2216
  this.limitDataMSB(channel, 0, 127);
2218
- const coarseTuning = channel.dataMSB;
2217
+ const coarseTuning = (channel.dataMSB - 64) * 100;
2219
2218
  this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
2220
2219
  }
2221
2220
  setCoarseTuning(channelNumber, value, scheduleTime) {
@@ -2224,7 +2223,7 @@ export class MidyGM2 {
2224
2223
  return;
2225
2224
  scheduleTime ??= this.audioContext.currentTime;
2226
2225
  const prev = channel.coarseTuning;
2227
- const next = (value - 64) * 100; // cent
2226
+ const next = value;
2228
2227
  channel.coarseTuning = next;
2229
2228
  channel.detune += next - prev;
2230
2229
  this.updateChannelDetune(channel, scheduleTime);
@@ -2232,22 +2231,22 @@ export class MidyGM2 {
2232
2231
  handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
2233
2232
  const channel = this.channels[channelNumber];
2234
2233
  this.limitData(channel, 0, 127, 0, 127);
2235
- const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
2236
- this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
2234
+ const value = (channel.dataMSB + channel.dataLSB / 128) * 100;
2235
+ this.setModulationDepthRange(channelNumber, value, scheduleTime);
2237
2236
  }
2238
- setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2237
+ setModulationDepthRange(channelNumber, value, scheduleTime) {
2239
2238
  const channel = this.channels[channelNumber];
2240
2239
  if (channel.isDrum)
2241
2240
  return;
2242
2241
  scheduleTime ??= this.audioContext.currentTime;
2243
- channel.modulationDepthRange = modulationDepthRange;
2242
+ channel.modulationDepthRange = value;
2244
2243
  this.updateModulation(channel, scheduleTime);
2245
2244
  }
2246
2245
  allSoundOff(channelNumber, _value, scheduleTime) {
2247
2246
  scheduleTime ??= this.audioContext.currentTime;
2248
2247
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2249
2248
  }
2250
- resetAllStates(channelNumber) {
2249
+ resetChannelStates(channelNumber) {
2251
2250
  const scheduleTime = this.audioContext.currentTime;
2252
2251
  const channel = this.channels[channelNumber];
2253
2252
  const state = channel.state;
@@ -2265,8 +2264,8 @@ export class MidyGM2 {
2265
2264
  }
2266
2265
  this.resetChannelTable(channel);
2267
2266
  this.mode = "GM2";
2268
- this.masterFineTuning = 0; // cb
2269
- this.masterCoarseTuning = 0; // cb
2267
+ this.masterFineTuning = 0; // cent
2268
+ this.masterCoarseTuning = 0; // cent
2270
2269
  }
2271
2270
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2272
2271
  resetAllControllers(channelNumber, _value, scheduleTime) {
@@ -2400,9 +2399,9 @@ export class MidyGM2 {
2400
2399
  case 9:
2401
2400
  switch (data[3]) {
2402
2401
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2403
- return this.handlePressureSysEx(data, "channelPressureTable");
2402
+ return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
2404
2403
  case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2405
- return this.handleControlChangeSysEx(data);
2404
+ return this.handleControlChangeSysEx(data, scheduleTime);
2406
2405
  default:
2407
2406
  console.warn(`Unsupported Exclusive Message: ${data}`);
2408
2407
  }
@@ -2435,12 +2434,13 @@ export class MidyGM2 {
2435
2434
  }
2436
2435
  }
2437
2436
  handleMasterFineTuningSysEx(data, scheduleTime) {
2438
- const fineTuning = data[5] * 128 + data[4];
2437
+ const value = (data[5] * 128 + data[4]) / 16383;
2438
+ const fineTuning = (value - 8192) / 8192 * 100;
2439
2439
  this.setMasterFineTuning(fineTuning, scheduleTime);
2440
2440
  }
2441
2441
  setMasterFineTuning(value, scheduleTime) {
2442
2442
  const prev = this.masterFineTuning;
2443
- const next = (value - 8192) / 8.192; // cent
2443
+ const next = value;
2444
2444
  this.masterFineTuning = next;
2445
2445
  const detuneChange = next - prev;
2446
2446
  for (let i = 0; i < this.channels.length; i++) {
@@ -2452,12 +2452,12 @@ export class MidyGM2 {
2452
2452
  }
2453
2453
  }
2454
2454
  handleMasterCoarseTuningSysEx(data, scheduleTime) {
2455
- const coarseTuning = data[4];
2455
+ const coarseTuning = (data[4] - 64) * 100;
2456
2456
  this.setMasterCoarseTuning(coarseTuning, scheduleTime);
2457
2457
  }
2458
2458
  setMasterCoarseTuning(value, scheduleTime) {
2459
2459
  const prev = this.masterCoarseTuning;
2460
- const next = (value - 64) * 100; // cent
2460
+ const next = value;
2461
2461
  this.masterCoarseTuning = next;
2462
2462
  const detuneChange = next - prev;
2463
2463
  for (let i = 0; i < this.channels.length; i++) {
@@ -2721,9 +2721,9 @@ export class MidyGM2 {
2721
2721
  : 0;
2722
2722
  return channelPressure / 127;
2723
2723
  }
2724
- setControllerParameters(channel, note, table, scheduleTime) {
2724
+ setEffects(channel, note, table, scheduleTime) {
2725
2725
  if (0 <= table[0])
2726
- this.updateDetune(channel, note, scueduleTime);
2726
+ this.updateDetune(channel, note, scheduleTime);
2727
2727
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2728
2728
  if (0 <= table[1]) {
2729
2729
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2745,7 +2745,7 @@ export class MidyGM2 {
2745
2745
  if (0 <= table[5])
2746
2746
  this.setModLfoToVolume(channel, note, scheduleTime);
2747
2747
  }
2748
- handlePressureSysEx(data, tableName) {
2748
+ handlePressureSysEx(data, tableName, scheduleTime) {
2749
2749
  const channelNumber = data[4];
2750
2750
  const channel = this.channels[channelNumber];
2751
2751
  if (channel.isDrum)
@@ -2756,36 +2756,42 @@ export class MidyGM2 {
2756
2756
  const rr = data[i + 1];
2757
2757
  table[pp] = rr;
2758
2758
  }
2759
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2760
+ this.setEffects(channel, note, table, scheduleTime);
2761
+ });
2759
2762
  }
2760
2763
  initControlTable() {
2761
2764
  const ccCount = 128;
2762
2765
  const slotSize = 6;
2763
2766
  return new Int8Array(ccCount * slotSize).fill(-1);
2764
2767
  }
2765
- applyControlTable(channel, controllerType, scheduleTime) {
2768
+ setControlChangeEffects(channel, controllerType, scheduleTime) {
2766
2769
  const slotSize = 6;
2767
2770
  const offset = controllerType * slotSize;
2768
2771
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2769
2772
  this.processScheduledNotes(channel, (note) => {
2770
- this.setControllerParameters(channel, note, table, scheduleTime);
2773
+ this.setEffects(channel, note, table, scheduleTime);
2771
2774
  });
2772
2775
  }
2773
- handleControlChangeSysEx(data) {
2776
+ handleControlChangeSysEx(data, scheduleTime) {
2774
2777
  const channelNumber = data[4];
2775
2778
  const channel = this.channels[channelNumber];
2776
2779
  if (channel.isDrum)
2777
2780
  return;
2781
+ const slotSize = 6;
2778
2782
  const controllerType = data[5];
2779
- const table = channel.controlTable[controllerType];
2780
- for (let i = 6; i < data.length - 1; i += 2) {
2783
+ const offset = controllerType * slotSize;
2784
+ const table = channel.controlTable;
2785
+ for (let i = 6; i < data.length; i += 2) {
2781
2786
  const pp = data[i];
2782
2787
  const rr = data[i + 1];
2783
- table[pp] = rr;
2788
+ table[offset + pp] = rr;
2784
2789
  }
2790
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
2785
2791
  }
2786
2792
  getKeyBasedValue(channel, keyNumber, controllerType) {
2787
2793
  const index = keyNumber * 128 + controllerType;
2788
- const controlValue = channel.keyBasedInstrumentControlTable[index];
2794
+ const controlValue = channel.keyBasedTable[index];
2789
2795
  return controlValue;
2790
2796
  }
2791
2797
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
@@ -2794,14 +2800,33 @@ export class MidyGM2 {
2794
2800
  if (!channel.isDrum)
2795
2801
  return;
2796
2802
  const keyNumber = data[5];
2797
- const table = channel.keyBasedInstrumentControlTable;
2798
- for (let i = 6; i < data.length - 1; i += 2) {
2803
+ const table = channel.keyBasedTable;
2804
+ for (let i = 6; i < data.length; i += 2) {
2799
2805
  const controllerType = data[i];
2800
2806
  const value = data[i + 1];
2801
2807
  const index = keyNumber * 128 + controllerType;
2802
2808
  table[index] = value;
2809
+ switch (controllerType) {
2810
+ case 7:
2811
+ case 10:
2812
+ this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
2813
+ break;
2814
+ case 91:
2815
+ this.processScheduledNotes(channel, (note) => {
2816
+ if (note.noteNumber === keyNumber) {
2817
+ this.setReverbSend(channel, note, scheduleTime);
2818
+ }
2819
+ });
2820
+ break;
2821
+ case 93:
2822
+ this.processScheduledNotes(channel, (note) => {
2823
+ if (note.noteNumber === keyNumber) {
2824
+ this.setChorusSend(channel, note, scheduleTime);
2825
+ }
2826
+ });
2827
+ break;
2828
+ }
2803
2829
  }
2804
- this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2805
2830
  }
2806
2831
  handleSysEx(data, scheduleTime) {
2807
2832
  switch (data[0]) {
@@ -2850,7 +2875,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2850
2875
  rpnLSB: 127,
2851
2876
  mono: false, // CC#124, CC#125
2852
2877
  modulationDepthRange: 50, // cent
2853
- fineTuning: 0, // cb
2854
- coarseTuning: 0, // cb
2878
+ fineTuning: 0, // cent
2879
+ coarseTuning: 0, // cent
2855
2880
  }
2856
2881
  });