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