@marmooo/midy 0.0.9 → 0.1.1

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.
@@ -67,12 +67,6 @@ class MidyGM2 {
67
67
  writable: true,
68
68
  value: 0
69
69
  });
70
- Object.defineProperty(this, "reverbFactor", {
71
- enumerable: true,
72
- configurable: true,
73
- writable: true,
74
- value: 0.1
75
- });
76
70
  Object.defineProperty(this, "masterFineTuning", {
77
71
  enumerable: true,
78
72
  configurable: true,
@@ -85,6 +79,27 @@ class MidyGM2 {
85
79
  writable: true,
86
80
  value: 0
87
81
  }); // cb
82
+ Object.defineProperty(this, "reverb", {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: {
87
+ time: this.getReverbTime(64),
88
+ feedback: 0.25,
89
+ }
90
+ });
91
+ Object.defineProperty(this, "chorus", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: {
96
+ modRate: this.getChorusModRate(3),
97
+ modDepth: this.getChorusModDepth(19),
98
+ feedback: this.getChorusFeedback(8),
99
+ sendToReverb: this.getChorusSendToReverb(0),
100
+ delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
101
+ }
102
+ });
88
103
  Object.defineProperty(this, "mono", {
89
104
  enumerable: true,
90
105
  configurable: true,
@@ -193,8 +208,19 @@ class MidyGM2 {
193
208
  writable: true,
194
209
  value: {
195
210
  reverbAlgorithm: (audioContext) => {
196
- // return this.createConvolutionReverb(audioContext);
197
- return this.createSchroederReverb(audioContext);
211
+ const { time: rt60, feedback } = this.reverb;
212
+ // const delay = this.calcDelay(rt60, feedback);
213
+ // const impulse = this.createConvolutionReverbImpulse(
214
+ // audioContext,
215
+ // rt60,
216
+ // delay,
217
+ // );
218
+ // return this.createConvolutionReverb(audioContext, impulse);
219
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
220
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
221
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
222
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
223
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
198
224
  },
199
225
  }
200
226
  });
@@ -593,12 +619,7 @@ class MidyGM2 {
593
619
  }
594
620
  return noteList[0];
595
621
  }
596
- createConvolutionReverb(audioContext, options = {}) {
597
- const { decay = 0.8, preDecay = 0, } = options;
598
- const input = new GainNode(audioContext);
599
- const output = new GainNode(audioContext);
600
- const dryGain = new GainNode(audioContext);
601
- const wetGain = new GainNode(audioContext);
622
+ createConvolutionReverbImpulse(audioContext, decay, preDecay) {
602
623
  const sampleRate = audioContext.sampleRate;
603
624
  const length = sampleRate * decay;
604
625
  const impulse = new AudioBuffer({
@@ -618,18 +639,17 @@ class MidyGM2 {
618
639
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
619
640
  }
620
641
  }
642
+ return impulse;
643
+ }
644
+ createConvolutionReverb(audioContext, impulse) {
645
+ const output = new GainNode(audioContext);
621
646
  const convolverNode = new ConvolverNode(audioContext, {
622
647
  buffer: impulse,
623
648
  });
624
- input.connect(convolverNode);
625
- convolverNode.connect(wetGain);
626
- wetGain.connect(output);
627
- dryGain.connect(output);
649
+ convolverNode.connect(output);
628
650
  return {
629
- input,
651
+ input: convolverNode,
630
652
  output,
631
- dryGain,
632
- wetGain,
633
653
  convolverNode,
634
654
  };
635
655
  }
@@ -657,17 +677,22 @@ class MidyGM2 {
657
677
  delayNode.connect(passGain);
658
678
  return passGain;
659
679
  }
680
+ generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
681
+ const variance = center * varianceRatio;
682
+ const array = new Array(count);
683
+ for (let i = 0; i < count; i++) {
684
+ const fraction = i / (count - 1 || 1);
685
+ const value = center - variance + fraction * 2 * variance;
686
+ array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
687
+ }
688
+ return array;
689
+ }
660
690
  // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
661
691
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
662
- createSchroederReverb(audioContext, options = {}) {
663
- const { combDelays = [0.31, 0.34, 0.37, 0.40], combFeedbacks = [0.86, 0.87, 0.88, 0.89], allpassDelays = [0.02, 0.05], allpassFeedbacks = [0.7, 0.7], mix = 0.5, } = options;
692
+ createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
664
693
  const input = new GainNode(audioContext);
665
694
  const output = new GainNode(audioContext);
666
- const mergerGain = new GainNode(audioContext, {
667
- gain: 1 / (combDelays.length * 2),
668
- });
669
- const dryGain = new GainNode(audioContext, { gain: 1 - mix });
670
- const wetGain = new GainNode(audioContext, { gain: mix });
695
+ const mergerGain = new GainNode(audioContext);
671
696
  for (let i = 0; i < combDelays.length; i++) {
672
697
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
673
698
  comb.connect(mergerGain);
@@ -677,68 +702,65 @@ class MidyGM2 {
677
702
  const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
678
703
  allpasses.push(allpass);
679
704
  }
680
- allpasses.at(-1).connect(wetGain);
681
- input.connect(dryGain);
682
- dryGain.connect(output);
683
- wetGain.connect(output);
684
- return { input, output, dryGain, wetGain };
685
- }
686
- createChorusEffect(audioContext, options = {}) {
687
- const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
688
- const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
689
- const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
705
+ allpasses.at(-1).connect(output);
706
+ return { input, output };
707
+ }
708
+ createChorusEffect(audioContext) {
709
+ const input = new GainNode(audioContext);
690
710
  const output = new GainNode(audioContext);
691
- const chorusGains = [];
711
+ const sendGain = new GainNode(audioContext);
712
+ const lfo = new OscillatorNode(audioContext, {
713
+ frequency: this.chorus.modRate,
714
+ });
715
+ const lfoGain = new GainNode(audioContext, {
716
+ gain: this.chorus.modDepth / 2,
717
+ });
718
+ const delayTimes = this.chorus.delayTimes;
692
719
  const delayNodes = [];
693
- const baseGain = 1 / chorusCount;
694
- for (let i = 0; i < chorusCount; i++) {
695
- const randomDelayFactor = (Math.random() - 0.5) * variance;
696
- const delayTime = (i + 1) * delay + randomDelayFactor;
720
+ const feedbackGains = [];
721
+ for (let i = 0; i < delayTimes.length; i++) {
722
+ const delayTime = delayTimes[i];
697
723
  const delayNode = new DelayNode(audioContext, {
698
- maxDelayTime: delayTime,
724
+ maxDelayTime: 0.1, // generally, 5ms < delayTime < 50ms
725
+ delayTime,
726
+ });
727
+ const feedbackGain = new GainNode(audioContext, {
728
+ gain: this.chorus.feedback,
699
729
  });
700
- const chorusGain = new GainNode(audioContext, { gain: baseGain });
701
730
  delayNodes.push(delayNode);
702
- chorusGains.push(chorusGain);
731
+ feedbackGains.push(feedbackGain);
732
+ input.connect(delayNode);
703
733
  lfoGain.connect(delayNode.delayTime);
704
- delayNode.connect(chorusGain);
705
- chorusGain.connect(output);
734
+ delayNode.connect(feedbackGain);
735
+ feedbackGain.connect(delayNode);
736
+ delayNode.connect(output);
706
737
  }
738
+ output.connect(sendGain);
707
739
  lfo.connect(lfoGain);
708
740
  lfo.start();
709
741
  return {
742
+ input,
743
+ output,
744
+ sendGain,
710
745
  lfo,
711
746
  lfoGain,
712
747
  delayNodes,
713
- chorusGains,
714
- output,
748
+ feedbackGains,
715
749
  };
716
750
  }
717
751
  connectEffects(channel, gainNode) {
718
752
  gainNode.connect(channel.merger);
719
- if (channel.reverb === 0) {
720
- if (channel.chorus === 0) { // no effect
721
- channel.merger.connect(this.masterGain);
722
- }
723
- else { // chorus
724
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
725
- channel.merger.connect(delayNode);
726
- });
727
- channel.chorusEffect.output.connect(this.masterGain);
728
- }
753
+ channel.merger.connect(this.masterGain);
754
+ if (0 < channel.reverbSendLevel) {
755
+ channel.merger.connect(channel.reverbEffect.input);
756
+ channel.reverbEffect.output.connect(this.masterGain);
729
757
  }
730
- else {
731
- if (channel.chorus === 0) { // reverb
732
- channel.merger.connect(channel.reverbEffect.input);
733
- channel.reverbEffect.output.connect(this.masterGain);
734
- }
735
- else { // reverb + chorus
736
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
737
- channel.merger.connect(delayNode);
738
- });
739
- channel.merger.connect(channel.reverbEffect.input);
740
- channel.reverbEffect.output.connect(this.masterGain);
741
- }
758
+ if (0 < channel.chorusSendLevel) {
759
+ channel.merger.connect(channel.chorusEffect.input);
760
+ channel.reverbEffect.output.connect(this.masterGain);
761
+ }
762
+ if (0 < this.chorus.sendToReverb) {
763
+ channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
742
764
  }
743
765
  }
744
766
  cbToRatio(cb) {
@@ -1139,23 +1161,25 @@ class MidyGM2 {
1139
1161
  this.releaseSustainPedal(channelNumber, value);
1140
1162
  }
1141
1163
  }
1164
+ // TODO
1142
1165
  setPortamento(channelNumber, value) {
1143
1166
  this.channels[channelNumber].portamento = value >= 64;
1144
1167
  }
1145
- setReverbSendLevel(channelNumber, reverb) {
1168
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1146
1169
  const now = this.audioContext.currentTime;
1147
1170
  const channel = this.channels[channelNumber];
1148
1171
  const reverbEffect = channel.reverbEffect;
1149
- channel.reverb = reverb / 127 * this.reverbFactor;
1150
- reverbEffect.dryGain.gain.cancelScheduledValues(now);
1151
- reverbEffect.dryGain.gain.setValueAtTime(1 - channel.reverb, now);
1152
- reverbEffect.wetGain.gain.cancelScheduledValues(now);
1153
- reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1172
+ channel.reverbSendLevel = reverbSendLevel / 127;
1173
+ reverbEffect.output.gain.cancelScheduledValues(now);
1174
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1154
1175
  }
1155
- setChorusSendLevel(channelNumber, chorus) {
1176
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1177
+ const now = this.audioContext.currentTime;
1156
1178
  const channel = this.channels[channelNumber];
1157
- channel.chorus = chorus / 127;
1158
- channel.chorusEffect.lfoGain = channel.chorus;
1179
+ const chorusEffect = channel.chorusEffect;
1180
+ channel.chorusSendLevel = chorusSendLevel / 127;
1181
+ chorusEffect.output.gain.cancelScheduledValues(now);
1182
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1159
1183
  }
1160
1184
  setSostenutoPedal(channelNumber, value) {
1161
1185
  const isOn = value >= 64;
@@ -1351,11 +1375,11 @@ class MidyGM2 {
1351
1375
  this.GM2SystemOn();
1352
1376
  break;
1353
1377
  default:
1354
- console.warn(`Unsupported Exclusive Message ${data}`);
1378
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1355
1379
  }
1356
1380
  break;
1357
1381
  default:
1358
- console.warn(`Unsupported Exclusive Message ${data}`);
1382
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1359
1383
  }
1360
1384
  }
1361
1385
  GM1SystemOn() {
@@ -1386,9 +1410,10 @@ class MidyGM2 {
1386
1410
  return this.handleMasterFineTuningSysEx(data);
1387
1411
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1388
1412
  return this.handleMasterCoarseTuningSysEx(data);
1389
- // case 5: // TODO: Global Parameter Control
1413
+ case 5:
1414
+ return this.handleGlobalParameterControlSysEx(data);
1390
1415
  default:
1391
- console.warn(`Unsupported Exclusive Message ${data}`);
1416
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1392
1417
  }
1393
1418
  break;
1394
1419
  case 8:
@@ -1397,7 +1422,7 @@ class MidyGM2 {
1397
1422
  // // TODO
1398
1423
  // return this.handleScaleOctaveTuning1ByteFormat();
1399
1424
  default:
1400
- console.warn(`Unsupported Exclusive Message ${data}`);
1425
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1401
1426
  }
1402
1427
  break;
1403
1428
  case 9:
@@ -1409,7 +1434,7 @@ class MidyGM2 {
1409
1434
  // // TODO
1410
1435
  // return this.setControlChange();
1411
1436
  default:
1412
- console.warn(`Unsupported Exclusive Message ${data}`);
1437
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1413
1438
  }
1414
1439
  break;
1415
1440
  case 10:
@@ -1418,11 +1443,11 @@ class MidyGM2 {
1418
1443
  // // TODO
1419
1444
  // return this.handleKeyBasedInstrumentControl();
1420
1445
  default:
1421
- console.warn(`Unsupported Exclusive Message ${data}`);
1446
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1422
1447
  }
1423
1448
  break;
1424
1449
  default:
1425
- console.warn(`Unsupported Exclusive Message ${data}`);
1450
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1426
1451
  }
1427
1452
  }
1428
1453
  handleMasterVolumeSysEx(data) {
@@ -1463,8 +1488,192 @@ class MidyGM2 {
1463
1488
  this.masterCoarseTuning = coarseTuning - 64;
1464
1489
  }
1465
1490
  }
1491
+ handleGlobalParameterControlSysEx(data) {
1492
+ if (data[7] === 1) {
1493
+ switch (data[8]) {
1494
+ case 1:
1495
+ return this.handleReverbParameterSysEx(data);
1496
+ case 2:
1497
+ return this.handleChorusParameterSysEx(data);
1498
+ default:
1499
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1500
+ }
1501
+ }
1502
+ else {
1503
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1504
+ }
1505
+ }
1506
+ handleReverbParameterSysEx(data) {
1507
+ switch (data[9]) {
1508
+ case 0:
1509
+ return this.setReverbType(data[10]);
1510
+ case 1:
1511
+ return this.setReverbTime(data[10]);
1512
+ }
1513
+ }
1514
+ setReverbType(type) {
1515
+ this.reverb.time = this.getReverbTimeFromType(type);
1516
+ this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1517
+ const { audioContext, channels, options } = this;
1518
+ for (let i = 0; i < channels.length; i++) {
1519
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1520
+ }
1521
+ }
1522
+ getReverbTimeFromType(type) {
1523
+ switch (type) {
1524
+ case 0:
1525
+ return this.getReverbTime(44);
1526
+ case 1:
1527
+ return this.getReverbTime(50);
1528
+ case 2:
1529
+ return this.getReverbTime(56);
1530
+ case 3:
1531
+ return this.getReverbTime(64);
1532
+ case 4:
1533
+ return this.getReverbTime(64);
1534
+ case 8:
1535
+ return this.getReverbTime(50);
1536
+ default:
1537
+ console.warn(`Unsupported Reverb Time: ${type}`);
1538
+ }
1539
+ }
1540
+ setReverbTime(value) {
1541
+ this.reverb.time = this.getReverbTime(value);
1542
+ const { audioContext, channels, options } = this;
1543
+ for (let i = 0; i < channels.length; i++) {
1544
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1545
+ }
1546
+ }
1547
+ getReverbTime(value) {
1548
+ return Math.pow(Math.E, (value - 40) * 0.025);
1549
+ }
1550
+ // mean free path equation
1551
+ // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
1552
+ // 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
1553
+ // V: room size (m^3)
1554
+ // S: room surface area (m^2)
1555
+ // meanFreePath = 4V / S (m)
1556
+ // delay estimation using mean free path
1557
+ // t: degree Celsius, generally used 20
1558
+ // c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
1559
+ // delay = meanFreePath / c (s)
1560
+ // feedback equation
1561
+ // RT60 means that the energy is reduced to Math.pow(10, -6).
1562
+ // Since energy is proportional to the square of the amplitude,
1563
+ // the amplitude is reduced to Math.pow(10, -3).
1564
+ // When this is done through n feedbacks,
1565
+ // Math.pow(feedback, n) = Math.pow(10, -3)
1566
+ // Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
1567
+ // RT60 / delay * Math.log10(feedback) = -3
1568
+ // RT60 = -3 * delay / Math.log10(feedback)
1569
+ // feedback = Math.pow(10, -3 * delay / RT60)
1570
+ // delay estimation using ideal feedback
1571
+ // The structure of a concert hall is complex,
1572
+ // so estimates based on mean free path are unstable.
1573
+ // It is easier to determine the delay based on ideal feedback.
1574
+ // The average sound absorption coefficient
1575
+ // suitable for playing musical instruments is 0.18 to 0.28.
1576
+ // delay = -RT60 * Math.log10(feedback) / 3
1577
+ calcDelay(rt60, feedback) {
1578
+ return -rt60 * Math.log10(feedback) / 3;
1579
+ }
1580
+ handleChorusParameterSysEx(data) {
1581
+ switch (data[9]) {
1582
+ case 0:
1583
+ return this.setChorusType(data[10]);
1584
+ case 1:
1585
+ return this.setChorusModRate(data[10]);
1586
+ case 2:
1587
+ return this.setChorusModDepth(data[10]);
1588
+ case 3:
1589
+ return this.setChorusFeedback(data[10]);
1590
+ case 4:
1591
+ return this.setChorusSendToReverb(data[10]);
1592
+ }
1593
+ }
1594
+ setChorusType(type) {
1595
+ switch (type) {
1596
+ case 0:
1597
+ return this.setChorusParameter(3, 5, 0, 0);
1598
+ case 1:
1599
+ return this.setChorusParameter(9, 19, 5, 0);
1600
+ case 2:
1601
+ return this.setChorusParameter(3, 19, 8, 0);
1602
+ case 3:
1603
+ return this.setChorusParameter(9, 16, 16, 0);
1604
+ case 4:
1605
+ return this.setChorusParameter(2, 24, 64, 0);
1606
+ case 5:
1607
+ return this.setChorusParameter(1, 5, 112, 0);
1608
+ default:
1609
+ console.warn(`Unsupported Chorus Type: ${type}`);
1610
+ }
1611
+ }
1612
+ setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
1613
+ this.setChorusModRate(modRate);
1614
+ this.setChorusModDepth(modDepth);
1615
+ this.setChorusFeedback(feedback);
1616
+ this.setChorusSendToReverb(sendToReverb);
1617
+ }
1618
+ setChorusModRate(value) {
1619
+ const now = this.audioContext.currentTime;
1620
+ const modRate = this.getChorusModRate(value);
1621
+ this.chorus.modRate = modRate;
1622
+ for (let i = 0; i < this.channels.length; i++) {
1623
+ const lfo = this.channels[i].chorusEffect.lfo;
1624
+ lfo.frequency.setValueAtTime(modRate, now);
1625
+ }
1626
+ }
1627
+ getChorusModRate(value) {
1628
+ return value * 0.122; // Hz
1629
+ }
1630
+ setChorusModDepth(value) {
1631
+ const now = this.audioContext.currentTime;
1632
+ const modDepth = this.getChorusModDepth(value);
1633
+ this.chorus.modDepth = modDepth;
1634
+ for (let i = 0; i < this.channels.length; i++) {
1635
+ const chorusEffect = this.channels[i].chorusEffect;
1636
+ chorusEffect.lfoGain.gain
1637
+ .cancelScheduledValues(now)
1638
+ .setValueAtTime(modDepth / 2, now);
1639
+ }
1640
+ }
1641
+ getChorusModDepth(value) {
1642
+ return (value + 1) / 3200; // second
1643
+ }
1644
+ setChorusFeedback(value) {
1645
+ const now = this.audioContext.currentTime;
1646
+ const feedback = this.getChorusFeedback(value);
1647
+ this.chorus.feedback = feedback;
1648
+ for (let i = 0; i < this.channels.length; i++) {
1649
+ const chorusEffect = this.channels[i].chorusEffect;
1650
+ for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1651
+ const feedbackGain = chorusEffect.feedbackGains[j];
1652
+ feedbackGain.gain
1653
+ .cancelScheduledValues(now)
1654
+ .setValueAtTime(feedback, now);
1655
+ }
1656
+ }
1657
+ }
1658
+ getChorusFeedback(value) {
1659
+ return value * 0.00763;
1660
+ }
1661
+ setChorusSendToReverb(value) {
1662
+ const now = this.audioContext.currentTime;
1663
+ const sendToReverb = this.getChorusSendToReverb(value);
1664
+ this.chorus.sendToReverb = sendToReverb;
1665
+ for (let i = 0; i < this.channels.length; i++) {
1666
+ const chorusEffect = this.channels[i].chorusEffect;
1667
+ chorusEffect.sendGain.gain
1668
+ .cancelScheduledValues(now)
1669
+ .setValueAtTime(sendToReverb, now);
1670
+ }
1671
+ }
1672
+ getChorusSendToReverb(value) {
1673
+ return value * 0.00787;
1674
+ }
1466
1675
  handleExclusiveMessage(data) {
1467
- console.warn(`Unsupported Exclusive Message ${data}`);
1676
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1468
1677
  }
1469
1678
  handleSysEx(data) {
1470
1679
  switch (data[0]) {
@@ -1498,8 +1707,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1498
1707
  volume: 100 / 127,
1499
1708
  pan: 64,
1500
1709
  portamentoTime: 0,
1501
- reverb: 0,
1502
- chorus: 0,
1710
+ reverbSendLevel: 0,
1711
+ chorusSendLevel: 0,
1503
1712
  bank: 121 * 128,
1504
1713
  bankMSB: 121,
1505
1714
  bankLSB: 0,
@@ -896,11 +896,11 @@ class MidyGMLite {
896
896
  case 2: // GM System Off
897
897
  break;
898
898
  default:
899
- console.warn(`Unsupported Exclusive Message ${data}`);
899
+ console.warn(`Unsupported Exclusive Message: ${data}`);
900
900
  }
901
901
  break;
902
902
  default:
903
- console.warn(`Unsupported Exclusive Message ${data}`);
903
+ console.warn(`Unsupported Exclusive Message: ${data}`);
904
904
  }
905
905
  }
906
906
  GM1SystemOn() {
@@ -919,11 +919,11 @@ class MidyGMLite {
919
919
  case 1:
920
920
  return this.handleMasterVolumeSysEx(data);
921
921
  default:
922
- console.warn(`Unsupported Exclusive Message ${data}`);
922
+ console.warn(`Unsupported Exclusive Message: ${data}`);
923
923
  }
924
924
  break;
925
925
  default:
926
- console.warn(`Unsupported Exclusive Message ${data}`);
926
+ console.warn(`Unsupported Exclusive Message: ${data}`);
927
927
  }
928
928
  }
929
929
  handleMasterVolumeSysEx(data) {
@@ -941,7 +941,7 @@ class MidyGMLite {
941
941
  }
942
942
  }
943
943
  handleExclusiveMessage(data) {
944
- console.warn(`Unsupported Exclusive Message ${data}`);
944
+ console.warn(`Unsupported Exclusive Message: ${data}`);
945
945
  }
946
946
  handleSysEx(data) {
947
947
  switch (data[0]) {