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