@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.
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,
@@ -75,13 +75,13 @@ export class MidyGM2 {
75
75
  configurable: true,
76
76
  writable: true,
77
77
  value: 0
78
- });
78
+ }); // cb
79
79
  Object.defineProperty(this, "masterCoarseTuning", {
80
80
  enumerable: true,
81
81
  configurable: true,
82
82
  writable: true,
83
83
  value: 0
84
- });
84
+ }); // cb
85
85
  Object.defineProperty(this, "mono", {
86
86
  enumerable: true,
87
87
  configurable: true,
@@ -184,7 +184,19 @@ export class MidyGM2 {
184
184
  writable: true,
185
185
  value: []
186
186
  });
187
+ Object.defineProperty(this, "defaultOptions", {
188
+ enumerable: true,
189
+ configurable: true,
190
+ writable: true,
191
+ value: {
192
+ reverbAlgorithm: (audioContext) => {
193
+ // return this.createConvolutionReverb(audioContext);
194
+ return this.createSchroederReverb(audioContext);
195
+ },
196
+ }
197
+ });
187
198
  this.audioContext = audioContext;
199
+ this.options = { ...this.defaultOptions, ...options };
188
200
  this.masterGain = new GainNode(audioContext);
189
201
  this.masterGain.connect(audioContext.destination);
190
202
  this.channels = this.createChannels(audioContext);
@@ -225,23 +237,18 @@ export class MidyGM2 {
225
237
  this.totalTime = this.calcTotalTime();
226
238
  }
227
239
  setChannelAudioNodes(audioContext) {
228
- const { gainLeft, gainRight } = this.panToGain(MidyGM2.channelSettings.pan);
240
+ const { gainLeft, gainRight } = this.panToGain(this.constructor.channelSettings.pan);
229
241
  const gainL = new GainNode(audioContext, { gain: gainLeft });
230
242
  const gainR = new GainNode(audioContext, { gain: gainRight });
231
243
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
232
244
  gainL.connect(merger, 0, 0);
233
245
  gainR.connect(merger, 0, 1);
234
- merger.connect(this.masterGain);
235
- const reverbEffect = this.createReverbEffect(audioContext);
246
+ const reverbEffect = this.options.reverbAlgorithm(audioContext);
236
247
  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
248
  return {
243
249
  gainL,
244
250
  gainR,
251
+ merger,
245
252
  reverbEffect,
246
253
  chorusEffect,
247
254
  };
@@ -249,13 +256,13 @@ export class MidyGM2 {
249
256
  createChannels(audioContext) {
250
257
  const channels = Array.from({ length: 16 }, () => {
251
258
  return {
252
- ...MidyGM2.channelSettings,
253
- ...MidyGM2.effectSettings,
259
+ ...this.constructor.channelSettings,
260
+ ...this.constructor.effectSettings,
254
261
  ...this.setChannelAudioNodes(audioContext),
255
262
  scheduledNotes: new Map(),
256
263
  sostenutoNotes: new Map(),
257
264
  channelPressure: {
258
- ...MidyGM2.controllerDestinationSettings,
265
+ ...this.constructor.controllerDestinationSettings,
259
266
  },
260
267
  };
261
268
  });
@@ -583,8 +590,12 @@ export class MidyGM2 {
583
590
  }
584
591
  return noteList[0];
585
592
  }
586
- createReverbEffect(audioContext, options = {}) {
593
+ createConvolutionReverb(audioContext, options = {}) {
587
594
  const { decay = 0.8, preDecay = 0, } = options;
595
+ const input = new GainNode(audioContext);
596
+ const output = new GainNode(audioContext);
597
+ const dryGain = new GainNode(audioContext);
598
+ const wetGain = new GainNode(audioContext);
588
599
  const sampleRate = audioContext.sampleRate;
589
600
  const length = sampleRate * decay;
590
601
  const impulse = new AudioBuffer({
@@ -598,27 +609,82 @@ export class MidyGM2 {
598
609
  for (let i = 0; i < preDecayLength; i++) {
599
610
  channelData[i] = Math.random() * 2 - 1;
600
611
  }
612
+ const attenuationFactor = 1 / (sampleRate * decay);
601
613
  for (let i = preDecayLength; i < length; i++) {
602
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
614
+ const attenuation = Math.exp(-(i - preDecayLength) * attenuationFactor);
603
615
  channelData[i] = (Math.random() * 2 - 1) * attenuation;
604
616
  }
605
617
  }
606
618
  const convolverNode = new ConvolverNode(audioContext, {
607
619
  buffer: impulse,
608
620
  });
609
- const dryGain = new GainNode(audioContext);
610
- const wetGain = new GainNode(audioContext);
621
+ input.connect(convolverNode);
611
622
  convolverNode.connect(wetGain);
623
+ wetGain.connect(output);
624
+ dryGain.connect(output);
612
625
  return {
613
- convolverNode,
626
+ input,
627
+ output,
614
628
  dryGain,
615
629
  wetGain,
630
+ convolverNode,
616
631
  };
617
632
  }
633
+ createCombFilter(audioContext, input, delay, feedback) {
634
+ const delayNode = new DelayNode(audioContext, {
635
+ maxDelayTime: delay,
636
+ delayTime: delay,
637
+ });
638
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
639
+ input.connect(delayNode);
640
+ delayNode.connect(feedbackGain);
641
+ feedbackGain.connect(delayNode);
642
+ return delayNode;
643
+ }
644
+ createAllpassFilter(audioContext, input, delay, feedback) {
645
+ const delayNode = new DelayNode(audioContext, {
646
+ maxDelayTime: delay,
647
+ delayTime: delay,
648
+ });
649
+ const feedbackGain = new GainNode(audioContext, { gain: feedback });
650
+ const passGain = new GainNode(audioContext, { gain: 1 - feedback });
651
+ input.connect(delayNode);
652
+ delayNode.connect(feedbackGain);
653
+ feedbackGain.connect(delayNode);
654
+ delayNode.connect(passGain);
655
+ return passGain;
656
+ }
657
+ // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
658
+ // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
659
+ createSchroederReverb(audioContext, options = {}) {
660
+ 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;
661
+ const input = new GainNode(audioContext);
662
+ const output = new GainNode(audioContext);
663
+ const mergerGain = new GainNode(audioContext, {
664
+ gain: 1 / (combDelays.length * 2),
665
+ });
666
+ const dryGain = new GainNode(audioContext, { gain: 1 - mix });
667
+ const wetGain = new GainNode(audioContext, { gain: mix });
668
+ for (let i = 0; i < combDelays.length; i++) {
669
+ const comb = this.createCombFilter(audioContext, input, combDelays[i], combFeedbacks[i]);
670
+ comb.connect(mergerGain);
671
+ }
672
+ const allpasses = [];
673
+ for (let i = 0; i < allpassDelays.length; i++) {
674
+ const allpass = this.createAllpassFilter(audioContext, (i === 0) ? mergerGain : allpasses.at(-1), allpassDelays[i], allpassFeedbacks[i]);
675
+ allpasses.push(allpass);
676
+ }
677
+ allpasses.at(-1).connect(wetGain);
678
+ input.connect(dryGain);
679
+ dryGain.connect(output);
680
+ wetGain.connect(output);
681
+ return { input, output, dryGain, wetGain };
682
+ }
618
683
  createChorusEffect(audioContext, options = {}) {
619
684
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
620
685
  const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
621
686
  const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
687
+ const output = new GainNode(audioContext);
622
688
  const chorusGains = [];
623
689
  const delayNodes = [];
624
690
  const baseGain = 1 / chorusCount;
@@ -628,50 +694,47 @@ export class MidyGM2 {
628
694
  const delayNode = new DelayNode(audioContext, {
629
695
  maxDelayTime: delayTime,
630
696
  });
631
- delayNodes.push(delayNode);
632
697
  const chorusGain = new GainNode(audioContext, { gain: baseGain });
698
+ delayNodes.push(delayNode);
633
699
  chorusGains.push(chorusGain);
634
- lfo.connect(lfoGain);
635
700
  lfoGain.connect(delayNode.delayTime);
636
701
  delayNode.connect(chorusGain);
702
+ chorusGain.connect(output);
637
703
  }
704
+ lfo.connect(lfoGain);
705
+ lfo.start();
638
706
  return {
639
707
  lfo,
640
708
  lfoGain,
641
709
  delayNodes,
642
710
  chorusGains,
711
+ output,
643
712
  };
644
713
  }
645
- connectNoteEffects(channel, gainNode) {
714
+ connectEffects(channel, gainNode) {
715
+ gainNode.connect(channel.merger);
646
716
  if (channel.reverb === 0) {
647
717
  if (channel.chorus === 0) { // no effect
648
- gainNode.connect(channel.gainL);
649
- gainNode.connect(channel.gainR);
718
+ channel.merger.connect(this.masterGain);
650
719
  }
651
720
  else { // chorus
652
721
  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);
722
+ channel.merger.connect(delayNode);
658
723
  });
724
+ channel.chorusEffect.output.connect(this.masterGain);
659
725
  }
660
726
  }
661
727
  else {
662
728
  if (channel.chorus === 0) { // reverb
663
- gainNode.connect(channel.reverbEffect.convolverNode);
664
- gainNode.connect(channel.reverbEffect.dryGain);
729
+ channel.merger.connect(channel.reverbEffect.input);
730
+ channel.reverbEffect.output.connect(this.masterGain);
665
731
  }
666
732
  else { // reverb + chorus
667
- gainNode.connect(channel.reverbEffect.convolverNode);
668
- gainNode.connect(channel.reverbEffect.dryGain);
669
733
  channel.chorusEffect.delayNodes.forEach((delayNode) => {
670
- gainNode.connect(delayNode);
671
- });
672
- channel.chorusEffect.chorusGains.forEach((chorusGain) => {
673
- chorusGain.connect(channel.reverbEffect.convolverNode);
734
+ channel.merger.connect(delayNode);
674
735
  });
736
+ channel.merger.connect(channel.reverbEffect.input);
737
+ channel.reverbEffect.output.connect(this.masterGain);
675
738
  }
676
739
  }
677
740
  }
@@ -742,7 +805,7 @@ export class MidyGM2 {
742
805
  startModulation(channel, note, time) {
743
806
  const { instrumentKey } = note;
744
807
  note.modLFOGain = new GainNode(this.audioContext, {
745
- gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
808
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
746
809
  });
747
810
  note.modLFO = new OscillatorNode(this.audioContext, {
748
811
  frequency: this.centToHz(instrumentKey.freqModLFO),
@@ -794,7 +857,7 @@ export class MidyGM2 {
794
857
  if (!instrumentKey)
795
858
  return;
796
859
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
797
- this.connectNoteEffects(channel, note.gainNode);
860
+ this.connectEffects(channel, note.gainNode);
798
861
  if (channel.sostenutoPedal) {
799
862
  channel.sostenutoNotes.set(noteNumber, note);
800
863
  }
@@ -818,42 +881,48 @@ export class MidyGM2 {
818
881
  return;
819
882
  if (!channel.scheduledNotes.has(noteNumber))
820
883
  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)
884
+ const scheduledNotes = channel.scheduledNotes.get(noteNumber);
885
+ for (let i = 0; i < scheduledNotes.length; i++) {
886
+ const note = scheduledNotes[i];
887
+ if (!note)
825
888
  continue;
826
- if (targetNote.ending)
889
+ if (note.ending)
827
890
  continue;
828
- const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
829
891
  const velocityRate = (velocity + 127) / 127;
830
- const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
831
- gainNode.gain.cancelScheduledValues(stopTime);
832
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
892
+ const volEndTime = stopTime +
893
+ note.instrumentKey.volRelease * velocityRate;
894
+ note.gainNode.gain
895
+ .cancelScheduledValues(stopTime)
896
+ .linearRampToValueAtTime(0, volEndTime);
833
897
  const maxFreq = this.audioContext.sampleRate / 2;
834
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
898
+ const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
835
899
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
836
- const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
837
- filterNode.frequency
900
+ const modEndTime = stopTime +
901
+ note.instrumentKey.modRelease * velocityRate;
902
+ note.filterNode.frequency
838
903
  .cancelScheduledValues(stopTime)
839
904
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
840
- targetNote.ending = true;
905
+ note.ending = true;
841
906
  this.scheduleTask(() => {
842
- bufferSource.loop = false;
907
+ note.bufferSource.loop = false;
843
908
  }, stopTime);
844
909
  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();
910
+ note.bufferSource.onended = () => {
911
+ scheduledNotes[i] = null;
912
+ note.bufferSource.disconnect();
913
+ note.filterNode.disconnect();
914
+ note.gainNode.disconnect();
915
+ if (note.modLFOGain)
916
+ note.modLFOGain.disconnect();
917
+ if (note.vibLFOGain)
918
+ note.vibLFOGain.disconnect();
919
+ if (note.modLFO)
920
+ note.modLFO.stop();
921
+ if (note.vibLFO)
922
+ note.vibLFO.stop();
854
923
  resolve();
855
924
  };
856
- bufferSource.stop(volEndTime);
925
+ note.bufferSource.stop(volEndTime);
857
926
  });
858
927
  }
859
928
  }
@@ -951,7 +1020,7 @@ export class MidyGM2 {
951
1020
  case 5:
952
1021
  return this.setPortamentoTime(channelNumber, value);
953
1022
  case 6:
954
- return this.setDataEntry(channelNumber, value, true);
1023
+ return this.dataEntryMSB(channelNumber, value);
955
1024
  case 7:
956
1025
  return this.setVolume(channelNumber, value);
957
1026
  case 10:
@@ -961,7 +1030,7 @@ export class MidyGM2 {
961
1030
  case 32:
962
1031
  return this.setBankLSB(channelNumber, value);
963
1032
  case 38:
964
- return this.setDataEntry(channelNumber, value, false);
1033
+ return this.dataEntryLSB(channelNumber, value);
965
1034
  case 64:
966
1035
  return this.setSustainPedal(channelNumber, value);
967
1036
  case 65:
@@ -971,9 +1040,9 @@ export class MidyGM2 {
971
1040
  case 67:
972
1041
  return this.setSoftPedal(channelNumber, value);
973
1042
  case 91:
974
- return this.setReverb(channelNumber, value);
1043
+ return this.setReverbSendLevel(channelNumber, value);
975
1044
  case 93:
976
- return this.setChorus(channelNumber, value);
1045
+ return this.setChorusSendLevel(channelNumber, value);
977
1046
  case 100:
978
1047
  return this.setRPNLSB(channelNumber, value);
979
1048
  case 101:
@@ -999,22 +1068,24 @@ export class MidyGM2 {
999
1068
  setBankMSB(channelNumber, msb) {
1000
1069
  this.channels[channelNumber].bankMSB = msb;
1001
1070
  }
1002
- setModulation(channelNumber, modulation) {
1071
+ updateModulation(channel) {
1003
1072
  const now = this.audioContext.currentTime;
1004
- const channel = this.channels[channelNumber];
1005
- channel.modulation = (modulation / 127) *
1006
- (channel.modulationDepthRange * 100);
1007
1073
  const activeNotes = this.getActiveNotes(channel, now);
1008
1074
  activeNotes.forEach((activeNote) => {
1009
1075
  if (activeNote.modLFO) {
1010
- activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
1011
- channel.modulation, now);
1076
+ const { gainNode, instrumentKey } = activeNote;
1077
+ gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
1012
1078
  }
1013
1079
  else {
1014
1080
  this.startModulation(channel, activeNote, now);
1015
1081
  }
1016
1082
  });
1017
1083
  }
1084
+ setModulation(channelNumber, modulation) {
1085
+ const channel = this.channels[channelNumber];
1086
+ channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1087
+ this.updateModulation(channel);
1088
+ }
1018
1089
  setPortamentoTime(channelNumber, portamentoTime) {
1019
1090
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
1020
1091
  }
@@ -1043,6 +1114,10 @@ export class MidyGM2 {
1043
1114
  setBankLSB(channelNumber, lsb) {
1044
1115
  this.channels[channelNumber].bankLSB = lsb;
1045
1116
  }
1117
+ dataEntryLSB(channelNumber, value) {
1118
+ this.channels[channelNumber].dataLSB = value;
1119
+ this.handleRPN(channelNumber);
1120
+ }
1046
1121
  updateChannelGain(channel) {
1047
1122
  const now = this.audioContext.currentTime;
1048
1123
  const volume = channel.volume * channel.expression;
@@ -1064,7 +1139,7 @@ export class MidyGM2 {
1064
1139
  setPortamento(channelNumber, value) {
1065
1140
  this.channels[channelNumber].portamento = value >= 64;
1066
1141
  }
1067
- setReverb(channelNumber, reverb) {
1142
+ setReverbSendLevel(channelNumber, reverb) {
1068
1143
  const now = this.audioContext.currentTime;
1069
1144
  const channel = this.channels[channelNumber];
1070
1145
  const reverbEffect = channel.reverbEffect;
@@ -1074,7 +1149,7 @@ export class MidyGM2 {
1074
1149
  reverbEffect.wetGain.gain.cancelScheduledValues(now);
1075
1150
  reverbEffect.wetGain.gain.setValueAtTime(channel.reverb, now);
1076
1151
  }
1077
- setChorus(channelNumber, chorus) {
1152
+ setChorusSendLevel(channelNumber, chorus) {
1078
1153
  const channel = this.channels[channelNumber];
1079
1154
  channel.chorus = chorus / 127;
1080
1155
  channel.chorusEffect.lfoGain = channel.chorus;
@@ -1122,21 +1197,21 @@ export class MidyGM2 {
1122
1197
  channel.dataMSB = minMSB;
1123
1198
  }
1124
1199
  }
1125
- handleRPN(channelNumber, value) {
1200
+ handleRPN(channelNumber) {
1126
1201
  const channel = this.channels[channelNumber];
1127
1202
  const rpn = channel.rpnMSB * 128 + channel.rpnLSB;
1128
1203
  switch (rpn) {
1129
1204
  case 0:
1130
- channel.dataLSB += value;
1131
- this.handlePitchBendRangeMessage(channelNumber);
1205
+ this.handlePitchBendRangeRPN(channelNumber);
1132
1206
  break;
1133
1207
  case 1:
1134
- channel.dataLSB += value;
1135
- this.handleFineTuningMessage(channelNumber);
1208
+ this.handleFineTuningRPN(channelNumber);
1136
1209
  break;
1137
1210
  case 2:
1138
- channel.dataMSB += value;
1139
- this.handleCoarseTuningMessage(channelNumber);
1211
+ this.handleCoarseTuningRPN(channelNumber);
1212
+ break;
1213
+ case 5:
1214
+ this.handleModulationDepthRangeRPN(channelNumber);
1140
1215
  break;
1141
1216
  default:
1142
1217
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -1148,9 +1223,8 @@ export class MidyGM2 {
1148
1223
  setRPNLSB(channelNumber, value) {
1149
1224
  this.channels[channelNumber].rpnLSB = value;
1150
1225
  }
1151
- setDataEntry(channelNumber, value, isMSB) {
1152
- const channel = this.channels[channelNumber];
1153
- isMSB ? channel.dataMSB = value : channel.dataLSB = value;
1226
+ dataEntryMSB(channelNumber, value) {
1227
+ this.channels[channelNumber].dataMSB = value;
1154
1228
  this.handleRPN(channelNumber);
1155
1229
  }
1156
1230
  updateDetune(channel, detuneChange) {
@@ -1164,7 +1238,7 @@ export class MidyGM2 {
1164
1238
  .setValueAtTime(detune, now);
1165
1239
  });
1166
1240
  }
1167
- handlePitchBendRangeMessage(channelNumber) {
1241
+ handlePitchBendRangeRPN(channelNumber) {
1168
1242
  const channel = this.channels[channelNumber];
1169
1243
  this.limitData(channel, 0, 127, 0, 99);
1170
1244
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
@@ -1178,7 +1252,7 @@ export class MidyGM2 {
1178
1252
  channel.pitchBend * 100;
1179
1253
  this.updateDetune(channel, detuneChange);
1180
1254
  }
1181
- handleFineTuningMessage(channelNumber) {
1255
+ handleFineTuningRPN(channelNumber) {
1182
1256
  const channel = this.channels[channelNumber];
1183
1257
  this.limitData(channel, 0, 127, 0, 127);
1184
1258
  const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
@@ -1186,9 +1260,12 @@ export class MidyGM2 {
1186
1260
  }
1187
1261
  setFineTuning(channelNumber, fineTuning) {
1188
1262
  const channel = this.channels[channelNumber];
1263
+ const prevFineTuning = channel.fineTuning;
1189
1264
  channel.fineTuning = fineTuning;
1265
+ const detuneChange = channel.fineTuning - prevFineTuning;
1266
+ this.updateDetune(channel, detuneChange);
1190
1267
  }
1191
- handleCoarseTuningMessage(channelNumber) {
1268
+ handleCoarseTuningRPN(channelNumber) {
1192
1269
  const channel = this.channels[channelNumber];
1193
1270
  this.limitDataMSB(channel, 0, 127);
1194
1271
  const coarseTuning = channel.dataMSB - 64;
@@ -1196,7 +1273,22 @@ export class MidyGM2 {
1196
1273
  }
1197
1274
  setCoarseTuning(channelNumber, coarseTuning) {
1198
1275
  const channel = this.channels[channelNumber];
1199
- channel.fineTuning = coarseTuning;
1276
+ const prevCoarseTuning = channel.coarseTuning;
1277
+ channel.coarseTuning = coarseTuning;
1278
+ const detuneChange = channel.coarseTuning - prevCoarseTuning;
1279
+ this.updateDetune(channel, detuneChange);
1280
+ }
1281
+ handleModulationDepthRangeRPN(channelNumber) {
1282
+ const channel = this.channels[channelNumber];
1283
+ this.limitData(channel, 0, 127, 0, 127);
1284
+ const modulationDepthRange = dataMSB + dataLSB / 128;
1285
+ this.setModulationDepthRange(channelNumber, modulationDepthRange);
1286
+ }
1287
+ setModulationDepthRange(channelNumber, modulationDepthRange) {
1288
+ const channel = this.channels[channelNumber];
1289
+ channel.modulationDepthRange = modulationDepthRange;
1290
+ channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1291
+ this.updateModulation(channel);
1200
1292
  }
1201
1293
  allSoundOff(channelNumber) {
1202
1294
  const now = this.audioContext.currentTime;
@@ -1287,9 +1379,9 @@ export class MidyGM2 {
1287
1379
  switch (data[3]) {
1288
1380
  case 1:
1289
1381
  return this.handleMasterVolumeSysEx(data);
1290
- case 3:
1382
+ case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1291
1383
  return this.handleMasterFineTuningSysEx(data);
1292
- case 4:
1384
+ case 4: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca25.pdf
1293
1385
  return this.handleMasterCoarseTuningSysEx(data);
1294
1386
  // case 5: // TODO: Global Parameter Control
1295
1387
  default:
@@ -1400,7 +1492,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1400
1492
  value: {
1401
1493
  currentBufferSource: null,
1402
1494
  volume: 100 / 127,
1403
- pan: 0,
1495
+ pan: 64,
1404
1496
  portamentoTime: 0,
1405
1497
  reverb: 0,
1406
1498
  chorus: 0,
@@ -1411,9 +1503,9 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1411
1503
  dataLSB: 0,
1412
1504
  program: 0,
1413
1505
  pitchBend: 0,
1414
- fineTuning: 0,
1415
- coarseTuning: 0,
1416
- modulationDepthRange: 0.5,
1506
+ fineTuning: 0, // cb
1507
+ coarseTuning: 0, // cb
1508
+ modulationDepthRange: 0.5, // cb
1417
1509
  }
1418
1510
  });
1419
1511
  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"}