@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.
@@ -420,12 +420,13 @@ export class MidyGMLite {
420
420
  const now = this.audioContext.currentTime;
421
421
  const channel = this.channels[channelNumber];
422
422
  channel.scheduledNotes.forEach((noteList) => {
423
- noteList.forEach((note) => {
424
- if (note) {
425
- const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
426
- this.notePromises.push(promise);
427
- }
428
- });
423
+ for (let i = 0; i < noteList.length; i++) {
424
+ const note = noteList[i];
425
+ if (!note)
426
+ continue;
427
+ const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
428
+ this.notePromises.push(promise);
429
+ }
429
430
  });
430
431
  channel.scheduledNotes.clear();
431
432
  await Promise.all(this.notePromises);
@@ -516,7 +517,6 @@ export class MidyGMLite {
516
517
  }
517
518
  setVolumeEnvelope(note) {
518
519
  const { instrumentKey, startTime } = note;
519
- note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
520
520
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
521
521
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
522
522
  const volDelay = startTime + instrumentKey.volDelay;
@@ -524,6 +524,8 @@ export class MidyGMLite {
524
524
  const volHold = volAttack + instrumentKey.volHold;
525
525
  const volDecay = volHold + instrumentKey.volDecay;
526
526
  note.volumeNode.gain
527
+ .cancelScheduledValues(startTime)
528
+ .setValueAtTime(0, startTime)
527
529
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
528
530
  .exponentialRampToValueAtTime(attackVolume, volAttack)
529
531
  .setValueAtTime(attackVolume, volHold)
@@ -547,29 +549,27 @@ export class MidyGMLite {
547
549
  .setValueAtTime(peekPitch, modHold)
548
550
  .linearRampToValueAtTime(basePitch, modDecay);
549
551
  }
550
- setFilterNode(channel, note) {
551
- const { instrumentKey, noteNumber, startTime } = note;
552
- const softPedalFactor = 1 -
553
- (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
554
- const maxFreq = this.audioContext.sampleRate / 2;
555
- const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
556
- softPedalFactor;
557
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
552
+ clampCutoffFrequency(frequency) {
553
+ const minFrequency = 20; // min Hz of initialFilterFc
554
+ const maxFrequency = 20000; // max Hz of initialFilterFc
555
+ return Math.max(minFrequency, Math.min(frequency, maxFrequency));
556
+ }
557
+ setFilterEnvelope(note) {
558
+ const { instrumentKey, startTime } = note;
559
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
560
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc);
558
561
  const sustainFreq = baseFreq +
559
562
  (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
560
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
561
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
562
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
563
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
564
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
565
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
563
566
  const modDelay = startTime + instrumentKey.modDelay;
564
567
  const modAttack = modDelay + instrumentKey.modAttack;
565
568
  const modHold = modAttack + instrumentKey.modHold;
566
569
  const modDecay = modHold + instrumentKey.modDecay;
567
- note.filterNode = new BiquadFilterNode(this.audioContext, {
568
- type: "lowpass",
569
- Q: instrumentKey.initialFilterQ / 10, // dB
570
- frequency: adjustedBaseFreq,
571
- });
572
570
  note.filterNode.frequency
571
+ .cancelScheduledValues(startTime)
572
+ .setValueAtTime(adjustedBaseFreq, startTime)
573
573
  .setValueAtTime(adjustedBaseFreq, modDelay)
574
574
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
575
575
  .setValueAtTime(adjustedPeekFreq, modHold)
@@ -606,8 +606,13 @@ export class MidyGMLite {
606
606
  const semitoneOffset = this.calcSemitoneOffset(channel);
607
607
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
608
608
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
609
- this.setFilterNode(channel, note);
609
+ note.volumeNode = new GainNode(this.audioContext);
610
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
611
+ type: "lowpass",
612
+ Q: instrumentKey.initialFilterQ / 10 * channel.filterResonance, // dB
613
+ });
610
614
  this.setVolumeEnvelope(note);
615
+ this.setFilterEnvelope(note);
611
616
  if (0 < channel.modulationDepth) {
612
617
  this.setPitch(note, semitoneOffset);
613
618
  this.startModulation(channel, note, startTime);
@@ -646,7 +651,7 @@ export class MidyGMLite {
646
651
  const now = this.audioContext.currentTime;
647
652
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
648
653
  }
649
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
654
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
650
655
  const channel = this.channels[channelNumber];
651
656
  if (stopPedal && channel.sustainPedal)
652
657
  return;
@@ -659,14 +664,11 @@ export class MidyGMLite {
659
664
  continue;
660
665
  if (note.ending)
661
666
  continue;
662
- const velocityRate = (velocity + 127) / 127;
663
- const volEndTime = stopTime +
664
- note.instrumentKey.volRelease * velocityRate;
667
+ const volEndTime = stopTime + note.instrumentKey.volRelease;
665
668
  note.volumeNode.gain
666
669
  .cancelScheduledValues(stopTime)
667
670
  .linearRampToValueAtTime(0, volEndTime);
668
- const modRelease = stopTime +
669
- note.instrumentKey.modRelease * velocityRate;
671
+ const modRelease = stopTime + note.instrumentKey.modRelease;
670
672
  note.filterNode.frequency
671
673
  .cancelScheduledValues(stopTime)
672
674
  .linearRampToValueAtTime(0, modRelease);
@@ -702,13 +704,14 @@ export class MidyGMLite {
702
704
  const promises = [];
703
705
  channel.sustainPedal = false;
704
706
  channel.scheduledNotes.forEach((noteList) => {
705
- noteList.forEach((note) => {
706
- if (note) {
707
- const { noteNumber } = note;
708
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
709
- promises.push(promise);
710
- }
711
- });
707
+ for (let i = 0; i < noteList.length; i++) {
708
+ const note = noteList[i];
709
+ if (!note)
710
+ continue;
711
+ const { noteNumber } = note;
712
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
713
+ promises.push(promise);
714
+ }
712
715
  });
713
716
  return promises;
714
717
  }
@@ -778,15 +781,19 @@ export class MidyGMLite {
778
781
  }
779
782
  updateModulation(channel) {
780
783
  const now = this.audioContext.currentTime;
781
- const activeNotes = this.getActiveNotes(channel, now);
782
- activeNotes.forEach((activeNote) => {
783
- if (activeNote.modulationDepth) {
784
- activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
785
- }
786
- else {
787
- const semitoneOffset = this.calcSemitoneOffset(channel);
788
- this.setPitch(activeNote, semitoneOffset);
789
- this.startModulation(channel, activeNote, now);
784
+ channel.scheduledNotes.forEach((noteList) => {
785
+ for (let i = 0; i < noteList.length; i++) {
786
+ const note = noteList[i];
787
+ if (!note)
788
+ continue;
789
+ if (note.modulationDepth) {
790
+ note.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
791
+ }
792
+ else {
793
+ const semitoneOffset = this.calcSemitoneOffset(channel);
794
+ this.setPitch(note, semitoneOffset);
795
+ this.startModulation(channel, note, now);
796
+ }
790
797
  }
791
798
  });
792
799
  }
@@ -862,13 +869,17 @@ export class MidyGMLite {
862
869
  }
863
870
  updateDetune(channel, detuneChange) {
864
871
  const now = this.audioContext.currentTime;
865
- const activeNotes = this.getActiveNotes(channel, now);
866
- activeNotes.forEach((activeNote) => {
867
- const { bufferSource } = activeNote;
868
- const detune = bufferSource.detune.value + detuneChange;
869
- bufferSource.detune
870
- .cancelScheduledValues(now)
871
- .setValueAtTime(detune, now);
872
+ channel.scheduledNotes.forEach((noteList) => {
873
+ for (let i = 0; i < noteList.length; i++) {
874
+ const note = noteList[i];
875
+ if (!note)
876
+ continue;
877
+ const { bufferSource } = note;
878
+ const detune = bufferSource.detune.value + detuneChange;
879
+ bufferSource.detune
880
+ .cancelScheduledValues(now)
881
+ .setValueAtTime(detune, now);
882
+ }
872
883
  });
873
884
  }
874
885
  handlePitchBendRangeRPN(channelNumber) {
package/esm/midy.d.ts CHANGED
@@ -4,6 +4,11 @@ export class Midy {
4
4
  volume: number;
5
5
  pan: number;
6
6
  portamentoTime: number;
7
+ filterResonance: number;
8
+ releaseTime: number;
9
+ attackTime: number;
10
+ brightness: number;
11
+ decayTime: number;
7
12
  reverbSendLevel: number;
8
13
  chorusSendLevel: number;
9
14
  vibratoRate: number;
@@ -165,16 +170,17 @@ export class Midy {
165
170
  centToHz(cent: any): number;
166
171
  calcSemitoneOffset(channel: any): any;
167
172
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
168
- setVolumeEnvelope(note: any): void;
173
+ setVolumeEnvelope(channel: any, note: any): void;
169
174
  setPitch(note: any, semitoneOffset: any): void;
170
- setFilterNode(channel: any, note: any): void;
175
+ clampCutoffFrequency(frequency: any): number;
176
+ setFilterEnvelope(channel: any, note: any): void;
171
177
  startModulation(channel: any, note: any, startTime: any): void;
172
178
  startVibrato(channel: any, note: any, startTime: any): void;
173
179
  createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
174
180
  calcBank(channel: any, channelNumber: any): any;
175
181
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
176
182
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
177
- scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
183
+ scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
178
184
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
179
185
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
180
186
  releaseSostenutoPedal(channelNumber: any, halfVelocity: any): any[];
@@ -205,6 +211,11 @@ export class Midy {
205
211
  setChorusSendLevel(channelNumber: any, chorusSendLevel: any): void;
206
212
  setSostenutoPedal(channelNumber: any, value: any): void;
207
213
  setSoftPedal(channelNumber: any, softPedal: any): void;
214
+ setFilterResonance(channelNumber: any, filterResonance: any): void;
215
+ setReleaseTime(channelNumber: any, releaseTime: any): void;
216
+ setAttackTime(channelNumber: any, attackTime: any): void;
217
+ setBrightness(channelNumber: any, brightness: any): void;
218
+ setDecayTime(channelNumber: any, dacayTime: any): void;
208
219
  setVibratoRate(channelNumber: any, vibratoRate: any): void;
209
220
  setVibratoDepth(channelNumber: any, vibratoDepth: any): void;
210
221
  setVibratoDelay(channelNumber: any, vibratoDelay: any): void;
package/esm/midy.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"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"}
package/esm/midy.js CHANGED
@@ -551,12 +551,13 @@ export class Midy {
551
551
  const now = this.audioContext.currentTime;
552
552
  const channel = this.channels[channelNumber];
553
553
  channel.scheduledNotes.forEach((noteList) => {
554
- noteList.forEach((note) => {
555
- if (note) {
556
- const promise = this.scheduleNoteRelease(channelNumber, note.noteNumber, velocity, now, stopPedal);
557
- this.notePromises.push(promise);
558
- }
559
- });
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
+ }
560
561
  });
561
562
  channel.scheduledNotes.clear();
562
563
  await Promise.all(this.notePromises);
@@ -777,16 +778,17 @@ export class Midy {
777
778
  return instrumentKey.playbackRate(noteNumber) *
778
779
  Math.pow(2, semitoneOffset / 12);
779
780
  }
780
- setVolumeEnvelope(note) {
781
+ setVolumeEnvelope(channel, note) {
781
782
  const { instrumentKey, startTime } = note;
782
- note.volumeNode = new GainNode(this.audioContext, { gain: 0 });
783
783
  const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation);
784
784
  const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
785
785
  const volDelay = startTime + instrumentKey.volDelay;
786
- const volAttack = volDelay + instrumentKey.volAttack;
786
+ const volAttack = volDelay + instrumentKey.volAttack * channel.attackTime;
787
787
  const volHold = volAttack + instrumentKey.volHold;
788
- const volDecay = volHold + instrumentKey.volDecay;
788
+ const volDecay = volHold + instrumentKey.volDecay * channel.decayTime;
789
789
  note.volumeNode.gain
790
+ .cancelScheduledValues(startTime)
791
+ .setValueAtTime(0, startTime)
790
792
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
791
793
  .exponentialRampToValueAtTime(attackVolume, volAttack)
792
794
  .setValueAtTime(attackVolume, volHold)
@@ -810,29 +812,30 @@ export class Midy {
810
812
  .setValueAtTime(peekPitch, modHold)
811
813
  .linearRampToValueAtTime(basePitch, modDecay);
812
814
  }
813
- setFilterNode(channel, note) {
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
+ }
820
+ setFilterEnvelope(channel, note) {
814
821
  const { instrumentKey, noteNumber, startTime } = note;
815
822
  const softPedalFactor = 1 -
816
823
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
817
- const maxFreq = this.audioContext.sampleRate / 2;
818
824
  const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
819
- softPedalFactor;
820
- const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
825
+ softPedalFactor * channel.brightness;
826
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor * channel.brightness;
821
827
  const sustainFreq = baseFreq +
822
828
  (peekFreq - baseFreq) * (1 - instrumentKey.modSustain);
823
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
824
- const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
825
- const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
829
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
830
+ const adjustedPeekFreq = this.clampCutoffFrequency(peekFreq);
831
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
826
832
  const modDelay = startTime + instrumentKey.modDelay;
827
833
  const modAttack = modDelay + instrumentKey.modAttack;
828
834
  const modHold = modAttack + instrumentKey.modHold;
829
835
  const modDecay = modHold + instrumentKey.modDecay;
830
- note.filterNode = new BiquadFilterNode(this.audioContext, {
831
- type: "lowpass",
832
- Q: instrumentKey.initialFilterQ / 10, // dB
833
- frequency: adjustedBaseFreq,
834
- });
835
836
  note.filterNode.frequency
837
+ .cancelScheduledValues(startTime)
838
+ .setValueAtTime(adjustedBaseFreq, startTime)
836
839
  .setValueAtTime(adjustedBaseFreq, modDelay)
837
840
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
838
841
  .setValueAtTime(adjustedPeekFreq, modHold)
@@ -885,8 +888,13 @@ export class Midy {
885
888
  const semitoneOffset = this.calcSemitoneOffset(channel);
886
889
  const note = new Note(noteNumber, velocity, startTime, instrumentKey);
887
890
  note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
888
- this.setFilterNode(channel, note);
889
- 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);
897
+ this.setFilterEnvelope(channel, note);
890
898
  if (0 < channel.vibratoDepth) {
891
899
  this.startVibrato(channel, note, startTime);
892
900
  }
@@ -944,7 +952,7 @@ export class Midy {
944
952
  const now = this.audioContext.currentTime;
945
953
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, now);
946
954
  }
947
- scheduleNoteRelease(channelNumber, noteNumber, velocity, stopTime, stopPedal = false) {
955
+ scheduleNoteRelease(channelNumber, noteNumber, _velocity, stopTime, stopPedal = false) {
948
956
  const channel = this.channels[channelNumber];
949
957
  if (stopPedal && channel.sustainPedal)
950
958
  return;
@@ -959,14 +967,12 @@ export class Midy {
959
967
  continue;
960
968
  if (note.ending)
961
969
  continue;
962
- const velocityRate = (velocity + 127) / 127;
963
970
  const volEndTime = stopTime +
964
- note.instrumentKey.volRelease * velocityRate;
971
+ note.instrumentKey.volRelease * channel.releaseTime;
965
972
  note.volumeNode.gain
966
973
  .cancelScheduledValues(stopTime)
967
974
  .linearRampToValueAtTime(0, volEndTime);
968
- const modRelease = stopTime +
969
- note.instrumentKey.modRelease * velocityRate;
975
+ const modRelease = stopTime + note.instrumentKey.modRelease;
970
976
  note.filterNode.frequency
971
977
  .cancelScheduledValues(stopTime)
972
978
  .linearRampToValueAtTime(0, modRelease);
@@ -1006,13 +1012,14 @@ export class Midy {
1006
1012
  const promises = [];
1007
1013
  channel.sustainPedal = false;
1008
1014
  channel.scheduledNotes.forEach((noteList) => {
1009
- noteList.forEach((note) => {
1010
- if (note) {
1011
- const { noteNumber } = note;
1012
- const promise = this.releaseNote(channelNumber, noteNumber, velocity);
1013
- promises.push(promise);
1014
- }
1015
- });
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
+ }
1016
1023
  });
1017
1024
  return promises;
1018
1025
  }
@@ -1126,7 +1133,16 @@ export class Midy {
1126
1133
  return this.setSostenutoPedal(channelNumber, value);
1127
1134
  case 67:
1128
1135
  return this.setSoftPedal(channelNumber, value);
1129
- // 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);
1130
1146
  case 76:
1131
1147
  return this.setVibratoRate(channelNumber, value);
1132
1148
  case 77:
@@ -1168,15 +1184,19 @@ export class Midy {
1168
1184
  }
1169
1185
  updateModulation(channel) {
1170
1186
  const now = this.audioContext.currentTime;
1171
- const activeNotes = this.getActiveNotes(channel, now);
1172
- activeNotes.forEach((activeNote) => {
1173
- if (activeNote.modulationDepth) {
1174
- activeNote.modulationDepth.gain.setValueAtTime(channel.modulationDepth, now);
1175
- }
1176
- else {
1177
- const semitoneOffset = this.calcSemitoneOffset(channel);
1178
- this.setPitch(activeNote, semitoneOffset);
1179
- 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
+ }
1180
1200
  }
1181
1201
  });
1182
1202
  }
@@ -1282,6 +1302,64 @@ export class Midy {
1282
1302
  const channel = this.channels[channelNumber];
1283
1303
  channel.softPedal = softPedal / 127;
1284
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
+ }
1285
1363
  setVibratoRate(channelNumber, vibratoRate) {
1286
1364
  const channel = this.channels[channelNumber];
1287
1365
  channel.vibratoRate = vibratoRate / 64;
@@ -1362,13 +1440,17 @@ export class Midy {
1362
1440
  }
1363
1441
  updateDetune(channel, detuneChange) {
1364
1442
  const now = this.audioContext.currentTime;
1365
- const activeNotes = this.getActiveNotes(channel, now);
1366
- activeNotes.forEach((activeNote) => {
1367
- const { bufferSource } = activeNote;
1368
- const detune = bufferSource.detune.value + detuneChange;
1369
- bufferSource.detune
1370
- .cancelScheduledValues(now)
1371
- .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
+ }
1372
1454
  });
1373
1455
  }
1374
1456
  handlePitchBendRangeRPN(channelNumber) {
@@ -1788,6 +1870,11 @@ Object.defineProperty(Midy, "channelSettings", {
1788
1870
  volume: 100 / 127,
1789
1871
  pan: 64,
1790
1872
  portamentoTime: 0,
1873
+ filterResonance: 1,
1874
+ releaseTime: 1,
1875
+ attackTime: 1,
1876
+ brightness: 1,
1877
+ decayTime: 1,
1791
1878
  reverbSendLevel: 0,
1792
1879
  chorusSendLevel: 0,
1793
1880
  vibratoRate: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -79,12 +79,13 @@ export class MidyGM1 {
79
79
  calcPlaybackRate(instrumentKey: any, noteNumber: any, semitoneOffset: any): number;
80
80
  setVolumeEnvelope(note: any): void;
81
81
  setPitch(note: any, semitoneOffset: any): void;
82
- setFilterNode(channel: any, note: any): void;
82
+ clampCutoffFrequency(frequency: any): number;
83
+ setFilterEnvelope(note: any): void;
83
84
  startModulation(channel: any, note: any, startTime: any): void;
84
85
  createNote(channel: any, instrumentKey: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
85
86
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
86
87
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
87
- scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
88
+ scheduleNoteRelease(channelNumber: any, noteNumber: any, _velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
88
89
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
89
90
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
90
91
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAwBA;IAmBE;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA9CD,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;IAyBhB,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,sCAGC;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,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAUC;IAED,kDAKC;IAED,iEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA5iCD;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"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAwBA;IAmBE;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA9CD,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;IAyBhB,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,sCAGC;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,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,oDAaC;IAED,kDAKC;IAED,iEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,+CAEC;IAED,8CAEC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AApjCD;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"}