@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/esm/midy.js CHANGED
@@ -51,7 +51,7 @@ class Note {
51
51
  }
52
52
  }
53
53
  export class Midy {
54
- constructor(audioContext) {
54
+ constructor(audioContext, options = this.defaultOptions) {
55
55
  Object.defineProperty(this, "ticksPerBeat", {
56
56
  enumerable: true,
57
57
  configurable: true,
@@ -64,12 +64,6 @@ export class Midy {
64
64
  writable: true,
65
65
  value: 0
66
66
  });
67
- Object.defineProperty(this, "reverbFactor", {
68
- enumerable: true,
69
- configurable: true,
70
- writable: true,
71
- value: 0.1
72
- });
73
67
  Object.defineProperty(this, "masterFineTuning", {
74
68
  enumerable: true,
75
69
  configurable: true,
@@ -82,6 +76,26 @@ export class Midy {
82
76
  writable: true,
83
77
  value: 0
84
78
  }); // cb
79
+ Object.defineProperty(this, "reverb", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: {
84
+ time: this.getReverbTime(64),
85
+ feedback: 0.2,
86
+ }
87
+ });
88
+ Object.defineProperty(this, "chorus", {
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true,
92
+ value: {
93
+ modRate: 3 * 0.122,
94
+ modDepth: (3 + 1) / 3.2,
95
+ feedback: 8 * 0.763,
96
+ sendToReverb: 0 * 0.787,
97
+ }
98
+ });
85
99
  Object.defineProperty(this, "mono", {
86
100
  enumerable: true,
87
101
  configurable: true,
@@ -184,7 +198,30 @@ export class Midy {
184
198
  writable: true,
185
199
  value: []
186
200
  });
201
+ Object.defineProperty(this, "defaultOptions", {
202
+ enumerable: true,
203
+ configurable: true,
204
+ writable: true,
205
+ value: {
206
+ reverbAlgorithm: (audioContext) => {
207
+ const { time: rt60, feedback } = this.reverb;
208
+ // const delay = this.calcDelay(rt60, feedback);
209
+ // const impulse = this.createConvolutionReverbImpulse(
210
+ // audioContext,
211
+ // rt60,
212
+ // delay,
213
+ // );
214
+ // return this.createConvolutionReverb(audioContext, impulse);
215
+ const combFeedbacks = this.generateDistributedArray(feedback, 4);
216
+ const combDelays = combFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
217
+ const allpassFeedbacks = this.generateDistributedArray(feedback, 4);
218
+ const allpassDelays = allpassFeedbacks.map((feedback) => this.calcDelay(rt60, feedback));
219
+ return this.createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays);
220
+ },
221
+ }
222
+ });
187
223
  this.audioContext = audioContext;
224
+ this.options = { ...this.defaultOptions, ...options };
188
225
  this.masterGain = new GainNode(audioContext);
189
226
  this.masterGain.connect(audioContext.destination);
190
227
  this.channels = this.createChannels(audioContext);
@@ -225,23 +262,18 @@ export class Midy {
225
262
  this.totalTime = this.calcTotalTime();
226
263
  }
227
264
  setChannelAudioNodes(audioContext) {
228
- const { gainLeft, gainRight } = this.panToGain(Midy.channelSettings.pan);
265
+ const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
229
266
  const gainL = new GainNode(audioContext, { gain: gainLeft });
230
267
  const gainR = new GainNode(audioContext, { gain: gainRight });
231
268
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
232
269
  gainL.connect(merger, 0, 0);
233
270
  gainR.connect(merger, 0, 1);
234
- merger.connect(this.masterGain);
235
- const reverbEffect = this.createReverbEffect(audioContext);
271
+ const reverbEffect = this.options.reverbAlgorithm(audioContext);
236
272
  const chorusEffect = this.createChorusEffect(audioContext);
237
- chorusEffect.lfo.start();
238
- reverbEffect.dryGain.connect(gainL);
239
- reverbEffect.dryGain.connect(gainR);
240
- reverbEffect.wetGain.connect(gainL);
241
- reverbEffect.wetGain.connect(gainR);
242
273
  return {
243
274
  gainL,
244
275
  gainR,
276
+ merger,
245
277
  reverbEffect,
246
278
  chorusEffect,
247
279
  };
@@ -249,16 +281,16 @@ export class Midy {
249
281
  createChannels(audioContext) {
250
282
  const channels = Array.from({ length: 16 }, () => {
251
283
  return {
252
- ...Midy.channelSettings,
253
- ...Midy.effectSettings,
284
+ ...this.constructor.channelSettings,
285
+ ...this.constructor.effectSettings,
254
286
  ...this.setChannelAudioNodes(audioContext),
255
287
  scheduledNotes: new Map(),
256
288
  sostenutoNotes: new Map(),
257
289
  polyphonicKeyPressure: {
258
- ...Midy.controllerDestinationSettings,
290
+ ...this.constructor.controllerDestinationSettings,
259
291
  },
260
292
  channelPressure: {
261
- ...Midy.controllerDestinationSettings,
293
+ ...this.constructor.controllerDestinationSettings,
262
294
  },
263
295
  };
264
296
  });
@@ -589,8 +621,7 @@ export class Midy {
589
621
  }
590
622
  return noteList[0];
591
623
  }
592
- createReverbEffect(audioContext, options = {}) {
593
- const { decay = 0.8, preDecay = 0, } = options;
624
+ createConvolutionReverbImpulse(audioContext, decay, preDecay) {
594
625
  const sampleRate = audioContext.sampleRate;
595
626
  const length = sampleRate * decay;
596
627
  const impulse = new AudioBuffer({
@@ -604,27 +635,85 @@ export class Midy {
604
635
  for (let i = 0; i < preDecayLength; i++) {
605
636
  channelData[i] = Math.random() * 2 - 1;
606
637
  }
638
+ const attenuationFactor = 1 / (sampleRate * decay);
607
639
  for (let i = preDecayLength; i < length; i++) {
608
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
640
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
609
641
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
610
642
  }
611
643
  }
644
+ return impulse;
645
+ }
646
+ createConvolutionReverb(audioContext, impulse) {
647
+ const output = new GainNode(audioContext);
612
648
  const convolverNode = new ConvolverNode(audioContext, {
613
649
  buffer: impulse,
614
650
  });
615
- const dryGain = new GainNode(audioContext);
616
- const wetGain = new GainNode(audioContext);
617
- convolverNode.connect(wetGain);
651
+ convolverNode.connect(output);
618
652
  return {
653
+ input: convolverNode,
654
+ output,
619
655
  convolverNode,
620
- dryGain,
621
- wetGain,
622
656
  };
623
657
  }
658
+ createCombFilter(audioContext, input, delay, feedback) {
659
+ const delayNode = new DelayNode(audioContext, {
660
+ maxDelayTime: delay,
661
+ delayTime: delay,
662
+ });
663
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
664
+ input.connect(delayNode);
665
+ delayNode.connect(feedbackGain);
666
+ feedbackGain.connect(delayNode);
667
+ return delayNode;
668
+ }
669
+ createAllpassFilter(audioContext, input, delay, feedback) {
670
+ const delayNode = new DelayNode(audioContext, {
671
+ maxDelayTime: delay,
672
+ delayTime: delay,
673
+ });
674
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
675
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
676
+ input.connect(delayNode);
677
+ delayNode.connect(feedbackGain);
678
+ feedbackGain.connect(delayNode);
679
+ delayNode.connect(passGain);
680
+ return passGain;
681
+ }
682
+ generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
683
+ const variance = center * varianceRatio;
684
+ const array = new Array(count);
685
+ for (let i = 0; i < count; i++) {
686
+ const fraction = i / (count - 1 || 1);
687
+ const value = center - variance + fraction * 2 * variance;
688
+ array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
689
+ }
690
+ return array;
691
+ }
692
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
693
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
694
+ createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
695
+ const input = new GainNode(audioContext);
696
+ const output = new GainNode(audioContext);
697
+ const mergerGain = new GainNode(audioContext, {
698
+ gain: 1 / (combDelays.length * 2),
699
+ });
700
+ for (let i = 0; i < combDelays.length; i++) {
701
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
702
+ comb.connect(mergerGain);
703
+ }
704
+ const allpasses = [];
705
+ for (let i = 0; i < allpassDelays.length; i++) {
706
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
707
+ allpasses.push(allpass);
708
+ }
709
+ allpasses.at(-1).connect(output);
710
+ return { input, output };
711
+ }
624
712
  createChorusEffect(audioContext, options = {}) {
625
713
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
626
714
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
627
715
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
716
+ const output = new GainNode(audioContext);
628
717
  const chorusGains = [];
629
718
  const delayNodes = [];
630
719
  const baseGain = 1 / chorusCount;
@@ -634,50 +723,45 @@ export class Midy {
634
723
  const delayNode = new DelayNode(audioContext, {
635
724
  maxDelayTime: delayTime,
636
725
  });
637
- delayNodes.push(delayNode);
638
726
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
727
+ delayNodes.push(delayNode);
639
728
  chorusGains.push(chorusGain);
640
- lfo.connect(lfoGain);
641
729
  lfoGain.connect(delayNode.delayTime);
642
730
  delayNode.connect(chorusGain);
731
+ chorusGain.connect(output);
643
732
  }
733
+ lfo.connect(lfoGain);
734
+ lfo.start();
644
735
  return {
645
736
  lfo,
646
737
  lfoGain,
647
738
  delayNodes,
648
739
  chorusGains,
740
+ output,
649
741
  };
650
742
  }
651
- connectNoteEffects(channel, gainNode) {
652
- if (channel.reverb === 0) {
653
- if (channel.chorus === 0) { // no effect
654
- gainNode.connect(channel.gainL);
655
- gainNode.connect(channel.gainR);
656
- }
657
- else { // chorus
743
+ connectEffects(channel, gainNode) {
744
+ gainNode.connect(channel.merger);
745
+ channel.merger.connect(this.masterGain);
746
+ if (channel.reverbSendLevel === 0) {
747
+ if (channel.chorusSendLevel !== 0) { // chorus
658
748
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
659
- gainNode.connect(delayNode);
660
- });
661
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
662
- chorusGain.connect(channel.gainL);
663
- chorusGain.connect(channel.gainR);
749
+ channel.merger.connect(delayNode);
664
750
  });
751
+ channel.chorusEffect.output.connect(this.masterGain);
665
752
  }
666
753
  }
667
754
  else {
668
- if (channel.chorus === 0) { // reverb
669
- gainNode.connect(channel.reverbEffect.convolverNode);
670
- gainNode.connect(channel.reverbEffect.dryGain);
755
+ if (channel.chorusSendLevel === 0) { // reverb
756
+ channel.merger.connect(channel.reverbEffect.input);
757
+ channel.reverbEffect.output.connect(this.masterGain);
671
758
  }
672
759
  else { // reverb + chorus
673
- gainNode.connect(channel.reverbEffect.convolverNode);
674
- gainNode.connect(channel.reverbEffect.dryGain);
675
760
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
676
- gainNode.connect(delayNode);
677
- });
678
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
679
- chorusGain.connect(channel.reverbEffect.convolverNode);
761
+ channel.merger.connect(delayNode);
680
762
  });
763
+ channel.merger.connect(channel.reverbEffect.input);
764
+ channel.reverbEffect.output.connect(this.masterGain);
681
765
  }
682
766
  }
683
767
  }
@@ -817,7 +901,7 @@ export class Midy {
817
901
  if (!instrumentKey)
818
902
  return;
819
903
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
820
- this.connectNoteEffects(channel, note.gainNode);
904
+ this.connectEffects(channel, note.gainNode);
821
905
  if (channel.sostenutoPedal) {
822
906
  channel.sostenutoNotes.set(noteNumber, note);
823
907
  }
@@ -841,46 +925,48 @@ export class Midy {
841
925
  return;
842
926
  if (!channel.scheduledNotes.has(noteNumber))
843
927
  return;
844
- const targetNotes = channel.scheduledNotes.get(noteNumber);
845
- for (let i = 0; i < targetNotes.length; i++) {
846
- const targetNote = targetNotes[i];
847
- if (!targetNote)
928
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
929
+ for (let i = 0; i < scheduledNotes.length; i++) {
930
+ const note = scheduledNotes[i];
931
+ if (!note)
848
932
  continue;
849
- if (targetNote.ending)
933
+ if (note.ending)
850
934
  continue;
851
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
852
935
  const velocityRate = (velocity + 127) / 127;
853
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
854
- gainNode.gain.cancelScheduledValues(stopTime);
855
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
936
+ const volEndTime = stopTime +
937
+ note.instrumentKey.volRelease * velocityRate;
938
+ note.gainNode.gain
939
+ .cancelScheduledValues(stopTime)
940
+ .linearRampToValueAtTime(0, volEndTime);
856
941
  const maxFreq = this.audioContext.sampleRate / 2;
857
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
942
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
858
943
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
859
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
860
- filterNode.frequency
944
+ const modEndTime = stopTime +
945
+ note.instrumentKey.modRelease * velocityRate;
946
+ note.filterNode.frequency
861
947
  .cancelScheduledValues(stopTime)
862
948
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
863
- targetNote.ending = true;
949
+ note.ending = true;
864
950
  this.scheduleTask(() => {
865
- bufferSource.loop = false;
951
+ note.bufferSource.loop = false;
866
952
  }, stopTime);
867
953
  return new Promise((resolve) => {
868
- bufferSource.onended = () => {
869
- targetNotes[i] = null;
870
- bufferSource.disconnect(0);
871
- filterNode.disconnect(0);
872
- gainNode.disconnect(0);
873
- if (modLFOGain)
874
- modLFOGain.disconnect(0);
875
- if (vibLFOGain)
876
- vibLFOGain.disconnect(0);
877
- if (modLFO)
878
- modLFO.stop();
879
- if (vibLFO)
880
- vibLFO.stop();
954
+ note.bufferSource.onended = () => {
955
+ scheduledNotes[i] = null;
956
+ note.bufferSource.disconnect();
957
+ note.filterNode.disconnect();
958
+ note.gainNode.disconnect();
959
+ if (note.modLFOGain)
960
+ note.modLFOGain.disconnect();
961
+ if (note.vibLFOGain)
962
+ note.vibLFOGain.disconnect();
963
+ if (note.modLFO)
964
+ note.modLFO.stop();
965
+ if (note.vibLFO)
966
+ note.vibLFO.stop();
881
967
  resolve();
882
968
  };
883
- bufferSource.stop(volEndTime);
969
+ note.bufferSource.stop(volEndTime);
884
970
  });
885
971
  }
886
972
  }
@@ -1122,23 +1208,22 @@ export class Midy {
1122
1208
  this.releaseSustainPedal(channelNumber, value);
1123
1209
  }
1124
1210
  }
1211
+ // TODO
1125
1212
  setPortamento(channelNumber, value) {
1126
1213
  this.channels[channelNumber].portamento = value >= 64;
1127
1214
  }
1128
- setReverbSendLevel(channelNumber, reverb) {
1215
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1129
1216
  const now = this.audioContext.currentTime;
1130
1217
  const channel = this.channels[channelNumber];
1131
1218
  const reverbEffect = channel.reverbEffect;
1132
- channel.reverb = reverb / 127 * this.reverbFactor;
1133
- reverbEffect.dryGain.gain.cancelScheduledValues(now);
1134
- reverbEffect.dryGain.gain.setValueAtTime(1 - channel.reverb, now);
1135
- reverbEffect.wetGain.gain.cancelScheduledValues(now);
1136
- reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1219
+ channel.reverbSendLevel = reverbSendLevel / 127;
1220
+ reverbEffect.output.gain.cancelScheduledValues(now);
1221
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1137
1222
  }
1138
- setChorusSendLevel(channelNumber, chorus) {
1223
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1139
1224
  const channel = this.channels[channelNumber];
1140
- channel.chorus = chorus / 127;
1141
- channel.chorusEffect.lfoGain = channel.chorus;
1225
+ channel.chorusSendLevel = chorusSendLevel / 127;
1226
+ channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1142
1227
  }
1143
1228
  setSostenutoPedal(channelNumber, value) {
1144
1229
  const isOn = value >= 64;
@@ -1362,11 +1447,11 @@ export class Midy {
1362
1447
  this.GM2SystemOn();
1363
1448
  break;
1364
1449
  default:
1365
- console.warn(`Unsupported Exclusive Message ${data}`);
1450
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1366
1451
  }
1367
1452
  break;
1368
1453
  default:
1369
- console.warn(`Unsupported Exclusive Message ${data}`);
1454
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1370
1455
  }
1371
1456
  }
1372
1457
  GM1SystemOn() {
@@ -1397,9 +1482,10 @@ export class Midy {
1397
1482
  return this.handleMasterFineTuningSysEx(data);
1398
1483
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1399
1484
  return this.handleMasterCoarseTuningSysEx(data);
1400
- // case 5: // TODO: Global Parameter Control
1485
+ case 5:
1486
+ return this.handleGlobalParameterControl(data);
1401
1487
  default:
1402
- console.warn(`Unsupported Exclusive Message ${data}`);
1488
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1403
1489
  }
1404
1490
  break;
1405
1491
  case 8:
@@ -1408,7 +1494,7 @@ export class Midy {
1408
1494
  // // TODO
1409
1495
  // return this.handleScaleOctaveTuning1ByteFormat();
1410
1496
  default:
1411
- console.warn(`Unsupported Exclusive Message ${data}`);
1497
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1412
1498
  }
1413
1499
  break;
1414
1500
  case 9:
@@ -1420,7 +1506,7 @@ export class Midy {
1420
1506
  // // TODO
1421
1507
  // return this.setControlChange();
1422
1508
  default:
1423
- console.warn(`Unsupported Exclusive Message ${data}`);
1509
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1424
1510
  }
1425
1511
  break;
1426
1512
  case 10:
@@ -1429,11 +1515,11 @@ export class Midy {
1429
1515
  // // TODO
1430
1516
  // return this.handleKeyBasedInstrumentControl();
1431
1517
  default:
1432
- console.warn(`Unsupported Exclusive Message ${data}`);
1518
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1433
1519
  }
1434
1520
  break;
1435
1521
  default:
1436
- console.warn(`Unsupported Exclusive Message ${data}`);
1522
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1437
1523
  }
1438
1524
  }
1439
1525
  handleMasterVolumeSysEx(data) {
@@ -1474,8 +1560,124 @@ export class Midy {
1474
1560
  this.masterCoarseTuning = coarseTuning - 64;
1475
1561
  }
1476
1562
  }
1563
+ handleGlobalParameterControl(data) {
1564
+ if (data[5] === 1) {
1565
+ switch (data[6]) {
1566
+ case 1:
1567
+ return this.handleReverbParameter(data);
1568
+ case 2:
1569
+ return this.handleChorusParameter(data);
1570
+ default:
1571
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1572
+ }
1573
+ }
1574
+ else {
1575
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1576
+ }
1577
+ }
1578
+ handleReverbParameter(data) {
1579
+ switch (data[7]) {
1580
+ case 0:
1581
+ return this.setReverbType(data[8]);
1582
+ case 1:
1583
+ return this.setReverbTime(data[8]);
1584
+ }
1585
+ }
1586
+ setReverbType(type) {
1587
+ this.reverb.time = this.getReverbTimeFromType(type);
1588
+ this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1589
+ const { audioContext, channels, options } = this;
1590
+ for (let i = 0; i < channels.length; i++) {
1591
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1592
+ }
1593
+ }
1594
+ getReverbTimeFromType(type) {
1595
+ switch (type) {
1596
+ case 0:
1597
+ return this.getReverbTime(44);
1598
+ case 1:
1599
+ return this.getReverbTime(50);
1600
+ case 2:
1601
+ return this.getReverbTime(56);
1602
+ case 3:
1603
+ return this.getReverbTime(64);
1604
+ case 4:
1605
+ return this.getReverbTime(64);
1606
+ case 8:
1607
+ return this.getReverbTime(50);
1608
+ default:
1609
+ console.warn(`Unsupported Reverb Time: ${type}`);
1610
+ }
1611
+ }
1612
+ setReverbTime(value) {
1613
+ this.reverb.time = this.getReverbTime(value);
1614
+ const { audioContext, channels, options } = this;
1615
+ for (let i = 0; i < channels.length; i++) {
1616
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1617
+ }
1618
+ }
1619
+ getReverbTime(value) {
1620
+ return Math.pow(Math.E, (value - 40) * 0.025);
1621
+ }
1622
+ // mean free path equation
1623
+ // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
1624
+ // 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
1625
+ // V: room size (m^3)
1626
+ // S: room surface area (m^2)
1627
+ // meanFreePath = 4V / S (m)
1628
+ // delay estimation using mean free path
1629
+ // t: degree Celsius, generally used 20
1630
+ // c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
1631
+ // delay = meanFreePath / c (s)
1632
+ // feedback equation
1633
+ // RT60 means that the energy is reduced to Math.pow(10, -6).
1634
+ // Since energy is proportional to the square of the amplitude,
1635
+ // the amplitude is reduced to Math.pow(10, -3).
1636
+ // When this is done through n feedbacks,
1637
+ // Math.pow(feedback, n) = Math.pow(10, -3)
1638
+ // Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
1639
+ // RT60 / delay * Math.log10(feedback) = -3
1640
+ // RT60 = -3 * delay / Math.log10(feedback)
1641
+ // feedback = Math.pow(10, -3 * delay / RT60)
1642
+ // delay estimation using ideal feedback
1643
+ // A suitable average sound absorption coefficient is 0.18-0.28.
1644
+ // Since the structure of the hall is complex,
1645
+ // It would be easier to determine the delay based on the ideal feedback.
1646
+ // delay = -RT60 * Math.log10(feedback) / 3
1647
+ calcDelay(rt60, feedback) {
1648
+ return -rt60 * Math.log10(feedback) / 3;
1649
+ }
1650
+ handleChorusParameter(data) {
1651
+ switch (data[7]) {
1652
+ case 0:
1653
+ return this.setChorusType(data[8]);
1654
+ case 1:
1655
+ return this.setChorusModRate(data[8]);
1656
+ case 2:
1657
+ return this.setChorusModDepth(data[8]);
1658
+ case 3:
1659
+ return this.setChorusFeedback(data[8]);
1660
+ case 4:
1661
+ return this.setChorusSendToReverb(data[8]);
1662
+ }
1663
+ }
1664
+ setChorusType(type) {
1665
+ // TODO
1666
+ }
1667
+ setChorusModRate(value) {
1668
+ // TODO
1669
+ }
1670
+ setChorusModDepth(value) {
1671
+ // TODO
1672
+ }
1673
+ setChorusFeedback(value) {
1674
+ // TODO
1675
+ }
1676
+ setChorusSendToReverb(value) {
1677
+ // TODO
1678
+ }
1477
1679
  handleExclusiveMessage(data) {
1478
- console.warn(`Unsupported Exclusive Message ${data}`);
1680
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1479
1681
  }
1480
1682
  handleSysEx(data) {
1481
1683
  switch (data[0]) {
@@ -1508,8 +1710,8 @@ Object.defineProperty(Midy, "channelSettings", {
1508
1710
  volume: 100 / 127,
1509
1711
  pan: 64,
1510
1712
  portamentoTime: 0,
1511
- reverb: 0,
1512
- chorus: 0,
1713
+ reverbSendLevel: 0,
1714
+ chorusSendLevel: 0,
1513
1715
  vibratoRate: 5,
1514
1716
  vibratoDepth: 0.5,
1515
1717
  vibratoDelay: 2.5,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -39,27 +39,7 @@ export class MidyGM1 {
39
39
  notePromises: any[];
40
40
  audioContext: any;
41
41
  masterGain: any;
42
- channels: {
43
- scheduledNotes: Map<any, any>;
44
- gainL: any;
45
- gainR: any;
46
- expression: number;
47
- modulation: number;
48
- sustainPedal: boolean;
49
- rpnMSB: number;
50
- rpnLSB: number;
51
- pitchBendRange: number;
52
- volume: number;
53
- pan: number;
54
- bank: number;
55
- dataMSB: number;
56
- dataLSB: number;
57
- program: number;
58
- pitchBend: number;
59
- fineTuning: number;
60
- coarseTuning: number;
61
- modulationDepthRange: number;
62
- }[];
42
+ channels: any[];
63
43
  initSoundFontTable(): any[];
64
44
  addSoundFont(soundFont: any): void;
65
45
  loadSoundFont(soundFontUrl: any): Promise<void>;
@@ -67,28 +47,9 @@ export class MidyGM1 {
67
47
  setChannelAudioNodes(audioContext: any): {
68
48
  gainL: any;
69
49
  gainR: any;
50
+ merger: any;
70
51
  };
71
- createChannels(audioContext: any): {
72
- scheduledNotes: Map<any, any>;
73
- gainL: any;
74
- gainR: any;
75
- expression: number;
76
- modulation: number;
77
- sustainPedal: boolean;
78
- rpnMSB: number;
79
- rpnLSB: number;
80
- pitchBendRange: number;
81
- volume: number;
82
- pan: number;
83
- bank: number;
84
- dataMSB: number;
85
- dataLSB: number;
86
- program: number;
87
- pitchBend: number;
88
- fineTuning: number;
89
- coarseTuning: number;
90
- modulationDepthRange: number;
91
- }[];
52
+ createChannels(audioContext: any): any[];
92
53
  createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
93
54
  createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
94
55
  convertToFloat32Array(uint8Array: any): Float32Array;
@@ -111,7 +72,7 @@ export class MidyGM1 {
111
72
  currentTime(): number;
112
73
  getActiveNotes(channel: any, time: any): Map<any, any>;
113
74
  getActiveNote(noteList: any, time: any): any;
114
- connectNoteEffects(channel: any, gainNode: any): void;
75
+ connectEffects(channel: any, gainNode: any): void;
115
76
  cbToRatio(cb: any): number;
116
77
  centToHz(cent: any): number;
117
78
  calcSemitoneOffset(channel: any): any;