@marmooo/midy 0.1.3 → 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.
@@ -435,12 +435,13 @@ class MidyGM1 {
435
435
  const now = this.audioContext.currentTime;
436
436
  const channel = this.channels[channelNumber];
437
437
  channel.scheduledNotes.forEach((noteList) => {
438
- noteList.forEach((note) => {
439
- if (note) {
440
- const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
441
- this.notePromises.push(promise);
442
- }
443
- });
438
+ for (let i = 0; i < noteList.length; i++) {
439
+ const note = noteList[i];
440
+ if (!note)
441
+ continue;
442
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
443
+ this.notePromises.push(promise);
444
+ }
444
445
  });
445
446
  channel.scheduledNotes.clear();
446
447
  await Promise.all(this.notePromises);
@@ -532,7 +533,6 @@ class MidyGM1 {
532
533
  }
533
534
  setVolumeEnvelope(note) {
534
535
  const { instrumentKey, startTime } = note;
535
- note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
536
536
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
537
537
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
538
538
  const volDelay = startTime + instrumentKey.volDelay;
@@ -540,6 +540,8 @@ class MidyGM1 {
540
540
  const volHold = volAttack + instrumentKey.volHold;
541
541
  const volDecay = volHold + instrumentKey.volDecay;
542
542
  note.volumeNode.gain
543
+ .cancelScheduledValues(startTime)
544
+ .setValueAtTime(0, startTime)
543
545
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
544
546
  .exponentialRampToValueAtTime(attackVolume, volAttack)
545
547
  .setValueAtTime(attackVolume, volHold)
@@ -563,29 +565,27 @@ class MidyGM1 {
563
565
  .setValueAtTime(peekPitch, modHold)
564
566
  .linearRampToValueAtTime(basePitch, modDecay);
565
567
  }
566
- setFilterNode(channel, note) {
567
- const { instrumentKey, noteNumber, startTime } = note;
568
- const softPedalFactor = 1 -
569
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
570
- const maxFreq = this.audioContext.sampleRate / 2;
571
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
572
- softPedalFactor;
573
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
574
- const sustainFreq = (baseFreq +
575
- (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
576
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
577
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
578
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
568
+ clampCutoffFrequency(frequency) {
569
+ const minFrequency = 20; // min Hz of initialFilterFc
570
+ const maxFrequency = 20000; // max Hz of initialFilterFc
571
+ return Math.max(minFrequency, Math.min(frequency, maxFrequency));
572
+ }
573
+ setFilterEnvelope(note) {
574
+ const { instrumentKey, startTime } = note;
575
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
576
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc);
577
+ const sustainFreq = baseFreq +
578
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
579
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
580
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
581
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
579
582
  const modDelay = startTime + instrumentKey.modDelay;
580
583
  const modAttack = modDelay + instrumentKey.modAttack;
581
584
  const modHold = modAttack + instrumentKey.modHold;
582
585
  const modDecay = modHold + instrumentKey.modDecay;
583
- note.filterNode = new BiquadFilterNode(this.audioContext, {
584
- type: "lowpass",
585
- Q: instrumentKey.initialFilterQ / 10, // dB
586
- frequency: adjustedBaseFreq,
587
- });
588
586
  note.filterNode.frequency
587
+ .cancelScheduledValues(startTime)
588
+ .setValueAtTime(adjustedBaseFreq, startTime)
589
589
  .setValueAtTime(adjustedBaseFreq, modDelay)
590
590
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
591
591
  .setValueAtTime(adjustedPeekFreq, modHold)
@@ -622,8 +622,13 @@ class MidyGM1 {
622
622
  const semitoneOffset = this.calcSemitoneOffset(channel);
623
623
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
624
624
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
625
- this.setFilterNode(channel, note);
625
+ note.volumeNode = new GainNode(this.audioContext);
626
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
627
+ type: "lowpass",
628
+ Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
629
+ });
626
630
  this.setVolumeEnvelope(note);
631
+ this.setFilterEnvelope(note);
627
632
  if (0 < channel.modulationDepth) {
628
633
  this.setPitch(note, semitoneOffset);
629
634
  this.startModulation(channel, note, startTime);
@@ -662,7 +667,7 @@ class MidyGM1 {
662
667
  const now = this.audioContext.currentTime;
663
668
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
664
669
  }
665
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
670
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
666
671
  const channel = this.channels[channelNumber];
667
672
  if (stopPedal && channel.sustainPedal)
668
673
  return;
@@ -675,14 +680,11 @@ class MidyGM1 {
675
680
  continue;
676
681
  if (note.ending)
677
682
  continue;
678
- const velocityRate = (velocity + 127) / 127;
679
- const volEndTime = stopTime +
680
- note.instrumentKey.volRelease * velocityRate;
683
+ const volEndTime = stopTime + note.instrumentKey.volRelease;
681
684
  note.volumeNode.gain
682
685
  .cancelScheduledValues(stopTime)
683
686
  .linearRampToValueAtTime(0, volEndTime);
684
- const modRelease = stopTime +
685
- note.instrumentKey.modRelease * velocityRate;
687
+ const modRelease = stopTime + note.instrumentKey.modRelease;
686
688
  note.filterNode.frequency
687
689
  .cancelScheduledValues(stopTime)
688
690
  .linearRampToValueAtTime(0, modRelease);
@@ -718,13 +720,14 @@ class MidyGM1 {
718
720
  const promises = [];
719
721
  channel.sustainPedal = false;
720
722
  channel.scheduledNotes.forEach((noteList) => {
721
- noteList.forEach((note) => {
722
- if (note) {
723
- const { noteNumber } = note;
724
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
725
- promises.push(promise);
726
- }
727
- });
723
+ for (let i = 0; i < noteList.length; i++) {
724
+ const note = noteList[i];
725
+ if (!note)
726
+ continue;
727
+ const { noteNumber } = note;
728
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
729
+ promises.push(promise);
730
+ }
728
731
  });
729
732
  return promises;
730
733
  }
@@ -794,15 +797,19 @@ class MidyGM1 {
794
797
  }
795
798
  updateModulation(channel) {
796
799
  const now = this.audioContext.currentTime;
797
- const activeNotes = this.getActiveNotes(channel, now);
798
- activeNotes.forEach((activeNote) => {
799
- if (activeNote.modulationDepth) {
800
- activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
801
- }
802
- else {
803
- const semitoneOffset = this.calcSemitoneOffset(channel);
804
- this.setPitch(activeNote, semitoneOffset);
805
- this.startModulation(channel, activeNote, now);
800
+ channel.scheduledNotes.forEach((noteList) => {
801
+ for (let i = 0; i < noteList.length; i++) {
802
+ const note = noteList[i];
803
+ if (!note)
804
+ continue;
805
+ if (note.modulationDepth) {
806
+ note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
807
+ }
808
+ else {
809
+ const semitoneOffset = this.calcSemitoneOffset(channel);
810
+ this.setPitch(note, semitoneOffset);
811
+ this.startModulation(channel, note, now);
812
+ }
806
813
  }
807
814
  });
808
815
  }
@@ -910,13 +917,17 @@ class MidyGM1 {
910
917
  }
911
918
  updateDetune(channel, detuneChange) {
912
919
  const now = this.audioContext.currentTime;
913
- const activeNotes = this.getActiveNotes(channel, now);
914
- activeNotes.forEach((activeNote) => {
915
- const { bufferSource } = activeNote;
916
- const detune = bufferSource.detune.value + detuneChange;
917
- bufferSource.detune
918
- .cancelScheduledValues(now)
919
- .setValueAtTime(detune, now);
920
+ channel.scheduledNotes.forEach((noteList) => {
921
+ for (let i = 0; i < noteList.length; i++) {
922
+ const note = noteList[i];
923
+ if (!note)
924
+ continue;
925
+ const { bufferSource } = note;
926
+ const detune = bufferSource.detune.value + detuneChange;
927
+ bufferSource.detune
928
+ .cancelScheduledValues(now)
929
+ .setValueAtTime(detune, now);
930
+ }
920
931
  });
921
932
  }
922
933
  handlePitchBendRangeRPN(channelNumber) {
@@ -167,14 +167,15 @@ export class MidyGM2 {
167
167
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
168
168
  setVolumeEnvelope(note: any): void;
169
169
  setPitch(note: any, semitoneOffset: any): void;
170
- setFilterNode(channel: any, note: any): void;
170
+ clampCutoffFrequency(frequency: any): number;
171
+ setFilterEnvelope(channel: any, note: any): void;
171
172
  startModulation(channel: any, note: any, startTime: any): void;
172
173
  startVibrato(channel: any, note: any, startTime: any): void;
173
174
  createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
174
175
  calcBank(channel: any, channelNumber: any): any;
175
176
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
176
177
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
177
- scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
178
+ scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
178
179
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
179
180
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
180
181
  releaseSostenutoPedal(channelNumber: any, halfVelocity: any): any[];
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"AAwBA;IAkCE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAgCF;;;;;OAWC;IAtHD,qBAAmB;IACnB,kBAAc;IACd,yBAAqB;IACrB,2BAAuB;IACvB;;;MAGE;IACF;;;;;;MAME;IACF,cAAa;IACb,cAAa;IACb,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA8ClB;;;;;MA4BE;IAGA,kBAAgC;IAChC;;;;;MAAqD;IACrD,gBAA4C;IAC5C,gBAAiD;IACjD;;;MAA8D;IAC9D;;;;;;;;MAAyD;IAO3D,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAcC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAkDC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgGC;IAED,mFAmBC;IAED,yDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,kFAuBC;IAED;;;;MAWC;IAED,gFAUC;IAED,mFAYC;IAED,sGAcC;IAID;;;MA+BC;IAED;;;;;;;;MA0CC;IAED,2BAEC;IAED,4BAEC;IAED,sCAKC;IAED,mFAGC;IAED,mCAeC;IAED,+CAwBC;IAED,6CAIC;IAED,iDAyBC;IAED,+DA0BC;IAED,4DAiBC;IAED,wHA0CC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,uIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gFAmBC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,uDAOC;IAED,2FAuDC;IAED,+CAEC;IAED,qCAkBC;IAED,8DAIC;IAED,iEAEC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,+CAEC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAGD,oDAEC;IAED,mEAWC;IAED,mEAWC;IAED,wDAWC;IAED,uDAGC;IAED,kFAeC;IAED,2DAMC;IAED,oCAqBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAaC;IAED,kDAKC;IAED,iEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,wDAKC;IAED,6EAKC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAiDC;IAED,yCAGC;IAED,mCAQC;IAED,6CAGC;IAED,2CAMC;IAED,+CAGC;IAED,+CAMC;IAED,mDAeC;IAED,4CAOC;IAED,+BAKC;IAED,qDAiBC;IAED,gCAMC;IAED,kCAEC;IA6BD,4CAEC;IAED,4CAaC;IAED,+BAiBC;IAED,wFAKC;IAED,mCAQC;IAED,qCAEC;IAED,oCAUC;IAED,sCAEC;IAED,oCAaC;IAED,sCAEC;IAED,wCAWC;IAED,0CAEC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAhyDD;IAUE,gFAKC;IAdD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IAGX,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -548,12 +548,13 @@ class MidyGM2 {
548
548
  const now = this.audioContext.currentTime;
549
549
  const channel = this.channels[channelNumber];
550
550
  channel.scheduledNotes.forEach((noteList) => {
551
- noteList.forEach((note) => {
552
- if (note) {
553
- const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
554
- this.notePromises.push(promise);
555
- }
556
- });
551
+ for (let i = 0; i < noteList.length; i++) {
552
+ const note = noteList[i];
553
+ if (!note)
554
+ continue;
555
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
556
+ this.notePromises.push(promise);
557
+ }
557
558
  });
558
559
  channel.scheduledNotes.clear();
559
560
  await Promise.all(this.notePromises);
@@ -776,7 +777,6 @@ class MidyGM2 {
776
777
  }
777
778
  setVolumeEnvelope(note) {
778
779
  const { instrumentKey, startTime } = note;
779
- note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
780
780
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
781
781
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
782
782
  const volDelay = startTime + instrumentKey.volDelay;
@@ -784,6 +784,8 @@ class MidyGM2 {
784
784
  const volHold = volAttack + instrumentKey.volHold;
785
785
  const volDecay = volHold + instrumentKey.volDecay;
786
786
  note.volumeNode.gain
787
+ .cancelScheduledValues(startTime)
788
+ .setValueAtTime(0, startTime)
787
789
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
788
790
  .exponentialRampToValueAtTime(attackVolume, volAttack)
789
791
  .setValueAtTime(attackVolume, volHold)
@@ -807,29 +809,30 @@ class MidyGM2 {
807
809
  .setValueAtTime(peekPitch, modHold)
808
810
  .linearRampToValueAtTime(basePitch, modDecay);
809
811
  }
810
- setFilterNode(channel, note) {
812
+ clampCutoffFrequency(frequency) {
813
+ const minFrequency = 20; // min Hz of initialFilterFc
814
+ const maxFrequency = 20000; // max Hz of initialFilterFc
815
+ return Math.max(minFrequency, Math.min(frequency, maxFrequency));
816
+ }
817
+ setFilterEnvelope(channel, note) {
811
818
  const { instrumentKey, noteNumber, startTime } = note;
812
819
  const softPedalFactor = 1 -
813
820
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
814
- const maxFreq = this.audioContext.sampleRate / 2;
815
821
  const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
816
822
  softPedalFactor;
817
823
  const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
818
824
  const sustainFreq = baseFreq +
819
825
  (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
820
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
821
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
822
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
826
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
827
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
828
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
823
829
  const modDelay = startTime + instrumentKey.modDelay;
824
830
  const modAttack = modDelay + instrumentKey.modAttack;
825
831
  const modHold = modAttack + instrumentKey.modHold;
826
832
  const modDecay = modHold + instrumentKey.modDecay;
827
- note.filterNode = new BiquadFilterNode(this.audioContext, {
828
- type: "lowpass",
829
- Q: instrumentKey.initialFilterQ / 10, // dB
830
- frequency: adjustedBaseFreq,
831
- });
832
833
  note.filterNode.frequency
834
+ .cancelScheduledValues(startTime)
835
+ .setValueAtTime(adjustedBaseFreq, startTime)
833
836
  .setValueAtTime(adjustedBaseFreq, modDelay)
834
837
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
835
838
  .setValueAtTime(adjustedPeekFreq, modHold)
@@ -882,8 +885,13 @@ class MidyGM2 {
882
885
  const semitoneOffset = this.calcSemitoneOffset(channel);
883
886
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
884
887
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
885
- this.setFilterNode(channel, note);
888
+ note.volumeNode = new GainNode(this.audioContext);
889
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
890
+ type: "lowpass",
891
+ Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
892
+ });
886
893
  this.setVolumeEnvelope(note);
894
+ this.setFilterEnvelope(channel, note);
887
895
  if (0 < channel.vibratoDepth) {
888
896
  this.startVibrato(channel, note, startTime);
889
897
  }
@@ -941,7 +949,7 @@ class MidyGM2 {
941
949
  const now = this.audioContext.currentTime;
942
950
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
943
951
  }
944
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
952
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
945
953
  const channel = this.channels[channelNumber];
946
954
  if (stopPedal && channel.sustainPedal)
947
955
  return;
@@ -956,14 +964,11 @@ class MidyGM2 {
956
964
  continue;
957
965
  if (note.ending)
958
966
  continue;
959
- const velocityRate = (velocity + 127) / 127;
960
- const volEndTime = stopTime +
961
- note.instrumentKey.volRelease * velocityRate;
967
+ const volEndTime = stopTime + note.instrumentKey.volRelease;
962
968
  note.volumeNode.gain
963
969
  .cancelScheduledValues(stopTime)
964
970
  .linearRampToValueAtTime(0, volEndTime);
965
- const modRelease = stopTime +
966
- note.instrumentKey.modRelease * velocityRate;
971
+ const modRelease = stopTime + note.instrumentKey.modRelease;
967
972
  note.filterNode.frequency
968
973
  .cancelScheduledValues(stopTime)
969
974
  .linearRampToValueAtTime(0, modRelease);
@@ -1003,13 +1008,14 @@ class MidyGM2 {
1003
1008
  const promises = [];
1004
1009
  channel.sustainPedal = false;
1005
1010
  channel.scheduledNotes.forEach((noteList) => {
1006
- noteList.forEach((note) => {
1007
- if (note) {
1008
- const { noteNumber } = note;
1009
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
1010
- promises.push(promise);
1011
- }
1012
- });
1011
+ for (let i = 0; i < noteList.length; i++) {
1012
+ const note = noteList[i];
1013
+ if (!note)
1014
+ continue;
1015
+ const { noteNumber } = note;
1016
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
1017
+ promises.push(promise);
1018
+ }
1013
1019
  });
1014
1020
  return promises;
1015
1021
  }
@@ -1137,15 +1143,19 @@ class MidyGM2 {
1137
1143
  }
1138
1144
  updateModulation(channel) {
1139
1145
  const now = this.audioContext.currentTime;
1140
- const activeNotes = this.getActiveNotes(channel, now);
1141
- activeNotes.forEach((activeNote) => {
1142
- if (activeNote.modulationDepth) {
1143
- activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1144
- }
1145
- else {
1146
- const semitoneOffset = this.calcSemitoneOffset(channel);
1147
- this.setPitch(activeNote, semitoneOffset);
1148
- this.startModulation(channel, activeNote, now);
1146
+ channel.scheduledNotes.forEach((noteList) => {
1147
+ for (let i = 0; i < noteList.length; i++) {
1148
+ const note = noteList[i];
1149
+ if (!note)
1150
+ continue;
1151
+ if (note.modulationDepth) {
1152
+ note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1153
+ }
1154
+ else {
1155
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1156
+ this.setPitch(note, semitoneOffset);
1157
+ this.startModulation(channel, note, now);
1158
+ }
1149
1159
  }
1150
1160
  });
1151
1161
  }
@@ -1309,13 +1319,17 @@ class MidyGM2 {
1309
1319
  }
1310
1320
  updateDetune(channel, detuneChange) {
1311
1321
  const now = this.audioContext.currentTime;
1312
- const activeNotes = this.getActiveNotes(channel, now);
1313
- activeNotes.forEach((activeNote) => {
1314
- const { bufferSource } = activeNote;
1315
- const detune = bufferSource.detune.value + detuneChange;
1316
- bufferSource.detune
1317
- .cancelScheduledValues(now)
1318
- .setValueAtTime(detune, now);
1322
+ channel.scheduledNotes.forEach((noteList) => {
1323
+ for (let i = 0; i < noteList.length; i++) {
1324
+ const note = noteList[i];
1325
+ if (!note)
1326
+ continue;
1327
+ const { bufferSource } = note;
1328
+ const detune = bufferSource.detune.value + detuneChange;
1329
+ bufferSource.detune
1330
+ .cancelScheduledValues(now)
1331
+ .setValueAtTime(detune, now);
1332
+ }
1319
1333
  });
1320
1334
  }
1321
1335
  handlePitchBendRangeRPN(channelNumber) {
@@ -77,12 +77,13 @@ export class MidyGMLite {
77
77
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
78
78
  setVolumeEnvelope(note: any): void;
79
79
  setPitch(note: any, semitoneOffset: any): void;
80
- setFilterNode(channel: any, note: any): void;
80
+ clampCutoffFrequency(frequency: any): number;
81
+ setFilterEnvelope(note: any): void;
81
82
  startModulation(channel: any, note: any, startTime: any): void;
82
83
  createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
83
84
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
84
85
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
85
- scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
86
+ scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
86
87
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
87
88
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
88
89
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAsBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAC5C,gBAAiD;IAKnD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,mFAmBC;IAED,yDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,mCAcC;IAED,+CAwBC;IAED,6CA6BC;IAED,+DA0BC;IAED,wHA8BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED,2FA+BC;IAED,qCAeC;IAED,8DAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA1+BD;IAQE,gFAKC;IAZD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAsBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAuBhB,kBAAgC;IAChC,gBAA4C;IAC5C,gBAAiD;IAKnD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,mFAmBC;IAED,yDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,mCAeC;IAED,+CAwBC;IAED,6CAIC;IAED,mCAsBC;IAED,+DA0BC;IAED,wHAmCC;IAED,kGA6BC;IAED,0EAGC;IAED,uIAyCC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAOC;IAED,2FA+BC;IAED,qCAkBC;IAED,8DAIC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,sCAUC;IAED,sDAMC;IAED,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAaC;IAED,kDAKC;IAED,iEAOC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAl/BD;IAQE,gFAKC;IAZD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -423,12 +423,13 @@ class MidyGMLite {
423
423
  const now = this.audioContext.currentTime;
424
424
  const channel = this.channels[channelNumber];
425
425
  channel.scheduledNotes.forEach((noteList) => {
426
- noteList.forEach((note) => {
427
- if (note) {
428
- const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
429
- this.notePromises.push(promise);
430
- }
431
- });
426
+ for (let i = 0; i < noteList.length; i++) {
427
+ const note = noteList[i];
428
+ if (!note)
429
+ continue;
430
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
431
+ this.notePromises.push(promise);
432
+ }
432
433
  });
433
434
  channel.scheduledNotes.clear();
434
435
  await Promise.all(this.notePromises);
@@ -519,7 +520,6 @@ class MidyGMLite {
519
520
  }
520
521
  setVolumeEnvelope(note) {
521
522
  const { instrumentKey, startTime } = note;
522
- note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
523
523
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
524
524
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
525
525
  const volDelay = startTime + instrumentKey.volDelay;
@@ -527,6 +527,8 @@ class MidyGMLite {
527
527
  const volHold = volAttack + instrumentKey.volHold;
528
528
  const volDecay = volHold + instrumentKey.volDecay;
529
529
  note.volumeNode.gain
530
+ .cancelScheduledValues(startTime)
531
+ .setValueAtTime(0, startTime)
530
532
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
531
533
  .exponentialRampToValueAtTime(attackVolume, volAttack)
532
534
  .setValueAtTime(attackVolume, volHold)
@@ -550,29 +552,27 @@ class MidyGMLite {
550
552
  .setValueAtTime(peekPitch, modHold)
551
553
  .linearRampToValueAtTime(basePitch, modDecay);
552
554
  }
553
- setFilterNode(channel, note) {
554
- const { instrumentKey, noteNumber, startTime } = note;
555
- const softPedalFactor = 1 -
556
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
557
- const maxFreq = this.audioContext.sampleRate / 2;
558
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
559
- softPedalFactor;
560
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
555
+ clampCutoffFrequency(frequency) {
556
+ const minFrequency = 20; // min Hz of initialFilterFc
557
+ const maxFrequency = 20000; // max Hz of initialFilterFc
558
+ return Math.max(minFrequency, Math.min(frequency, maxFrequency));
559
+ }
560
+ setFilterEnvelope(note) {
561
+ const { instrumentKey, startTime } = note;
562
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
563
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc);
561
564
  const sustainFreq = baseFreq +
562
565
  (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
563
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
564
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
565
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
566
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
567
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
568
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
566
569
  const modDelay = startTime + instrumentKey.modDelay;
567
570
  const modAttack = modDelay + instrumentKey.modAttack;
568
571
  const modHold = modAttack + instrumentKey.modHold;
569
572
  const modDecay = modHold + instrumentKey.modDecay;
570
- note.filterNode = new BiquadFilterNode(this.audioContext, {
571
- type: "lowpass",
572
- Q: instrumentKey.initialFilterQ / 10, // dB
573
- frequency: adjustedBaseFreq,
574
- });
575
573
  note.filterNode.frequency
574
+ .cancelScheduledValues(startTime)
575
+ .setValueAtTime(adjustedBaseFreq, startTime)
576
576
  .setValueAtTime(adjustedBaseFreq, modDelay)
577
577
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
578
578
  .setValueAtTime(adjustedPeekFreq, modHold)
@@ -609,8 +609,13 @@ class MidyGMLite {
609
609
  const semitoneOffset = this.calcSemitoneOffset(channel);
610
610
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
611
611
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
612
- this.setFilterNode(channel, note);
612
+ note.volumeNode = new GainNode(this.audioContext);
613
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
614
+ type: "lowpass",
615
+ Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
616
+ });
613
617
  this.setVolumeEnvelope(note);
618
+ this.setFilterEnvelope(note);
614
619
  if (0 < channel.modulationDepth) {
615
620
  this.setPitch(note, semitoneOffset);
616
621
  this.startModulation(channel, note, startTime);
@@ -649,7 +654,7 @@ class MidyGMLite {
649
654
  const now = this.audioContext.currentTime;
650
655
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
651
656
  }
652
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
657
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
653
658
  const channel = this.channels[channelNumber];
654
659
  if (stopPedal && channel.sustainPedal)
655
660
  return;
@@ -662,14 +667,11 @@ class MidyGMLite {
662
667
  continue;
663
668
  if (note.ending)
664
669
  continue;
665
- const velocityRate = (velocity + 127) / 127;
666
- const volEndTime = stopTime +
667
- note.instrumentKey.volRelease * velocityRate;
670
+ const volEndTime = stopTime + note.instrumentKey.volRelease;
668
671
  note.volumeNode.gain
669
672
  .cancelScheduledValues(stopTime)
670
673
  .linearRampToValueAtTime(0, volEndTime);
671
- const modRelease = stopTime +
672
- note.instrumentKey.modRelease * velocityRate;
674
+ const modRelease = stopTime + note.instrumentKey.modRelease;
673
675
  note.filterNode.frequency
674
676
  .cancelScheduledValues(stopTime)
675
677
  .linearRampToValueAtTime(0, modRelease);
@@ -705,13 +707,14 @@ class MidyGMLite {
705
707
  const promises = [];
706
708
  channel.sustainPedal = false;
707
709
  channel.scheduledNotes.forEach((noteList) => {
708
- noteList.forEach((note) => {
709
- if (note) {
710
- const { noteNumber } = note;
711
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
712
- promises.push(promise);
713
- }
714
- });
710
+ for (let i = 0; i < noteList.length; i++) {
711
+ const note = noteList[i];
712
+ if (!note)
713
+ continue;
714
+ const { noteNumber } = note;
715
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
716
+ promises.push(promise);
717
+ }
715
718
  });
716
719
  return promises;
717
720
  }
@@ -781,15 +784,19 @@ class MidyGMLite {
781
784
  }
782
785
  updateModulation(channel) {
783
786
  const now = this.audioContext.currentTime;
784
- const activeNotes = this.getActiveNotes(channel, now);
785
- activeNotes.forEach((activeNote) => {
786
- if (activeNote.modulationDepth) {
787
- activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
788
- }
789
- else {
790
- const semitoneOffset = this.calcSemitoneOffset(channel);
791
- this.setPitch(activeNote, semitoneOffset);
792
- this.startModulation(channel, activeNote, now);
787
+ channel.scheduledNotes.forEach((noteList) => {
788
+ for (let i = 0; i < noteList.length; i++) {
789
+ const note = noteList[i];
790
+ if (!note)
791
+ continue;
792
+ if (note.modulationDepth) {
793
+ note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
794
+ }
795
+ else {
796
+ const semitoneOffset = this.calcSemitoneOffset(channel);
797
+ this.setPitch(note, semitoneOffset);
798
+ this.startModulation(channel, note, now);
799
+ }
793
800
  }
794
801
  });
795
802
  }
@@ -865,13 +872,17 @@ class MidyGMLite {
865
872
  }
866
873
  updateDetune(channel, detuneChange) {
867
874
  const now = this.audioContext.currentTime;
868
- const activeNotes = this.getActiveNotes(channel, now);
869
- activeNotes.forEach((activeNote) => {
870
- const { bufferSource } = activeNote;
871
- const detune = bufferSource.detune.value + detuneChange;
872
- bufferSource.detune
873
- .cancelScheduledValues(now)
874
- .setValueAtTime(detune, now);
875
+ channel.scheduledNotes.forEach((noteList) => {
876
+ for (let i = 0; i < noteList.length; i++) {
877
+ const note = noteList[i];
878
+ if (!note)
879
+ continue;
880
+ const { bufferSource } = note;
881
+ const detune = bufferSource.detune.value + detuneChange;
882
+ bufferSource.detune
883
+ .cancelScheduledValues(now)
884
+ .setValueAtTime(detune, now);
885
+ }
875
886
  });
876
887
  }
877
888
  handlePitchBendRangeRPN(channelNumber) {