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