@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-GM2.js CHANGED
@@ -51,7 +51,7 @@ class Note {
51
51
  }
52
52
  }
53
53
  export class MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
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 MidyGM2 {
225
262
  this.totalTime = this.calcTotalTime();
226
263
  }
227
264
  setChannelAudioNodes(audioContext) {
228
- const { gainLeft, gainRight } = this.panToGain(MidyGM2.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,13 +281,13 @@ export class MidyGM2 {
249
281
  createChannels(audioContext) {
250
282
  const channels = Array.from({ length: 16 }, () => {
251
283
  return {
252
- ...MidyGM2.channelSettings,
253
- ...MidyGM2.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
  channelPressure: {
258
- ...MidyGM2.controllerDestinationSettings,
290
+ ...this.constructor.controllerDestinationSettings,
259
291
  },
260
292
  };
261
293
  });
@@ -583,8 +615,7 @@ export class MidyGM2 {
583
615
  }
584
616
  return noteList[0];
585
617
  }
586
- createReverbEffect(audioContext, options = {}) {
587
- const { decay = 0.8, preDecay = 0, } = options;
618
+ createConvolutionReverbImpulse(audioContext, decay, preDecay) {
588
619
  const sampleRate = audioContext.sampleRate;
589
620
  const length = sampleRate * decay;
590
621
  const impulse = new AudioBuffer({
@@ -598,27 +629,85 @@ export class MidyGM2 {
598
629
  for (let i = 0; i < preDecayLength; i++) {
599
630
  channelData[i] = Math.random() * 2 - 1;
600
631
  }
632
+ const attenuationFactor = 1 / (sampleRate * decay);
601
633
  for (let i = preDecayLength; i < length; i++) {
602
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
634
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
603
635
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
604
636
  }
605
637
  }
638
+ return impulse;
639
+ }
640
+ createConvolutionReverb(audioContext, impulse) {
641
+ const output = new GainNode(audioContext);
606
642
  const convolverNode = new ConvolverNode(audioContext, {
607
643
  buffer: impulse,
608
644
  });
609
- const dryGain = new GainNode(audioContext);
610
- const wetGain = new GainNode(audioContext);
611
- convolverNode.connect(wetGain);
645
+ convolverNode.connect(output);
612
646
  return {
647
+ input: convolverNode,
648
+ output,
613
649
  convolverNode,
614
- dryGain,
615
- wetGain,
616
650
  };
617
651
  }
652
+ createCombFilter(audioContext, input, delay, feedback) {
653
+ const delayNode = new DelayNode(audioContext, {
654
+ maxDelayTime: delay,
655
+ delayTime: delay,
656
+ });
657
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
658
+ input.connect(delayNode);
659
+ delayNode.connect(feedbackGain);
660
+ feedbackGain.connect(delayNode);
661
+ return delayNode;
662
+ }
663
+ createAllpassFilter(audioContext, input, delay, feedback) {
664
+ const delayNode = new DelayNode(audioContext, {
665
+ maxDelayTime: delay,
666
+ delayTime: delay,
667
+ });
668
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
669
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
670
+ input.connect(delayNode);
671
+ delayNode.connect(feedbackGain);
672
+ feedbackGain.connect(delayNode);
673
+ delayNode.connect(passGain);
674
+ return passGain;
675
+ }
676
+ generateDistributedArray(center, count, varianceRatio = 0.1, randomness = 0.05) {
677
+ const variance = center * varianceRatio;
678
+ const array = new Array(count);
679
+ for (let i = 0; i < count; i++) {
680
+ const fraction = i / (count - 1 || 1);
681
+ const value = center - variance + fraction * 2 * variance;
682
+ array[i] = value * (1 - (Math.random() * 2 - 1) * randomness);
683
+ }
684
+ return array;
685
+ }
686
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
687
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
688
+ createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
689
+ const input = new GainNode(audioContext);
690
+ const output = new GainNode(audioContext);
691
+ const mergerGain = new GainNode(audioContext, {
692
+ gain: 1 / (combDelays.length * 2),
693
+ });
694
+ for (let i = 0; i < combDelays.length; i++) {
695
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
696
+ comb.connect(mergerGain);
697
+ }
698
+ const allpasses = [];
699
+ for (let i = 0; i < allpassDelays.length; i++) {
700
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
701
+ allpasses.push(allpass);
702
+ }
703
+ allpasses.at(-1).connect(output);
704
+ return { input, output };
705
+ }
618
706
  createChorusEffect(audioContext, options = {}) {
619
707
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
620
708
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
621
709
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
710
+ const output = new GainNode(audioContext);
622
711
  const chorusGains = [];
623
712
  const delayNodes = [];
624
713
  const baseGain = 1 / chorusCount;
@@ -628,50 +717,45 @@ export class MidyGM2 {
628
717
  const delayNode = new DelayNode(audioContext, {
629
718
  maxDelayTime: delayTime,
630
719
  });
631
- delayNodes.push(delayNode);
632
720
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
721
+ delayNodes.push(delayNode);
633
722
  chorusGains.push(chorusGain);
634
- lfo.connect(lfoGain);
635
723
  lfoGain.connect(delayNode.delayTime);
636
724
  delayNode.connect(chorusGain);
725
+ chorusGain.connect(output);
637
726
  }
727
+ lfo.connect(lfoGain);
728
+ lfo.start();
638
729
  return {
639
730
  lfo,
640
731
  lfoGain,
641
732
  delayNodes,
642
733
  chorusGains,
734
+ output,
643
735
  };
644
736
  }
645
- connectNoteEffects(channel, gainNode) {
646
- if (channel.reverb === 0) {
647
- if (channel.chorus === 0) { // no effect
648
- gainNode.connect(channel.gainL);
649
- gainNode.connect(channel.gainR);
650
- }
651
- else { // chorus
737
+ connectEffects(channel, gainNode) {
738
+ gainNode.connect(channel.merger);
739
+ channel.merger.connect(this.masterGain);
740
+ if (channel.reverbSendLevel === 0) {
741
+ if (channel.chorusSendLevel !== 0) { // chorus
652
742
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
653
- gainNode.connect(delayNode);
654
- });
655
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
656
- chorusGain.connect(channel.gainL);
657
- chorusGain.connect(channel.gainR);
743
+ channel.merger.connect(delayNode);
658
744
  });
745
+ channel.chorusEffect.output.connect(this.masterGain);
659
746
  }
660
747
  }
661
748
  else {
662
- if (channel.chorus === 0) { // reverb
663
- gainNode.connect(channel.reverbEffect.convolverNode);
664
- gainNode.connect(channel.reverbEffect.dryGain);
749
+ if (channel.chorusSendLevel === 0) { // reverb
750
+ channel.merger.connect(channel.reverbEffect.input);
751
+ channel.reverbEffect.output.connect(this.masterGain);
665
752
  }
666
753
  else { // reverb + chorus
667
- gainNode.connect(channel.reverbEffect.convolverNode);
668
- gainNode.connect(channel.reverbEffect.dryGain);
669
754
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
670
- gainNode.connect(delayNode);
671
- });
672
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
673
- chorusGain.connect(channel.reverbEffect.convolverNode);
755
+ channel.merger.connect(delayNode);
674
756
  });
757
+ channel.merger.connect(channel.reverbEffect.input);
758
+ channel.reverbEffect.output.connect(this.masterGain);
675
759
  }
676
760
  }
677
761
  }
@@ -742,7 +826,7 @@ export class MidyGM2 {
742
826
  startModulation(channel, note, time) {
743
827
  const { instrumentKey } = note;
744
828
  note.modLFOGain = new GainNode(this.audioContext, {
745
- gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
829
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
746
830
  });
747
831
  note.modLFO = new OscillatorNode(this.audioContext, {
748
832
  frequency: this.centToHz(instrumentKey.freqModLFO),
@@ -794,7 +878,7 @@ export class MidyGM2 {
794
878
  if (!instrumentKey)
795
879
  return;
796
880
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
797
- this.connectNoteEffects(channel, note.gainNode);
881
+ this.connectEffects(channel, note.gainNode);
798
882
  if (channel.sostenutoPedal) {
799
883
  channel.sostenutoNotes.set(noteNumber, note);
800
884
  }
@@ -818,42 +902,48 @@ export class MidyGM2 {
818
902
  return;
819
903
  if (!channel.scheduledNotes.has(noteNumber))
820
904
  return;
821
- const targetNotes = channel.scheduledNotes.get(noteNumber);
822
- for (let i = 0; i < targetNotes.length; i++) {
823
- const targetNote = targetNotes[i];
824
- if (!targetNote)
905
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
906
+ for (let i = 0; i < scheduledNotes.length; i++) {
907
+ const note = scheduledNotes[i];
908
+ if (!note)
825
909
  continue;
826
- if (targetNote.ending)
910
+ if (note.ending)
827
911
  continue;
828
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
829
912
  const velocityRate = (velocity + 127) / 127;
830
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
831
- gainNode.gain.cancelScheduledValues(stopTime);
832
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
913
+ const volEndTime = stopTime +
914
+ note.instrumentKey.volRelease * velocityRate;
915
+ note.gainNode.gain
916
+ .cancelScheduledValues(stopTime)
917
+ .linearRampToValueAtTime(0, volEndTime);
833
918
  const maxFreq = this.audioContext.sampleRate / 2;
834
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
919
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
835
920
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
836
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
837
- filterNode.frequency
921
+ const modEndTime = stopTime +
922
+ note.instrumentKey.modRelease * velocityRate;
923
+ note.filterNode.frequency
838
924
  .cancelScheduledValues(stopTime)
839
925
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
840
- targetNote.ending = true;
926
+ note.ending = true;
841
927
  this.scheduleTask(() => {
842
- bufferSource.loop = false;
928
+ note.bufferSource.loop = false;
843
929
  }, stopTime);
844
930
  return new Promise((resolve) => {
845
- bufferSource.onended = () => {
846
- targetNotes[i] = null;
847
- bufferSource.disconnect(0);
848
- filterNode.disconnect(0);
849
- gainNode.disconnect(0);
850
- if (modLFOGain)
851
- modLFOGain.disconnect(0);
852
- if (modLFO)
853
- modLFO.stop();
931
+ note.bufferSource.onended = () => {
932
+ scheduledNotes[i] = null;
933
+ note.bufferSource.disconnect();
934
+ note.filterNode.disconnect();
935
+ note.gainNode.disconnect();
936
+ if (note.modLFOGain)
937
+ note.modLFOGain.disconnect();
938
+ if (note.vibLFOGain)
939
+ note.vibLFOGain.disconnect();
940
+ if (note.modLFO)
941
+ note.modLFO.stop();
942
+ if (note.vibLFO)
943
+ note.vibLFO.stop();
854
944
  resolve();
855
945
  };
856
- bufferSource.stop(volEndTime);
946
+ note.bufferSource.stop(volEndTime);
857
947
  });
858
948
  }
859
949
  }
@@ -1067,23 +1157,22 @@ export class MidyGM2 {
1067
1157
  this.releaseSustainPedal(channelNumber, value);
1068
1158
  }
1069
1159
  }
1160
+ // TODO
1070
1161
  setPortamento(channelNumber, value) {
1071
1162
  this.channels[channelNumber].portamento = value >= 64;
1072
1163
  }
1073
- setReverbSendLevel(channelNumber, reverb) {
1164
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1074
1165
  const now = this.audioContext.currentTime;
1075
1166
  const channel = this.channels[channelNumber];
1076
1167
  const reverbEffect = channel.reverbEffect;
1077
- channel.reverb = reverb / 127 * this.reverbFactor;
1078
- reverbEffect.dryGain.gain.cancelScheduledValues(now);
1079
- reverbEffect.dryGain.gain.setValueAtTime(1 - channel.reverb, now);
1080
- reverbEffect.wetGain.gain.cancelScheduledValues(now);
1081
- reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1168
+ channel.reverbSendLevel = reverbSendLevel / 127;
1169
+ reverbEffect.output.gain.cancelScheduledValues(now);
1170
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1082
1171
  }
1083
- setChorusSendLevel(channelNumber, chorus) {
1172
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1084
1173
  const channel = this.channels[channelNumber];
1085
- channel.chorus = chorus / 127;
1086
- channel.chorusEffect.lfoGain = channel.chorus;
1174
+ channel.chorusSendLevel = chorusSendLevel / 127;
1175
+ channel.chorusEffect.lfoGain = channel.chorusSendLevel;
1087
1176
  }
1088
1177
  setSostenutoPedal(channelNumber, value) {
1089
1178
  const isOn = value >= 64;
@@ -1279,11 +1368,11 @@ export class MidyGM2 {
1279
1368
  this.GM2SystemOn();
1280
1369
  break;
1281
1370
  default:
1282
- console.warn(`Unsupported Exclusive Message ${data}`);
1371
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1283
1372
  }
1284
1373
  break;
1285
1374
  default:
1286
- console.warn(`Unsupported Exclusive Message ${data}`);
1375
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1287
1376
  }
1288
1377
  }
1289
1378
  GM1SystemOn() {
@@ -1314,9 +1403,10 @@ export class MidyGM2 {
1314
1403
  return this.handleMasterFineTuningSysEx(data);
1315
1404
  case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1316
1405
  return this.handleMasterCoarseTuningSysEx(data);
1317
- // case 5: // TODO: Global Parameter Control
1406
+ case 5:
1407
+ return this.handleGlobalParameterControl(data);
1318
1408
  default:
1319
- console.warn(`Unsupported Exclusive Message ${data}`);
1409
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1320
1410
  }
1321
1411
  break;
1322
1412
  case 8:
@@ -1325,7 +1415,7 @@ export class MidyGM2 {
1325
1415
  // // TODO
1326
1416
  // return this.handleScaleOctaveTuning1ByteFormat();
1327
1417
  default:
1328
- console.warn(`Unsupported Exclusive Message ${data}`);
1418
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1329
1419
  }
1330
1420
  break;
1331
1421
  case 9:
@@ -1337,7 +1427,7 @@ export class MidyGM2 {
1337
1427
  // // TODO
1338
1428
  // return this.setControlChange();
1339
1429
  default:
1340
- console.warn(`Unsupported Exclusive Message ${data}`);
1430
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1341
1431
  }
1342
1432
  break;
1343
1433
  case 10:
@@ -1346,11 +1436,11 @@ export class MidyGM2 {
1346
1436
  // // TODO
1347
1437
  // return this.handleKeyBasedInstrumentControl();
1348
1438
  default:
1349
- console.warn(`Unsupported Exclusive Message ${data}`);
1439
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1350
1440
  }
1351
1441
  break;
1352
1442
  default:
1353
- console.warn(`Unsupported Exclusive Message ${data}`);
1443
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1354
1444
  }
1355
1445
  }
1356
1446
  handleMasterVolumeSysEx(data) {
@@ -1391,8 +1481,124 @@ export class MidyGM2 {
1391
1481
  this.masterCoarseTuning = coarseTuning - 64;
1392
1482
  }
1393
1483
  }
1484
+ handleGlobalParameterControl(data) {
1485
+ if (data[5] === 1) {
1486
+ switch (data[6]) {
1487
+ case 1:
1488
+ return this.handleReverbParameter(data);
1489
+ case 2:
1490
+ return this.handleChorusParameter(data);
1491
+ default:
1492
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1493
+ }
1494
+ }
1495
+ else {
1496
+ console.warn(`Unsupported Global Parameter Control Message: ${data}`);
1497
+ }
1498
+ }
1499
+ handleReverbParameter(data) {
1500
+ switch (data[7]) {
1501
+ case 0:
1502
+ return this.setReverbType(data[8]);
1503
+ case 1:
1504
+ return this.setReverbTime(data[8]);
1505
+ }
1506
+ }
1507
+ setReverbType(type) {
1508
+ this.reverb.time = this.getReverbTimeFromType(type);
1509
+ this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1510
+ const { audioContext, channels, options } = this;
1511
+ for (let i = 0; i < channels.length; i++) {
1512
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1513
+ }
1514
+ }
1515
+ getReverbTimeFromType(type) {
1516
+ switch (type) {
1517
+ case 0:
1518
+ return this.getReverbTime(44);
1519
+ case 1:
1520
+ return this.getReverbTime(50);
1521
+ case 2:
1522
+ return this.getReverbTime(56);
1523
+ case 3:
1524
+ return this.getReverbTime(64);
1525
+ case 4:
1526
+ return this.getReverbTime(64);
1527
+ case 8:
1528
+ return this.getReverbTime(50);
1529
+ default:
1530
+ console.warn(`Unsupported Reverb Time: ${type}`);
1531
+ }
1532
+ }
1533
+ setReverbTime(value) {
1534
+ this.reverb.time = this.getReverbTime(value);
1535
+ const { audioContext, channels, options } = this;
1536
+ for (let i = 0; i < channels.length; i++) {
1537
+ channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1538
+ }
1539
+ }
1540
+ getReverbTime(value) {
1541
+ return Math.pow(Math.E, (value - 40) * 0.025);
1542
+ }
1543
+ // mean free path equation
1544
+ // https://repository.dl.itc.u-tokyo.ac.jp/record/8550/files/A31912.pdf
1545
+ // 江田和司, 拡散性制御に基づく室内音響設計に向けた音場解析に関する研究, 2015
1546
+ // V: room size (m^3)
1547
+ // S: room surface area (m^2)
1548
+ // meanFreePath = 4V / S (m)
1549
+ // delay estimation using mean free path
1550
+ // t: degree Celsius, generally used 20
1551
+ // c: speed of sound = 331.5 + 0.61t = 331.5 * 0.61 * 20 = 343.7 (m/s)
1552
+ // delay = meanFreePath / c (s)
1553
+ // feedback equation
1554
+ // RT60 means that the energy is reduced to Math.pow(10, -6).
1555
+ // Since energy is proportional to the square of the amplitude,
1556
+ // the amplitude is reduced to Math.pow(10, -3).
1557
+ // When this is done through n feedbacks,
1558
+ // Math.pow(feedback, n) = Math.pow(10, -3)
1559
+ // Math.pow(feedback, RT60 / delay) = Math.pow(10, -3)
1560
+ // RT60 / delay * Math.log10(feedback) = -3
1561
+ // RT60 = -3 * delay / Math.log10(feedback)
1562
+ // feedback = Math.pow(10, -3 * delay / RT60)
1563
+ // delay estimation using ideal feedback
1564
+ // A suitable average sound absorption coefficient is 0.18-0.28.
1565
+ // Since the structure of the hall is complex,
1566
+ // It would be easier to determine the delay based on the ideal feedback.
1567
+ // delay = -RT60 * Math.log10(feedback) / 3
1568
+ calcDelay(rt60, feedback) {
1569
+ return -rt60 * Math.log10(feedback) / 3;
1570
+ }
1571
+ handleChorusParameter(data) {
1572
+ switch (data[7]) {
1573
+ case 0:
1574
+ return this.setChorusType(data[8]);
1575
+ case 1:
1576
+ return this.setChorusModRate(data[8]);
1577
+ case 2:
1578
+ return this.setChorusModDepth(data[8]);
1579
+ case 3:
1580
+ return this.setChorusFeedback(data[8]);
1581
+ case 4:
1582
+ return this.setChorusSendToReverb(data[8]);
1583
+ }
1584
+ }
1585
+ setChorusType(type) {
1586
+ // TODO
1587
+ }
1588
+ setChorusModRate(value) {
1589
+ // TODO
1590
+ }
1591
+ setChorusModDepth(value) {
1592
+ // TODO
1593
+ }
1594
+ setChorusFeedback(value) {
1595
+ // TODO
1596
+ }
1597
+ setChorusSendToReverb(value) {
1598
+ // TODO
1599
+ }
1394
1600
  handleExclusiveMessage(data) {
1395
- console.warn(`Unsupported Exclusive Message ${data}`);
1601
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1396
1602
  }
1397
1603
  handleSysEx(data) {
1398
1604
  switch (data[0]) {
@@ -1425,8 +1631,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1425
1631
  volume: 100 / 127,
1426
1632
  pan: 64,
1427
1633
  portamentoTime: 0,
1428
- reverb: 0,
1429
- chorus: 0,
1634
+ reverbSendLevel: 0,
1635
+ chorusSendLevel: 0,
1430
1636
  bank: 121 * 128,
1431
1637
  bankMSB: 121,
1432
1638
  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"}