@marmooo/midy 0.0.7 → 0.0.9

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,
@@ -78,13 +78,13 @@ class MidyGM2 {
78
78
  configurable: true,
79
79
  writable: true,
80
80
  value: 0
81
- });
81
+ }); // cb
82
82
  Object.defineProperty(this, "masterCoarseTuning", {
83
83
  enumerable: true,
84
84
  configurable: true,
85
85
  writable: true,
86
86
  value: 0
87
- });
87
+ }); // cb
88
88
  Object.defineProperty(this, "mono", {
89
89
  enumerable: true,
90
90
  configurable: true,
@@ -187,7 +187,19 @@ class MidyGM2 {
187
187
  writable: true,
188
188
  value: []
189
189
  });
190
+ Object.defineProperty(this, "defaultOptions", {
191
+ enumerable: true,
192
+ configurable: true,
193
+ writable: true,
194
+ value: {
195
+ reverbAlgorithm: (audioContext) => {
196
+ // return this.createConvolutionReverb(audioContext);
197
+ return this.createSchroederReverb(audioContext);
198
+ },
199
+ }
200
+ });
190
201
  this.audioContext = audioContext;
202
+ this.options = { ...this.defaultOptions, ...options };
191
203
  this.masterGain = new GainNode(audioContext);
192
204
  this.masterGain.connect(audioContext.destination);
193
205
  this.channels = this.createChannels(audioContext);
@@ -228,23 +240,18 @@ class MidyGM2 {
228
240
  this.totalTime = this.calcTotalTime();
229
241
  }
230
242
  setChannelAudioNodes(audioContext) {
231
- const { gainLeft, gainRight } = this.panToGain(MidyGM2.channelSettings.pan);
243
+ const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
232
244
  const gainL = new GainNode(audioContext, { gain: gainLeft });
233
245
  const gainR = new GainNode(audioContext, { gain: gainRight });
234
246
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
235
247
  gainL.connect(merger, 0, 0);
236
248
  gainR.connect(merger, 0, 1);
237
- merger.connect(this.masterGain);
238
- const reverbEffect = this.createReverbEffect(audioContext);
249
+ const reverbEffect = this.options.reverbAlgorithm(audioContext);
239
250
  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
251
  return {
246
252
  gainL,
247
253
  gainR,
254
+ merger,
248
255
  reverbEffect,
249
256
  chorusEffect,
250
257
  };
@@ -252,13 +259,13 @@ class MidyGM2 {
252
259
  createChannels(audioContext) {
253
260
  const channels = Array.from({ length: 16 }, () => {
254
261
  return {
255
- ...MidyGM2.channelSettings,
256
- ...MidyGM2.effectSettings,
262
+ ...this.constructor.channelSettings,
263
+ ...this.constructor.effectSettings,
257
264
  ...this.setChannelAudioNodes(audioContext),
258
265
  scheduledNotes: new Map(),
259
266
  sostenutoNotes: new Map(),
260
267
  channelPressure: {
261
- ...MidyGM2.controllerDestinationSettings,
268
+ ...this.constructor.controllerDestinationSettings,
262
269
  },
263
270
  };
264
271
  });
@@ -586,8 +593,12 @@ class MidyGM2 {
586
593
  }
587
594
  return noteList[0];
588
595
  }
589
- createReverbEffect(audioContext, options = {}) {
596
+ createConvolutionReverb(audioContext, options = {}) {
590
597
  const { decay = 0.8, preDecay = 0, } = options;
598
+ const input = new GainNode(audioContext);
599
+ const output = new GainNode(audioContext);
600
+ const dryGain = new GainNode(audioContext);
601
+ const wetGain = new GainNode(audioContext);
591
602
  const sampleRate = audioContext.sampleRate;
592
603
  const length = sampleRate * decay;
593
604
  const impulse = new AudioBuffer({
@@ -601,27 +612,82 @@ class MidyGM2 {
601
612
  for (let i = 0; i < preDecayLength; i++) {
602
613
  channelData[i] = Math.random() * 2 - 1;
603
614
  }
615
+ const attenuationFactor = 1 / (sampleRate * decay);
604
616
  for (let i = preDecayLength; i < length; i++) {
605
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
617
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
606
618
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
607
619
  }
608
620
  }
609
621
  const convolverNode = new ConvolverNode(audioContext, {
610
622
  buffer: impulse,
611
623
  });
612
- const dryGain = new GainNode(audioContext);
613
- const wetGain = new GainNode(audioContext);
624
+ input.connect(convolverNode);
614
625
  convolverNode.connect(wetGain);
626
+ wetGain.connect(output);
627
+ dryGain.connect(output);
615
628
  return {
616
- convolverNode,
629
+ input,
630
+ output,
617
631
  dryGain,
618
632
  wetGain,
633
+ convolverNode,
619
634
  };
620
635
  }
636
+ createCombFilter(audioContext, input, delay, feedback) {
637
+ const delayNode = new DelayNode(audioContext, {
638
+ maxDelayTime: delay,
639
+ delayTime: delay,
640
+ });
641
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
642
+ input.connect(delayNode);
643
+ delayNode.connect(feedbackGain);
644
+ feedbackGain.connect(delayNode);
645
+ return delayNode;
646
+ }
647
+ createAllpassFilter(audioContext, input, delay, feedback) {
648
+ const delayNode = new DelayNode(audioContext, {
649
+ maxDelayTime: delay,
650
+ delayTime: delay,
651
+ });
652
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
653
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
654
+ input.connect(delayNode);
655
+ delayNode.connect(feedbackGain);
656
+ feedbackGain.connect(delayNode);
657
+ delayNode.connect(passGain);
658
+ return passGain;
659
+ }
660
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
661
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
662
+ createSchroederReverb(audioContext, options = {}) {
663
+ const { combDelays = [0.31, 0.34, 0.37, 0.40], combFeedbacks = [0.86, 0.87, 0.88, 0.89], allpassDelays = [0.02, 0.05], allpassFeedbacks = [0.7, 0.7], mix = 0.5, } = options;
664
+ const input = new GainNode(audioContext);
665
+ const output = new GainNode(audioContext);
666
+ const mergerGain = new GainNode(audioContext, {
667
+ gain: 1 / (combDelays.length * 2),
668
+ });
669
+ const dryGain = new GainNode(audioContext, { gain: 1 - mix });
670
+ const wetGain = new GainNode(audioContext, { gain: mix });
671
+ for (let i = 0; i < combDelays.length; i++) {
672
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
673
+ comb.connect(mergerGain);
674
+ }
675
+ const allpasses = [];
676
+ for (let i = 0; i < allpassDelays.length; i++) {
677
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
678
+ allpasses.push(allpass);
679
+ }
680
+ allpasses.at(-1).connect(wetGain);
681
+ input.connect(dryGain);
682
+ dryGain.connect(output);
683
+ wetGain.connect(output);
684
+ return { input, output, dryGain, wetGain };
685
+ }
621
686
  createChorusEffect(audioContext, options = {}) {
622
687
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
623
688
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
624
689
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
690
+ const output = new GainNode(audioContext);
625
691
  const chorusGains = [];
626
692
  const delayNodes = [];
627
693
  const baseGain = 1 / chorusCount;
@@ -631,50 +697,47 @@ class MidyGM2 {
631
697
  const delayNode = new DelayNode(audioContext, {
632
698
  maxDelayTime: delayTime,
633
699
  });
634
- delayNodes.push(delayNode);
635
700
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
701
+ delayNodes.push(delayNode);
636
702
  chorusGains.push(chorusGain);
637
- lfo.connect(lfoGain);
638
703
  lfoGain.connect(delayNode.delayTime);
639
704
  delayNode.connect(chorusGain);
705
+ chorusGain.connect(output);
640
706
  }
707
+ lfo.connect(lfoGain);
708
+ lfo.start();
641
709
  return {
642
710
  lfo,
643
711
  lfoGain,
644
712
  delayNodes,
645
713
  chorusGains,
714
+ output,
646
715
  };
647
716
  }
648
- connectNoteEffects(channel, gainNode) {
717
+ connectEffects(channel, gainNode) {
718
+ gainNode.connect(channel.merger);
649
719
  if (channel.reverb === 0) {
650
720
  if (channel.chorus === 0) { // no effect
651
- gainNode.connect(channel.gainL);
652
- gainNode.connect(channel.gainR);
721
+ channel.merger.connect(this.masterGain);
653
722
  }
654
723
  else { // chorus
655
724
  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);
725
+ channel.merger.connect(delayNode);
661
726
  });
727
+ channel.chorusEffect.output.connect(this.masterGain);
662
728
  }
663
729
  }
664
730
  else {
665
731
  if (channel.chorus === 0) { // reverb
666
- gainNode.connect(channel.reverbEffect.convolverNode);
667
- gainNode.connect(channel.reverbEffect.dryGain);
732
+ channel.merger.connect(channel.reverbEffect.input);
733
+ channel.reverbEffect.output.connect(this.masterGain);
668
734
  }
669
735
  else { // reverb + chorus
670
- gainNode.connect(channel.reverbEffect.convolverNode);
671
- gainNode.connect(channel.reverbEffect.dryGain);
672
736
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
673
- gainNode.connect(delayNode);
674
- });
675
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
676
- chorusGain.connect(channel.reverbEffect.convolverNode);
737
+ channel.merger.connect(delayNode);
677
738
  });
739
+ channel.merger.connect(channel.reverbEffect.input);
740
+ channel.reverbEffect.output.connect(this.masterGain);
678
741
  }
679
742
  }
680
743
  }
@@ -745,7 +808,7 @@ class MidyGM2 {
745
808
  startModulation(channel, note, time) {
746
809
  const { instrumentKey } = note;
747
810
  note.modLFOGain = new GainNode(this.audioContext, {
748
- gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
811
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
749
812
  });
750
813
  note.modLFO = new OscillatorNode(this.audioContext, {
751
814
  frequency: this.centToHz(instrumentKey.freqModLFO),
@@ -797,7 +860,7 @@ class MidyGM2 {
797
860
  if (!instrumentKey)
798
861
  return;
799
862
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
800
- this.connectNoteEffects(channel, note.gainNode);
863
+ this.connectEffects(channel, note.gainNode);
801
864
  if (channel.sostenutoPedal) {
802
865
  channel.sostenutoNotes.set(noteNumber, note);
803
866
  }
@@ -821,42 +884,48 @@ class MidyGM2 {
821
884
  return;
822
885
  if (!channel.scheduledNotes.has(noteNumber))
823
886
  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)
887
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
888
+ for (let i = 0; i < scheduledNotes.length; i++) {
889
+ const note = scheduledNotes[i];
890
+ if (!note)
828
891
  continue;
829
- if (targetNote.ending)
892
+ if (note.ending)
830
893
  continue;
831
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
832
894
  const velocityRate = (velocity + 127) / 127;
833
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
834
- gainNode.gain.cancelScheduledValues(stopTime);
835
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
895
+ const volEndTime = stopTime +
896
+ note.instrumentKey.volRelease * velocityRate;
897
+ note.gainNode.gain
898
+ .cancelScheduledValues(stopTime)
899
+ .linearRampToValueAtTime(0, volEndTime);
836
900
  const maxFreq = this.audioContext.sampleRate / 2;
837
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
901
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
838
902
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
839
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
840
- filterNode.frequency
903
+ const modEndTime = stopTime +
904
+ note.instrumentKey.modRelease * velocityRate;
905
+ note.filterNode.frequency
841
906
  .cancelScheduledValues(stopTime)
842
907
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
843
- targetNote.ending = true;
908
+ note.ending = true;
844
909
  this.scheduleTask(() => {
845
- bufferSource.loop = false;
910
+ note.bufferSource.loop = false;
846
911
  }, stopTime);
847
912
  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();
913
+ note.bufferSource.onended = () => {
914
+ scheduledNotes[i] = null;
915
+ note.bufferSource.disconnect();
916
+ note.filterNode.disconnect();
917
+ note.gainNode.disconnect();
918
+ if (note.modLFOGain)
919
+ note.modLFOGain.disconnect();
920
+ if (note.vibLFOGain)
921
+ note.vibLFOGain.disconnect();
922
+ if (note.modLFO)
923
+ note.modLFO.stop();
924
+ if (note.vibLFO)
925
+ note.vibLFO.stop();
857
926
  resolve();
858
927
  };
859
- bufferSource.stop(volEndTime);
928
+ note.bufferSource.stop(volEndTime);
860
929
  });
861
930
  }
862
931
  }
@@ -954,7 +1023,7 @@ class MidyGM2 {
954
1023
  case 5:
955
1024
  return this.setPortamentoTime(channelNumber, value);
956
1025
  case 6:
957
- return this.setDataEntry(channelNumber, value, true);
1026
+ return this.dataEntryMSB(channelNumber, value);
958
1027
  case 7:
959
1028
  return this.setVolume(channelNumber, value);
960
1029
  case 10:
@@ -964,7 +1033,7 @@ class MidyGM2 {
964
1033
  case 32:
965
1034
  return this.setBankLSB(channelNumber, value);
966
1035
  case 38:
967
- return this.setDataEntry(channelNumber, value, false);
1036
+ return this.dataEntryLSB(channelNumber, value);
968
1037
  case 64:
969
1038
  return this.setSustainPedal(channelNumber, value);
970
1039
  case 65:
@@ -974,9 +1043,9 @@ class MidyGM2 {
974
1043
  case 67:
975
1044
  return this.setSoftPedal(channelNumber, value);
976
1045
  case 91:
977
- return this.setReverb(channelNumber, value);
1046
+ return this.setReverbSendLevel(channelNumber, value);
978
1047
  case 93:
979
- return this.setChorus(channelNumber, value);
1048
+ return this.setChorusSendLevel(channelNumber, value);
980
1049
  case 100:
981
1050
  return this.setRPNLSB(channelNumber, value);
982
1051
  case 101:
@@ -1002,22 +1071,24 @@ class MidyGM2 {
1002
1071
  setBankMSB(channelNumber, msb) {
1003
1072
  this.channels[channelNumber].bankMSB = msb;
1004
1073
  }
1005
- setModulation(channelNumber, modulation) {
1074
+ updateModulation(channel) {
1006
1075
  const now = this.audioContext.currentTime;
1007
- const channel = this.channels[channelNumber];
1008
- channel.modulation = (modulation / 127) *
1009
- (channel.modulationDepthRange * 100);
1010
1076
  const activeNotes = this.getActiveNotes(channel, now);
1011
1077
  activeNotes.forEach((activeNote) => {
1012
1078
  if (activeNote.modLFO) {
1013
- activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
1014
- channel.modulation, now);
1079
+ const { gainNode, instrumentKey } = activeNote;
1080
+ gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
1015
1081
  }
1016
1082
  else {
1017
1083
  this.startModulation(channel, activeNote, now);
1018
1084
  }
1019
1085
  });
1020
1086
  }
1087
+ setModulation(channelNumber, modulation) {
1088
+ const channel = this.channels[channelNumber];
1089
+ channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1090
+ this.updateModulation(channel);
1091
+ }
1021
1092
  setPortamentoTime(channelNumber, portamentoTime) {
1022
1093
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
1023
1094
  }
@@ -1046,6 +1117,10 @@ class MidyGM2 {
1046
1117
  setBankLSB(channelNumber, lsb) {
1047
1118
  this.channels[channelNumber].bankLSB = lsb;
1048
1119
  }
1120
+ dataEntryLSB(channelNumber, value) {
1121
+ this.channels[channelNumber].dataLSB = value;
1122
+ this.handleRPN(channelNumber);
1123
+ }
1049
1124
  updateChannelGain(channel) {
1050
1125
  const now = this.audioContext.currentTime;
1051
1126
  const volume = channel.volume * channel.expression;
@@ -1067,7 +1142,7 @@ class MidyGM2 {
1067
1142
  setPortamento(channelNumber, value) {
1068
1143
  this.channels[channelNumber].portamento = value >= 64;
1069
1144
  }
1070
- setReverb(channelNumber, reverb) {
1145
+ setReverbSendLevel(channelNumber, reverb) {
1071
1146
  const now = this.audioContext.currentTime;
1072
1147
  const channel = this.channels[channelNumber];
1073
1148
  const reverbEffect = channel.reverbEffect;
@@ -1077,7 +1152,7 @@ class MidyGM2 {
1077
1152
  reverbEffect.wetGain.gain.cancelScheduledValues(now);
1078
1153
  reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1079
1154
  }
1080
- setChorus(channelNumber, chorus) {
1155
+ setChorusSendLevel(channelNumber, chorus) {
1081
1156
  const channel = this.channels[channelNumber];
1082
1157
  channel.chorus = chorus / 127;
1083
1158
  channel.chorusEffect.lfoGain = channel.chorus;
@@ -1125,21 +1200,21 @@ class MidyGM2 {
1125
1200
  channel.dataMSB = minMSB;
1126
1201
  }
1127
1202
  }
1128
- handleRPN(channelNumber, value) {
1203
+ handleRPN(channelNumber) {
1129
1204
  const channel = this.channels[channelNumber];
1130
1205
  const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
1131
1206
  switch (rpn) {
1132
1207
  case 0:
1133
- channel.dataLSB += value;
1134
- this.handlePitchBendRangeMessage(channelNumber);
1208
+ this.handlePitchBendRangeRPN(channelNumber);
1135
1209
  break;
1136
1210
  case 1:
1137
- channel.dataLSB += value;
1138
- this.handleFineTuningMessage(channelNumber);
1211
+ this.handleFineTuningRPN(channelNumber);
1139
1212
  break;
1140
1213
  case 2:
1141
- channel.dataMSB += value;
1142
- this.handleCoarseTuningMessage(channelNumber);
1214
+ this.handleCoarseTuningRPN(channelNumber);
1215
+ break;
1216
+ case 5:
1217
+ this.handleModulationDepthRangeRPN(channelNumber);
1143
1218
  break;
1144
1219
  default:
1145
1220
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -1151,9 +1226,8 @@ class MidyGM2 {
1151
1226
  setRPNLSB(channelNumber, value) {
1152
1227
  this.channels[channelNumber].rpnLSB = value;
1153
1228
  }
1154
- setDataEntry(channelNumber, value, isMSB) {
1155
- const channel = this.channels[channelNumber];
1156
- isMSB ? channel.dataMSB = value : channel.dataLSB = value;
1229
+ dataEntryMSB(channelNumber, value) {
1230
+ this.channels[channelNumber].dataMSB = value;
1157
1231
  this.handleRPN(channelNumber);
1158
1232
  }
1159
1233
  updateDetune(channel, detuneChange) {
@@ -1167,7 +1241,7 @@ class MidyGM2 {
1167
1241
  .setValueAtTime(detune, now);
1168
1242
  });
1169
1243
  }
1170
- handlePitchBendRangeMessage(channelNumber) {
1244
+ handlePitchBendRangeRPN(channelNumber) {
1171
1245
  const channel = this.channels[channelNumber];
1172
1246
  this.limitData(channel, 0, 127, 0, 99);
1173
1247
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
@@ -1181,7 +1255,7 @@ class MidyGM2 {
1181
1255
  channel.pitchBend * 100;
1182
1256
  this.updateDetune(channel, detuneChange);
1183
1257
  }
1184
- handleFineTuningMessage(channelNumber) {
1258
+ handleFineTuningRPN(channelNumber) {
1185
1259
  const channel = this.channels[channelNumber];
1186
1260
  this.limitData(channel, 0, 127, 0, 127);
1187
1261
  const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
@@ -1189,9 +1263,12 @@ class MidyGM2 {
1189
1263
  }
1190
1264
  setFineTuning(channelNumber, fineTuning) {
1191
1265
  const channel = this.channels[channelNumber];
1266
+ const prevFineTuning = channel.fineTuning;
1192
1267
  channel.fineTuning = fineTuning;
1268
+ const detuneChange = channel.fineTuning - prevFineTuning;
1269
+ this.updateDetune(channel, detuneChange);
1193
1270
  }
1194
- handleCoarseTuningMessage(channelNumber) {
1271
+ handleCoarseTuningRPN(channelNumber) {
1195
1272
  const channel = this.channels[channelNumber];
1196
1273
  this.limitDataMSB(channel, 0, 127);
1197
1274
  const coarseTuning = channel.dataMSB - 64;
@@ -1199,7 +1276,22 @@ class MidyGM2 {
1199
1276
  }
1200
1277
  setCoarseTuning(channelNumber, coarseTuning) {
1201
1278
  const channel = this.channels[channelNumber];
1202
- channel.fineTuning = coarseTuning;
1279
+ const prevCoarseTuning = channel.coarseTuning;
1280
+ channel.coarseTuning = coarseTuning;
1281
+ const detuneChange = channel.coarseTuning - prevCoarseTuning;
1282
+ this.updateDetune(channel, detuneChange);
1283
+ }
1284
+ handleModulationDepthRangeRPN(channelNumber) {
1285
+ const channel = this.channels[channelNumber];
1286
+ this.limitData(channel, 0, 127, 0, 127);
1287
+ const modulationDepthRange = dataMSB + dataLSB / 128;
1288
+ this.setModulationDepthRange(channelNumber, modulationDepthRange);
1289
+ }
1290
+ setModulationDepthRange(channelNumber, modulationDepthRange) {
1291
+ const channel = this.channels[channelNumber];
1292
+ channel.modulationDepthRange = modulationDepthRange;
1293
+ channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1294
+ this.updateModulation(channel);
1203
1295
  }
1204
1296
  allSoundOff(channelNumber) {
1205
1297
  const now = this.audioContext.currentTime;
@@ -1290,9 +1382,9 @@ class MidyGM2 {
1290
1382
  switch (data[3]) {
1291
1383
  case 1:
1292
1384
  return this.handleMasterVolumeSysEx(data);
1293
- case 3:
1385
+ case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1294
1386
  return this.handleMasterFineTuningSysEx(data);
1295
- case 4:
1387
+ case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1296
1388
  return this.handleMasterCoarseTuningSysEx(data);
1297
1389
  // case 5: // TODO: Global Parameter Control
1298
1390
  default:
@@ -1404,7 +1496,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1404
1496
  value: {
1405
1497
  currentBufferSource: null,
1406
1498
  volume: 100 / 127,
1407
- pan: 0,
1499
+ pan: 64,
1408
1500
  portamentoTime: 0,
1409
1501
  reverb: 0,
1410
1502
  chorus: 0,
@@ -1415,9 +1507,9 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1415
1507
  dataLSB: 0,
1416
1508
  program: 0,
1417
1509
  pitchBend: 0,
1418
- fineTuning: 0,
1419
- coarseTuning: 0,
1420
- modulationDepthRange: 0.5,
1510
+ fineTuning: 0, // cb
1511
+ coarseTuning: 0, // cb
1512
+ modulationDepthRange: 0.5, // cb
1421
1513
  }
1422
1514
  });
1423
1515
  Object.defineProperty(MidyGM2, "effectSettings", {
@@ -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;
@@ -124,6 +89,7 @@ export class MidyGMLite {
124
89
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
125
90
  setPitchBend(channelNumber: any, pitchBend: any): void;
126
91
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
92
+ updateModulation(channel: any): void;
127
93
  setModulation(channelNumber: any, modulation: any): void;
128
94
  setVolume(channelNumber: any, volume: any): void;
129
95
  panToGain(pan: any): {
@@ -132,14 +98,15 @@ export class MidyGMLite {
132
98
  };
133
99
  setPan(channelNumber: any, pan: any): void;
134
100
  setExpression(channelNumber: any, expression: any): void;
101
+ dataEntryLSB(channelNumber: any, value: any): void;
135
102
  updateChannelGain(channel: any): void;
136
103
  setSustainPedal(channelNumber: any, value: any): void;
137
104
  handleRPN(channelNumber: any): void;
138
105
  setRPNMSB(channelNumber: any, value: any): void;
139
106
  setRPNLSB(channelNumber: any, value: any): void;
140
- setDataEntry(channelNumber: any, value: any, isMSB: any): void;
107
+ dataEntryMSB(channelNumber: any, value: any): void;
141
108
  updateDetune(channel: any, detuneChange: any): void;
142
- handlePitchBendRangeMessage(channelNumber: any): void;
109
+ handlePitchBendRangeRPN(channelNumber: any): void;
143
110
  setPitchBendRange(channelNumber: any, pitchBendRange: any): void;
144
111
  allSoundOff(channelNumber: any): any[];
145
112
  resetAllControllers(channelNumber: any): void;
@@ -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,yDAiBC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,+DAIC;IAED,oDAUC;IAED,sDAKC;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;AAl/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"}