@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.
@@ -54,7 +54,7 @@ class Note {
54
54
  }
55
55
  }
56
56
  class MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
228
265
  this.totalTime = this.calcTotalTime();
229
266
  }
230
267
  setChannelAudioNodes(audioContext) {
231
- const { gainLeft, gainRight } = this.panToGain(MidyGM2.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,13 +284,13 @@ class MidyGM2 {
252
284
  createChannels(audioContext) {
253
285
  const channels = Array.from({ length: 16 }, () => {
254
286
  return {
255
- ...MidyGM2.channelSettings,
256
- ...MidyGM2.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
  channelPressure: {
261
- ...MidyGM2.controllerDestinationSettings,
293
+ ...this.constructor.controllerDestinationSettings,
262
294
  },
263
295
  };
264
296
  });
@@ -586,8 +618,7 @@ class MidyGM2 {
586
618
  }
587
619
  return noteList[0];
588
620
  }
589
- createReverbEffect(audioContext, options = {}) {
590
- const { decay = 0.8, preDecay = 0, } = options;
621
+ createConvolutionReverbImpulse(audioContext, decay, preDecay) {
591
622
  const sampleRate = audioContext.sampleRate;
592
623
  const length = sampleRate * decay;
593
624
  const impulse = new AudioBuffer({
@@ -601,27 +632,85 @@ class MidyGM2 {
601
632
  for (let i = 0; i < preDecayLength; i++) {
602
633
  channelData[i] = Math.random() * 2 - 1;
603
634
  }
635
+ const attenuationFactor = 1 / (sampleRate * decay);
604
636
  for (let i = preDecayLength; i < length; i++) {
605
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
637
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
606
638
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
607
639
  }
608
640
  }
641
+ return impulse;
642
+ }
643
+ createConvolutionReverb(audioContext, impulse) {
644
+ const output = new GainNode(audioContext);
609
645
  const convolverNode = new ConvolverNode(audioContext, {
610
646
  buffer: impulse,
611
647
  });
612
- const dryGain = new GainNode(audioContext);
613
- const wetGain = new GainNode(audioContext);
614
- convolverNode.connect(wetGain);
648
+ convolverNode.connect(output);
615
649
  return {
650
+ input: convolverNode,
651
+ output,
616
652
  convolverNode,
617
- dryGain,
618
- wetGain,
619
653
  };
620
654
  }
655
+ createCombFilter(audioContext, input, delay, feedback) {
656
+ const delayNode = new DelayNode(audioContext, {
657
+ maxDelayTime: delay,
658
+ delayTime: delay,
659
+ });
660
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
661
+ input.connect(delayNode);
662
+ delayNode.connect(feedbackGain);
663
+ feedbackGain.connect(delayNode);
664
+ return delayNode;
665
+ }
666
+ createAllpassFilter(audioContext, input, delay, feedback) {
667
+ const delayNode = new DelayNode(audioContext, {
668
+ maxDelayTime: delay,
669
+ delayTime: delay,
670
+ });
671
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
672
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
673
+ input.connect(delayNode);
674
+ delayNode.connect(feedbackGain);
675
+ feedbackGain.connect(delayNode);
676
+ delayNode.connect(passGain);
677
+ return passGain;
678
+ }
679
+ generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
680
+ const variance = center * varianceRatio;
681
+ const array = new Array(count);
682
+ for (let i = 0; i < count; i++) {
683
+ const fraction = i / (count - 1 || 1);
684
+ const value = center - variance + fraction * 2 * variance;
685
+ array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
686
+ }
687
+ return array;
688
+ }
689
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
690
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
691
+ createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
692
+ const input = new GainNode(audioContext);
693
+ const output = new GainNode(audioContext);
694
+ const mergerGain = new GainNode(audioContext, {
695
+ gain: 1 / (combDelays.length * 2),
696
+ });
697
+ for (let i = 0; i < combDelays.length; i++) {
698
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
699
+ comb.connect(mergerGain);
700
+ }
701
+ const allpasses = [];
702
+ for (let i = 0; i < allpassDelays.length; i++) {
703
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
704
+ allpasses.push(allpass);
705
+ }
706
+ allpasses.at(-1).connect(output);
707
+ return { input, output };
708
+ }
621
709
  createChorusEffect(audioContext, options = {}) {
622
710
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
623
711
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
624
712
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
713
+ const output = new GainNode(audioContext);
625
714
  const chorusGains = [];
626
715
  const delayNodes = [];
627
716
  const baseGain = 1 / chorusCount;
@@ -631,50 +720,45 @@ class MidyGM2 {
631
720
  const delayNode = new DelayNode(audioContext, {
632
721
  maxDelayTime: delayTime,
633
722
  });
634
- delayNodes.push(delayNode);
635
723
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
724
+ delayNodes.push(delayNode);
636
725
  chorusGains.push(chorusGain);
637
- lfo.connect(lfoGain);
638
726
  lfoGain.connect(delayNode.delayTime);
639
727
  delayNode.connect(chorusGain);
728
+ chorusGain.connect(output);
640
729
  }
730
+ lfo.connect(lfoGain);
731
+ lfo.start();
641
732
  return {
642
733
  lfo,
643
734
  lfoGain,
644
735
  delayNodes,
645
736
  chorusGains,
737
+ output,
646
738
  };
647
739
  }
648
- connectNoteEffects(channel, gainNode) {
649
- if (channel.reverb === 0) {
650
- if (channel.chorus === 0) { // no effect
651
- gainNode.connect(channel.gainL);
652
- gainNode.connect(channel.gainR);
653
- }
654
- else { // chorus
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
655
745
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
656
- gainNode.connect(delayNode);
657
- });
658
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
659
- chorusGain.connect(channel.gainL);
660
- chorusGain.connect(channel.gainR);
746
+ channel.merger.connect(delayNode);
661
747
  });
748
+ channel.chorusEffect.output.connect(this.masterGain);
662
749
  }
663
750
  }
664
751
  else {
665
- if (channel.chorus === 0) { // reverb
666
- gainNode.connect(channel.reverbEffect.convolverNode);
667
- gainNode.connect(channel.reverbEffect.dryGain);
752
+ if (channel.chorusSendLevel === 0) { // reverb
753
+ channel.merger.connect(channel.reverbEffect.input);
754
+ channel.reverbEffect.output.connect(this.masterGain);
668
755
  }
669
756
  else { // reverb + chorus
670
- gainNode.connect(channel.reverbEffect.convolverNode);
671
- gainNode.connect(channel.reverbEffect.dryGain);
672
757
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
673
- gainNode.connect(delayNode);
674
- });
675
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
676
- chorusGain.connect(channel.reverbEffect.convolverNode);
758
+ channel.merger.connect(delayNode);
677
759
  });
760
+ channel.merger.connect(channel.reverbEffect.input);
761
+ channel.reverbEffect.output.connect(this.masterGain);
678
762
  }
679
763
  }
680
764
  }
@@ -745,7 +829,7 @@ class MidyGM2 {
745
829
  startModulation(channel, note, time) {
746
830
  const { instrumentKey } = note;
747
831
  note.modLFOGain = new GainNode(this.audioContext, {
748
- gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
832
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
749
833
  });
750
834
  note.modLFO = new OscillatorNode(this.audioContext, {
751
835
  frequency: this.centToHz(instrumentKey.freqModLFO),
@@ -797,7 +881,7 @@ class MidyGM2 {
797
881
  if (!instrumentKey)
798
882
  return;
799
883
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
800
- this.connectNoteEffects(channel, note.gainNode);
884
+ this.connectEffects(channel, note.gainNode);
801
885
  if (channel.sostenutoPedal) {
802
886
  channel.sostenutoNotes.set(noteNumber, note);
803
887
  }
@@ -821,42 +905,48 @@ class MidyGM2 {
821
905
  return;
822
906
  if (!channel.scheduledNotes.has(noteNumber))
823
907
  return;
824
- const targetNotes = channel.scheduledNotes.get(noteNumber);
825
- for (let i = 0; i < targetNotes.length; i++) {
826
- const targetNote = targetNotes[i];
827
- if (!targetNote)
908
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
909
+ for (let i = 0; i < scheduledNotes.length; i++) {
910
+ const note = scheduledNotes[i];
911
+ if (!note)
828
912
  continue;
829
- if (targetNote.ending)
913
+ if (note.ending)
830
914
  continue;
831
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
832
915
  const velocityRate = (velocity + 127) / 127;
833
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
834
- gainNode.gain.cancelScheduledValues(stopTime);
835
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
916
+ const volEndTime = stopTime +
917
+ note.instrumentKey.volRelease * velocityRate;
918
+ note.gainNode.gain
919
+ .cancelScheduledValues(stopTime)
920
+ .linearRampToValueAtTime(0, volEndTime);
836
921
  const maxFreq = this.audioContext.sampleRate / 2;
837
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
922
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
838
923
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
839
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
840
- filterNode.frequency
924
+ const modEndTime = stopTime +
925
+ note.instrumentKey.modRelease * velocityRate;
926
+ note.filterNode.frequency
841
927
  .cancelScheduledValues(stopTime)
842
928
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
843
- targetNote.ending = true;
929
+ note.ending = true;
844
930
  this.scheduleTask(() => {
845
- bufferSource.loop = false;
931
+ note.bufferSource.loop = false;
846
932
  }, stopTime);
847
933
  return new Promise((resolve) => {
848
- bufferSource.onended = () => {
849
- targetNotes[i] = null;
850
- bufferSource.disconnect(0);
851
- filterNode.disconnect(0);
852
- gainNode.disconnect(0);
853
- if (modLFOGain)
854
- modLFOGain.disconnect(0);
855
- if (modLFO)
856
- modLFO.stop();
934
+ note.bufferSource.onended = () => {
935
+ scheduledNotes[i] = null;
936
+ note.bufferSource.disconnect();
937
+ note.filterNode.disconnect();
938
+ note.gainNode.disconnect();
939
+ if (note.modLFOGain)
940
+ note.modLFOGain.disconnect();
941
+ if (note.vibLFOGain)
942
+ note.vibLFOGain.disconnect();
943
+ if (note.modLFO)
944
+ note.modLFO.stop();
945
+ if (note.vibLFO)
946
+ note.vibLFO.stop();
857
947
  resolve();
858
948
  };
859
- bufferSource.stop(volEndTime);
949
+ note.bufferSource.stop(volEndTime);
860
950
  });
861
951
  }
862
952
  }
@@ -1070,23 +1160,22 @@ class MidyGM2 {
1070
1160
  this.releaseSustainPedal(channelNumber, value);
1071
1161
  }
1072
1162
  }
1163
+ // TODO
1073
1164
  setPortamento(channelNumber, value) {
1074
1165
  this.channels[channelNumber].portamento = value >= 64;
1075
1166
  }
1076
- setReverbSendLevel(channelNumber, reverb) {
1167
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1077
1168
  const now = this.audioContext.currentTime;
1078
1169
  const channel = this.channels[channelNumber];
1079
1170
  const reverbEffect = channel.reverbEffect;
1080
- channel.reverb = reverb / 127 * this.reverbFactor;
1081
- reverbEffect.dryGain.gain.cancelScheduledValues(now);
1082
- reverbEffect.dryGain.gain.setValueAtTime(1 - channel.reverb, now);
1083
- reverbEffect.wetGain.gain.cancelScheduledValues(now);
1084
- reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1171
+ channel.reverbSendLevel = reverbSendLevel / 127;
1172
+ reverbEffect.output.gain.cancelScheduledValues(now);
1173
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1085
1174
  }
1086
- setChorusSendLevel(channelNumber, chorus) {
1175
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1087
1176
  const channel = this.channels[channelNumber];
1088
- channel.chorus = chorus / 127;
1089
- channel.chorusEffect.lfoGain = channel.chorus;
1177
+ channel.chorusSendLevel = chorusSendLevel / 127;
1178
+ channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1090
1179
  }
1091
1180
  setSostenutoPedal(channelNumber, value) {
1092
1181
  const isOn = value >= 64;
@@ -1282,11 +1371,11 @@ class MidyGM2 {
1282
1371
  this.GM2SystemOn();
1283
1372
  break;
1284
1373
  default:
1285
- console.warn(`Unsupported Exclusive Message ${data}`);
1374
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1286
1375
  }
1287
1376
  break;
1288
1377
  default:
1289
- console.warn(`Unsupported Exclusive Message ${data}`);
1378
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1290
1379
  }
1291
1380
  }
1292
1381
  GM1SystemOn() {
@@ -1317,9 +1406,10 @@ class MidyGM2 {
1317
1406
  return this.handleMasterFineTuningSysEx(data);
1318
1407
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1319
1408
  return this.handleMasterCoarseTuningSysEx(data);
1320
- // case 5: // TODO: Global Parameter Control
1409
+ case 5:
1410
+ return this.handleGlobalParameterControl(data);
1321
1411
  default:
1322
- console.warn(`Unsupported Exclusive Message ${data}`);
1412
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1323
1413
  }
1324
1414
  break;
1325
1415
  case 8:
@@ -1328,7 +1418,7 @@ class MidyGM2 {
1328
1418
  // // TODO
1329
1419
  // return this.handleScaleOctaveTuning1ByteFormat();
1330
1420
  default:
1331
- console.warn(`Unsupported Exclusive Message ${data}`);
1421
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1332
1422
  }
1333
1423
  break;
1334
1424
  case 9:
@@ -1340,7 +1430,7 @@ class MidyGM2 {
1340
1430
  // // TODO
1341
1431
  // return this.setControlChange();
1342
1432
  default:
1343
- console.warn(`Unsupported Exclusive Message ${data}`);
1433
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1344
1434
  }
1345
1435
  break;
1346
1436
  case 10:
@@ -1349,11 +1439,11 @@ class MidyGM2 {
1349
1439
  // // TODO
1350
1440
  // return this.handleKeyBasedInstrumentControl();
1351
1441
  default:
1352
- console.warn(`Unsupported Exclusive Message ${data}`);
1442
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1353
1443
  }
1354
1444
  break;
1355
1445
  default:
1356
- console.warn(`Unsupported Exclusive Message ${data}`);
1446
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1357
1447
  }
1358
1448
  }
1359
1449
  handleMasterVolumeSysEx(data) {
@@ -1394,8 +1484,124 @@ class MidyGM2 {
1394
1484
  this.masterCoarseTuning = coarseTuning - 64;
1395
1485
  }
1396
1486
  }
1487
+ handleGlobalParameterControl(data) {
1488
+ if (data[5] === 1) {
1489
+ switch (data[6]) {
1490
+ case 1:
1491
+ return this.handleReverbParameter(data);
1492
+ case 2:
1493
+ return this.handleChorusParameter(data);
1494
+ default:
1495
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1496
+ }
1497
+ }
1498
+ else {
1499
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1500
+ }
1501
+ }
1502
+ handleReverbParameter(data) {
1503
+ switch (data[7]) {
1504
+ case 0:
1505
+ return this.setReverbType(data[8]);
1506
+ case 1:
1507
+ return this.setReverbTime(data[8]);
1508
+ }
1509
+ }
1510
+ setReverbType(type) {
1511
+ 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
+ }
1517
+ }
1518
+ getReverbTimeFromType(type) {
1519
+ switch (type) {
1520
+ case 0:
1521
+ return this.getReverbTime(44);
1522
+ case 1:
1523
+ return this.getReverbTime(50);
1524
+ case 2:
1525
+ return this.getReverbTime(56);
1526
+ case 3:
1527
+ return this.getReverbTime(64);
1528
+ case 4:
1529
+ return this.getReverbTime(64);
1530
+ case 8:
1531
+ return this.getReverbTime(50);
1532
+ default:
1533
+ console.warn(`Unsupported Reverb Time: ${type}`);
1534
+ }
1535
+ }
1536
+ setReverbTime(value) {
1537
+ this.reverb.time = this.getReverbTime(value);
1538
+ const { audioContext, channels, options } = this;
1539
+ for (let i = 0; i < channels.length; i++) {
1540
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1541
+ }
1542
+ }
1543
+ getReverbTime(value) {
1544
+ return Math.pow(Math.E, (value - 40) * 0.025);
1545
+ }
1546
+ // mean free path equation
1547
+ // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
1548
+ // 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
1549
+ // V: room size (m^3)
1550
+ // S: room surface area (m^2)
1551
+ // meanFreePath = 4V / S (m)
1552
+ // delay estimation using mean free path
1553
+ // t: degree Celsius, generally used 20
1554
+ // c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
1555
+ // delay = meanFreePath / c (s)
1556
+ // feedback equation
1557
+ // RT60 means that the energy is reduced to Math.pow(10, -6).
1558
+ // Since energy is proportional to the square of the amplitude,
1559
+ // the amplitude is reduced to Math.pow(10, -3).
1560
+ // When this is done through n feedbacks,
1561
+ // Math.pow(feedback, n) = Math.pow(10, -3)
1562
+ // Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
1563
+ // RT60 / delay * Math.log10(feedback) = -3
1564
+ // RT60 = -3 * delay / Math.log10(feedback)
1565
+ // feedback = Math.pow(10, -3 * delay / RT60)
1566
+ // 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.
1570
+ // delay = -RT60 * Math.log10(feedback) / 3
1571
+ calcDelay(rt60, feedback) {
1572
+ return -rt60 * Math.log10(feedback) / 3;
1573
+ }
1574
+ handleChorusParameter(data) {
1575
+ switch (data[7]) {
1576
+ case 0:
1577
+ return this.setChorusType(data[8]);
1578
+ case 1:
1579
+ return this.setChorusModRate(data[8]);
1580
+ case 2:
1581
+ return this.setChorusModDepth(data[8]);
1582
+ case 3:
1583
+ return this.setChorusFeedback(data[8]);
1584
+ case 4:
1585
+ return this.setChorusSendToReverb(data[8]);
1586
+ }
1587
+ }
1588
+ setChorusType(type) {
1589
+ // TODO
1590
+ }
1591
+ setChorusModRate(value) {
1592
+ // TODO
1593
+ }
1594
+ setChorusModDepth(value) {
1595
+ // TODO
1596
+ }
1597
+ setChorusFeedback(value) {
1598
+ // TODO
1599
+ }
1600
+ setChorusSendToReverb(value) {
1601
+ // TODO
1602
+ }
1397
1603
  handleExclusiveMessage(data) {
1398
- console.warn(`Unsupported Exclusive Message ${data}`);
1604
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1399
1605
  }
1400
1606
  handleSysEx(data) {
1401
1607
  switch (data[0]) {
@@ -1429,8 +1635,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1429
1635
  volume: 100 / 127,
1430
1636
  pan: 64,
1431
1637
  portamentoTime: 0,
1432
- reverb: 0,
1433
- chorus: 0,
1638
+ reverbSendLevel: 0,
1639
+ chorusSendLevel: 0,
1434
1640
  bank: 121 * 128,
1435
1641
  bankMSB: 121,
1436
1642
  bankLSB: 0,
@@ -37,25 +37,7 @@ export class MidyGMLite {
37
37
  notePromises: any[];
38
38
  audioContext: any;
39
39
  masterGain: any;
40
- channels: {
41
- scheduledNotes: Map<any, any>;
42
- gainL: any;
43
- gainR: any;
44
- expression: number;
45
- modulation: number;
46
- sustainPedal: boolean;
47
- rpnMSB: number;
48
- rpnLSB: number;
49
- pitchBendRange: number;
50
- volume: number;
51
- pan: number;
52
- bank: number;
53
- dataMSB: number;
54
- dataLSB: number;
55
- program: number;
56
- pitchBend: number;
57
- modulationDepthRange: number;
58
- }[];
40
+ channels: any[];
59
41
  initSoundFontTable(): any[];
60
42
  addSoundFont(soundFont: any): void;
61
43
  loadSoundFont(soundFontUrl: any): Promise<void>;
@@ -63,26 +45,9 @@ export class MidyGMLite {
63
45
  setChannelAudioNodes(audioContext: any): {
64
46
  gainL: any;
65
47
  gainR: any;
48
+ merger: any;
66
49
  };
67
- createChannels(audioContext: any): {
68
- scheduledNotes: Map<any, any>;
69
- gainL: any;
70
- gainR: any;
71
- expression: number;
72
- modulation: number;
73
- sustainPedal: boolean;
74
- rpnMSB: number;
75
- rpnLSB: number;
76
- pitchBendRange: number;
77
- volume: number;
78
- pan: number;
79
- bank: number;
80
- dataMSB: number;
81
- dataLSB: number;
82
- program: number;
83
- pitchBend: number;
84
- modulationDepthRange: number;
85
- }[];
50
+ createChannels(audioContext: any): any[];
86
51
  createNoteBuffer(instrumentKey: any, isSF3: any): Promise<any>;
87
52
  createNoteBufferNode(instrumentKey: any, isSF3: any): Promise<any>;
88
53
  convertToFloat32Array(uint8Array: any): Float32Array;
@@ -105,7 +70,7 @@ export class MidyGMLite {
105
70
  currentTime(): number;
106
71
  getActiveNotes(channel: any, time: any): Map<any, any>;
107
72
  getActiveNote(noteList: any, time: any): any;
108
- connectNoteEffects(channel: any, gainNode: any): void;
73
+ connectEffects(channel: any, gainNode: any): void;
109
74
  cbToRatio(cb: any): number;
110
75
  centToHz(cent: any): number;
111
76
  calcSemitoneOffset(channel: any): number;
@@ -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;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAcC;IAED;;;;;;;;;;;;;;;;;;QAUC;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,sDAGC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIAmDC;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;AAx/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;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"}