@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-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"AAuBA;IAiCE;;;;;;;;;;;;;;;;;MAiBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAgCF;;;;;OAOC;IA9GD,qBAAmB;IACnB,kBAAc;IACd,yBAAqB;IACrB,2BAAuB;IACvB;;;MAGE;IACF;;;;;MAKE;IACF,cAAa;IACb,cAAa;IACb,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA2ClB;;;;;MA4BE;IAGA,kBAAgC;IAChC;;;;;MAAqD;IACrD,gBAA4C;IAE5C,gBAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;;MAkBC;IAED,yCAcC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAkDC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgGC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,kFAuBC;IAED;;;;MAWC;IAED,gFAUC;IAED,mFAYC;IAED,sGAcC;IAID;;;MAiCC;IAED;;;;;;MAoCC;IAED,kDAsBC;IAED,2BAEC;IAED,4BAEC;IAED,sCAKC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHAiCC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,sIAiDC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,wFAmBC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,uDAOC;IAED,mFAuDC;IAED,+CAEC;IAED,qCAcC;IAED,yDAIC;IAED,iEAEC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,+CAEC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAGD,oDAEC;IAED,mEAOC;IAED,mEAIC;IAED,wDAWC;IAED,uDAGC;IAED,kFAeC;IAED,2DAMC;IAED,oCAqBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,wDAKC;IAED,6EAKC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAiDC;IAED,yCAGC;IAED,mCAQC;IAED,6CAGC;IAED,2CAMC;IAED,+CAGC;IAED,+CAMC;IAED,8CAeC;IAED,uCAOC;IAED,+BAOC;IAED,qDAiBC;IAED,gCAMC;IAED,kCAEC;IA2BD,4CAEC;IAED,uCAaC;IAED,+BAEC;IAED,mCAEC;IAED,oCAEC;IAED,oCAEC;IAED,wCAEC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAhsDD;IASE,gFAKC;IAbD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
1
+ {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
@@ -85,7 +85,7 @@ class MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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) {
@@ -688,12 +690,10 @@ class MidyGM2 {
688
690
  }
689
691
  // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
690
692
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
691
- createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
693
+ createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
692
694
  const input = new GainNode(audioContext);
693
695
  const output = new GainNode(audioContext);
694
- const mergerGain = new GainNode(audioContext, {
695
- gain: 1 / (combDelays.length * 2),
696
- });
696
+ const mergerGain = new GainNode(audioContext);
697
697
  for (let i = 0; i < combDelays.length; i++) {
698
698
  const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
699
699
  comb.connect(mergerGain);
@@ -706,62 +706,49 @@ class MidyGM2 {
706
706
  allpasses.at(-1).connect(output);
707
707
  return { input, output };
708
708
  }
709
- createChorusEffect(audioContext, options = {}) {
710
- const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
711
- const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
712
- const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
709
+ createChorusEffect(audioContext) {
710
+ const input = new GainNode(audioContext);
713
711
  const output = new GainNode(audioContext);
714
- const chorusGains = [];
712
+ const sendGain = new GainNode(audioContext);
713
+ const lfo = new OscillatorNode(audioContext, {
714
+ frequency: this.chorus.modRate,
715
+ });
716
+ const lfoGain = new GainNode(audioContext, {
717
+ gain: this.chorus.modDepth / 2,
718
+ });
719
+ const delayTimes = this.chorus.delayTimes;
715
720
  const delayNodes = [];
716
- const baseGain = 1 / chorusCount;
717
- for (let i = 0; i < chorusCount; i++) {
718
- const randomDelayFactor = (Math.random() - 0.5) * variance;
719
- const delayTime = (i + 1) * delay + randomDelayFactor;
721
+ const feedbackGains = [];
722
+ for (let i = 0; i < delayTimes.length; i++) {
723
+ const delayTime = delayTimes[i];
720
724
  const delayNode = new DelayNode(audioContext, {
721
- maxDelayTime: delayTime,
725
+ maxDelayTime: 0.1, // generally, 5ms < delayTime < 50ms
726
+ delayTime,
727
+ });
728
+ const feedbackGain = new GainNode(audioContext, {
729
+ gain: this.chorus.feedback,
722
730
  });
723
- const chorusGain = new GainNode(audioContext, { gain: baseGain });
724
731
  delayNodes.push(delayNode);
725
- chorusGains.push(chorusGain);
732
+ feedbackGains.push(feedbackGain);
733
+ input.connect(delayNode);
726
734
  lfoGain.connect(delayNode.delayTime);
727
- delayNode.connect(chorusGain);
728
- chorusGain.connect(output);
735
+ delayNode.connect(feedbackGain);
736
+ feedbackGain.connect(delayNode);
737
+ delayNode.connect(output);
729
738
  }
739
+ output.connect(sendGain);
730
740
  lfo.connect(lfoGain);
731
741
  lfo.start();
732
742
  return {
743
+ input,
744
+ output,
745
+ sendGain,
733
746
  lfo,
734
747
  lfoGain,
735
748
  delayNodes,
736
- chorusGains,
737
- output,
749
+ feedbackGains,
738
750
  };
739
751
  }
740
- connectEffects(channel, gainNode) {
741
- gainNode.connect(channel.merger);
742
- channel.merger.connect(this.masterGain);
743
- if (channel.reverbSendLevel === 0) {
744
- if (channel.chorusSendLevel !== 0) { // chorus
745
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
746
- channel.merger.connect(delayNode);
747
- });
748
- channel.chorusEffect.output.connect(this.masterGain);
749
- }
750
- }
751
- else {
752
- if (channel.chorusSendLevel === 0) { // reverb
753
- channel.merger.connect(channel.reverbEffect.input);
754
- channel.reverbEffect.output.connect(this.masterGain);
755
- }
756
- else { // reverb + chorus
757
- channel.chorusEffect.delayNodes.forEach((delayNode) => {
758
- channel.merger.connect(delayNode);
759
- });
760
- channel.merger.connect(channel.reverbEffect.input);
761
- channel.reverbEffect.output.connect(this.masterGain);
762
- }
763
- }
764
- }
765
752
  cbToRatio(cb) {
766
753
  return Math.pow(10, cb / 200);
767
754
  }
@@ -778,14 +765,10 @@ class MidyGM2 {
778
765
  return instrumentKey.playbackRate(noteNumber) *
779
766
  Math.pow(2, semitoneOffset / 12);
780
767
  }
781
- setVolumeEnvelope(channel, note) {
782
- const { instrumentKey, startTime, velocity } = note;
768
+ setVolumeEnvelope(note) {
769
+ const { instrumentKey, startTime } = note;
783
770
  note.gainNode = new GainNode(this.audioContext, { gain: 0 });
784
- let volume = (velocity / 127) * channel.volume * channel.expression;
785
- if (volume === 0)
786
- volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
787
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
788
- volume;
771
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
789
772
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
790
773
  const volDelay = startTime + instrumentKey.volDelay;
791
774
  const volAttack = volDelay + instrumentKey.volAttack;
@@ -845,7 +828,7 @@ class MidyGM2 {
845
828
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
846
829
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
847
830
  note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
848
- this.setVolumeEnvelope(channel, note);
831
+ this.setVolumeEnvelope(note);
849
832
  this.setFilterEnvelope(channel, note);
850
833
  if (channel.modulation > 0) {
851
834
  const delayModLFO = startTime + instrumentKey.delayModLFO;
@@ -881,7 +864,8 @@ class MidyGM2 {
881
864
  if (!instrumentKey)
882
865
  return;
883
866
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
884
- this.connectEffects(channel, note.gainNode);
867
+ note.gainNode.connect(channel.gainL);
868
+ note.gainNode.connect(channel.gainR);
885
869
  if (channel.sostenutoPedal) {
886
870
  channel.sostenutoNotes.set(noteNumber, note);
887
871
  }
@@ -1024,13 +1008,13 @@ class MidyGM2 {
1024
1008
  }
1025
1009
  }
1026
1010
  handlePitchBendMessage(channelNumber, lsb, msb) {
1027
- const pitchBend = msb * 128 + lsb;
1011
+ const pitchBend = msb * 128 + lsb - 8192;
1028
1012
  this.setPitchBend(channelNumber, pitchBend);
1029
1013
  }
1030
1014
  setPitchBend(channelNumber, pitchBend) {
1031
1015
  const channel = this.channels[channelNumber];
1032
1016
  const prevPitchBend = channel.pitchBend;
1033
- channel.pitchBend = (pitchBend - 8192) / 8192;
1017
+ channel.pitchBend = pitchBend / 8192;
1034
1018
  const detuneChange = (channel.pitchBend - prevPitchBend) *
1035
1019
  channel.pitchBendRange * 100;
1036
1020
  this.updateDetune(channel, detuneChange);
@@ -1165,17 +1149,30 @@ class MidyGM2 {
1165
1149
  this.channels[channelNumber].portamento = value >= 64;
1166
1150
  }
1167
1151
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1168
- const now = this.audioContext.currentTime;
1169
1152
  const channel = this.channels[channelNumber];
1170
- const reverbEffect = channel.reverbEffect;
1171
- channel.reverbSendLevel = reverbSendLevel / 127;
1172
- reverbEffect.output.gain.cancelScheduledValues(now);
1173
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1153
+ const reverbEffect = this.reverbEffect;
1154
+ if (0 < reverbSendLevel) {
1155
+ const now = this.audioContext.currentTime;
1156
+ channel.reverbSendLevel = reverbSendLevel / 127;
1157
+ reverbEffect.output.gain.cancelScheduledValues(now);
1158
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1159
+ }
1160
+ else if (channel.reverbSendLevel !== 0) {
1161
+ channel.merger.disconnect(reverbEffect.input);
1162
+ }
1174
1163
  }
1175
1164
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1176
1165
  const channel = this.channels[channelNumber];
1177
- channel.chorusSendLevel = chorusSendLevel / 127;
1178
- channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1166
+ const chorusEffect = this.chorusEffect;
1167
+ if (0 < chorusSendLevel) {
1168
+ const now = this.audioContext.currentTime;
1169
+ channel.chorusSendLevel = chorusSendLevel / 127;
1170
+ chorusEffect.output.gain.cancelScheduledValues(now);
1171
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1172
+ }
1173
+ else if (channel.chorusSendLevel !== 0) {
1174
+ channel.merger.disconnect(chorusEffect.input);
1175
+ }
1179
1176
  }
1180
1177
  setSostenutoPedal(channelNumber, value) {
1181
1178
  const isOn = value >= 64;
@@ -1340,7 +1337,7 @@ class MidyGM2 {
1340
1337
  channel.scheduledNotes.forEach((noteList) => {
1341
1338
  const activeNote = this.getActiveNote(noteList, now);
1342
1339
  if (activeNote) {
1343
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1340
+ const notePromise = this.scheduleNoteRelease(channelNumber, activeNote.noteNumber, velocity, now, stopPedal);
1344
1341
  promises.push(notePromise);
1345
1342
  }
1346
1343
  });
@@ -1407,7 +1404,7 @@ class MidyGM2 {
1407
1404
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1408
1405
  return this.handleMasterCoarseTuningSysEx(data);
1409
1406
  case 5:
1410
- return this.handleGlobalParameterControl(data);
1407
+ return this.handleGlobalParameterControlSysEx(data);
1411
1408
  default:
1412
1409
  console.warn(`Unsupported Exclusive Message: ${data}`);
1413
1410
  }
@@ -1484,13 +1481,13 @@ class MidyGM2 {
1484
1481
  this.masterCoarseTuning = coarseTuning - 64;
1485
1482
  }
1486
1483
  }
1487
- handleGlobalParameterControl(data) {
1488
- if (data[5] === 1) {
1489
- switch (data[6]) {
1484
+ handleGlobalParameterControlSysEx(data) {
1485
+ if (data[7] === 1) {
1486
+ switch (data[8]) {
1490
1487
  case 1:
1491
- return this.handleReverbParameter(data);
1488
+ return this.handleReverbParameterSysEx(data);
1492
1489
  case 2:
1493
- return this.handleChorusParameter(data);
1490
+ return this.handleChorusParameterSysEx(data);
1494
1491
  default:
1495
1492
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1496
1493
  }
@@ -1499,21 +1496,19 @@ class MidyGM2 {
1499
1496
  console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1500
1497
  }
1501
1498
  }
1502
- handleReverbParameter(data) {
1503
- switch (data[7]) {
1499
+ handleReverbParameterSysEx(data) {
1500
+ switch (data[9]) {
1504
1501
  case 0:
1505
- return this.setReverbType(data[8]);
1502
+ return this.setReverbType(data[10]);
1506
1503
  case 1:
1507
- return this.setReverbTime(data[8]);
1504
+ return this.setReverbTime(data[10]);
1508
1505
  }
1509
1506
  }
1510
1507
  setReverbType(type) {
1511
1508
  this.reverb.time = this.getReverbTimeFromType(type);
1512
- this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1513
- const { audioContext, channels, options } = this;
1514
- for (let i = 0; i < channels.length; i++) {
1515
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1516
- }
1509
+ this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
1510
+ const { audioContext, options } = this;
1511
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1517
1512
  }
1518
1513
  getReverbTimeFromType(type) {
1519
1514
  switch (type) {
@@ -1564,41 +1559,111 @@ class MidyGM2 {
1564
1559
  // RT60 = -3 * delay / Math.log10(feedback)
1565
1560
  // feedback = Math.pow(10, -3 * delay / RT60)
1566
1561
  // delay estimation using ideal feedback
1567
- // A suitable average sound absorption coefficient is 0.18-0.28.
1568
- // Since the structure of the hall is complex,
1569
- // It would be easier to determine the delay based on the ideal feedback.
1562
+ // The structure of a concert hall is complex,
1563
+ // so estimates based on mean free path are unstable.
1564
+ // It is easier to determine the delay based on ideal feedback.
1565
+ // The average sound absorption coefficient
1566
+ // suitable for playing musical instruments is 0.18 to 0.28.
1570
1567
  // delay = -RT60 * Math.log10(feedback) / 3
1571
1568
  calcDelay(rt60, feedback) {
1572
1569
  return -rt60 * Math.log10(feedback) / 3;
1573
1570
  }
1574
- handleChorusParameter(data) {
1575
- switch (data[7]) {
1571
+ handleChorusParameterSysEx(data) {
1572
+ switch (data[9]) {
1576
1573
  case 0:
1577
- return this.setChorusType(data[8]);
1574
+ return this.setChorusType(data[10]);
1578
1575
  case 1:
1579
- return this.setChorusModRate(data[8]);
1576
+ return this.setChorusModRate(data[10]);
1580
1577
  case 2:
1581
- return this.setChorusModDepth(data[8]);
1578
+ return this.setChorusModDepth(data[10]);
1582
1579
  case 3:
1583
- return this.setChorusFeedback(data[8]);
1580
+ return this.setChorusFeedback(data[10]);
1584
1581
  case 4:
1585
- return this.setChorusSendToReverb(data[8]);
1582
+ return this.setChorusSendToReverb(data[10]);
1586
1583
  }
1587
1584
  }
1588
1585
  setChorusType(type) {
1589
- // TODO
1586
+ switch (type) {
1587
+ case 0:
1588
+ return this.setChorusParameter(3, 5, 0, 0);
1589
+ case 1:
1590
+ return this.setChorusParameter(9, 19, 5, 0);
1591
+ case 2:
1592
+ return this.setChorusParameter(3, 19, 8, 0);
1593
+ case 3:
1594
+ return this.setChorusParameter(9, 16, 16, 0);
1595
+ case 4:
1596
+ return this.setChorusParameter(2, 24, 64, 0);
1597
+ case 5:
1598
+ return this.setChorusParameter(1, 5, 112, 0);
1599
+ default:
1600
+ console.warn(`Unsupported Chorus Type: ${type}`);
1601
+ }
1602
+ }
1603
+ setChorusParameter(modRate, modDepth, feedback, sendToReverb) {
1604
+ this.setChorusModRate(modRate);
1605
+ this.setChorusModDepth(modDepth);
1606
+ this.setChorusFeedback(feedback);
1607
+ this.setChorusSendToReverb(sendToReverb);
1590
1608
  }
1591
1609
  setChorusModRate(value) {
1592
- // TODO
1610
+ const now = this.audioContext.currentTime;
1611
+ const modRate = this.getChorusModRate(value);
1612
+ this.chorus.modRate = modRate;
1613
+ for (let i = 0; i < this.channels.length; i++) {
1614
+ const lfo = this.channels[i].chorusEffect.lfo;
1615
+ lfo.frequency.setValueAtTime(modRate, now);
1616
+ }
1617
+ }
1618
+ getChorusModRate(value) {
1619
+ return value * 0.122; // Hz
1593
1620
  }
1594
1621
  setChorusModDepth(value) {
1595
- // TODO
1622
+ const now = this.audioContext.currentTime;
1623
+ const modDepth = this.getChorusModDepth(value);
1624
+ this.chorus.modDepth = modDepth;
1625
+ for (let i = 0; i < this.channels.length; i++) {
1626
+ const chorusEffect = this.channels[i].chorusEffect;
1627
+ chorusEffect.lfoGain.gain
1628
+ .cancelScheduledValues(now)
1629
+ .setValueAtTime(modDepth / 2, now);
1630
+ }
1631
+ }
1632
+ getChorusModDepth(value) {
1633
+ return (value + 1) / 3200; // second
1596
1634
  }
1597
1635
  setChorusFeedback(value) {
1598
- // TODO
1636
+ const now = this.audioContext.currentTime;
1637
+ const feedback = this.getChorusFeedback(value);
1638
+ this.chorus.feedback = feedback;
1639
+ for (let i = 0; i < this.channels.length; i++) {
1640
+ const chorusEffect = this.channels[i].chorusEffect;
1641
+ for (let j = 0; j < chorusEffect.feedbackGains.length; j++) {
1642
+ const feedbackGain = chorusEffect.feedbackGains[j];
1643
+ feedbackGain.gain
1644
+ .cancelScheduledValues(now)
1645
+ .setValueAtTime(feedback, now);
1646
+ }
1647
+ }
1648
+ }
1649
+ getChorusFeedback(value) {
1650
+ return value * 0.00763;
1599
1651
  }
1600
1652
  setChorusSendToReverb(value) {
1601
- // TODO
1653
+ const sendToReverb = this.getChorusSendToReverb(value);
1654
+ if (0 < sendToReverb) {
1655
+ const now = this.audioContext.currentTime;
1656
+ this.chorus.sendToReverb = sendToReverb;
1657
+ this.chorusEffect.sendGain.gain
1658
+ .cancelScheduledValues(now)
1659
+ .setValueAtTime(sendToReverb, now);
1660
+ }
1661
+ else if (this.chorus.sendToReverb !== 0) {
1662
+ this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
1663
+ }
1664
+ }
1665
+ getChorusSendToReverb(value) {
1666
+ return value * 0.00787;
1602
1667
  }
1603
1668
  handleExclusiveMessage(data) {
1604
1669
  console.warn(`Unsupported Exclusive Message: ${data}`);
@@ -70,12 +70,11 @@ export class MidyGMLite {
70
70
  currentTime(): number;
71
71
  getActiveNotes(channel: any, time: any): Map<any, any>;
72
72
  getActiveNote(noteList: any, time: any): any;
73
- connectEffects(channel: any, gainNode: any): void;
74
73
  cbToRatio(cb: any): number;
75
74
  centToHz(cent: any): number;
76
75
  calcSemitoneOffset(channel: any): number;
77
76
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
78
- setVolumeEnvelope(channel: any, note: any): void;
77
+ setVolumeEnvelope(note: any): void;
79
78
  setFilterEnvelope(channel: any, note: any): void;
80
79
  startModulation(channel: any, note: any, time: any): void;
81
80
  createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C,gBAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAcC;IAED,yCAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,kDAGC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIA8CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED,mFA+BC;IAED,qCAcC;IAED,yDAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAn/BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAC5C,gBAAiD;IAKnD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,mCAcC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIA8CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED,mFA+BC;IAED,qCAcC;IAED,yDAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA5+BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -147,8 +147,8 @@ class MidyGMLite {
147
147
  });
148
148
  this.audioContext = audioContext;
149
149
  this.masterGain = new GainNode(audioContext);
150
- this.masterGain.connect(audioContext.destination);
151
150
  this.channels = this.createChannels(audioContext);
151
+ this.masterGain.connect(audioContext.destination);
152
152
  this.GM1SystemOn();
153
153
  }
154
154
  initSoundFontTable() {
@@ -192,6 +192,7 @@ class MidyGMLite {
192
192
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
193
193
  gainL.connect(merger, 0, 0);
194
194
  gainR.connect(merger, 0, 1);
195
+ merger.connect(this.masterGain);
195
196
  return {
196
197
  gainL,
197
198
  gainR,
@@ -494,10 +495,6 @@ class MidyGMLite {
494
495
  }
495
496
  return noteList[0];
496
497
  }
497
- connectEffects(channel, gainNode) {
498
- gainNode.connect(channel.merger);
499
- merger.connect(this.masterGain);
500
- }
501
498
  cbToRatio(cb) {
502
499
  return Math.pow(10, cb / 200);
503
500
  }
@@ -511,14 +508,10 @@ class MidyGMLite {
511
508
  return instrumentKey.playbackRate(noteNumber) *
512
509
  Math.pow(2, semitoneOffset / 12);
513
510
  }
514
- setVolumeEnvelope(channel, note) {
515
- const { instrumentKey, startTime, velocity } = note;
511
+ setVolumeEnvelope(note) {
512
+ const { instrumentKey, startTime } = note;
516
513
  note.gainNode = new GainNode(this.audioContext, { gain: 0 });
517
- let volume = (velocity / 127) * channel.volume * channel.expression;
518
- if (volume === 0)
519
- volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
520
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
521
- volume;
514
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
522
515
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
523
516
  const volDelay = startTime + instrumentKey.volDelay;
524
517
  const volAttack = volDelay + instrumentKey.volAttack;
@@ -578,7 +571,7 @@ class MidyGMLite {
578
571
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
579
572
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
580
573
  note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
581
- this.setVolumeEnvelope(channel, note);
574
+ this.setVolumeEnvelope(note);
582
575
  this.setFilterEnvelope(channel, note);
583
576
  if (channel.modulation > 0) {
584
577
  const delayModLFO = startTime + instrumentKey.delayModLFO;
@@ -601,7 +594,8 @@ class MidyGMLite {
601
594
  if (!instrumentKey)
602
595
  return;
603
596
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
604
- this.connectEffects(channel, note.gainNode);
597
+ note.gainNode.connect(channel.gainL);
598
+ note.gainNode.connect(channel.gainR);
605
599
  const scheduledNotes = channel.scheduledNotes;
606
600
  if (scheduledNotes.has(noteNumber)) {
607
601
  scheduledNotes.get(noteNumber).push(note);
@@ -704,13 +698,13 @@ class MidyGMLite {
704
698
  channel.program = program;
705
699
  }
706
700
  handlePitchBendMessage(channelNumber, lsb, msb) {
707
- const pitchBend = msb * 128 + lsb;
701
+ const pitchBend = msb * 128 + lsb - 8192;
708
702
  this.setPitchBend(channelNumber, pitchBend);
709
703
  }
710
704
  setPitchBend(channelNumber, pitchBend) {
711
705
  const channel = this.channels[channelNumber];
712
706
  const prevPitchBend = channel.pitchBend;
713
- channel.pitchBend = (pitchBend - 8192) / 8192;
707
+ channel.pitchBend = pitchBend / 8192;
714
708
  const detuneChange = (channel.pitchBend - prevPitchBend) *
715
709
  channel.pitchBendRange * 100;
716
710
  this.updateDetune(channel, detuneChange);
@@ -880,7 +874,7 @@ class MidyGMLite {
880
874
  channel.scheduledNotes.forEach((noteList) => {
881
875
  const activeNote = this.getActiveNote(noteList, now);
882
876
  if (activeNote) {
883
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
877
+ const notePromise = this.scheduleNoteRelease(channelNumber, activeNote.noteNumber, velocity, now, stopPedal);
884
878
  promises.push(notePromise);
885
879
  }
886
880
  });
package/script/midy.d.ts CHANGED
@@ -59,6 +59,7 @@ export class Midy {
59
59
  modDepth: number;
60
60
  feedback: number;
61
61
  sendToReverb: number;
62
+ delayTimes: any[];
62
63
  };
63
64
  mono: boolean;
64
65
  omni: boolean;
@@ -92,6 +93,19 @@ export class Midy {
92
93
  };
93
94
  masterGain: any;
94
95
  channels: any[];
96
+ reverbEffect: {
97
+ input: any;
98
+ output: any;
99
+ };
100
+ chorusEffect: {
101
+ input: any;
102
+ output: any;
103
+ sendGain: any;
104
+ lfo: any;
105
+ lfoGain: any;
106
+ delayNodes: any[];
107
+ feedbackGains: any[];
108
+ };
95
109
  initSoundFontTable(): any[];
96
110
  addSoundFont(soundFont: any): void;
97
111
  loadSoundFont(soundFontUrl: any): Promise<void>;
@@ -100,17 +114,6 @@ export class Midy {
100
114
  gainL: any;
101
115
  gainR: any;
102
116
  merger: any;
103
- reverbEffect: {
104
- input: any;
105
- output: any;
106
- };
107
- chorusEffect: {
108
- lfo: any;
109
- lfoGain: any;
110
- delayNodes: any[];
111
- chorusGains: any[];
112
- output: any;
113
- };
114
117
  };
115
118
  createChannels(audioContext: any): any[];
116
119
  createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
@@ -144,23 +147,24 @@ export class Midy {
144
147
  createCombFilter(audioContext: any, input: any, delay: any, feedback: any): any;
145
148
  createAllpassFilter(audioContext: any, input: any, delay: any, feedback: any): any;
146
149
  generateDistributedArray(center: any, count: any, varianceRatio?: number, randomness?: number): any[];
147
- createSchroederReverb(audioContext: any, combDelays: any, combFeedbacks: any, allpassDelays: any, allpassFeedbacks: any): {
150
+ createSchroederReverb(audioContext: any, combFeedbacks: any, combDelays: any, allpassFeedbacks: any, allpassDelays: any): {
148
151
  input: any;
149
152
  output: any;
150
153
  };
151
- createChorusEffect(audioContext: any, options?: {}): {
154
+ createChorusEffect(audioContext: any): {
155
+ input: any;
156
+ output: any;
157
+ sendGain: any;
152
158
  lfo: any;
153
159
  lfoGain: any;
154
160
  delayNodes: any[];
155
- chorusGains: any[];
156
- output: any;
161
+ feedbackGains: any[];
157
162
  };
158
- connectEffects(channel: any, gainNode: any): void;
159
163
  cbToRatio(cb: any): number;
160
164
  centToHz(cent: any): number;
161
165
  calcSemitoneOffset(channel: any): any;
162
166
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
163
- setVolumeEnvelope(channel: any, note: any): void;
167
+ setVolumeEnvelope(note: any): void;
164
168
  setFilterEnvelope(channel: any, note: any): void;
165
169
  startModulation(channel: any, note: any, time: any): void;
166
170
  startVibrato(channel: any, note: any, time: any): void;
@@ -236,19 +240,24 @@ export class Midy {
236
240
  setMasterFineTuning(fineTuning: any): void;
237
241
  handleMasterCoarseTuningSysEx(data: any): void;
238
242
  setMasterCoarseTuning(coarseTuning: any): void;
239
- handleGlobalParameterControl(data: any): void;
240
- handleReverbParameter(data: any): void;
243
+ handleGlobalParameterControlSysEx(data: any): void;
244
+ handleReverbParameterSysEx(data: any): void;
241
245
  setReverbType(type: any): void;
242
246
  getReverbTimeFromType(type: any): number | undefined;
243
247
  setReverbTime(value: any): void;
244
248
  getReverbTime(value: any): number;
245
249
  calcDelay(rt60: any, feedback: any): number;
246
- handleChorusParameter(data: any): void;
250
+ handleChorusParameterSysEx(data: any): void;
247
251
  setChorusType(type: any): void;
252
+ setChorusParameter(modRate: any, modDepth: any, feedback: any, sendToReverb: any): void;
248
253
  setChorusModRate(value: any): void;
254
+ getChorusModRate(value: any): number;
249
255
  setChorusModDepth(value: any): void;
256
+ getChorusModDepth(value: any): number;
250
257
  setChorusFeedback(value: any): void;
258
+ getChorusFeedback(value: any): number;
251
259
  setChorusSendToReverb(value: any): void;
260
+ getChorusSendToReverb(value: any): number;
252
261
  handleExclusiveMessage(data: any): void;
253
262
  handleSysEx(data: any): void;
254
263
  scheduleTask(callback: any, startTime: any): Promise<any>;