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