@marmooo/midy 0.1.2 → 0.1.4

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 +19 -13
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +171 -131
  7. package/esm/midy-GM2.d.ts +22 -14
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +186 -133
  10. package/esm/midy-GMLite.d.ts +17 -13
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +159 -131
  13. package/esm/midy.d.ts +30 -16
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +266 -166
  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 +19 -13
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +171 -131
  23. package/script/midy-GM2.d.ts +22 -14
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +186 -133
  26. package/script/midy-GMLite.d.ts +17 -13
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +159 -131
  29. package/script/midy.d.ts +30 -16
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +266 -166
  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,
@@ -413,7 +419,7 @@ class Midy {
413
419
  const t = this.audioContext.currentTime + offset;
414
420
  queueIndex = await this.scheduleTimelineEvents(t, offset, queueIndex);
415
421
  if (this.isPausing) {
416
- await this.stopNotes();
422
+ await this.stopNotes(0, true);
417
423
  this.notePromises = [];
418
424
  resolve();
419
425
  this.isPausing = false;
@@ -421,7 +427,7 @@ class Midy {
421
427
  return;
422
428
  }
423
429
  else if (this.isStopping) {
424
- await this.stopNotes();
430
+ await this.stopNotes(0, true);
425
431
  this.notePromises = [];
426
432
  resolve();
427
433
  this.isStopping = false;
@@ -429,7 +435,7 @@ class Midy {
429
435
  return;
430
436
  }
431
437
  else if (this.isSeeking) {
432
- this.stopNotes();
438
+ this.stopNotes(0, true);
433
439
  this.startTime = this.audioContext.currentTime;
434
440
  queueIndex = this.getQueueIndex(this.resumeTime);
435
441
  offset = this.resumeTime - this.startTime;
@@ -544,21 +550,25 @@ class Midy {
544
550
  }
545
551
  return { instruments, timeline };
546
552
  }
547
- stopNotes() {
553
+ async stopChannelNotes(channelNumber, velocity, stopPedal) {
548
554
  const now = this.audioContext.currentTime;
549
- const velocity = 0;
550
- const stopPedal = true;
551
- this.channels.forEach((channel, channelNumber) => {
552
- channel.scheduledNotes.forEach((scheduledNotes) => {
553
- scheduledNotes.forEach((scheduledNote) => {
554
- if (scheduledNote) {
555
- const promise = this.scheduleNoteRelease(channelNumber, scheduledNote.noteNumber, velocity, now, stopPedal);
556
- this.notePromises.push(promise);
557
- }
558
- });
559
- });
560
- channel.scheduledNotes.clear();
555
+ const channel = this.channels[channelNumber];
556
+ channel.scheduledNotes.forEach((noteList) => {
557
+ for (let i = 0; i < noteList.length; i++) {
558
+ const note = noteList[i];
559
+ if (!note)
560
+ continue;
561
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
562
+ this.notePromises.push(promise);
563
+ }
561
564
  });
565
+ channel.scheduledNotes.clear();
566
+ await Promise.all(this.notePromises);
567
+ }
568
+ stopNotes(velocity, stopPedal) {
569
+ for (let i = 0; i < this.channels.length; i++) {
570
+ this.stopChannelNotes(i, velocity, stopPedal);
571
+ }
562
572
  return Promise.all(this.notePromises);
563
573
  }
564
574
  async start() {
@@ -771,98 +781,139 @@ class Midy {
771
781
  return instrumentKey.playbackRate(noteNumber) *
772
782
  Math.pow(2, semitoneOffset / 12);
773
783
  }
774
- setVolumeEnvelope(note) {
784
+ setVolumeEnvelope(channel, note) {
775
785
  const { instrumentKey, startTime } = note;
776
- note.gainNode = new GainNode(this.audioContext, { gain: 0 });
777
786
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
778
787
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
779
788
  const volDelay = startTime + instrumentKey.volDelay;
780
- const volAttack = volDelay + instrumentKey.volAttack;
789
+ const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
781
790
  const volHold = volAttack + instrumentKey.volHold;
782
- const volDecay = volHold + instrumentKey.volDecay;
783
- note.gainNode.gain
791
+ const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
792
+ note.volumeNode.gain
793
+ .cancelScheduledValues(startTime)
794
+ .setValueAtTime(0, startTime)
784
795
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
785
796
  .exponentialRampToValueAtTime(attackVolume, volAttack)
786
797
  .setValueAtTime(attackVolume, volHold)
787
798
  .linearRampToValueAtTime(sustainVolume, volDecay);
788
799
  }
800
+ setPitch(note, semitoneOffset) {
801
+ const { instrumentKey, noteNumber, startTime } = note;
802
+ const modEnvToPitch = instrumentKey.modEnvToPitch / 100;
803
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
804
+ if (modEnvToPitch === 0)
805
+ return;
806
+ const basePitch = note.bufferSource.playbackRate.value;
807
+ const peekPitch = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset + modEnvToPitch);
808
+ const modDelay = startTime + instrumentKey.modDelay;
809
+ const modAttack = modDelay + instrumentKey.modAttack;
810
+ const modHold = modAttack + instrumentKey.modHold;
811
+ const modDecay = modHold + instrumentKey.modDecay;
812
+ note.bufferSource.playbackRate.value
813
+ .setValueAtTime(basePitch, modDelay)
814
+ .exponentialRampToValueAtTime(peekPitch, modAttack)
815
+ .setValueAtTime(peekPitch, modHold)
816
+ .linearRampToValueAtTime(basePitch, modDecay);
817
+ }
818
+ clampCutoffFrequency(frequency) {
819
+ const minFrequency = 20; // min Hz of initialFilterFc
820
+ const maxFrequency = 20000; // max Hz of initialFilterFc
821
+ return Math.max(minFrequency, Math.min(frequency, maxFrequency));
822
+ }
789
823
  setFilterEnvelope(channel, note) {
790
- const { instrumentKey, startTime, noteNumber } = note;
824
+ const { instrumentKey, noteNumber, startTime } = note;
791
825
  const softPedalFactor = 1 -
792
826
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
793
- const maxFreq = this.audioContext.sampleRate / 2;
794
827
  const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
795
- softPedalFactor;
796
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
797
- const sustainFreq = (baseFreq +
798
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
828
+ softPedalFactor * channel.brightness;
829
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
830
+ const sustainFreq = baseFreq +
831
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
832
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
833
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
834
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
799
835
  const modDelay = startTime + instrumentKey.modDelay;
800
836
  const modAttack = modDelay + instrumentKey.modAttack;
801
837
  const modHold = modAttack + instrumentKey.modHold;
802
838
  const modDecay = modHold + instrumentKey.modDecay;
803
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
804
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
805
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
806
- note.filterNode = new BiquadFilterNode(this.audioContext, {
807
- type: "lowpass",
808
- Q: instrumentKey.initialFilterQ / 10, // dB
809
- frequency: adjustedBaseFreq,
810
- });
811
839
  note.filterNode.frequency
840
+ .cancelScheduledValues(startTime)
841
+ .setValueAtTime(adjustedBaseFreq, startTime)
812
842
  .setValueAtTime(adjustedBaseFreq, modDelay)
813
843
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
814
844
  .setValueAtTime(adjustedPeekFreq, modHold)
815
845
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
816
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
817
846
  }
818
- startModulation(channel, note, time) {
847
+ startModulation(channel, note, startTime) {
819
848
  const { instrumentKey } = note;
820
- note.modLFOGain = new GainNode(this.audioContext, {
821
- gain: this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation),
822
- });
823
- note.modLFO = new OscillatorNode(this.audioContext, {
849
+ const { modLfoToPitch, modLfoToVolume } = instrumentKey;
850
+ note.modulationLFO = new OscillatorNode(this.audioContext, {
824
851
  frequency: this.centToHz(instrumentKey.freqModLFO),
825
852
  });
826
- note.modLFO.start(time);
827
- note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
828
- note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
829
- note.modLFO.connect(note.modLFOGain);
830
- note.modLFOGain.connect(note.bufferSource.detune);
831
- }
832
- startVibrato(channel, note, time) {
833
- const { instrumentKey } = note;
834
- note.vibLFOGain = new GainNode(this.audioContext, {
835
- gain: channel.vibratoDepth,
853
+ note.filterDepth = new GainNode(this.audioContext, {
854
+ gain: instrumentKey.modLfoToFilterFc,
855
+ });
856
+ const modulationDepth = Math.abs(modLfoToPitch) + channel.modulationDepth;
857
+ const modulationDepthSign = (0 < modLfoToPitch) ? 1 : -1;
858
+ note.modulationDepth = new GainNode(this.audioContext, {
859
+ gain: modulationDepth * modulationDepthSign,
836
860
  });
837
- note.vibLFO = new OscillatorNode(this.audioContext, {
838
- frequency: this.centToHz(instrumentKey.freqModLFO) +
861
+ const volumeDepth = this.cbToRatio(Math.abs(modLfoToVolume)) - 1;
862
+ const volumeDepthSign = (0 < modLfoToVolume) ? 1 : -1;
863
+ note.volumeDepth = new GainNode(this.audioContext, {
864
+ gain: volumeDepth * volumeDepthSign,
865
+ });
866
+ note.modulationLFO.start(startTime + instrumentKey.delayModLFO);
867
+ note.modulationLFO.connect(note.filterDepth);
868
+ note.filterDepth.connect(note.filterNode.frequency);
869
+ note.modulationLFO.connect(note.modulationDepth);
870
+ note.modulationDepth.connect(note.bufferSource.detune);
871
+ note.modulationLFO.connect(note.volumeDepth);
872
+ note.volumeDepth.connect(note.volumeNode.gain);
873
+ }
874
+ startVibrato(channel, note, startTime) {
875
+ const { instrumentKey } = note;
876
+ const { vibLfoToPitch } = instrumentKey;
877
+ note.vibratoLFO = new OscillatorNode(this.audioContext, {
878
+ frequency: this.centToHz(instrumentKey.freqVibLFO) *
839
879
  channel.vibratoRate,
840
880
  });
841
- note.vibLFO.start(time + channel.vibratoDelay);
842
- note.vibLFO.connect(note.vibLFOGain);
843
- note.vibLFOGain.connect(note.bufferSource.detune);
881
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.vibratoDepth;
882
+ const vibratoDepthSign = 0 < vibLfoToPitch;
883
+ note.vibratoDepth = new GainNode(this.audioContext, {
884
+ gain: vibratoDepth * vibratoDepthSign,
885
+ });
886
+ note.vibratoLFO.start(startTime + instrumentKey.delayVibLFO * channel.vibratoDelay);
887
+ note.vibratoLFO.connect(note.vibratoDepth);
888
+ note.vibratoDepth.connect(note.bufferSource.detune);
844
889
  }
845
890
  async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
846
891
  const semitoneOffset = this.calcSemitoneOffset(channel);
847
892
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
848
893
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
849
- note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
850
- this.setVolumeEnvelope(note);
894
+ note.volumeNode = new GainNode(this.audioContext);
895
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
896
+ type: "lowpass",
897
+ Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
898
+ });
899
+ this.setVolumeEnvelope(channel, note);
851
900
  this.setFilterEnvelope(channel, note);
852
- if (channel.modulation > 0) {
853
- const delayModLFO = startTime + instrumentKey.delayModLFO;
854
- this.startModulation(channel, note, delayModLFO);
901
+ if (0 < channel.vibratoDepth) {
902
+ this.startVibrato(channel, note, startTime);
903
+ }
904
+ if (0 < channel.modulationDepth) {
905
+ this.setPitch(note, semitoneOffset);
906
+ this.startModulation(channel, note, startTime);
855
907
  }
856
- if (channel.vibratoDepth > 0) {
857
- const delayVibLFO = startTime + instrumentKey.delayVibLFO;
858
- this.startVibrato(channel, note, delayVibLFO);
908
+ else {
909
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
859
910
  }
860
911
  if (this.mono && channel.currentBufferSource) {
861
912
  channel.currentBufferSource.stop(startTime);
862
913
  channel.currentBufferSource = note.bufferSource;
863
914
  }
864
915
  note.bufferSource.connect(note.filterNode);
865
- note.filterNode.connect(note.gainNode);
916
+ note.filterNode.connect(note.volumeNode);
866
917
  note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
867
918
  return note;
868
919
  }
@@ -887,8 +938,8 @@ class Midy {
887
938
  if (!instrumentKey)
888
939
  return;
889
940
  const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
890
- note.gainNode.connect(channel.gainL);
891
- note.gainNode.connect(channel.gainR);
941
+ note.volumeNode.connect(channel.gainL);
942
+ note.volumeNode.connect(channel.gainR);
892
943
  if (channel.sostenutoPedal) {
893
944
  channel.sostenutoNotes.set(noteNumber, note);
894
945
  }
@@ -904,7 +955,7 @@ class Midy {
904
955
  const now = this.audioContext.currentTime;
905
956
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
906
957
  }
907
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
958
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
908
959
  const channel = this.channels[channelNumber];
909
960
  if (stopPedal && channel.sustainPedal)
910
961
  return;
@@ -919,20 +970,15 @@ class Midy {
919
970
  continue;
920
971
  if (note.ending)
921
972
  continue;
922
- const velocityRate = (velocity + 127) / 127;
923
973
  const volEndTime = stopTime +
924
- note.instrumentKey.volRelease * velocityRate;
925
- note.gainNode.gain
974
+ note.instrumentKey.volRelease * channel.releaseTime;
975
+ note.volumeNode.gain
926
976
  .cancelScheduledValues(stopTime)
927
977
  .linearRampToValueAtTime(0, volEndTime);
928
- const maxFreq = this.audioContext.sampleRate / 2;
929
- const baseFreq = this.centToHz(note.instrumentKey.initialFilterFc);
930
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
931
- const modEndTime = stopTime +
932
- note.instrumentKey.modRelease * velocityRate;
978
+ const modRelease = stopTime + note.instrumentKey.modRelease;
933
979
  note.filterNode.frequency
934
980
  .cancelScheduledValues(stopTime)
935
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
981
+ .linearRampToValueAtTime(0, modRelease);
936
982
  note.ending = true;
937
983
  this.scheduleTask(() => {
938
984
  note.bufferSource.loop = false;
@@ -941,16 +987,18 @@ class Midy {
941
987
  note.bufferSource.onended = () => {
942
988
  scheduledNotes[i] = null;
943
989
  note.bufferSource.disconnect();
990
+ note.volumeNode.disconnect();
944
991
  note.filterNode.disconnect();
945
- note.gainNode.disconnect();
946
- if (note.modLFOGain)
947
- note.modLFOGain.disconnect();
948
- if (note.vibLFOGain)
949
- note.vibLFOGain.disconnect();
950
- if (note.modLFO)
951
- note.modLFO.stop();
952
- if (note.vibLFO)
953
- note.vibLFO.stop();
992
+ if (note.volumeDepth)
993
+ note.volumeDepth.disconnect();
994
+ if (note.modulationDepth)
995
+ note.modulationDepth.disconnect();
996
+ if (note.modulationLFO)
997
+ note.modulationLFO.stop();
998
+ if (note.vibratoDepth)
999
+ note.vibratoDepth.disconnect();
1000
+ if (note.vibratoLFO)
1001
+ note.vibratoLFO.stop();
954
1002
  resolve();
955
1003
  };
956
1004
  note.bufferSource.stop(volEndTime);
@@ -966,14 +1014,15 @@ class Midy {
966
1014
  const channel = this.channels[channelNumber];
967
1015
  const promises = [];
968
1016
  channel.sustainPedal = false;
969
- channel.scheduledNotes.forEach((scheduledNotes) => {
970
- scheduledNotes.forEach((scheduledNote) => {
971
- if (scheduledNote) {
972
- const { noteNumber } = scheduledNote;
973
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
974
- promises.push(promise);
975
- }
976
- });
1017
+ channel.scheduledNotes.forEach((noteList) => {
1018
+ for (let i = 0; i < noteList.length; i++) {
1019
+ const note = noteList[i];
1020
+ if (!note)
1021
+ continue;
1022
+ const { noteNumber } = note;
1023
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
1024
+ promises.push(promise);
1025
+ }
977
1026
  });
978
1027
  return promises;
979
1028
  }
@@ -1020,8 +1069,8 @@ class Midy {
1020
1069
  if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
1021
1070
  if (activeNotes.has(noteNumber)) {
1022
1071
  const activeNote = activeNotes.get(noteNumber);
1023
- const gain = activeNote.gainNode.gain.value;
1024
- activeNote.gainNode.gain
1072
+ const gain = activeNote.volumeNode.gain.value;
1073
+ activeNote.volumeNode.gain
1025
1074
  .cancelScheduledValues(now)
1026
1075
  .setValueAtTime(gain * pressure, now);
1027
1076
  }
@@ -1040,8 +1089,8 @@ class Midy {
1040
1089
  const activeNotes = this.getActiveNotes(channel, now);
1041
1090
  if (channel.channelPressure.amplitudeControl !== 1) {
1042
1091
  activeNotes.forEach((activeNote) => {
1043
- const gain = activeNote.gainNode.gain.value;
1044
- activeNote.gainNode.gain
1092
+ const gain = activeNote.volumeNode.gain.value;
1093
+ activeNote.volumeNode.gain
1045
1094
  .cancelScheduledValues(now)
1046
1095
  .setValueAtTime(gain * pressure, now);
1047
1096
  });
@@ -1064,7 +1113,7 @@ class Midy {
1064
1113
  case 0:
1065
1114
  return this.setBankMSB(channelNumber, value);
1066
1115
  case 1:
1067
- return this.setModulation(channelNumber, value);
1116
+ return this.setModulationDepth(channelNumber, value);
1068
1117
  case 5:
1069
1118
  return this.setPortamentoTime(channelNumber, value);
1070
1119
  case 6:
@@ -1087,7 +1136,16 @@ class Midy {
1087
1136
  return this.setSostenutoPedal(channelNumber, value);
1088
1137
  case 67:
1089
1138
  return this.setSoftPedal(channelNumber, value);
1090
- // TODO: 71-75
1139
+ case 71:
1140
+ return this.setFilterResonance(channelNumber, value);
1141
+ case 72:
1142
+ return this.setReleaseTime(channelNumber, value);
1143
+ case 73:
1144
+ return this.setAttackTime(channelNumber, value);
1145
+ case 74:
1146
+ return this.setBrightness(channelNumber, value);
1147
+ case 75:
1148
+ return this.setDecayTime(channelNumber, value);
1091
1149
  case 76:
1092
1150
  return this.setVibratoRate(channelNumber, value);
1093
1151
  case 77:
@@ -1129,20 +1187,25 @@ class Midy {
1129
1187
  }
1130
1188
  updateModulation(channel) {
1131
1189
  const now = this.audioContext.currentTime;
1132
- const activeNotes = this.getActiveNotes(channel, now);
1133
- activeNotes.forEach((activeNote) => {
1134
- if (activeNote.modLFO) {
1135
- const { gainNode, instrumentKey } = activeNote;
1136
- gainNode.gain.setValueAtTime(this.cbToRatio(instrumentKey.modLfoToVolume + channel.modulation), now);
1137
- }
1138
- else {
1139
- this.startModulation(channel, activeNote, now);
1190
+ channel.scheduledNotes.forEach((noteList) => {
1191
+ for (let i = 0; i < noteList.length; i++) {
1192
+ const note = noteList[i];
1193
+ if (!note)
1194
+ continue;
1195
+ if (note.modulationDepth) {
1196
+ note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1197
+ }
1198
+ else {
1199
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1200
+ this.setPitch(note, semitoneOffset);
1201
+ this.startModulation(channel, note, now);
1202
+ }
1140
1203
  }
1141
1204
  });
1142
1205
  }
1143
- setModulation(channelNumber, modulation) {
1206
+ setModulationDepth(channelNumber, modulation) {
1144
1207
  const channel = this.channels[channelNumber];
1145
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1208
+ channel.modulationDepth = (modulation / 127) * channel.modulationDepthRange;
1146
1209
  this.updateModulation(channel);
1147
1210
  }
1148
1211
  setPortamentoTime(channelNumber, portamentoTime) {
@@ -1242,23 +1305,75 @@ class Midy {
1242
1305
  const channel = this.channels[channelNumber];
1243
1306
  channel.softPedal = softPedal / 127;
1244
1307
  }
1308
+ setFilterResonance(channelNumber, filterResonance) {
1309
+ const now = this.audioContext.currentTime;
1310
+ const channel = this.channels[channelNumber];
1311
+ channel.filterResonance = filterResonance / 64;
1312
+ channel.scheduledNotes.forEach((noteList) => {
1313
+ for (let i = 0; i < noteList.length; i++) {
1314
+ const note = noteList[i];
1315
+ if (!note)
1316
+ continue;
1317
+ const Q = note.instrumentKey.initialFilterQ / 10 *
1318
+ channel.filterResonance;
1319
+ note.filterNode.Q.setValueAtTime(Q, now);
1320
+ }
1321
+ });
1322
+ }
1323
+ setReleaseTime(channelNumber, releaseTime) {
1324
+ const channel = this.channels[channelNumber];
1325
+ channel.releaseTime = releaseTime / 64;
1326
+ }
1327
+ setAttackTime(channelNumber, attackTime) {
1328
+ const now = this.audioContext.currentTime;
1329
+ const channel = this.channels[channelNumber];
1330
+ channel.attackTime = attackTime / 64;
1331
+ channel.scheduledNotes.forEach((noteList) => {
1332
+ for (let i = 0; i < noteList.length; i++) {
1333
+ const note = noteList[i];
1334
+ if (!note)
1335
+ continue;
1336
+ if (note.startTime < now)
1337
+ continue;
1338
+ this.setVolumeEnvelope(channel, note);
1339
+ }
1340
+ });
1341
+ }
1342
+ setBrightness(channelNumber, brightness) {
1343
+ const channel = this.channels[channelNumber];
1344
+ channel.brightness = brightness / 64;
1345
+ channel.scheduledNotes.forEach((noteList) => {
1346
+ for (let i = 0; i < noteList.length; i++) {
1347
+ const note = noteList[i];
1348
+ if (!note)
1349
+ continue;
1350
+ this.setFilterEnvelope(channel, note);
1351
+ }
1352
+ });
1353
+ }
1354
+ setDecayTime(channelNumber, dacayTime) {
1355
+ const channel = this.channels[channelNumber];
1356
+ channel.decayTime = dacayTime / 64;
1357
+ channel.scheduledNotes.forEach((noteList) => {
1358
+ for (let i = 0; i < noteList.length; i++) {
1359
+ const note = noteList[i];
1360
+ if (!note)
1361
+ continue;
1362
+ this.setVolumeEnvelope(channel, note);
1363
+ }
1364
+ });
1365
+ }
1245
1366
  setVibratoRate(channelNumber, vibratoRate) {
1246
1367
  const channel = this.channels[channelNumber];
1247
- channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1368
+ channel.vibratoRate = vibratoRate / 64;
1248
1369
  }
1249
1370
  setVibratoDepth(channelNumber, vibratoDepth) {
1250
1371
  const channel = this.channels[channelNumber];
1251
- channel.vibratoDepth = vibratoDepth / 127;
1372
+ channel.vibratoDepth = vibratoDepth / 64;
1252
1373
  }
1253
1374
  setVibratoDelay(channelNumber, vibratoDelay) {
1254
- // Access Virus: 0-10sec
1255
- // Elektron: 0-5sec
1256
- // Korg: 0-5sec
1257
- // Nord: 0-5sec
1258
- // Roland: 0-5sec
1259
- // Yamaha: 0-8sec
1260
1375
  const channel = this.channels[channelNumber];
1261
- channel.vibratoDelay = vibratoDelay / 127 * 5; // 0-5sec
1376
+ channel.vibratoDelay = vibratoDelay / 64;
1262
1377
  }
1263
1378
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1264
1379
  if (maxLSB < channel.dataLSB) {
@@ -1328,13 +1443,17 @@ class Midy {
1328
1443
  }
1329
1444
  updateDetune(channel, detuneChange) {
1330
1445
  const now = this.audioContext.currentTime;
1331
- const activeNotes = this.getActiveNotes(channel, now);
1332
- activeNotes.forEach((activeNote) => {
1333
- const { bufferSource } = activeNote;
1334
- const detune = bufferSource.detune.value + detuneChange;
1335
- bufferSource.detune
1336
- .cancelScheduledValues(now)
1337
- .setValueAtTime(detune, now);
1446
+ channel.scheduledNotes.forEach((noteList) => {
1447
+ for (let i = 0; i < noteList.length; i++) {
1448
+ const note = noteList[i];
1449
+ if (!note)
1450
+ continue;
1451
+ const { bufferSource } = note;
1452
+ const detune = bufferSource.detune.value + detuneChange;
1453
+ bufferSource.detune
1454
+ .cancelScheduledValues(now)
1455
+ .setValueAtTime(detune, now);
1456
+ }
1338
1457
  });
1339
1458
  }
1340
1459
  handlePitchBendRangeRPN(channelNumber) {
@@ -1380,47 +1499,23 @@ class Midy {
1380
1499
  handleModulationDepthRangeRPN(channelNumber) {
1381
1500
  const channel = this.channels[channelNumber];
1382
1501
  this.limitData(channel, 0, 127, 0, 127);
1383
- const modulationDepthRange = dataMSB + dataLSB / 128;
1502
+ const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
1384
1503
  this.setModulationDepthRange(channelNumber, modulationDepthRange);
1385
1504
  }
1386
1505
  setModulationDepthRange(channelNumber, modulationDepthRange) {
1387
1506
  const channel = this.channels[channelNumber];
1388
1507
  channel.modulationDepthRange = modulationDepthRange;
1389
- channel.modulation = (modulation / 127) * channel.modulationDepthRange;
1508
+ channel.modulationDepth = (modulation / 127) * modulationDepthRange;
1390
1509
  this.updateModulation(channel);
1391
1510
  }
1392
1511
  allSoundOff(channelNumber) {
1393
- const now = this.audioContext.currentTime;
1394
- const channel = this.channels[channelNumber];
1395
- const velocity = 0;
1396
- const stopPedal = true;
1397
- const promises = [];
1398
- channel.scheduledNotes.forEach((noteList) => {
1399
- const activeNote = this.getActiveNote(noteList, now);
1400
- if (activeNote) {
1401
- const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1402
- promises.push(notePromise);
1403
- }
1404
- });
1405
- return promises;
1512
+ return this.stopChannelNotes(channelNumber, 0, true);
1406
1513
  }
1407
1514
  resetAllControllers(channelNumber) {
1408
1515
  Object.assign(this.channels[channelNumber], this.effectSettings);
1409
1516
  }
1410
1517
  allNotesOff(channelNumber) {
1411
- const now = this.audioContext.currentTime;
1412
- const channel = this.channels[channelNumber];
1413
- const velocity = 0;
1414
- const stopPedal = false;
1415
- const promises = [];
1416
- channel.scheduledNotes.forEach((noteList) => {
1417
- const activeNote = this.getActiveNote(noteList, now);
1418
- if (activeNote) {
1419
- const notePromise = this.scheduleNoteRelease(channelNumber, activeNote.noteNumber, velocity, now, stopPedal);
1420
- promises.push(notePromise);
1421
- }
1422
- });
1423
- return promises;
1518
+ return this.stopChannelNotes(channelNumber, 0, false);
1424
1519
  }
1425
1520
  omniOff() {
1426
1521
  this.omni = false;
@@ -1779,11 +1874,16 @@ Object.defineProperty(Midy, "channelSettings", {
1779
1874
  volume: 100 / 127,
1780
1875
  pan: 64,
1781
1876
  portamentoTime: 0,
1877
+ filterResonance: 1,
1878
+ releaseTime: 1,
1879
+ attackTime: 1,
1880
+ brightness: 1,
1881
+ decayTime: 1,
1782
1882
  reverbSendLevel: 0,
1783
1883
  chorusSendLevel: 0,
1784
- vibratoRate: 5,
1785
- vibratoDepth: 0.5,
1786
- vibratoDelay: 2.5,
1884
+ vibratoRate: 1,
1885
+ vibratoDepth: 1,
1886
+ vibratoDelay: 1,
1787
1887
  bank: 121 * 128,
1788
1888
  bankMSB: 121,
1789
1889
  bankLSB: 0,
@@ -1793,7 +1893,7 @@ Object.defineProperty(Midy, "channelSettings", {
1793
1893
  pitchBend: 0,
1794
1894
  fineTuning: 0, // cb
1795
1895
  coarseTuning: 0, // cb
1796
- modulationDepthRange: 0.5, // cb
1896
+ modulationDepthRange: 50, // cent
1797
1897
  }
1798
1898
  });
1799
1899
  Object.defineProperty(Midy, "effectSettings", {
@@ -1802,7 +1902,7 @@ Object.defineProperty(Midy, "effectSettings", {
1802
1902
  writable: true,
1803
1903
  value: {
1804
1904
  expression: 1,
1805
- modulation: 0,
1905
+ modulationDepth: 0,
1806
1906
  sustainPedal: false,
1807
1907
  portamento: false,
1808
1908
  sostenutoPedal: false,