@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/script/midy.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Midy = 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 Midy {
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 Midy {
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 Midy {
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) {
@@ -412,7 +419,7 @@ class Midy {
412
419
  const t = this.audioContext.currentTime + offset;
413
420
  queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
414
421
  if (this.isPausing) {
415
- await this.stopNotes();
422
+ await this.stopNotes(0, true);
416
423
  this.notePromises = [];
417
424
  resolve();
418
425
  this.isPausing = false;
@@ -420,7 +427,7 @@ class Midy {
420
427
  return;
421
428
  }
422
429
  else if (this.isStopping) {
423
- await this.stopNotes();
430
+ await this.stopNotes(0, true);
424
431
  this.notePromises = [];
425
432
  resolve();
426
433
  this.isStopping = false;
@@ -428,7 +435,7 @@ class Midy {
428
435
  return;
429
436
  }
430
437
  else if (this.isSeeking) {
431
- this.stopNotes();
438
+ this.stopNotes(0, true);
432
439
  this.startTime = this.audioContext.currentTime;
433
440
  queueIndex = this.getQueueIndex(this.resumeTime);
434
441
  offset = this.resumeTime - this.startTime;
@@ -543,21 +550,24 @@ class Midy {
543
550
  }
544
551
  return { instruments, timeline };
545
552
  }
546
- stopNotes() {
553
+ async stopChannelNotes(channelNumber, velocity, stopPedal) {
547
554
  const now = this.audioContext.currentTime;
548
- const velocity = 0;
549
- const stopPedal = true;
550
- this.channels.forEach((channel, channelNumber) => {
551
- channel.scheduledNotes.forEach((scheduledNotes) => {
552
- scheduledNotes.forEach((scheduledNote) => {
553
- if (scheduledNote) {
554
- const promise = this.scheduleNoteRelease(channelNumber, scheduledNote.noteNumber, velocity, now, stopPedal);
555
- this.notePromises.push(promise);
556
- }
557
- });
555
+ const channel = this.channels[channelNumber];
556
+ channel.scheduledNotes.forEach((noteList) => {
557
+ noteList.forEach((note) => {
558
+ if (note) {
559
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
560
+ this.notePromises.push(promise);
561
+ }
558
562
  });
559
- channel.scheduledNotes.clear();
560
563
  });
564
+ channel.scheduledNotes.clear();
565
+ await Promise.all(this.notePromises);
566
+ }
567
+ stopNotes(velocity, stopPedal) {
568
+ for (let i = 0; i < this.channels.length; i++) {
569
+ this.stopChannelNotes(i, velocity, stopPedal);
570
+ }
561
571
  return Promise.all(this.notePromises);
562
572
  }
563
573
  async start() {
@@ -695,7 +705,7 @@ class Midy {
695
705
  }
696
706
  // https://hajim.rochester.edu/ece/sites/zduan/teaching/ece472/reading/Schroeder_1962.pdf
697
707
  // M.R.Schroeder, "Natural Sounding Artificial Reverberation", J.Audio Eng. Soc., vol.10, p.219, 1962
698
- createSchroederReverb(audioContext, combDelays, combFeedbacks, allpassDelays, allpassFeedbacks) {
708
+ createSchroederReverb(audioContext, combFeedbacks, combDelays, allpassFeedbacks, allpassDelays) {
699
709
  const input = new GainNode(audioContext);
700
710
  const output = new GainNode(audioContext);
701
711
  const mergerGain = new GainNode(audioContext);
@@ -754,21 +764,6 @@ class Midy {
754
764
  feedbackGains,
755
765
  };
756
766
  }
757
- connectEffects(channel, gainNode) {
758
- gainNode.connect(channel.merger);
759
- channel.merger.connect(this.masterGain);
760
- if (0 < channel.reverbSendLevel) {
761
- channel.merger.connect(channel.reverbEffect.input);
762
- channel.reverbEffect.output.connect(this.masterGain);
763
- }
764
- if (0 < channel.chorusSendLevel) {
765
- channel.merger.connect(channel.chorusEffect.input);
766
- channel.reverbEffect.output.connect(this.masterGain);
767
- }
768
- if (0 < this.chorus.sendToReverb) {
769
- channel.chorusEffect.sendGain.connect(channel.reverbEffect.input);
770
- }
771
- }
772
767
  cbToRatio(cb) {
773
768
  return Math.pow(10, cb / 200);
774
769
  }
@@ -785,42 +780,56 @@ class Midy {
785
780
  return instrumentKey.playbackRate(noteNumber) *
786
781
  Math.pow(2, semitoneOffset / 12);
787
782
  }
788
- setVolumeEnvelope(channel, note) {
789
- const { instrumentKey, startTime, velocity } = note;
790
- note.gainNode = new GainNode(this.audioContext, { gain: 0 });
791
- let volume = (velocity / 127) * channel.volume * channel.expression;
792
- if (volume === 0)
793
- volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
794
- const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
795
- volume;
783
+ setVolumeEnvelope(note) {
784
+ const { instrumentKey, startTime } = note;
785
+ note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
786
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
796
787
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
797
788
  const volDelay = startTime + instrumentKey.volDelay;
798
789
  const volAttack = volDelay + instrumentKey.volAttack;
799
790
  const volHold = volAttack + instrumentKey.volHold;
800
791
  const volDecay = volHold + instrumentKey.volDecay;
801
- note.gainNode.gain
792
+ note.volumeNode.gain
802
793
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
803
794
  .exponentialRampToValueAtTime(attackVolume, volAttack)
804
795
  .setValueAtTime(attackVolume, volHold)
805
796
  .linearRampToValueAtTime(sustainVolume, volDecay);
806
797
  }
807
- setFilterEnvelope(channel, note) {
808
- const { instrumentKey, startTime, noteNumber } = note;
798
+ setPitch(note, semitoneOffset) {
799
+ const { instrumentKey, noteNumber, startTime } = note;
800
+ const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
801
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
802
+ if (modEnvToPitch === 0)
803
+ return;
804
+ const basePitch = note.bufferSource.playbackRate.value;
805
+ const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
806
+ const modDelay = startTime + instrumentKey.modDelay;
807
+ const modAttack = modDelay + instrumentKey.modAttack;
808
+ const modHold = modAttack + instrumentKey.modHold;
809
+ const modDecay = modHold + instrumentKey.modDecay;
810
+ note.bufferSource.playbackRate.value
811
+ .setValueAtTime(basePitch, modDelay)
812
+ .exponentialRampToValueAtTime(peekPitch, modAttack)
813
+ .setValueAtTime(peekPitch, modHold)
814
+ .linearRampToValueAtTime(basePitch, modDecay);
815
+ }
816
+ setFilterNode(channel, note) {
817
+ const { instrumentKey, noteNumber, startTime } = note;
809
818
  const softPedalFactor = 1 -
810
819
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
811
820
  const maxFreq = this.audioContext.sampleRate / 2;
812
821
  const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
813
822
  softPedalFactor;
814
823
  const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
815
- const sustainFreq = (baseFreq +
816
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
824
+ const sustainFreq = baseFreq +
825
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
826
+ const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
827
+ const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
828
+ const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
817
829
  const modDelay = startTime + instrumentKey.modDelay;
818
830
  const modAttack = modDelay + instrumentKey.modAttack;
819
831
  const modHold = modAttack + instrumentKey.modHold;
820
832
  const modDecay = modHold + instrumentKey.modDecay;
821
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
822
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
823
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
824
833
  note.filterNode = new BiquadFilterNode(this.audioContext, {
825
834
  type: "lowpass",
826
835
  Q: instrumentKey.initialFilterQ / 10, // dB
@@ -831,56 +840,72 @@ class Midy {
831
840
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
832
841
  .setValueAtTime(adjustedPeekFreq, modHold)
833
842
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
834
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
835
843
  }
836
- startModulation(channel, note, time) {
844
+ startModulation(channel, note, startTime) {
837
845
  const { instrumentKey } = note;
838
- note.modLFOGain = new GainNode(this.audioContext, {
839
- gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
840
- });
841
- note.modLFO = new OscillatorNode(this.audioContext, {
846
+ const { modLfoToPitch, modLfoToVolume } = instrumentKey;
847
+ note.modulationLFO = new OscillatorNode(this.audioContext, {
842
848
  frequency: this.centToHz(instrumentKey.freqModLFO),
843
849
  });
844
- note.modLFO.start(time);
845
- note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
846
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
847
- note.modLFO.connect(note.modLFOGain);
848
- note.modLFOGain.connect(note.bufferSource.detune);
849
- }
850
- startVibrato(channel, note, time) {
851
- const { instrumentKey } = note;
852
- note.vibLFOGain = new GainNode(this.audioContext, {
853
- gain: channel.vibratoDepth,
850
+ note.filterDepth = new GainNode(this.audioContext, {
851
+ gain: instrumentKey.modLfoToFilterFc,
852
+ });
853
+ const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
854
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
855
+ note.modulationDepth = new GainNode(this.audioContext, {
856
+ gain: modulationDepth * modulationDepthSign,
854
857
  });
855
- note.vibLFO = new OscillatorNode(this.audioContext, {
856
- frequency: this.centToHz(instrumentKey.freqModLFO) +
858
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
859
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
860
+ note.volumeDepth = new GainNode(this.audioContext, {
861
+ gain: volumeDepth * volumeDepthSign,
862
+ });
863
+ note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
864
+ note.modulationLFO.connect(note.filterDepth);
865
+ note.filterDepth.connect(note.filterNode.frequency);
866
+ note.modulationLFO.connect(note.modulationDepth);
867
+ note.modulationDepth.connect(note.bufferSource.detune);
868
+ note.modulationLFO.connect(note.volumeDepth);
869
+ note.volumeDepth.connect(note.volumeNode.gain);
870
+ }
871
+ startVibrato(channel, note, startTime) {
872
+ const { instrumentKey } = note;
873
+ const { vibLfoToPitch } = instrumentKey;
874
+ note.vibratoLFO = new OscillatorNode(this.audioContext, {
875
+ frequency: this.centToHz(instrumentKey.freqVibLFO) *
857
876
  channel.vibratoRate,
858
877
  });
859
- note.vibLFO.start(time + channel.vibratoDelay);
860
- note.vibLFO.connect(note.vibLFOGain);
861
- note.vibLFOGain.connect(note.bufferSource.detune);
878
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
879
+ const vibratoDepthSign = 0 < vibLfoToPitch;
880
+ note.vibratoDepth = new GainNode(this.audioContext, {
881
+ gain: vibratoDepth * vibratoDepthSign,
882
+ });
883
+ note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
884
+ note.vibratoLFO.connect(note.vibratoDepth);
885
+ note.vibratoDepth.connect(note.bufferSource.detune);
862
886
  }
863
887
  async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
864
888
  const semitoneOffset = this.calcSemitoneOffset(channel);
865
889
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
866
890
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
867
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
868
- this.setVolumeEnvelope(channel, note);
869
- this.setFilterEnvelope(channel, note);
870
- if (channel.modulation > 0) {
871
- const delayModLFO = startTime + instrumentKey.delayModLFO;
872
- this.startModulation(channel, note, delayModLFO);
891
+ this.setFilterNode(channel, note);
892
+ this.setVolumeEnvelope(note);
893
+ if (0 < channel.vibratoDepth) {
894
+ this.startVibrato(channel, note, startTime);
873
895
  }
874
- if (channel.vibratoDepth > 0) {
875
- const delayVibLFO = startTime + instrumentKey.delayVibLFO;
876
- this.startVibrato(channel, note, delayVibLFO);
896
+ if (0 < channel.modulationDepth) {
897
+ this.setPitch(note, semitoneOffset);
898
+ this.startModulation(channel, note, startTime);
899
+ }
900
+ else {
901
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
877
902
  }
878
903
  if (this.mono && channel.currentBufferSource) {
879
904
  channel.currentBufferSource.stop(startTime);
880
905
  channel.currentBufferSource = note.bufferSource;
881
906
  }
882
907
  note.bufferSource.connect(note.filterNode);
883
- note.filterNode.connect(note.gainNode);
908
+ note.filterNode.connect(note.volumeNode);
884
909
  note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
885
910
  return note;
886
911
  }
@@ -905,7 +930,8 @@ class Midy {
905
930
  if (!instrumentKey)
906
931
  return;
907
932
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
908
- this.connectEffects(channel, note.gainNode);
933
+ note.volumeNode.connect(channel.gainL);
934
+ note.volumeNode.connect(channel.gainR);
909
935
  if (channel.sostenutoPedal) {
910
936
  channel.sostenutoNotes.set(noteNumber, note);
911
937
  }
@@ -939,17 +965,14 @@ class Midy {
939
965
  const velocityRate = (velocity + 127) / 127;
940
966
  const volEndTime = stopTime +
941
967
  note.instrumentKey.volRelease * velocityRate;
942
- note.gainNode.gain
968
+ note.volumeNode.gain
943
969
  .cancelScheduledValues(stopTime)
944
970
  .linearRampToValueAtTime(0, volEndTime);
945
- const maxFreq = this.audioContext.sampleRate / 2;
946
- const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
947
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
948
- const modEndTime = stopTime +
971
+ const modRelease = stopTime +
949
972
  note.instrumentKey.modRelease * velocityRate;
950
973
  note.filterNode.frequency
951
974
  .cancelScheduledValues(stopTime)
952
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
975
+ .linearRampToValueAtTime(0, modRelease);
953
976
  note.ending = true;
954
977
  this.scheduleTask(() => {
955
978
  note.bufferSource.loop = false;
@@ -958,16 +981,18 @@ class Midy {
958
981
  note.bufferSource.onended = () => {
959
982
  scheduledNotes[i] = null;
960
983
  note.bufferSource.disconnect();
984
+ note.volumeNode.disconnect();
961
985
  note.filterNode.disconnect();
962
- note.gainNode.disconnect();
963
- if (note.modLFOGain)
964
- note.modLFOGain.disconnect();
965
- if (note.vibLFOGain)
966
- note.vibLFOGain.disconnect();
967
- if (note.modLFO)
968
- note.modLFO.stop();
969
- if (note.vibLFO)
970
- note.vibLFO.stop();
986
+ if (note.volumeDepth)
987
+ note.volumeDepth.disconnect();
988
+ if (note.modulationDepth)
989
+ note.modulationDepth.disconnect();
990
+ if (note.modulationLFO)
991
+ note.modulationLFO.stop();
992
+ if (note.vibratoDepth)
993
+ note.vibratoDepth.disconnect();
994
+ if (note.vibratoLFO)
995
+ note.vibratoLFO.stop();
971
996
  resolve();
972
997
  };
973
998
  note.bufferSource.stop(volEndTime);
@@ -983,10 +1008,10 @@ class Midy {
983
1008
  const channel = this.channels[channelNumber];
984
1009
  const promises = [];
985
1010
  channel.sustainPedal = false;
986
- channel.scheduledNotes.forEach((scheduledNotes) => {
987
- scheduledNotes.forEach((scheduledNote) => {
988
- if (scheduledNote) {
989
- const { noteNumber } = scheduledNote;
1011
+ channel.scheduledNotes.forEach((noteList) => {
1012
+ noteList.forEach((note) => {
1013
+ if (note) {
1014
+ const { noteNumber } = note;
990
1015
  const promise = this.releaseNote(channelNumber, noteNumber, velocity);
991
1016
  promises.push(promise);
992
1017
  }
@@ -1037,8 +1062,8 @@ class Midy {
1037
1062
  if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
1038
1063
  if (activeNotes.has(noteNumber)) {
1039
1064
  const activeNote = activeNotes.get(noteNumber);
1040
- const gain = activeNote.gainNode.gain.value;
1041
- activeNote.gainNode.gain
1065
+ const gain = activeNote.volumeNode.gain.value;
1066
+ activeNote.volumeNode.gain
1042
1067
  .cancelScheduledValues(now)
1043
1068
  .setValueAtTime(gain * pressure, now);
1044
1069
  }
@@ -1057,21 +1082,21 @@ class Midy {
1057
1082
  const activeNotes = this.getActiveNotes(channel, now);
1058
1083
  if (channel.channelPressure.amplitudeControl !== 1) {
1059
1084
  activeNotes.forEach((activeNote) => {
1060
- const gain = activeNote.gainNode.gain.value;
1061
- activeNote.gainNode.gain
1085
+ const gain = activeNote.volumeNode.gain.value;
1086
+ activeNote.volumeNode.gain
1062
1087
  .cancelScheduledValues(now)
1063
1088
  .setValueAtTime(gain * pressure, now);
1064
1089
  });
1065
1090
  }
1066
1091
  }
1067
1092
  handlePitchBendMessage(channelNumber, lsb, msb) {
1068
- const pitchBend = msb * 128 + lsb;
1093
+ const pitchBend = msb * 128 + lsb - 8192;
1069
1094
  this.setPitchBend(channelNumber, pitchBend);
1070
1095
  }
1071
1096
  setPitchBend(channelNumber, pitchBend) {
1072
1097
  const channel = this.channels[channelNumber];
1073
1098
  const prevPitchBend = channel.pitchBend;
1074
- channel.pitchBend = (pitchBend - 8192) / 8192;
1099
+ channel.pitchBend = pitchBend / 8192;
1075
1100
  const detuneChange = (channel.pitchBend - prevPitchBend) *
1076
1101
  channel.pitchBendRange * 100;
1077
1102
  this.updateDetune(channel, detuneChange);
@@ -1081,7 +1106,7 @@ class Midy {
1081
1106
  case 0:
1082
1107
  return this.setBankMSB(channelNumber, value);
1083
1108
  case 1:
1084
- return this.setModulation(channelNumber, value);
1109
+ return this.setModulationDepth(channelNumber, value);
1085
1110
  case 5:
1086
1111
  return this.setPortamentoTime(channelNumber, value);
1087
1112
  case 6:
@@ -1148,18 +1173,19 @@ class Midy {
1148
1173
  const now = this.audioContext.currentTime;
1149
1174
  const activeNotes = this.getActiveNotes(channel, now);
1150
1175
  activeNotes.forEach((activeNote) => {
1151
- if (activeNote.modLFO) {
1152
- const { gainNode, instrumentKey } = activeNote;
1153
- gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
1176
+ if (activeNote.modulationDepth) {
1177
+ activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1154
1178
  }
1155
1179
  else {
1180
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1181
+ this.setPitch(activeNote, semitoneOffset);
1156
1182
  this.startModulation(channel, activeNote, now);
1157
1183
  }
1158
1184
  });
1159
1185
  }
1160
- setModulation(channelNumber, modulation) {
1186
+ setModulationDepth(channelNumber, modulation) {
1161
1187
  const channel = this.channels[channelNumber];
1162
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1188
+ channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1163
1189
  this.updateModulation(channel);
1164
1190
  }
1165
1191
  setPortamentoTime(channelNumber, portamentoTime) {
@@ -1217,20 +1243,30 @@ class Midy {
1217
1243
  this.channels[channelNumber].portamento = value >= 64;
1218
1244
  }
1219
1245
  setReverbSendLevel(channelNumber, reverbSendLevel) {
1220
- const now = this.audioContext.currentTime;
1221
1246
  const channel = this.channels[channelNumber];
1222
- const reverbEffect = channel.reverbEffect;
1223
- channel.reverbSendLevel = reverbSendLevel / 127;
1224
- reverbEffect.output.gain.cancelScheduledValues(now);
1225
- reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1247
+ const reverbEffect = this.reverbEffect;
1248
+ if (0 < reverbSendLevel) {
1249
+ const now = this.audioContext.currentTime;
1250
+ channel.reverbSendLevel = reverbSendLevel / 127;
1251
+ reverbEffect.output.gain.cancelScheduledValues(now);
1252
+ reverbEffect.output.gain.setValueAtTime(channel.reverbSendLevel, now);
1253
+ }
1254
+ else if (channel.reverbSendLevel !== 0) {
1255
+ channel.merger.disconnect(reverbEffect.input);
1256
+ }
1226
1257
  }
1227
1258
  setChorusSendLevel(channelNumber, chorusSendLevel) {
1228
- const now = this.audioContext.currentTime;
1229
1259
  const channel = this.channels[channelNumber];
1230
- const chorusEffect = channel.chorusEffect;
1231
- channel.chorusSendLevel = chorusSendLevel / 127;
1232
- chorusEffect.output.gain.cancelScheduledValues(now);
1233
- chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1260
+ const chorusEffect = this.chorusEffect;
1261
+ if (0 < chorusSendLevel) {
1262
+ const now = this.audioContext.currentTime;
1263
+ channel.chorusSendLevel = chorusSendLevel / 127;
1264
+ chorusEffect.output.gain.cancelScheduledValues(now);
1265
+ chorusEffect.output.gain.setValueAtTime(channel.chorusSendLevel, now);
1266
+ }
1267
+ else if (channel.chorusSendLevel !== 0) {
1268
+ channel.merger.disconnect(chorusEffect.input);
1269
+ }
1234
1270
  }
1235
1271
  setSostenutoPedal(channelNumber, value) {
1236
1272
  const isOn = value >= 64;
@@ -1251,21 +1287,15 @@ class Midy {
1251
1287
  }
1252
1288
  setVibratoRate(channelNumber, vibratoRate) {
1253
1289
  const channel = this.channels[channelNumber];
1254
- channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1290
+ channel.vibratoRate = vibratoRate / 64;
1255
1291
  }
1256
1292
  setVibratoDepth(channelNumber, vibratoDepth) {
1257
1293
  const channel = this.channels[channelNumber];
1258
- channel.vibratoDepth = vibratoDepth / 127;
1294
+ channel.vibratoDepth = vibratoDepth / 64;
1259
1295
  }
1260
1296
  setVibratoDelay(channelNumber, vibratoDelay) {
1261
- // Access Virus: 0-10sec
1262
- // Elektron: 0-5sec
1263
- // Korg: 0-5sec
1264
- // Nord: 0-5sec
1265
- // Roland: 0-5sec
1266
- // Yamaha: 0-8sec
1267
1297
  const channel = this.channels[channelNumber];
1268
- channel.vibratoDelay = vibratoDelay / 127 * 5; // 0-5sec
1298
+ channel.vibratoDelay = vibratoDelay / 64;
1269
1299
  }
1270
1300
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1271
1301
  if (maxLSB < channel.dataLSB) {
@@ -1387,47 +1417,23 @@ class Midy {
1387
1417
  handleModulationDepthRangeRPN(channelNumber) {
1388
1418
  const channel = this.channels[channelNumber];
1389
1419
  this.limitData(channel, 0, 127, 0, 127);
1390
- const modulationDepthRange = dataMSB + dataLSB / 128;
1420
+ const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
1391
1421
  this.setModulationDepthRange(channelNumber, modulationDepthRange);
1392
1422
  }
1393
1423
  setModulationDepthRange(channelNumber, modulationDepthRange) {
1394
1424
  const channel = this.channels[channelNumber];
1395
1425
  channel.modulationDepthRange = modulationDepthRange;
1396
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1426
+ channel.modulationDepth = (modulation / 127) * modulationDepthRange;
1397
1427
  this.updateModulation(channel);
1398
1428
  }
1399
1429
  allSoundOff(channelNumber) {
1400
- const now = this.audioContext.currentTime;
1401
- const channel = this.channels[channelNumber];
1402
- const velocity = 0;
1403
- const stopPedal = true;
1404
- const promises = [];
1405
- channel.scheduledNotes.forEach((noteList) => {
1406
- const activeNote = this.getActiveNote(noteList, now);
1407
- if (activeNote) {
1408
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1409
- promises.push(notePromise);
1410
- }
1411
- });
1412
- return promises;
1430
+ return this.stopChannelNotes(channelNumber, 0, true);
1413
1431
  }
1414
1432
  resetAllControllers(channelNumber) {
1415
1433
  Object.assign(this.channels[channelNumber], this.effectSettings);
1416
1434
  }
1417
1435
  allNotesOff(channelNumber) {
1418
- const now = this.audioContext.currentTime;
1419
- const channel = this.channels[channelNumber];
1420
- const velocity = 0;
1421
- const stopPedal = false;
1422
- const promises = [];
1423
- channel.scheduledNotes.forEach((noteList) => {
1424
- const activeNote = this.getActiveNote(noteList, now);
1425
- if (activeNote) {
1426
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1427
- promises.push(notePromise);
1428
- }
1429
- });
1430
- return promises;
1436
+ return this.stopChannelNotes(channelNumber, 0, false);
1431
1437
  }
1432
1438
  omniOff() {
1433
1439
  this.omni = false;
@@ -1592,11 +1598,9 @@ class Midy {
1592
1598
  }
1593
1599
  setReverbType(type) {
1594
1600
  this.reverb.time = this.getReverbTimeFromType(type);
1595
- this.reverb.feedback = (type === 8) ? 0.1 : 0.2;
1596
- const { audioContext, channels, options } = this;
1597
- for (let i = 0; i < channels.length; i++) {
1598
- channels[i].reverbEffect = options.reverbAlgorithm(audioContext);
1599
- }
1601
+ this.reverb.feedback = (type === 8) ? 0.9 : 0.8;
1602
+ const { audioContext, options } = this;
1603
+ this.reverbEffect = options.reverbAlgorithm(audioContext);
1600
1604
  }
1601
1605
  getReverbTimeFromType(type) {
1602
1606
  switch (type) {
@@ -1738,15 +1742,17 @@ class Midy {
1738
1742
  return value * 0.00763;
1739
1743
  }
1740
1744
  setChorusSendToReverb(value) {
1741
- const now = this.audioContext.currentTime;
1742
1745
  const sendToReverb = this.getChorusSendToReverb(value);
1743
- this.chorus.sendToReverb = sendToReverb;
1744
- for (let i = 0; i < this.channels.length; i++) {
1745
- const chorusEffect = this.channels[i].chorusEffect;
1746
- chorusEffect.sendGain.gain
1746
+ if (0 < sendToReverb) {
1747
+ const now = this.audioContext.currentTime;
1748
+ this.chorus.sendToReverb = sendToReverb;
1749
+ this.chorusEffect.sendGain.gain
1747
1750
  .cancelScheduledValues(now)
1748
1751
  .setValueAtTime(sendToReverb, now);
1749
1752
  }
1753
+ else if (this.chorus.sendToReverb !== 0) {
1754
+ this.chorusEffect.sendGain.disconnect(this.reverbEffect.input);
1755
+ }
1750
1756
  }
1751
1757
  getChorusSendToReverb(value) {
1752
1758
  return value * 0.00787;
@@ -1788,9 +1794,9 @@ Object.defineProperty(Midy, "channelSettings", {
1788
1794
  portamentoTime: 0,
1789
1795
  reverbSendLevel: 0,
1790
1796
  chorusSendLevel: 0,
1791
- vibratoRate: 5,
1792
- vibratoDepth: 0.5,
1793
- vibratoDelay: 2.5,
1797
+ vibratoRate: 1,
1798
+ vibratoDepth: 1,
1799
+ vibratoDelay: 1,
1794
1800
  bank: 121 * 128,
1795
1801
  bankMSB: 121,
1796
1802
  bankLSB: 0,
@@ -1800,7 +1806,7 @@ Object.defineProperty(Midy, "channelSettings", {
1800
1806
  pitchBend: 0,
1801
1807
  fineTuning: 0, // cb
1802
1808
  coarseTuning: 0, // cb
1803
- modulationDepthRange: 0.5, // cb
1809
+ modulationDepthRange: 50, // cent
1804
1810
  }
1805
1811
  });
1806
1812
  Object.defineProperty(Midy, "effectSettings", {
@@ -1809,7 +1815,7 @@ Object.defineProperty(Midy, "effectSettings", {
1809
1815
  writable: true,
1810
1816
  value: {
1811
1817
  expression: 1,
1812
- modulation: 0,
1818
+ modulationDepth: 0,
1813
1819
  sustainPedal: false,
1814
1820
  portamento: false,
1815
1821
  sostenutoPedal: false,