@marmooo/midy 0.1.1 → 0.1.3

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.
Files changed (35) hide show
  1. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts +153 -0
  2. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +1 -0
  3. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.2 → soundfont-parser@0.0.4}/+esm.js +73 -66
  4. package/esm/midy-GM1.d.ts +18 -14
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +133 -110
  7. package/esm/midy-GM2.d.ts +36 -30
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +190 -158
  10. package/esm/midy-GMLite.d.ts +16 -14
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +123 -112
  13. package/esm/midy.d.ts +33 -31
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +191 -185
  16. package/package.json +1 -1
  17. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts +153 -0
  18. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.d.ts.map +1 -0
  19. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.2 → soundfont-parser@0.0.4}/+esm.js +75 -68
  20. package/script/midy-GM1.d.ts +18 -14
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +133 -110
  23. package/script/midy-GM2.d.ts +36 -30
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +190 -158
  26. package/script/midy-GMLite.d.ts +16 -14
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +123 -112
  29. package/script/midy.d.ts +33 -31
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +191 -185
  32. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts +0 -135
  33. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +0 -1
  34. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts +0 -135
  35. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +0 -1
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM2 = void 0;
4
4
  const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
5
- const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js");
5
+ const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.4/+esm.js");
6
6
  class Note {
7
7
  constructor(noteNumber, velocity, startTime, instrumentKey) {
8
8
  Object.defineProperty(this, "bufferSource", {
@@ -11,37 +11,43 @@ class Note {
11
11
  writable: true,
12
12
  value: void 0
13
13
  });
14
- Object.defineProperty(this, "gainNode", {
14
+ Object.defineProperty(this, "filterNode", {
15
15
  enumerable: true,
16
16
  configurable: true,
17
17
  writable: true,
18
18
  value: void 0
19
19
  });
20
- Object.defineProperty(this, "filterNode", {
20
+ Object.defineProperty(this, "volumeNode", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
26
+ Object.defineProperty(this, "volumeDepth", {
21
27
  enumerable: true,
22
28
  configurable: true,
23
29
  writable: true,
24
30
  value: void 0
25
31
  });
26
- Object.defineProperty(this, "modLFO", {
32
+ Object.defineProperty(this, "modulationLFO", {
27
33
  enumerable: true,
28
34
  configurable: true,
29
35
  writable: true,
30
36
  value: void 0
31
37
  });
32
- Object.defineProperty(this, "modLFOGain", {
38
+ Object.defineProperty(this, "modulationDepth", {
33
39
  enumerable: true,
34
40
  configurable: true,
35
41
  writable: true,
36
42
  value: void 0
37
43
  });
38
- Object.defineProperty(this, "vibLFO", {
44
+ Object.defineProperty(this, "vibratoLFO", {
39
45
  enumerable: true,
40
46
  configurable: true,
41
47
  writable: true,
42
48
  value: void 0
43
49
  });
44
- Object.defineProperty(this, "vibLFOGain", {
50
+ Object.defineProperty(this, "vibratoDepth", {
45
51
  enumerable: true,
46
52
  configurable: true,
47
53
  writable: true,
@@ -85,7 +91,7 @@ class MidyGM2 {
85
91
  writable: true,
86
92
  value: {
87
93
  time: this.getReverbTime(64),
88
- feedback: 0.25,
94
+ feedback: 0.8,
89
95
  }
90
96
  });
91
97
  Object.defineProperty(this, "chorus", {
@@ -227,8 +233,12 @@ class MidyGM2 {
227
233
  this.audioContext = audioContext;
228
234
  this.options = { ...this.defaultOptions, ...options };
229
235
  this.masterGain = new GainNode(audioContext);
230
- this.masterGain.connect(audioContext.destination);
231
236
  this.channels = this.createChannels(audioContext);
237
+ this.reverbEffect = this.options.reverbAlgorithm(audioContext);
238
+ this.chorusEffect = this.createChorusEffect(audioContext);
239
+ this.chorusEffect.output.connect(this.masterGain);
240
+ this.reverbEffect.output.connect(this.masterGain);
241
+ this.masterGain.connect(audioContext.destination);
232
242
  this.GM2SystemOn();
233
243
  }
234
244
  initSoundFontTable() {
@@ -272,14 +282,11 @@ class MidyGM2 {
272
282
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
273
283
  gainL.connect(merger, 0, 0);
274
284
  gainR.connect(merger, 0, 1);
275
- const reverbEffect = this.options.reverbAlgorithm(audioContext);
276
- const chorusEffect = this.createChorusEffect(audioContext);
285
+ merger.connect(this.masterGain);
277
286
  return {
278
287
  gainL,
279
288
  gainR,
280
289
  merger,
281
- reverbEffect,
282
- chorusEffect,
283
290
  };
284
291
  }
285
292
  createChannels(audioContext) {
@@ -406,7 +413,7 @@ class MidyGM2 {
406
413
  const t = this.audioContext.currentTime + offset;
407
414
  queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
408
415
  if (this.isPausing) {
409
- await this.stopNotes();
416
+ await this.stopNotes(0, true);
410
417
  this.notePromises = [];
411
418
  resolve();
412
419
  this.isPausing = false;
@@ -414,7 +421,7 @@ class MidyGM2 {
414
421
  return;
415
422
  }
416
423
  else if (this.isStopping) {
417
- await this.stopNotes();
424
+ await this.stopNotes(0, true);
418
425
  this.notePromises = [];
419
426
  resolve();
420
427
  this.isStopping = false;
@@ -422,7 +429,7 @@ class MidyGM2 {
422
429
  return;
423
430
  }
424
431
  else if (this.isSeeking) {
425
- this.stopNotes();
432
+ this.stopNotes(0, true);
426
433
  this.startTime = this.audioContext.currentTime;
427
434
  queueIndex = this.getQueueIndex(this.resumeTime);
428
435
  offset = this.resumeTime - this.startTime;
@@ -537,21 +544,24 @@ class MidyGM2 {
537
544
  }
538
545
  return { instruments, timeline };
539
546
  }
540
- stopNotes() {
547
+ async stopChannelNotes(channelNumber, velocity, stopPedal) {
541
548
  const now = this.audioContext.currentTime;
542
- const velocity = 0;
543
- const stopPedal = true;
544
- this.channels.forEach((channel, channelNumber) => {
545
- channel.scheduledNotes.forEach((scheduledNotes) => {
546
- scheduledNotes.forEach((scheduledNote) => {
547
- if (scheduledNote) {
548
- const promise = this.scheduleNoteRelease(channelNumber, scheduledNote.noteNumber, velocity, now, stopPedal);
549
- this.notePromises.push(promise);
550
- }
551
- });
549
+ const channel = this.channels[channelNumber];
550
+ channel.scheduledNotes.forEach((noteList) => {
551
+ noteList.forEach((note) => {
552
+ if (note) {
553
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
554
+ this.notePromises.push(promise);
555
+ }
552
556
  });
553
- channel.scheduledNotes.clear();
554
557
  });
558
+ channel.scheduledNotes.clear();
559
+ await Promise.all(this.notePromises);
560
+ }
561
+ stopNotes(velocity, stopPedal) {
562
+ for (let i = 0; i < this.channels.length; i++) {
563
+ this.stopChannelNotes(i, velocity, stopPedal);
564
+ }
555
565
  return Promise.all(this.notePromises);
556
566
  }
557
567
  async start() {
@@ -689,7 +699,7 @@ class MidyGM2 {
689
699
  }
690
700
  // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
691
701
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
692
- createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
702
+ createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
693
703
  const input = new GainNode(audioContext);
694
704
  const output = new GainNode(audioContext);
695
705
  const mergerGain = new GainNode(audioContext);
@@ -748,21 +758,6 @@ class MidyGM2 {
748
758
  feedbackGains,
749
759
  };
750
760
  }
751
- connectEffects(channel, gainNode) {
752
- gainNode.connect(channel.merger);
753
- channel.merger.connect(this.masterGain);
754
- if (0 < channel.reverbSendLevel) {
755
- channel.merger.connect(channel.reverbEffect.input);
756
- channel.reverbEffect.output.connect(this.masterGain);
757
- }
758
- if (0 < channel.chorusSendLevel) {
759
- channel.merger.connect(channel.chorusEffect.input);
760
- channel.reverbEffect.output.connect(this.masterGain);
761
- }
762
- if (0 < this.chorus.sendToReverb) {
763
- channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
764
- }
765
- }
766
761
  cbToRatio(cb) {
767
762
  return Math.pow(10, cb / 200);
768
763
  }
@@ -779,42 +774,56 @@ class MidyGM2 {
779
774
  return instrumentKey.playbackRate(noteNumber) *
780
775
  Math.pow(2, semitoneOffset / 12);
781
776
  }
782
- setVolumeEnvelope(channel, note) {
783
- const { instrumentKey, startTime, velocity } = note;
784
- note.gainNode = new GainNode(this.audioContext, { gain: 0 });
785
- let volume = (velocity / 127) * channel.volume * channel.expression;
786
- if (volume === 0)
787
- volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
788
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
789
- volume;
777
+ setVolumeEnvelope(note) {
778
+ const { instrumentKey, startTime } = note;
779
+ note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
780
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
790
781
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
791
782
  const volDelay = startTime + instrumentKey.volDelay;
792
783
  const volAttack = volDelay + instrumentKey.volAttack;
793
784
  const volHold = volAttack + instrumentKey.volHold;
794
785
  const volDecay = volHold + instrumentKey.volDecay;
795
- note.gainNode.gain
786
+ note.volumeNode.gain
796
787
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
797
788
  .exponentialRampToValueAtTime(attackVolume, volAttack)
798
789
  .setValueAtTime(attackVolume, volHold)
799
790
  .linearRampToValueAtTime(sustainVolume, volDecay);
800
791
  }
801
- setFilterEnvelope(channel, note) {
802
- const { instrumentKey, startTime, noteNumber } = note;
792
+ setPitch(note, semitoneOffset) {
793
+ const { instrumentKey, noteNumber, startTime } = note;
794
+ const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
795
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
796
+ if (modEnvToPitch === 0)
797
+ return;
798
+ const basePitch = note.bufferSource.playbackRate.value;
799
+ const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
800
+ const modDelay = startTime + instrumentKey.modDelay;
801
+ const modAttack = modDelay + instrumentKey.modAttack;
802
+ const modHold = modAttack + instrumentKey.modHold;
803
+ const modDecay = modHold + instrumentKey.modDecay;
804
+ note.bufferSource.playbackRate.value
805
+ .setValueAtTime(basePitch, modDelay)
806
+ .exponentialRampToValueAtTime(peekPitch, modAttack)
807
+ .setValueAtTime(peekPitch, modHold)
808
+ .linearRampToValueAtTime(basePitch, modDecay);
809
+ }
810
+ setFilterNode(channel, note) {
811
+ const { instrumentKey, noteNumber, startTime } = note;
803
812
  const softPedalFactor = 1 -
804
813
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
805
814
  const maxFreq = this.audioContext.sampleRate / 2;
806
815
  const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
807
816
  softPedalFactor;
808
817
  const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
809
- const sustainFreq = (baseFreq +
810
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
818
+ const sustainFreq = baseFreq +
819
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
820
+ const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
821
+ const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
822
+ const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
811
823
  const modDelay = startTime + instrumentKey.modDelay;
812
824
  const modAttack = modDelay + instrumentKey.modAttack;
813
825
  const modHold = modAttack + instrumentKey.modHold;
814
826
  const modDecay = modHold + instrumentKey.modDecay;
815
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
816
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
817
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
818
827
  note.filterNode = new BiquadFilterNode(this.audioContext, {
819
828
  type: "lowpass",
820
829
  Q: instrumentKey.initialFilterQ / 10, // dB
@@ -825,39 +834,72 @@ class MidyGM2 {
825
834
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
826
835
  .setValueAtTime(adjustedPeekFreq, modHold)
827
836
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
828
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
829
837
  }
830
- startModulation(channel, note, time) {
838
+ startModulation(channel, note, startTime) {
831
839
  const { instrumentKey } = note;
832
- note.modLFOGain = new GainNode(this.audioContext, {
833
- gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
834
- });
835
- note.modLFO = new OscillatorNode(this.audioContext, {
840
+ const { modLfoToPitch, modLfoToVolume } = instrumentKey;
841
+ note.modulationLFO = new OscillatorNode(this.audioContext, {
836
842
  frequency: this.centToHz(instrumentKey.freqModLFO),
837
843
  });
838
- note.modLFO.start(time);
839
- note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
840
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
841
- note.modLFO.connect(note.modLFOGain);
842
- note.modLFOGain.connect(note.bufferSource.detune);
844
+ note.filterDepth = new GainNode(this.audioContext, {
845
+ gain: instrumentKey.modLfoToFilterFc,
846
+ });
847
+ const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
848
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
849
+ note.modulationDepth = new GainNode(this.audioContext, {
850
+ gain: modulationDepth * modulationDepthSign,
851
+ });
852
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
853
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
854
+ note.volumeDepth = new GainNode(this.audioContext, {
855
+ gain: volumeDepth * volumeDepthSign,
856
+ });
857
+ note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
858
+ note.modulationLFO.connect(note.filterDepth);
859
+ note.filterDepth.connect(note.filterNode.frequency);
860
+ note.modulationLFO.connect(note.modulationDepth);
861
+ note.modulationDepth.connect(note.bufferSource.detune);
862
+ note.modulationLFO.connect(note.volumeDepth);
863
+ note.volumeDepth.connect(note.volumeNode.gain);
864
+ }
865
+ startVibrato(channel, note, startTime) {
866
+ const { instrumentKey } = note;
867
+ const { vibLfoToPitch } = instrumentKey;
868
+ note.vibratoLFO = new OscillatorNode(this.audioContext, {
869
+ frequency: this.centToHz(instrumentKey.freqVibLFO) *
870
+ channel.vibratoRate,
871
+ });
872
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
873
+ const vibratoDepthSign = 0 < vibLfoToPitch;
874
+ note.vibratoDepth = new GainNode(this.audioContext, {
875
+ gain: vibratoDepth * vibratoDepthSign,
876
+ });
877
+ note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
878
+ note.vibratoLFO.connect(note.vibratoDepth);
879
+ note.vibratoDepth.connect(note.bufferSource.detune);
843
880
  }
844
881
  async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
845
882
  const semitoneOffset = this.calcSemitoneOffset(channel);
846
883
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
847
884
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
848
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
849
- this.setVolumeEnvelope(channel, note);
850
- this.setFilterEnvelope(channel, note);
851
- if (channel.modulation > 0) {
852
- const delayModLFO = startTime + instrumentKey.delayModLFO;
853
- this.startModulation(channel, note, delayModLFO);
885
+ this.setFilterNode(channel, note);
886
+ this.setVolumeEnvelope(note);
887
+ if (0 < channel.vibratoDepth) {
888
+ this.startVibrato(channel, note, startTime);
889
+ }
890
+ if (0 < channel.modulationDepth) {
891
+ this.setPitch(note, semitoneOffset);
892
+ this.startModulation(channel, note, startTime);
893
+ }
894
+ else {
895
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
854
896
  }
855
897
  if (this.mono && channel.currentBufferSource) {
856
898
  channel.currentBufferSource.stop(startTime);
857
899
  channel.currentBufferSource = note.bufferSource;
858
900
  }
859
901
  note.bufferSource.connect(note.filterNode);
860
- note.filterNode.connect(note.gainNode);
902
+ note.filterNode.connect(note.volumeNode);
861
903
  note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
862
904
  return note;
863
905
  }
@@ -882,7 +924,8 @@ class MidyGM2 {
882
924
  if (!instrumentKey)
883
925
  return;
884
926
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
885
- this.connectEffects(channel, note.gainNode);
927
+ note.volumeNode.connect(channel.gainL);
928
+ note.volumeNode.connect(channel.gainR);
886
929
  if (channel.sostenutoPedal) {
887
930
  channel.sostenutoNotes.set(noteNumber, note);
888
931
  }
@@ -916,17 +959,14 @@ class MidyGM2 {
916
959
  const velocityRate = (velocity + 127) / 127;
917
960
  const volEndTime = stopTime +
918
961
  note.instrumentKey.volRelease * velocityRate;
919
- note.gainNode.gain
962
+ note.volumeNode.gain
920
963
  .cancelScheduledValues(stopTime)
921
964
  .linearRampToValueAtTime(0, volEndTime);
922
- const maxFreq = this.audioContext.sampleRate / 2;
923
- const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
924
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
925
- const modEndTime = stopTime +
965
+ const modRelease = stopTime +
926
966
  note.instrumentKey.modRelease * velocityRate;
927
967
  note.filterNode.frequency
928
968
  .cancelScheduledValues(stopTime)
929
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
969
+ .linearRampToValueAtTime(0, modRelease);
930
970
  note.ending = true;
931
971
  this.scheduleTask(() => {
932
972
  note.bufferSource.loop = false;
@@ -935,16 +975,18 @@ class MidyGM2 {
935
975
  note.bufferSource.onended = () => {
936
976
  scheduledNotes[i] = null;
937
977
  note.bufferSource.disconnect();
978
+ note.volumeNode.disconnect();
938
979
  note.filterNode.disconnect();
939
- note.gainNode.disconnect();
940
- if (note.modLFOGain)
941
- note.modLFOGain.disconnect();
942
- if (note.vibLFOGain)
943
- note.vibLFOGain.disconnect();
944
- if (note.modLFO)
945
- note.modLFO.stop();
946
- if (note.vibLFO)
947
- note.vibLFO.stop();
980
+ if (note.volumeDepth)
981
+ note.volumeDepth.disconnect();
982
+ if (note.modulationDepth)
983
+ note.modulationDepth.disconnect();
984
+ if (note.modulationLFO)
985
+ note.modulationLFO.stop();
986
+ if (note.vibratoDepth)
987
+ note.vibratoDepth.disconnect();
988
+ if (note.vibratoLFO)
989
+ note.vibratoLFO.stop();
948
990
  resolve();
949
991
  };
950
992
  note.bufferSource.stop(volEndTime);
@@ -960,10 +1002,10 @@ class MidyGM2 {
960
1002
  const channel = this.channels[channelNumber];
961
1003
  const promises = [];
962
1004
  channel.sustainPedal = false;
963
- channel.scheduledNotes.forEach((scheduledNotes) => {
964
- scheduledNotes.forEach((scheduledNote) => {
965
- if (scheduledNote) {
966
- const { noteNumber } = scheduledNote;
1005
+ channel.scheduledNotes.forEach((noteList) => {
1006
+ noteList.forEach((note) => {
1007
+ if (note) {
1008
+ const { noteNumber } = note;
967
1009
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
968
1010
  promises.push(promise);
969
1011
  }
@@ -1017,21 +1059,21 @@ class MidyGM2 {
1017
1059
  const activeNotes = this.getActiveNotes(channel, now);
1018
1060
  if (channel.channelPressure.amplitudeControl !== 1) {
1019
1061
  activeNotes.forEach((activeNote) => {
1020
- const gain = activeNote.gainNode.gain.value;
1021
- activeNote.gainNode.gain
1062
+ const gain = activeNote.volumeNode.gain.value;
1063
+ activeNote.volumeNode.gain
1022
1064
  .cancelScheduledValues(now)
1023
1065
  .setValueAtTime(gain * pressure, now);
1024
1066
  });
1025
1067
  }
1026
1068
  }
1027
1069
  handlePitchBendMessage(channelNumber, lsb, msb) {
1028
- const pitchBend = msb * 128 + lsb;
1070
+ const pitchBend = msb * 128 + lsb - 8192;
1029
1071
  this.setPitchBend(channelNumber, pitchBend);
1030
1072
  }
1031
1073
  setPitchBend(channelNumber, pitchBend) {
1032
1074
  const channel = this.channels[channelNumber];
1033
1075
  const prevPitchBend = channel.pitchBend;
1034
- channel.pitchBend = (pitchBend - 8192) / 8192;
1076
+ channel.pitchBend = pitchBend / 8192;
1035
1077
  const detuneChange = (channel.pitchBend - prevPitchBend) *
1036
1078
  channel.pitchBendRange * 100;
1037
1079
  this.updateDetune(channel, detuneChange);
@@ -1041,7 +1083,7 @@ class MidyGM2 {
1041
1083
  case 0:
1042
1084
  return this.setBankMSB(channelNumber, value);
1043
1085
  case 1:
1044
- return this.setModulation(channelNumber, value);
1086
+ return this.setModulationDepth(channelNumber, value);
1045
1087
  case 5:
1046
1088
  return this.setPortamentoTime(channelNumber, value);
1047
1089
  case 6:
@@ -1097,18 +1139,19 @@ class MidyGM2 {
1097
1139
  const now = this.audioContext.currentTime;
1098
1140
  const activeNotes = this.getActiveNotes(channel, now);
1099
1141
  activeNotes.forEach((activeNote) => {
1100
- if (activeNote.modLFO) {
1101
- const { gainNode, instrumentKey } = activeNote;
1102
- gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
1142
+ if (activeNote.modulationDepth) {
1143
+ activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1103
1144
  }
1104
1145
  else {
1146
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1147
+ this.setPitch(activeNote, semitoneOffset);
1105
1148
  this.startModulation(channel, activeNote, now);
1106
1149
  }
1107
1150
  });
1108
1151
  }
1109
- setModulation(channelNumber, modulation) {
1152
+ setModulationDepth(channelNumber, modulation) {
1110
1153
  const channel = this.channels[channelNumber];
1111
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1154
+ channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1112
1155
  this.updateModulation(channel);
1113
1156
  }
1114
1157
  setPortamentoTime(channelNumber, portamentoTime) {
@@ -1166,20 +1209,30 @@ class MidyGM2 {
1166
1209
  this.channels[channelNumber].portamento = value >= 64;
1167
1210
  }
1168
1211
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1169
- const now = this.audioContext.currentTime;
1170
1212
  const channel = this.channels[channelNumber];
1171
- const reverbEffect = channel.reverbEffect;
1172
- channel.reverbSendLevel = reverbSendLevel / 127;
1173
- reverbEffect.output.gain.cancelScheduledValues(now);
1174
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1213
+ const reverbEffect = this.reverbEffect;
1214
+ if (0 < reverbSendLevel) {
1215
+ const now = this.audioContext.currentTime;
1216
+ channel.reverbSendLevel = reverbSendLevel / 127;
1217
+ reverbEffect.output.gain.cancelScheduledValues(now);
1218
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1219
+ }
1220
+ else if (channel.reverbSendLevel !== 0) {
1221
+ channel.merger.disconnect(reverbEffect.input);
1222
+ }
1175
1223
  }
1176
1224
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1177
- const now = this.audioContext.currentTime;
1178
1225
  const channel = this.channels[channelNumber];
1179
- const chorusEffect = channel.chorusEffect;
1180
- channel.chorusSendLevel = chorusSendLevel / 127;
1181
- chorusEffect.output.gain.cancelScheduledValues(now);
1182
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1226
+ const chorusEffect = this.chorusEffect;
1227
+ if (0 < chorusSendLevel) {
1228
+ const now = this.audioContext.currentTime;
1229
+ channel.chorusSendLevel = chorusSendLevel / 127;
1230
+ chorusEffect.output.gain.cancelScheduledValues(now);
1231
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1232
+ }
1233
+ else if (channel.chorusSendLevel !== 0) {
1234
+ channel.merger.disconnect(chorusEffect.input);
1235
+ }
1183
1236
  }
1184
1237
  setSostenutoPedal(channelNumber, value) {
1185
1238
  const isOn = value >= 64;
@@ -1308,47 +1361,23 @@ class MidyGM2 {
1308
1361
  handleModulationDepthRangeRPN(channelNumber) {
1309
1362
  const channel = this.channels[channelNumber];
1310
1363
  this.limitData(channel, 0, 127, 0, 127);
1311
- const modulationDepthRange = dataMSB + dataLSB / 128;
1364
+ const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
1312
1365
  this.setModulationDepthRange(channelNumber, modulationDepthRange);
1313
1366
  }
1314
1367
  setModulationDepthRange(channelNumber, modulationDepthRange) {
1315
1368
  const channel = this.channels[channelNumber];
1316
1369
  channel.modulationDepthRange = modulationDepthRange;
1317
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1370
+ channel.modulationDepth = (modulation / 127) * modulationDepthRange;
1318
1371
  this.updateModulation(channel);
1319
1372
  }
1320
1373
  allSoundOff(channelNumber) {
1321
- const now = this.audioContext.currentTime;
1322
- const channel = this.channels[channelNumber];
1323
- const velocity = 0;
1324
- const stopPedal = true;
1325
- const promises = [];
1326
- channel.scheduledNotes.forEach((noteList) => {
1327
- const activeNote = this.getActiveNote(noteList, now);
1328
- if (activeNote) {
1329
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1330
- promises.push(notePromise);
1331
- }
1332
- });
1333
- return promises;
1374
+ return this.stopChannelNotes(channelNumber, 0, true);
1334
1375
  }
1335
1376
  resetAllControllers(channelNumber) {
1336
1377
  Object.assign(this.channels[channelNumber], this.effectSettings);
1337
1378
  }
1338
1379
  allNotesOff(channelNumber) {
1339
- const now = this.audioContext.currentTime;
1340
- const channel = this.channels[channelNumber];
1341
- const velocity = 0;
1342
- const stopPedal = false;
1343
- const promises = [];
1344
- channel.scheduledNotes.forEach((noteList) => {
1345
- const activeNote = this.getActiveNote(noteList, now);
1346
- if (activeNote) {
1347
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1348
- promises.push(notePromise);
1349
- }
1350
- });
1351
- return promises;
1380
+ return this.stopChannelNotes(channelNumber, 0, false);
1352
1381
  }
1353
1382
  omniOff() {
1354
1383
  this.omni = false;
@@ -1513,11 +1542,9 @@ class MidyGM2 {
1513
1542
  }
1514
1543
  setReverbType(type) {
1515
1544
  this.reverb.time = this.getReverbTimeFromType(type);
1516
- this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1517
- const { audioContext, channels, options } = this;
1518
- for (let i = 0; i < channels.length; i++) {
1519
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1520
- }
1545
+ this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
1546
+ const { audioContext, options } = this;
1547
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1521
1548
  }
1522
1549
  getReverbTimeFromType(type) {
1523
1550
  switch (type) {
@@ -1659,15 +1686,17 @@ class MidyGM2 {
1659
1686
  return value * 0.00763;
1660
1687
  }
1661
1688
  setChorusSendToReverb(value) {
1662
- const now = this.audioContext.currentTime;
1663
1689
  const sendToReverb = this.getChorusSendToReverb(value);
1664
- this.chorus.sendToReverb = sendToReverb;
1665
- for (let i = 0; i < this.channels.length; i++) {
1666
- const chorusEffect = this.channels[i].chorusEffect;
1667
- chorusEffect.sendGain.gain
1690
+ if (0 < sendToReverb) {
1691
+ const now = this.audioContext.currentTime;
1692
+ this.chorus.sendToReverb = sendToReverb;
1693
+ this.chorusEffect.sendGain.gain
1668
1694
  .cancelScheduledValues(now)
1669
1695
  .setValueAtTime(sendToReverb, now);
1670
1696
  }
1697
+ else if (this.chorus.sendToReverb !== 0) {
1698
+ this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
1699
+ }
1671
1700
  }
1672
1701
  getChorusSendToReverb(value) {
1673
1702
  return value * 0.00787;
@@ -1709,6 +1738,9 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1709
1738
  portamentoTime: 0,
1710
1739
  reverbSendLevel: 0,
1711
1740
  chorusSendLevel: 0,
1741
+ vibratoRate: 1,
1742
+ vibratoDepth: 1,
1743
+ vibratoDelay: 1,
1712
1744
  bank: 121 * 128,
1713
1745
  bankMSB: 121,
1714
1746
  bankLSB: 0,
@@ -1718,7 +1750,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1718
1750
  pitchBend: 0,
1719
1751
  fineTuning: 0, // cb
1720
1752
  coarseTuning: 0, // cb
1721
- modulationDepthRange: 0.5, // cb
1753
+ modulationDepthRange: 50, // cent
1722
1754
  }
1723
1755
  });
1724
1756
  Object.defineProperty(MidyGM2, "effectSettings", {
@@ -1727,7 +1759,7 @@ Object.defineProperty(MidyGM2, "effectSettings", {
1727
1759
  writable: true,
1728
1760
  value: {
1729
1761
  expression: 1,
1730
- modulation: 0,
1762
+ modulationDepth: 0,
1731
1763
  sustainPedal: false,
1732
1764
  portamento: false,
1733
1765
  sostenutoPedal: false,