@marmooo/midy 0.1.0 → 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.
package/script/midy.js CHANGED
@@ -85,7 +85,7 @@ class Midy {
85
85
  writable: true,
86
86
  value: {
87
87
  time: this.getReverbTime(64),
88
- feedback: 0.2,
88
+ feedback: 0.25,
89
89
  }
90
90
  });
91
91
  Object.defineProperty(this, "chorus", {
@@ -93,10 +93,11 @@ class Midy {
93
93
  configurable: true,
94
94
  writable: true,
95
95
  value: {
96
- modRate: 3 * 0.122,
97
- modDepth: (3 + 1) / 3.2,
98
- feedback: 8 * 0.763,
99
- sendToReverb: 0 * 0.787,
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),
100
101
  }
101
102
  });
102
103
  Object.defineProperty(this, "mono", {
@@ -697,9 +698,7 @@ class Midy {
697
698
  createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
698
699
  const input = new GainNode(audioContext);
699
700
  const output = new GainNode(audioContext);
700
- const mergerGain = new GainNode(audioContext, {
701
- gain: 1 / (combDelays.length * 2),
702
- });
701
+ const mergerGain = new GainNode(audioContext);
703
702
  for (let i = 0; i < combDelays.length; i++) {
704
703
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
705
704
  comb.connect(mergerGain);
@@ -712,60 +711,62 @@ class Midy {
712
711
  allpasses.at(-1).connect(output);
713
712
  return { input, output };
714
713
  }
715
- createChorusEffect(audioContext, options = {}) {
716
- const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
717
- const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
718
- const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
714
+ createChorusEffect(audioContext) {
715
+ const input = new GainNode(audioContext);
719
716
  const output = new GainNode(audioContext);
720
- const chorusGains = [];
717
+ const sendGain = new GainNode(audioContext);
718
+ const lfo = new OscillatorNode(audioContext, {
719
+ frequency: this.chorus.modRate,
720
+ });
721
+ const lfoGain = new GainNode(audioContext, {
722
+ gain: this.chorus.modDepth / 2,
723
+ });
724
+ const delayTimes = this.chorus.delayTimes;
721
725
  const delayNodes = [];
722
- const baseGain = 1 / chorusCount;
723
- for (let i = 0; i < chorusCount; i++) {
724
- const randomDelayFactor = (Math.random() - 0.5) * variance;
725
- const delayTime = (i + 1) * delay + randomDelayFactor;
726
+ const feedbackGains = [];
727
+ for (let i = 0; i < delayTimes.length; i++) {
728
+ const delayTime = delayTimes[i];
726
729
  const delayNode = new DelayNode(audioContext, {
727
- maxDelayTime: delayTime,
730
+ maxDelayTime: 0.1, // generally, 5ms < delayTime < 50ms
731
+ delayTime,
732
+ });
733
+ const feedbackGain = new GainNode(audioContext, {
734
+ gain: this.chorus.feedback,
728
735
  });
729
- const chorusGain = new GainNode(audioContext, { gain: baseGain });
730
736
  delayNodes.push(delayNode);
731
- chorusGains.push(chorusGain);
737
+ feedbackGains.push(feedbackGain);
738
+ input.connect(delayNode);
732
739
  lfoGain.connect(delayNode.delayTime);
733
- delayNode.connect(chorusGain);
734
- chorusGain.connect(output);
740
+ delayNode.connect(feedbackGain);
741
+ feedbackGain.connect(delayNode);
742
+ delayNode.connect(output);
735
743
  }
744
+ output.connect(sendGain);
736
745
  lfo.connect(lfoGain);
737
746
  lfo.start();
738
747
  return {
748
+ input,
749
+ output,
750
+ sendGain,
739
751
  lfo,
740
752
  lfoGain,
741
753
  delayNodes,
742
- chorusGains,
743
- output,
754
+ feedbackGains,
744
755
  };
745
756
  }
746
757
  connectEffects(channel, gainNode) {
747
758
  gainNode.connect(channel.merger);
748
759
  channel.merger.connect(this.masterGain);
749
- if (channel.reverbSendLevel === 0) {
750
- if (channel.chorusSendLevel !== 0) { // chorus
751
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
752
- channel.merger.connect(delayNode);
753
- });
754
- channel.chorusEffect.output.connect(this.masterGain);
755
- }
760
+ if (0 < channel.reverbSendLevel) {
761
+ channel.merger.connect(channel.reverbEffect.input);
762
+ channel.reverbEffect.output.connect(this.masterGain);
756
763
  }
757
- else {
758
- if (channel.chorusSendLevel === 0) { // reverb
759
- channel.merger.connect(channel.reverbEffect.input);
760
- channel.reverbEffect.output.connect(this.masterGain);
761
- }
762
- else { // reverb + chorus
763
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
764
- channel.merger.connect(delayNode);
765
- });
766
- channel.merger.connect(channel.reverbEffect.input);
767
- channel.reverbEffect.output.connect(this.masterGain);
768
- }
764
+ if (0 < channel.chorusSendLevel) {
765
+ channel.merger.connect(channel.chorusEffect.input);
766
+ channel.reverbEffect.output.connect(this.masterGain);
767
+ }
768
+ if (0 < this.chorus.sendToReverb) {
769
+ channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
769
770
  }
770
771
  }
771
772
  cbToRatio(cb) {
@@ -1224,9 +1225,12 @@ class Midy {
1224
1225
  reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1225
1226
  }
1226
1227
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1228
+ const now = this.audioContext.currentTime;
1227
1229
  const channel = this.channels[channelNumber];
1230
+ const chorusEffect = channel.chorusEffect;
1228
1231
  channel.chorusSendLevel = chorusSendLevel / 127;
1229
- channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1232
+ chorusEffect.output.gain.cancelScheduledValues(now);
1233
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1230
1234
  }
1231
1235
  setSostenutoPedal(channelNumber, value) {
1232
1236
  const isOn = value >= 64;
@@ -1486,7 +1490,7 @@ class Midy {
1486
1490
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1487
1491
  return this.handleMasterCoarseTuningSysEx(data);
1488
1492
  case 5:
1489
- return this.handleGlobalParameterControl(data);
1493
+ return this.handleGlobalParameterControlSysEx(data);
1490
1494
  default:
1491
1495
  console.warn(`Unsupported Exclusive Message: ${data}`);
1492
1496
  }
@@ -1563,13 +1567,13 @@ class Midy {
1563
1567
  this.masterCoarseTuning = coarseTuning - 64;
1564
1568
  }
1565
1569
  }
1566
- handleGlobalParameterControl(data) {
1567
- if (data[5] === 1) {
1568
- switch (data[6]) {
1570
+ handleGlobalParameterControlSysEx(data) {
1571
+ if (data[7] === 1) {
1572
+ switch (data[8]) {
1569
1573
  case 1:
1570
- return this.handleReverbParameter(data);
1574
+ return this.handleReverbParameterSysEx(data);
1571
1575
  case 2:
1572
- return this.handleChorusParameter(data);
1576
+ return this.handleChorusParameterSysEx(data);
1573
1577
  default:
1574
1578
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1575
1579
  }
@@ -1578,12 +1582,12 @@ class Midy {
1578
1582
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1579
1583
  }
1580
1584
  }
1581
- handleReverbParameter(data) {
1582
- switch (data[7]) {
1585
+ handleReverbParameterSysEx(data) {
1586
+ switch (data[9]) {
1583
1587
  case 0:
1584
- return this.setReverbType(data[8]);
1588
+ return this.setReverbType(data[10]);
1585
1589
  case 1:
1586
- return this.setReverbTime(data[8]);
1590
+ return this.setReverbTime(data[10]);
1587
1591
  }
1588
1592
  }
1589
1593
  setReverbType(type) {
@@ -1643,41 +1647,109 @@ class Midy {
1643
1647
  // RT60 = -3 * delay / Math.log10(feedback)
1644
1648
  // feedback = Math.pow(10, -3 * delay / RT60)
1645
1649
  // delay estimation using ideal feedback
1646
- // A suitable average sound absorption coefficient is 0.18-0.28.
1647
- // Since the structure of the hall is complex,
1648
- // It would be easier to determine the delay based on the ideal feedback.
1650
+ // The structure of a concert hall is complex,
1651
+ // so estimates based on mean free path are unstable.
1652
+ // It is easier to determine the delay based on ideal feedback.
1653
+ // The average sound absorption coefficient
1654
+ // suitable for playing musical instruments is 0.18 to 0.28.
1649
1655
  // delay = -RT60 * Math.log10(feedback) / 3
1650
1656
  calcDelay(rt60, feedback) {
1651
1657
  return -rt60 * Math.log10(feedback) / 3;
1652
1658
  }
1653
- handleChorusParameter(data) {
1654
- switch (data[7]) {
1659
+ handleChorusParameterSysEx(data) {
1660
+ switch (data[9]) {
1655
1661
  case 0:
1656
- return this.setChorusType(data[8]);
1662
+ return this.setChorusType(data[10]);
1657
1663
  case 1:
1658
- return this.setChorusModRate(data[8]);
1664
+ return this.setChorusModRate(data[10]);
1659
1665
  case 2:
1660
- return this.setChorusModDepth(data[8]);
1666
+ return this.setChorusModDepth(data[10]);
1661
1667
  case 3:
1662
- return this.setChorusFeedback(data[8]);
1668
+ return this.setChorusFeedback(data[10]);
1663
1669
  case 4:
1664
- return this.setChorusSendToReverb(data[8]);
1670
+ return this.setChorusSendToReverb(data[10]);
1665
1671
  }
1666
1672
  }
1667
1673
  setChorusType(type) {
1668
- // TODO
1674
+ switch (type) {
1675
+ case 0:
1676
+ return this.setChorusParameter(3, 5, 0, 0);
1677
+ case 1:
1678
+ return this.setChorusParameter(9, 19, 5, 0);
1679
+ case 2:
1680
+ return this.setChorusParameter(3, 19, 8, 0);
1681
+ case 3:
1682
+ return this.setChorusParameter(9, 16, 16, 0);
1683
+ case 4:
1684
+ return this.setChorusParameter(2, 24, 64, 0);
1685
+ case 5:
1686
+ return this.setChorusParameter(1, 5, 112, 0);
1687
+ default:
1688
+ console.warn(`Unsupported Chorus Type: ${type}`);
1689
+ }
1690
+ }
1691
+ setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
1692
+ this.setChorusModRate(modRate);
1693
+ this.setChorusModDepth(modDepth);
1694
+ this.setChorusFeedback(feedback);
1695
+ this.setChorusSendToReverb(sendToReverb);
1669
1696
  }
1670
1697
  setChorusModRate(value) {
1671
- // TODO
1698
+ const now = this.audioContext.currentTime;
1699
+ const modRate = this.getChorusModRate(value);
1700
+ this.chorus.modRate = modRate;
1701
+ for (let i = 0; i < this.channels.length; i++) {
1702
+ const lfo = this.channels[i].chorusEffect.lfo;
1703
+ lfo.frequency.setValueAtTime(modRate, now);
1704
+ }
1705
+ }
1706
+ getChorusModRate(value) {
1707
+ return value * 0.122; // Hz
1672
1708
  }
1673
1709
  setChorusModDepth(value) {
1674
- // TODO
1710
+ const now = this.audioContext.currentTime;
1711
+ const modDepth = this.getChorusModDepth(value);
1712
+ this.chorus.modDepth = modDepth;
1713
+ for (let i = 0; i < this.channels.length; i++) {
1714
+ const chorusEffect = this.channels[i].chorusEffect;
1715
+ chorusEffect.lfoGain.gain
1716
+ .cancelScheduledValues(now)
1717
+ .setValueAtTime(modDepth / 2, now);
1718
+ }
1719
+ }
1720
+ getChorusModDepth(value) {
1721
+ return (value + 1) / 3200; // second
1675
1722
  }
1676
1723
  setChorusFeedback(value) {
1677
- // TODO
1724
+ const now = this.audioContext.currentTime;
1725
+ const feedback = this.getChorusFeedback(value);
1726
+ this.chorus.feedback = feedback;
1727
+ for (let i = 0; i < this.channels.length; i++) {
1728
+ const chorusEffect = this.channels[i].chorusEffect;
1729
+ for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1730
+ const feedbackGain = chorusEffect.feedbackGains[j];
1731
+ feedbackGain.gain
1732
+ .cancelScheduledValues(now)
1733
+ .setValueAtTime(feedback, now);
1734
+ }
1735
+ }
1736
+ }
1737
+ getChorusFeedback(value) {
1738
+ return value * 0.00763;
1678
1739
  }
1679
1740
  setChorusSendToReverb(value) {
1680
- // TODO
1741
+ const now = this.audioContext.currentTime;
1742
+ const sendToReverb = this.getChorusSendToReverb(value);
1743
+ this.chorus.sendToReverb = sendToReverb;
1744
+ for (let i = 0; i < this.channels.length; i++) {
1745
+ const chorusEffect = this.channels[i].chorusEffect;
1746
+ chorusEffect.sendGain.gain
1747
+ .cancelScheduledValues(now)
1748
+ .setValueAtTime(sendToReverb, now);
1749
+ }
1750
+ }
1751
+ getChorusSendToReverb(value) {
1752
+ return value * 0.00787;
1681
1753
  }
1682
1754
  handleExclusiveMessage(data) {
1683
1755
  console.warn(`Unsupported Exclusive Message: ${data}`);