@marmooo/midy 0.0.8 → 0.1.0

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
@@ -54,7 +54,7 @@ class Note {
54
54
  }
55
55
  }
56
56
  class Midy {
57
- constructor(audioContext) {
57
+ constructor(audioContext, options = this.defaultOptions) {
58
58
  Object.defineProperty(this, "ticksPerBeat", {
59
59
  enumerable: true,
60
60
  configurable: true,
@@ -67,12 +67,6 @@ class Midy {
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,26 @@ class Midy {
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.2,
89
+ }
90
+ });
91
+ Object.defineProperty(this, "chorus", {
92
+ enumerable: true,
93
+ configurable: true,
94
+ writable: true,
95
+ value: {
96
+ modRate: 3 * 0.122,
97
+ modDepth: (3 + 1) / 3.2,
98
+ feedback: 8 * 0.763,
99
+ sendToReverb: 0 * 0.787,
100
+ }
101
+ });
88
102
  Object.defineProperty(this, "mono", {
89
103
  enumerable: true,
90
104
  configurable: true,
@@ -187,7 +201,30 @@ class Midy {
187
201
  writable: true,
188
202
  value: []
189
203
  });
204
+ Object.defineProperty(this, "defaultOptions", {
205
+ enumerable: true,
206
+ configurable: true,
207
+ writable: true,
208
+ value: {
209
+ reverbAlgorithm: (audioContext) => {
210
+ const { time: rt60, feedback } = this.reverb;
211
+ // const delay = this.calcDelay(rt60, feedback);
212
+ // const impulse = this.createConvolutionReverbImpulse(
213
+ // audioContext,
214
+ // rt60,
215
+ // delay,
216
+ // );
217
+ // return this.createConvolutionReverb(audioContext, impulse);
218
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
219
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
220
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
221
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
222
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
223
+ },
224
+ }
225
+ });
190
226
  this.audioContext = audioContext;
227
+ this.options = { ...this.defaultOptions, ...options };
191
228
  this.masterGain = new GainNode(audioContext);
192
229
  this.masterGain.connect(audioContext.destination);
193
230
  this.channels = this.createChannels(audioContext);
@@ -228,23 +265,18 @@ class Midy {
228
265
  this.totalTime = this.calcTotalTime();
229
266
  }
230
267
  setChannelAudioNodes(audioContext) {
231
- const { gainLeft, gainRight } = this.panToGain(Midy.channelSettings.pan);
268
+ const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
232
269
  const gainL = new GainNode(audioContext, { gain: gainLeft });
233
270
  const gainR = new GainNode(audioContext, { gain: gainRight });
234
271
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
235
272
  gainL.connect(merger, 0, 0);
236
273
  gainR.connect(merger, 0, 1);
237
- merger.connect(this.masterGain);
238
- const reverbEffect = this.createReverbEffect(audioContext);
274
+ const reverbEffect = this.options.reverbAlgorithm(audioContext);
239
275
  const chorusEffect = this.createChorusEffect(audioContext);
240
- chorusEffect.lfo.start();
241
- reverbEffect.dryGain.connect(gainL);
242
- reverbEffect.dryGain.connect(gainR);
243
- reverbEffect.wetGain.connect(gainL);
244
- reverbEffect.wetGain.connect(gainR);
245
276
  return {
246
277
  gainL,
247
278
  gainR,
279
+ merger,
248
280
  reverbEffect,
249
281
  chorusEffect,
250
282
  };
@@ -252,16 +284,16 @@ class Midy {
252
284
  createChannels(audioContext) {
253
285
  const channels = Array.from({ length: 16 }, () => {
254
286
  return {
255
- ...Midy.channelSettings,
256
- ...Midy.effectSettings,
287
+ ...this.constructor.channelSettings,
288
+ ...this.constructor.effectSettings,
257
289
  ...this.setChannelAudioNodes(audioContext),
258
290
  scheduledNotes: new Map(),
259
291
  sostenutoNotes: new Map(),
260
292
  polyphonicKeyPressure: {
261
- ...Midy.controllerDestinationSettings,
293
+ ...this.constructor.controllerDestinationSettings,
262
294
  },
263
295
  channelPressure: {
264
- ...Midy.controllerDestinationSettings,
296
+ ...this.constructor.controllerDestinationSettings,
265
297
  },
266
298
  };
267
299
  });
@@ -592,8 +624,7 @@ class Midy {
592
624
  }
593
625
  return noteList[0];
594
626
  }
595
- createReverbEffect(audioContext, options = {}) {
596
- const { decay = 0.8, preDecay = 0, } = options;
627
+ createConvolutionReverbImpulse(audioContext, decay, preDecay) {
597
628
  const sampleRate = audioContext.sampleRate;
598
629
  const length = sampleRate * decay;
599
630
  const impulse = new AudioBuffer({
@@ -607,27 +638,85 @@ class Midy {
607
638
  for (let i = 0; i < preDecayLength; i++) {
608
639
  channelData[i] = Math.random() * 2 - 1;
609
640
  }
641
+ const attenuationFactor = 1 / (sampleRate * decay);
610
642
  for (let i = preDecayLength; i < length; i++) {
611
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
643
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
612
644
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
613
645
  }
614
646
  }
647
+ return impulse;
648
+ }
649
+ createConvolutionReverb(audioContext, impulse) {
650
+ const output = new GainNode(audioContext);
615
651
  const convolverNode = new ConvolverNode(audioContext, {
616
652
  buffer: impulse,
617
653
  });
618
- const dryGain = new GainNode(audioContext);
619
- const wetGain = new GainNode(audioContext);
620
- convolverNode.connect(wetGain);
654
+ convolverNode.connect(output);
621
655
  return {
656
+ input: convolverNode,
657
+ output,
622
658
  convolverNode,
623
- dryGain,
624
- wetGain,
625
659
  };
626
660
  }
661
+ createCombFilter(audioContext, input, delay, feedback) {
662
+ const delayNode = new DelayNode(audioContext, {
663
+ maxDelayTime: delay,
664
+ delayTime: delay,
665
+ });
666
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
667
+ input.connect(delayNode);
668
+ delayNode.connect(feedbackGain);
669
+ feedbackGain.connect(delayNode);
670
+ return delayNode;
671
+ }
672
+ createAllpassFilter(audioContext, input, delay, feedback) {
673
+ const delayNode = new DelayNode(audioContext, {
674
+ maxDelayTime: delay,
675
+ delayTime: delay,
676
+ });
677
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
678
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
679
+ input.connect(delayNode);
680
+ delayNode.connect(feedbackGain);
681
+ feedbackGain.connect(delayNode);
682
+ delayNode.connect(passGain);
683
+ return passGain;
684
+ }
685
+ generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
686
+ const variance = center * varianceRatio;
687
+ const array = new Array(count);
688
+ for (let i = 0; i < count; i++) {
689
+ const fraction = i / (count - 1 || 1);
690
+ const value = center - variance + fraction * 2 * variance;
691
+ array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
692
+ }
693
+ return array;
694
+ }
695
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
696
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
697
+ createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
698
+ const input = new GainNode(audioContext);
699
+ const output = new GainNode(audioContext);
700
+ const mergerGain = new GainNode(audioContext, {
701
+ gain: 1 / (combDelays.length * 2),
702
+ });
703
+ for (let i = 0; i < combDelays.length; i++) {
704
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
705
+ comb.connect(mergerGain);
706
+ }
707
+ const allpasses = [];
708
+ for (let i = 0; i < allpassDelays.length; i++) {
709
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
710
+ allpasses.push(allpass);
711
+ }
712
+ allpasses.at(-1).connect(output);
713
+ return { input, output };
714
+ }
627
715
  createChorusEffect(audioContext, options = {}) {
628
716
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
629
717
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
630
718
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
719
+ const output = new GainNode(audioContext);
631
720
  const chorusGains = [];
632
721
  const delayNodes = [];
633
722
  const baseGain = 1 / chorusCount;
@@ -637,50 +726,45 @@ class Midy {
637
726
  const delayNode = new DelayNode(audioContext, {
638
727
  maxDelayTime: delayTime,
639
728
  });
640
- delayNodes.push(delayNode);
641
729
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
730
+ delayNodes.push(delayNode);
642
731
  chorusGains.push(chorusGain);
643
- lfo.connect(lfoGain);
644
732
  lfoGain.connect(delayNode.delayTime);
645
733
  delayNode.connect(chorusGain);
734
+ chorusGain.connect(output);
646
735
  }
736
+ lfo.connect(lfoGain);
737
+ lfo.start();
647
738
  return {
648
739
  lfo,
649
740
  lfoGain,
650
741
  delayNodes,
651
742
  chorusGains,
743
+ output,
652
744
  };
653
745
  }
654
- connectNoteEffects(channel, gainNode) {
655
- if (channel.reverb === 0) {
656
- if (channel.chorus === 0) { // no effect
657
- gainNode.connect(channel.gainL);
658
- gainNode.connect(channel.gainR);
659
- }
660
- else { // chorus
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
661
751
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
662
- gainNode.connect(delayNode);
663
- });
664
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
665
- chorusGain.connect(channel.gainL);
666
- chorusGain.connect(channel.gainR);
752
+ channel.merger.connect(delayNode);
667
753
  });
754
+ channel.chorusEffect.output.connect(this.masterGain);
668
755
  }
669
756
  }
670
757
  else {
671
- if (channel.chorus === 0) { // reverb
672
- gainNode.connect(channel.reverbEffect.convolverNode);
673
- gainNode.connect(channel.reverbEffect.dryGain);
758
+ if (channel.chorusSendLevel === 0) { // reverb
759
+ channel.merger.connect(channel.reverbEffect.input);
760
+ channel.reverbEffect.output.connect(this.masterGain);
674
761
  }
675
762
  else { // reverb + chorus
676
- gainNode.connect(channel.reverbEffect.convolverNode);
677
- gainNode.connect(channel.reverbEffect.dryGain);
678
763
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
679
- gainNode.connect(delayNode);
680
- });
681
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
682
- chorusGain.connect(channel.reverbEffect.convolverNode);
764
+ channel.merger.connect(delayNode);
683
765
  });
766
+ channel.merger.connect(channel.reverbEffect.input);
767
+ channel.reverbEffect.output.connect(this.masterGain);
684
768
  }
685
769
  }
686
770
  }
@@ -820,7 +904,7 @@ class Midy {
820
904
  if (!instrumentKey)
821
905
  return;
822
906
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
823
- this.connectNoteEffects(channel, note.gainNode);
907
+ this.connectEffects(channel, note.gainNode);
824
908
  if (channel.sostenutoPedal) {
825
909
  channel.sostenutoNotes.set(noteNumber, note);
826
910
  }
@@ -844,46 +928,48 @@ class Midy {
844
928
  return;
845
929
  if (!channel.scheduledNotes.has(noteNumber))
846
930
  return;
847
- const targetNotes = channel.scheduledNotes.get(noteNumber);
848
- for (let i = 0; i < targetNotes.length; i++) {
849
- const targetNote = targetNotes[i];
850
- if (!targetNote)
931
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
932
+ for (let i = 0; i < scheduledNotes.length; i++) {
933
+ const note = scheduledNotes[i];
934
+ if (!note)
851
935
  continue;
852
- if (targetNote.ending)
936
+ if (note.ending)
853
937
  continue;
854
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
855
938
  const velocityRate = (velocity + 127) / 127;
856
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
857
- gainNode.gain.cancelScheduledValues(stopTime);
858
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
939
+ const volEndTime = stopTime +
940
+ note.instrumentKey.volRelease * velocityRate;
941
+ note.gainNode.gain
942
+ .cancelScheduledValues(stopTime)
943
+ .linearRampToValueAtTime(0, volEndTime);
859
944
  const maxFreq = this.audioContext.sampleRate / 2;
860
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
945
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
861
946
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
862
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
863
- filterNode.frequency
947
+ const modEndTime = stopTime +
948
+ note.instrumentKey.modRelease * velocityRate;
949
+ note.filterNode.frequency
864
950
  .cancelScheduledValues(stopTime)
865
951
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
866
- targetNote.ending = true;
952
+ note.ending = true;
867
953
  this.scheduleTask(() => {
868
- bufferSource.loop = false;
954
+ note.bufferSource.loop = false;
869
955
  }, stopTime);
870
956
  return new Promise((resolve) => {
871
- bufferSource.onended = () => {
872
- targetNotes[i] = null;
873
- bufferSource.disconnect(0);
874
- filterNode.disconnect(0);
875
- gainNode.disconnect(0);
876
- if (modLFOGain)
877
- modLFOGain.disconnect(0);
878
- if (vibLFOGain)
879
- vibLFOGain.disconnect(0);
880
- if (modLFO)
881
- modLFO.stop();
882
- if (vibLFO)
883
- vibLFO.stop();
957
+ note.bufferSource.onended = () => {
958
+ scheduledNotes[i] = null;
959
+ note.bufferSource.disconnect();
960
+ note.filterNode.disconnect();
961
+ note.gainNode.disconnect();
962
+ if (note.modLFOGain)
963
+ note.modLFOGain.disconnect();
964
+ if (note.vibLFOGain)
965
+ note.vibLFOGain.disconnect();
966
+ if (note.modLFO)
967
+ note.modLFO.stop();
968
+ if (note.vibLFO)
969
+ note.vibLFO.stop();
884
970
  resolve();
885
971
  };
886
- bufferSource.stop(volEndTime);
972
+ note.bufferSource.stop(volEndTime);
887
973
  });
888
974
  }
889
975
  }
@@ -1125,23 +1211,22 @@ class Midy {
1125
1211
  this.releaseSustainPedal(channelNumber, value);
1126
1212
  }
1127
1213
  }
1214
+ // TODO
1128
1215
  setPortamento(channelNumber, value) {
1129
1216
  this.channels[channelNumber].portamento = value >= 64;
1130
1217
  }
1131
- setReverbSendLevel(channelNumber, reverb) {
1218
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1132
1219
  const now = this.audioContext.currentTime;
1133
1220
  const channel = this.channels[channelNumber];
1134
1221
  const reverbEffect = channel.reverbEffect;
1135
- channel.reverb = reverb / 127 * this.reverbFactor;
1136
- reverbEffect.dryGain.gain.cancelScheduledValues(now);
1137
- reverbEffect.dryGain.gain.setValueAtTime(1 - channel.reverb, now);
1138
- reverbEffect.wetGain.gain.cancelScheduledValues(now);
1139
- reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1222
+ channel.reverbSendLevel = reverbSendLevel / 127;
1223
+ reverbEffect.output.gain.cancelScheduledValues(now);
1224
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1140
1225
  }
1141
- setChorusSendLevel(channelNumber, chorus) {
1226
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1142
1227
  const channel = this.channels[channelNumber];
1143
- channel.chorus = chorus / 127;
1144
- channel.chorusEffect.lfoGain = channel.chorus;
1228
+ channel.chorusSendLevel = chorusSendLevel / 127;
1229
+ channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1145
1230
  }
1146
1231
  setSostenutoPedal(channelNumber, value) {
1147
1232
  const isOn = value >= 64;
@@ -1365,11 +1450,11 @@ class Midy {
1365
1450
  this.GM2SystemOn();
1366
1451
  break;
1367
1452
  default:
1368
- console.warn(`Unsupported Exclusive Message ${data}`);
1453
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1369
1454
  }
1370
1455
  break;
1371
1456
  default:
1372
- console.warn(`Unsupported Exclusive Message ${data}`);
1457
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1373
1458
  }
1374
1459
  }
1375
1460
  GM1SystemOn() {
@@ -1400,9 +1485,10 @@ class Midy {
1400
1485
  return this.handleMasterFineTuningSysEx(data);
1401
1486
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1402
1487
  return this.handleMasterCoarseTuningSysEx(data);
1403
- // case 5: // TODO: Global Parameter Control
1488
+ case 5:
1489
+ return this.handleGlobalParameterControl(data);
1404
1490
  default:
1405
- console.warn(`Unsupported Exclusive Message ${data}`);
1491
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1406
1492
  }
1407
1493
  break;
1408
1494
  case 8:
@@ -1411,7 +1497,7 @@ class Midy {
1411
1497
  // // TODO
1412
1498
  // return this.handleScaleOctaveTuning1ByteFormat();
1413
1499
  default:
1414
- console.warn(`Unsupported Exclusive Message ${data}`);
1500
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1415
1501
  }
1416
1502
  break;
1417
1503
  case 9:
@@ -1423,7 +1509,7 @@ class Midy {
1423
1509
  // // TODO
1424
1510
  // return this.setControlChange();
1425
1511
  default:
1426
- console.warn(`Unsupported Exclusive Message ${data}`);
1512
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1427
1513
  }
1428
1514
  break;
1429
1515
  case 10:
@@ -1432,11 +1518,11 @@ class Midy {
1432
1518
  // // TODO
1433
1519
  // return this.handleKeyBasedInstrumentControl();
1434
1520
  default:
1435
- console.warn(`Unsupported Exclusive Message ${data}`);
1521
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1436
1522
  }
1437
1523
  break;
1438
1524
  default:
1439
- console.warn(`Unsupported Exclusive Message ${data}`);
1525
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1440
1526
  }
1441
1527
  }
1442
1528
  handleMasterVolumeSysEx(data) {
@@ -1477,8 +1563,124 @@ class Midy {
1477
1563
  this.masterCoarseTuning = coarseTuning - 64;
1478
1564
  }
1479
1565
  }
1566
+ handleGlobalParameterControl(data) {
1567
+ if (data[5] === 1) {
1568
+ switch (data[6]) {
1569
+ case 1:
1570
+ return this.handleReverbParameter(data);
1571
+ case 2:
1572
+ return this.handleChorusParameter(data);
1573
+ default:
1574
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1575
+ }
1576
+ }
1577
+ else {
1578
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1579
+ }
1580
+ }
1581
+ handleReverbParameter(data) {
1582
+ switch (data[7]) {
1583
+ case 0:
1584
+ return this.setReverbType(data[8]);
1585
+ case 1:
1586
+ return this.setReverbTime(data[8]);
1587
+ }
1588
+ }
1589
+ setReverbType(type) {
1590
+ 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
+ }
1596
+ }
1597
+ getReverbTimeFromType(type) {
1598
+ switch (type) {
1599
+ case 0:
1600
+ return this.getReverbTime(44);
1601
+ case 1:
1602
+ return this.getReverbTime(50);
1603
+ case 2:
1604
+ return this.getReverbTime(56);
1605
+ case 3:
1606
+ return this.getReverbTime(64);
1607
+ case 4:
1608
+ return this.getReverbTime(64);
1609
+ case 8:
1610
+ return this.getReverbTime(50);
1611
+ default:
1612
+ console.warn(`Unsupported Reverb Time: ${type}`);
1613
+ }
1614
+ }
1615
+ setReverbTime(value) {
1616
+ this.reverb.time = this.getReverbTime(value);
1617
+ const { audioContext, channels, options } = this;
1618
+ for (let i = 0; i < channels.length; i++) {
1619
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1620
+ }
1621
+ }
1622
+ getReverbTime(value) {
1623
+ return Math.pow(Math.E, (value - 40) * 0.025);
1624
+ }
1625
+ // mean free path equation
1626
+ // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
1627
+ // 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
1628
+ // V: room size (m^3)
1629
+ // S: room surface area (m^2)
1630
+ // meanFreePath = 4V / S (m)
1631
+ // delay estimation using mean free path
1632
+ // t: degree Celsius, generally used 20
1633
+ // c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
1634
+ // delay = meanFreePath / c (s)
1635
+ // feedback equation
1636
+ // RT60 means that the energy is reduced to Math.pow(10, -6).
1637
+ // Since energy is proportional to the square of the amplitude,
1638
+ // the amplitude is reduced to Math.pow(10, -3).
1639
+ // When this is done through n feedbacks,
1640
+ // Math.pow(feedback, n) = Math.pow(10, -3)
1641
+ // Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
1642
+ // RT60 / delay * Math.log10(feedback) = -3
1643
+ // RT60 = -3 * delay / Math.log10(feedback)
1644
+ // feedback = Math.pow(10, -3 * delay / RT60)
1645
+ // 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.
1649
+ // delay = -RT60 * Math.log10(feedback) / 3
1650
+ calcDelay(rt60, feedback) {
1651
+ return -rt60 * Math.log10(feedback) / 3;
1652
+ }
1653
+ handleChorusParameter(data) {
1654
+ switch (data[7]) {
1655
+ case 0:
1656
+ return this.setChorusType(data[8]);
1657
+ case 1:
1658
+ return this.setChorusModRate(data[8]);
1659
+ case 2:
1660
+ return this.setChorusModDepth(data[8]);
1661
+ case 3:
1662
+ return this.setChorusFeedback(data[8]);
1663
+ case 4:
1664
+ return this.setChorusSendToReverb(data[8]);
1665
+ }
1666
+ }
1667
+ setChorusType(type) {
1668
+ // TODO
1669
+ }
1670
+ setChorusModRate(value) {
1671
+ // TODO
1672
+ }
1673
+ setChorusModDepth(value) {
1674
+ // TODO
1675
+ }
1676
+ setChorusFeedback(value) {
1677
+ // TODO
1678
+ }
1679
+ setChorusSendToReverb(value) {
1680
+ // TODO
1681
+ }
1480
1682
  handleExclusiveMessage(data) {
1481
- console.warn(`Unsupported Exclusive Message ${data}`);
1683
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1482
1684
  }
1483
1685
  handleSysEx(data) {
1484
1686
  switch (data[0]) {
@@ -1512,8 +1714,8 @@ Object.defineProperty(Midy, "channelSettings", {
1512
1714
  volume: 100 / 127,
1513
1715
  pan: 64,
1514
1716
  portamentoTime: 0,
1515
- reverb: 0,
1516
- chorus: 0,
1717
+ reverbSendLevel: 0,
1718
+ chorusSendLevel: 0,
1517
1719
  vibratoRate: 5,
1518
1720
  vibratoDepth: 0.5,
1519
1721
  vibratoDelay: 2.5,