@marmooo/midy 0.1.0 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
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.8,
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", {
@@ -226,8 +227,12 @@ class Midy {
226
227
  this.audioContext = audioContext;
227
228
  this.options = { ...this.defaultOptions, ...options };
228
229
  this.masterGain = new GainNode(audioContext);
229
- this.masterGain.connect(audioContext.destination);
230
230
  this.channels = this.createChannels(audioContext);
231
+ this.reverbEffect = this.options.reverbAlgorithm(audioContext);
232
+ this.chorusEffect = this.createChorusEffect(audioContext);
233
+ this.chorusEffect.output.connect(this.masterGain);
234
+ this.reverbEffect.output.connect(this.masterGain);
235
+ this.masterGain.connect(audioContext.destination);
231
236
  this.GM2SystemOn();
232
237
  }
233
238
  initSoundFontTable() {
@@ -271,14 +276,11 @@ class Midy {
271
276
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
272
277
  gainL.connect(merger, 0, 0);
273
278
  gainR.connect(merger, 0, 1);
274
- const reverbEffect = this.options.reverbAlgorithm(audioContext);
275
- const chorusEffect = this.createChorusEffect(audioContext);
279
+ merger.connect(this.masterGain);
276
280
  return {
277
281
  gainL,
278
282
  gainR,
279
283
  merger,
280
- reverbEffect,
281
- chorusEffect,
282
284
  };
283
285
  }
284
286
  createChannels(audioContext) {
@@ -694,12 +696,10 @@ class Midy {
694
696
  }
695
697
  // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
696
698
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
697
- createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
699
+ createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
698
700
  const input = new GainNode(audioContext);
699
701
  const output = new GainNode(audioContext);
700
- const mergerGain = new GainNode(audioContext, {
701
- gain: 1 / (combDelays.length * 2),
702
- });
702
+ const mergerGain = new GainNode(audioContext);
703
703
  for (let i = 0; i < combDelays.length; i++) {
704
704
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
705
705
  comb.connect(mergerGain);
@@ -712,62 +712,49 @@ class Midy {
712
712
  allpasses.at(-1).connect(output);
713
713
  return { input, output };
714
714
  }
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 });
715
+ createChorusEffect(audioContext) {
716
+ const input = new GainNode(audioContext);
719
717
  const output = new GainNode(audioContext);
720
- const chorusGains = [];
718
+ const sendGain = new GainNode(audioContext);
719
+ const lfo = new OscillatorNode(audioContext, {
720
+ frequency: this.chorus.modRate,
721
+ });
722
+ const lfoGain = new GainNode(audioContext, {
723
+ gain: this.chorus.modDepth / 2,
724
+ });
725
+ const delayTimes = this.chorus.delayTimes;
721
726
  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;
727
+ const feedbackGains = [];
728
+ for (let i = 0; i < delayTimes.length; i++) {
729
+ const delayTime = delayTimes[i];
726
730
  const delayNode = new DelayNode(audioContext, {
727
- maxDelayTime: delayTime,
731
+ maxDelayTime: 0.1, // generally, 5ms < delayTime < 50ms
732
+ delayTime,
733
+ });
734
+ const feedbackGain = new GainNode(audioContext, {
735
+ gain: this.chorus.feedback,
728
736
  });
729
- const chorusGain = new GainNode(audioContext, { gain: baseGain });
730
737
  delayNodes.push(delayNode);
731
- chorusGains.push(chorusGain);
738
+ feedbackGains.push(feedbackGain);
739
+ input.connect(delayNode);
732
740
  lfoGain.connect(delayNode.delayTime);
733
- delayNode.connect(chorusGain);
734
- chorusGain.connect(output);
741
+ delayNode.connect(feedbackGain);
742
+ feedbackGain.connect(delayNode);
743
+ delayNode.connect(output);
735
744
  }
745
+ output.connect(sendGain);
736
746
  lfo.connect(lfoGain);
737
747
  lfo.start();
738
748
  return {
749
+ input,
750
+ output,
751
+ sendGain,
739
752
  lfo,
740
753
  lfoGain,
741
754
  delayNodes,
742
- chorusGains,
743
- output,
755
+ feedbackGains,
744
756
  };
745
757
  }
746
- connectEffects(channel, gainNode) {
747
- gainNode.connect(channel.merger);
748
- 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
- }
756
- }
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
- }
769
- }
770
- }
771
758
  cbToRatio(cb) {
772
759
  return Math.pow(10, cb / 200);
773
760
  }
@@ -784,14 +771,10 @@ class Midy {
784
771
  return instrumentKey.playbackRate(noteNumber) *
785
772
  Math.pow(2, semitoneOffset / 12);
786
773
  }
787
- setVolumeEnvelope(channel, note) {
788
- const { instrumentKey, startTime, velocity } = note;
774
+ setVolumeEnvelope(note) {
775
+ const { instrumentKey, startTime } = note;
789
776
  note.gainNode = new GainNode(this.audioContext, { gain: 0 });
790
- let volume = (velocity / 127) * channel.volume * channel.expression;
791
- if (volume === 0)
792
- volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
793
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
794
- volume;
777
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
795
778
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
796
779
  const volDelay = startTime + instrumentKey.volDelay;
797
780
  const volAttack = volDelay + instrumentKey.volAttack;
@@ -864,7 +847,7 @@ class Midy {
864
847
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
865
848
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
866
849
  note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
867
- this.setVolumeEnvelope(channel, note);
850
+ this.setVolumeEnvelope(note);
868
851
  this.setFilterEnvelope(channel, note);
869
852
  if (channel.modulation > 0) {
870
853
  const delayModLFO = startTime + instrumentKey.delayModLFO;
@@ -904,7 +887,8 @@ class Midy {
904
887
  if (!instrumentKey)
905
888
  return;
906
889
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
907
- this.connectEffects(channel, note.gainNode);
890
+ note.gainNode.connect(channel.gainL);
891
+ note.gainNode.connect(channel.gainR);
908
892
  if (channel.sostenutoPedal) {
909
893
  channel.sostenutoNotes.set(noteNumber, note);
910
894
  }
@@ -1064,13 +1048,13 @@ class Midy {
1064
1048
  }
1065
1049
  }
1066
1050
  handlePitchBendMessage(channelNumber, lsb, msb) {
1067
- const pitchBend = msb * 128 + lsb;
1051
+ const pitchBend = msb * 128 + lsb - 8192;
1068
1052
  this.setPitchBend(channelNumber, pitchBend);
1069
1053
  }
1070
1054
  setPitchBend(channelNumber, pitchBend) {
1071
1055
  const channel = this.channels[channelNumber];
1072
1056
  const prevPitchBend = channel.pitchBend;
1073
- channel.pitchBend = (pitchBend - 8192) / 8192;
1057
+ channel.pitchBend = pitchBend / 8192;
1074
1058
  const detuneChange = (channel.pitchBend - prevPitchBend) *
1075
1059
  channel.pitchBendRange * 100;
1076
1060
  this.updateDetune(channel, detuneChange);
@@ -1216,17 +1200,30 @@ class Midy {
1216
1200
  this.channels[channelNumber].portamento = value >= 64;
1217
1201
  }
1218
1202
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1219
- const now = this.audioContext.currentTime;
1220
1203
  const channel = this.channels[channelNumber];
1221
- const reverbEffect = channel.reverbEffect;
1222
- channel.reverbSendLevel = reverbSendLevel / 127;
1223
- reverbEffect.output.gain.cancelScheduledValues(now);
1224
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1204
+ const reverbEffect = this.reverbEffect;
1205
+ if (0 < reverbSendLevel) {
1206
+ const now = this.audioContext.currentTime;
1207
+ channel.reverbSendLevel = reverbSendLevel / 127;
1208
+ reverbEffect.output.gain.cancelScheduledValues(now);
1209
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1210
+ }
1211
+ else if (channel.reverbSendLevel !== 0) {
1212
+ channel.merger.disconnect(reverbEffect.input);
1213
+ }
1225
1214
  }
1226
1215
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1227
1216
  const channel = this.channels[channelNumber];
1228
- channel.chorusSendLevel = chorusSendLevel / 127;
1229
- channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1217
+ const chorusEffect = this.chorusEffect;
1218
+ if (0 < chorusSendLevel) {
1219
+ const now = this.audioContext.currentTime;
1220
+ channel.chorusSendLevel = chorusSendLevel / 127;
1221
+ chorusEffect.output.gain.cancelScheduledValues(now);
1222
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1223
+ }
1224
+ else if (channel.chorusSendLevel !== 0) {
1225
+ channel.merger.disconnect(chorusEffect.input);
1226
+ }
1230
1227
  }
1231
1228
  setSostenutoPedal(channelNumber, value) {
1232
1229
  const isOn = value >= 64;
@@ -1419,7 +1416,7 @@ class Midy {
1419
1416
  channel.scheduledNotes.forEach((noteList) => {
1420
1417
  const activeNote = this.getActiveNote(noteList, now);
1421
1418
  if (activeNote) {
1422
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1419
+ const notePromise = this.scheduleNoteRelease(channelNumber, activeNote.noteNumber, velocity, now, stopPedal);
1423
1420
  promises.push(notePromise);
1424
1421
  }
1425
1422
  });
@@ -1486,7 +1483,7 @@ class Midy {
1486
1483
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1487
1484
  return this.handleMasterCoarseTuningSysEx(data);
1488
1485
  case 5:
1489
- return this.handleGlobalParameterControl(data);
1486
+ return this.handleGlobalParameterControlSysEx(data);
1490
1487
  default:
1491
1488
  console.warn(`Unsupported Exclusive Message: ${data}`);
1492
1489
  }
@@ -1563,13 +1560,13 @@ class Midy {
1563
1560
  this.masterCoarseTuning = coarseTuning - 64;
1564
1561
  }
1565
1562
  }
1566
- handleGlobalParameterControl(data) {
1567
- if (data[5] === 1) {
1568
- switch (data[6]) {
1563
+ handleGlobalParameterControlSysEx(data) {
1564
+ if (data[7] === 1) {
1565
+ switch (data[8]) {
1569
1566
  case 1:
1570
- return this.handleReverbParameter(data);
1567
+ return this.handleReverbParameterSysEx(data);
1571
1568
  case 2:
1572
- return this.handleChorusParameter(data);
1569
+ return this.handleChorusParameterSysEx(data);
1573
1570
  default:
1574
1571
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1575
1572
  }
@@ -1578,21 +1575,19 @@ class Midy {
1578
1575
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1579
1576
  }
1580
1577
  }
1581
- handleReverbParameter(data) {
1582
- switch (data[7]) {
1578
+ handleReverbParameterSysEx(data) {
1579
+ switch (data[9]) {
1583
1580
  case 0:
1584
- return this.setReverbType(data[8]);
1581
+ return this.setReverbType(data[10]);
1585
1582
  case 1:
1586
- return this.setReverbTime(data[8]);
1583
+ return this.setReverbTime(data[10]);
1587
1584
  }
1588
1585
  }
1589
1586
  setReverbType(type) {
1590
1587
  this.reverb.time = this.getReverbTimeFromType(type);
1591
- this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1592
- const { audioContext, channels, options } = this;
1593
- for (let i = 0; i < channels.length; i++) {
1594
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1595
- }
1588
+ this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
1589
+ const { audioContext, options } = this;
1590
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1596
1591
  }
1597
1592
  getReverbTimeFromType(type) {
1598
1593
  switch (type) {
@@ -1643,41 +1638,111 @@ class Midy {
1643
1638
  // RT60 = -3 * delay / Math.log10(feedback)
1644
1639
  // feedback = Math.pow(10, -3 * delay / RT60)
1645
1640
  // 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.
1641
+ // The structure of a concert hall is complex,
1642
+ // so estimates based on mean free path are unstable.
1643
+ // It is easier to determine the delay based on ideal feedback.
1644
+ // The average sound absorption coefficient
1645
+ // suitable for playing musical instruments is 0.18 to 0.28.
1649
1646
  // delay = -RT60 * Math.log10(feedback) / 3
1650
1647
  calcDelay(rt60, feedback) {
1651
1648
  return -rt60 * Math.log10(feedback) / 3;
1652
1649
  }
1653
- handleChorusParameter(data) {
1654
- switch (data[7]) {
1650
+ handleChorusParameterSysEx(data) {
1651
+ switch (data[9]) {
1655
1652
  case 0:
1656
- return this.setChorusType(data[8]);
1653
+ return this.setChorusType(data[10]);
1657
1654
  case 1:
1658
- return this.setChorusModRate(data[8]);
1655
+ return this.setChorusModRate(data[10]);
1659
1656
  case 2:
1660
- return this.setChorusModDepth(data[8]);
1657
+ return this.setChorusModDepth(data[10]);
1661
1658
  case 3:
1662
- return this.setChorusFeedback(data[8]);
1659
+ return this.setChorusFeedback(data[10]);
1663
1660
  case 4:
1664
- return this.setChorusSendToReverb(data[8]);
1661
+ return this.setChorusSendToReverb(data[10]);
1665
1662
  }
1666
1663
  }
1667
1664
  setChorusType(type) {
1668
- // TODO
1665
+ switch (type) {
1666
+ case 0:
1667
+ return this.setChorusParameter(3, 5, 0, 0);
1668
+ case 1:
1669
+ return this.setChorusParameter(9, 19, 5, 0);
1670
+ case 2:
1671
+ return this.setChorusParameter(3, 19, 8, 0);
1672
+ case 3:
1673
+ return this.setChorusParameter(9, 16, 16, 0);
1674
+ case 4:
1675
+ return this.setChorusParameter(2, 24, 64, 0);
1676
+ case 5:
1677
+ return this.setChorusParameter(1, 5, 112, 0);
1678
+ default:
1679
+ console.warn(`Unsupported Chorus Type: ${type}`);
1680
+ }
1681
+ }
1682
+ setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
1683
+ this.setChorusModRate(modRate);
1684
+ this.setChorusModDepth(modDepth);
1685
+ this.setChorusFeedback(feedback);
1686
+ this.setChorusSendToReverb(sendToReverb);
1669
1687
  }
1670
1688
  setChorusModRate(value) {
1671
- // TODO
1689
+ const now = this.audioContext.currentTime;
1690
+ const modRate = this.getChorusModRate(value);
1691
+ this.chorus.modRate = modRate;
1692
+ for (let i = 0; i < this.channels.length; i++) {
1693
+ const lfo = this.channels[i].chorusEffect.lfo;
1694
+ lfo.frequency.setValueAtTime(modRate, now);
1695
+ }
1696
+ }
1697
+ getChorusModRate(value) {
1698
+ return value * 0.122; // Hz
1672
1699
  }
1673
1700
  setChorusModDepth(value) {
1674
- // TODO
1701
+ const now = this.audioContext.currentTime;
1702
+ const modDepth = this.getChorusModDepth(value);
1703
+ this.chorus.modDepth = modDepth;
1704
+ for (let i = 0; i < this.channels.length; i++) {
1705
+ const chorusEffect = this.channels[i].chorusEffect;
1706
+ chorusEffect.lfoGain.gain
1707
+ .cancelScheduledValues(now)
1708
+ .setValueAtTime(modDepth / 2, now);
1709
+ }
1710
+ }
1711
+ getChorusModDepth(value) {
1712
+ return (value + 1) / 3200; // second
1675
1713
  }
1676
1714
  setChorusFeedback(value) {
1677
- // TODO
1715
+ const now = this.audioContext.currentTime;
1716
+ const feedback = this.getChorusFeedback(value);
1717
+ this.chorus.feedback = feedback;
1718
+ for (let i = 0; i < this.channels.length; i++) {
1719
+ const chorusEffect = this.channels[i].chorusEffect;
1720
+ for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1721
+ const feedbackGain = chorusEffect.feedbackGains[j];
1722
+ feedbackGain.gain
1723
+ .cancelScheduledValues(now)
1724
+ .setValueAtTime(feedback, now);
1725
+ }
1726
+ }
1727
+ }
1728
+ getChorusFeedback(value) {
1729
+ return value * 0.00763;
1678
1730
  }
1679
1731
  setChorusSendToReverb(value) {
1680
- // TODO
1732
+ const sendToReverb = this.getChorusSendToReverb(value);
1733
+ if (0 < sendToReverb) {
1734
+ const now = this.audioContext.currentTime;
1735
+ this.chorus.sendToReverb = sendToReverb;
1736
+ this.chorusEffect.sendGain.gain
1737
+ .cancelScheduledValues(now)
1738
+ .setValueAtTime(sendToReverb, now);
1739
+ }
1740
+ else if (this.chorus.sendToReverb !== 0) {
1741
+ this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
1742
+ }
1743
+ }
1744
+ getChorusSendToReverb(value) {
1745
+ return value * 0.00787;
1681
1746
  }
1682
1747
  handleExclusiveMessage(data) {
1683
1748
  console.warn(`Unsupported Exclusive Message: ${data}`);