@marmooo/midy 0.0.4 → 0.0.5

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 (33) hide show
  1. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
  2. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
  3. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
  4. package/esm/midy-GM1.d.ts +24 -34
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +167 -106
  7. package/esm/midy-GM2.d.ts +123 -21
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +170 -116
  10. package/esm/midy-GMLite.d.ts +23 -35
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +156 -107
  13. package/esm/midy.d.ts +25 -23
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +191 -120
  16. package/package.json +1 -1
  17. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.d.ts +13 -6
  18. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.d.ts.map +1 -0
  19. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/{soundfont-parser@0.0.1 → soundfont-parser@0.0.2}/+esm.js +5 -5
  20. package/script/midy-GM1.d.ts +24 -34
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +167 -106
  23. package/script/midy-GM2.d.ts +123 -21
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +170 -116
  26. package/script/midy-GMLite.d.ts +23 -35
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +156 -107
  29. package/script/midy.d.ts +25 -23
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +191 -120
  32. package/esm/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
  33. package/script/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.d.ts.map +0 -1
package/esm/midy.js CHANGED
@@ -1,5 +1,55 @@
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.1/+esm.js";
2
+ import { parse, SoundFont, } from "./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js";
3
+ class Note {
4
+ constructor(noteNumber, velocity, startTime, instrumentKey) {
5
+ Object.defineProperty(this, "bufferSource", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: void 0
10
+ });
11
+ Object.defineProperty(this, "gainNode", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
17
+ Object.defineProperty(this, "filterNode", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: void 0
22
+ });
23
+ Object.defineProperty(this, "modLFO", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: void 0
28
+ });
29
+ Object.defineProperty(this, "modLFOGain", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: void 0
34
+ });
35
+ Object.defineProperty(this, "vibLFO", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
41
+ Object.defineProperty(this, "vibLFOGain", {
42
+ enumerable: true,
43
+ configurable: true,
44
+ writable: true,
45
+ value: void 0
46
+ });
47
+ this.noteNumber = noteNumber;
48
+ this.velocity = velocity;
49
+ this.startTime = startTime;
50
+ this.instrumentKey = instrumentKey;
51
+ }
52
+ }
3
53
  export class Midy {
4
54
  constructor(audioContext) {
5
55
  Object.defineProperty(this, "ticksPerBeat", {
@@ -181,10 +231,8 @@ export class Midy {
181
231
  const pannerNode = new StereoPannerNode(audioContext, {
182
232
  pan: Midy.channelSettings.pan,
183
233
  });
184
- const modulationEffect = this.createModulationEffect(audioContext);
185
234
  const reverbEffect = this.createReverbEffect(audioContext);
186
235
  const chorusEffect = this.createChorusEffect(audioContext);
187
- modulationEffect.lfo.start();
188
236
  chorusEffect.lfo.start();
189
237
  reverbEffect.dryGain.connect(pannerNode);
190
238
  reverbEffect.wetGain.connect(pannerNode);
@@ -193,7 +241,6 @@ export class Midy {
193
241
  return {
194
242
  gainNode,
195
243
  pannerNode,
196
- modulationEffect,
197
244
  reverbEffect,
198
245
  chorusEffect,
199
246
  };
@@ -216,11 +263,11 @@ export class Midy {
216
263
  });
217
264
  return channels;
218
265
  }
219
- async createNoteBuffer(noteInfo, isSF3) {
220
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
266
+ async createNoteBuffer(instrumentKey, isSF3) {
267
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
221
268
  if (isSF3) {
222
- const sample = new Uint8Array(noteInfo.sample.length);
223
- sample.set(noteInfo.sample);
269
+ const sample = new Uint8Array(instrumentKey.sample.length);
270
+ sample.set(instrumentKey.sample);
224
271
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
225
272
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
226
273
  const channelData = audioBuffer.getChannelData(channel);
@@ -229,26 +276,27 @@ export class Midy {
229
276
  return audioBuffer;
230
277
  }
231
278
  else {
232
- const sample = noteInfo.sample.subarray(0, sampleEnd);
279
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
233
280
  const floatSample = this.convertToFloat32Array(sample);
234
281
  const audioBuffer = new AudioBuffer({
235
282
  numberOfChannels: 1,
236
283
  length: sample.length,
237
- sampleRate: noteInfo.sampleRate,
284
+ sampleRate: instrumentKey.sampleRate,
238
285
  });
239
286
  const channelData = audioBuffer.getChannelData(0);
240
287
  channelData.set(floatSample);
241
288
  return audioBuffer;
242
289
  }
243
290
  }
244
- async createNoteBufferNode(noteInfo, isSF3) {
291
+ async createNoteBufferNode(instrumentKey, isSF3) {
245
292
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
246
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
293
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
247
294
  bufferSource.buffer = audioBuffer;
248
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
295
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
249
296
  if (bufferSource.loop) {
250
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
251
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
297
+ bufferSource.loopStart = instrumentKey.loopStart /
298
+ instrumentKey.sampleRate;
299
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
252
300
  }
253
301
  return bufferSource;
254
302
  }
@@ -530,30 +578,26 @@ export class Midy {
530
578
  const now = this.audioContext.currentTime;
531
579
  return this.resumeTime + now - this.startTime - this.startDelay;
532
580
  }
533
- getActiveNotes(channel) {
581
+ getActiveNotes(channel, time) {
534
582
  const activeNotes = new Map();
535
- channel.scheduledNotes.forEach((scheduledNotes) => {
536
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
583
+ channel.scheduledNotes.forEach((noteList) => {
584
+ const activeNote = this.getActiveNote(noteList, time);
537
585
  if (activeNote) {
538
586
  activeNotes.set(activeNote.noteNumber, activeNote);
539
587
  }
540
588
  });
541
589
  return activeNotes;
542
590
  }
543
- getActiveChannelNotes(scheduledNotes) {
544
- for (let i = 0; i < scheduledNotes; i++) {
545
- const scheduledNote = scheduledNotes[i];
546
- if (scheduledNote)
547
- return scheduledNote;
591
+ getActiveNote(noteList, time) {
592
+ for (let i = noteList.length - 1; i >= 0; i--) {
593
+ const note = noteList[i];
594
+ if (!note)
595
+ return;
596
+ if (time < note.startTime)
597
+ continue;
598
+ return (note.ending) ? null : note;
548
599
  }
549
- }
550
- createModulationEffect(audioContext) {
551
- const lfo = new OscillatorNode(audioContext, {
552
- frequency: 5,
553
- });
554
- return {
555
- lfo,
556
- };
600
+ return noteList[0];
557
601
  }
558
602
  createReverbEffect(audioContext, options = {}) {
559
603
  const { decay = 0.8, preDecay = 0, } = options;
@@ -663,77 +707,110 @@ export class Midy {
663
707
  const tuning = masterTuning + channelTuning;
664
708
  return channel.pitchBend * channel.pitchBendRange + tuning;
665
709
  }
666
- calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
667
- return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
710
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
711
+ return instrumentKey.playbackRate(noteNumber) *
712
+ Math.pow(2, semitoneOffset / 12);
668
713
  }
669
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
670
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
671
- const semitoneOffset = this.calcSemitoneOffset(channel);
672
- bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
673
- // volume envelope
674
- const gainNode = new GainNode(this.audioContext, {
714
+ setVolumeEnvelope(channel, note) {
715
+ const { instrumentKey, startTime, velocity } = note;
716
+ note.gainNode = new GainNode(this.audioContext, {
675
717
  gain: 0,
676
718
  });
677
719
  let volume = (velocity / 127) * channel.volume * channel.expression;
678
720
  if (volume === 0)
679
721
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
680
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
681
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
682
- const volDelay = startTime + noteInfo.volDelay;
683
- const volAttack = volDelay + noteInfo.volAttack;
684
- const volHold = volAttack + noteInfo.volHold;
685
- const volDecay = volHold + noteInfo.volDecay;
686
- gainNode.gain
722
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
723
+ volume;
724
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
725
+ const volDelay = startTime + instrumentKey.volDelay;
726
+ const volAttack = volDelay + instrumentKey.volAttack;
727
+ const volHold = volAttack + instrumentKey.volHold;
728
+ const volDecay = volHold + instrumentKey.volDecay;
729
+ note.gainNode.gain
687
730
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
688
731
  .exponentialRampToValueAtTime(attackVolume, volAttack)
689
732
  .setValueAtTime(attackVolume, volHold)
690
733
  .linearRampToValueAtTime(sustainVolume, volDecay);
691
- // filter envelope
734
+ }
735
+ setFilterEnvelope(channel, note) {
736
+ const { instrumentKey, startTime, noteNumber } = note;
692
737
  const softPedalFactor = 1 -
693
738
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
694
739
  const maxFreq = this.audioContext.sampleRate / 2;
695
- const baseFreq = this.centToHz(noteInfo.initialFilterFc) * softPedalFactor;
696
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc) * softPedalFactor;
740
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
741
+ softPedalFactor;
742
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
697
743
  const sustainFreq = (baseFreq +
698
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain)) * softPedalFactor;
744
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
745
+ const modDelay = startTime + instrumentKey.modDelay;
746
+ const modAttack = modDelay + instrumentKey.modAttack;
747
+ const modHold = modAttack + instrumentKey.modHold;
748
+ const modDecay = modHold + instrumentKey.modDecay;
699
749
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
700
750
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
701
751
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
702
- const filterNode = new BiquadFilterNode(this.audioContext, {
752
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
703
753
  type: "lowpass",
704
- Q: noteInfo.initialFilterQ / 10, // dB
754
+ Q: instrumentKey.initialFilterQ / 10, // dB
705
755
  frequency: adjustedBaseFreq,
706
756
  });
707
- const modDelay = startTime + noteInfo.modDelay;
708
- const modAttack = modDelay + noteInfo.modAttack;
709
- const modHold = modAttack + noteInfo.modHold;
710
- const modDecay = modHold + noteInfo.modDecay;
711
- filterNode.frequency
757
+ note.filterNode.frequency
712
758
  .setValueAtTime(adjustedBaseFreq, modDelay)
713
759
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
714
760
  .setValueAtTime(adjustedPeekFreq, modHold)
715
761
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
716
- let lfoGain;
762
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
763
+ }
764
+ startModulation(channel, note, time) {
765
+ const { instrumentKey } = note;
766
+ note.modLFOGain = new GainNode(this.audioContext, {
767
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
768
+ });
769
+ note.modLFO = new OscillatorNode(this.audioContext, {
770
+ frequency: this.centToHz(instrumentKey.freqModLFO),
771
+ });
772
+ note.modLFO.start(time);
773
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
774
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
775
+ note.modLFO.connect(note.modLFOGain);
776
+ note.modLFOGain.connect(note.bufferSource.detune);
777
+ }
778
+ startVibrato(channel, note, time) {
779
+ const { instrumentKey } = note;
780
+ note.vibLFOGain = new GainNode(this.audioContext, {
781
+ gain: channel.vibratoDepth,
782
+ });
783
+ note.vibLFO = new OscillatorNode(this.audioContext, {
784
+ frequency: this.centToHz(instrumentKey.freqModLFO) +
785
+ channel.vibratoRate,
786
+ });
787
+ note.vibLFO.start(time + channel.vibratoDelay);
788
+ note.vibLFO.connect(note.vibLFOGain);
789
+ note.vibLFOGain.connect(note.bufferSource.detune);
790
+ }
791
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
792
+ const semitoneOffset = this.calcSemitoneOffset(channel);
793
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
794
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
795
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
796
+ this.setVolumeEnvelope(channel, note);
797
+ this.setFilterEnvelope(channel, note);
717
798
  if (channel.modulation > 0) {
718
- const vibratoDelay = startTime + channel.vibratoDelay;
719
- const vibratoAttack = vibratoDelay + 0.1;
720
- lfoGain = new GainNode(this.audioContext, {
721
- gain: 0,
722
- });
723
- lfoGain.gain
724
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
725
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
726
- channel.modulationEffect.lfo.connect(lfoGain);
727
- lfoGain.connect(bufferSource.detune);
799
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
800
+ this.startModulation(channel, note, delayModLFO);
801
+ }
802
+ if (channel.vibratoDepth > 0) {
803
+ const delayVibLFO = startTime + instrumentKey.delayVibLFO;
804
+ this.startVibrato(channel, note, delayVibLFO);
728
805
  }
729
- bufferSource.connect(filterNode);
730
- filterNode.connect(gainNode);
731
806
  if (this.mono && channel.currentBufferSource) {
732
807
  channel.currentBufferSource.stop(startTime);
733
- channel.currentBufferSource = bufferSource;
808
+ channel.currentBufferSource = note.bufferSource;
734
809
  }
735
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
736
- return { bufferSource, gainNode, filterNode, lfoGain };
810
+ note.bufferSource.connect(note.filterNode);
811
+ note.filterNode.connect(note.gainNode);
812
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
813
+ return note;
737
814
  }
738
815
  calcBank(channel, channelNumber) {
739
816
  if (channel.bankMSB === 121) {
@@ -752,36 +829,20 @@ export class Midy {
752
829
  return;
753
830
  const soundFont = this.soundFonts[soundFontIndex];
754
831
  const isSF3 = soundFont.parsed.info.version.major === 3;
755
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
756
- if (!noteInfo)
832
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
833
+ if (!instrumentKey)
757
834
  return;
758
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
759
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
760
- this.connectNoteEffects(channel, gainNode);
835
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
836
+ this.connectNoteEffects(channel, note.gainNode);
761
837
  if (channel.sostenutoPedal) {
762
- channel.sostenutoNotes.set(noteNumber, {
763
- gainNode,
764
- filterNode,
765
- bufferSource,
766
- noteNumber,
767
- noteInfo,
768
- });
838
+ channel.sostenutoNotes.set(noteNumber, note);
769
839
  }
770
840
  const scheduledNotes = channel.scheduledNotes;
771
- const scheduledNote = {
772
- bufferSource,
773
- filterNode,
774
- gainNode,
775
- lfoGain,
776
- noteInfo,
777
- noteNumber,
778
- startTime,
779
- };
780
841
  if (scheduledNotes.has(noteNumber)) {
781
- scheduledNotes.get(noteNumber).push(scheduledNote);
842
+ scheduledNotes.get(noteNumber).push(note);
782
843
  }
783
844
  else {
784
- scheduledNotes.set(noteNumber, [scheduledNote]);
845
+ scheduledNotes.set(noteNumber, [note]);
785
846
  }
786
847
  }
787
848
  noteOn(channelNumber, noteNumber, velocity) {
@@ -803,15 +864,15 @@ export class Midy {
803
864
  continue;
804
865
  if (targetNote.ending)
805
866
  continue;
806
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
867
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
807
868
  const velocityRate = (velocity + 127) / 127;
808
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
869
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
809
870
  gainNode.gain.cancelScheduledValues(stopTime);
810
871
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
811
872
  const maxFreq = this.audioContext.sampleRate / 2;
812
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
873
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
813
874
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
814
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
875
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
815
876
  filterNode.frequency
816
877
  .cancelScheduledValues(stopTime)
817
878
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -825,8 +886,14 @@ export class Midy {
825
886
  bufferSource.disconnect(0);
826
887
  filterNode.disconnect(0);
827
888
  gainNode.disconnect(0);
828
- if (lfoGain)
829
- lfoGain.disconnect(0);
889
+ if (modLFOGain)
890
+ modLFOGain.disconnect(0);
891
+ if (vibLFOGain)
892
+ vibLFOGain.disconnect(0);
893
+ if (modLFO)
894
+ modLFO.stop();
895
+ if (vibLFO)
896
+ vibLFO.stop();
830
897
  resolve();
831
898
  };
832
899
  bufferSource.stop(volEndTime);
@@ -892,7 +959,7 @@ export class Midy {
892
959
  const now = this.audioContext.currentTime;
893
960
  const channel = this.channels[channelNumber];
894
961
  pressure /= 64;
895
- const activeNotes = this.getActiveNotes(channel);
962
+ const activeNotes = this.getActiveNotes(channel, now);
896
963
  if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
897
964
  if (activeNotes.has(noteNumber)) {
898
965
  const activeNote = activeNotes.get(noteNumber);
@@ -913,7 +980,7 @@ export class Midy {
913
980
  const channel = this.channels[channelNumber];
914
981
  pressure /= 64;
915
982
  channel.channelPressure = pressure;
916
- const activeNotes = this.getActiveNotes(channel);
983
+ const activeNotes = this.getActiveNotes(channel, now);
917
984
  if (channel.channelPressure.amplitudeControl !== 1) {
918
985
  activeNotes.forEach((activeNote) => {
919
986
  const gain = activeNote.gainNode.gain.value;
@@ -932,10 +999,10 @@ export class Midy {
932
999
  const channel = this.channels[channelNumber];
933
1000
  channel.pitchBend = (pitchBend - 8192) / 8192;
934
1001
  const semitoneOffset = this.calcSemitoneOffset(channel);
935
- const activeNotes = this.getActiveNotes(channel);
1002
+ const activeNotes = this.getActiveNotes(channel, now);
936
1003
  activeNotes.forEach((activeNote) => {
937
- const { bufferSource, noteInfo, noteNumber } = activeNote;
938
- const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
1004
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
1005
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
939
1006
  bufferSource.playbackRate
940
1007
  .cancelScheduledValues(now)
941
1008
  .setValueAtTime(playbackRate * pressure, now);
@@ -1010,9 +1077,20 @@ export class Midy {
1010
1077
  this.channels[channelNumber].bankMSB = msb;
1011
1078
  }
1012
1079
  setModulation(channelNumber, modulation) {
1080
+ const now = this.audioContext.currentTime;
1013
1081
  const channel = this.channels[channelNumber];
1014
1082
  channel.modulation = (modulation / 127) *
1015
1083
  (channel.modulationDepthRange * 100);
1084
+ const activeNotes = this.getActiveNotes(channel, now);
1085
+ activeNotes.forEach((activeNote) => {
1086
+ if (activeNote.modLFO) {
1087
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
1088
+ channel.modulation, now);
1089
+ }
1090
+ else {
1091
+ this.startModulation(channel, activeNote, now);
1092
+ }
1093
+ });
1016
1094
  }
1017
1095
  setPortamentoTime(channelNumber, portamentoTime) {
1018
1096
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1073,7 +1151,8 @@ export class Midy {
1073
1151
  const channel = this.channels[channelNumber];
1074
1152
  channel.sostenutoPedal = isOn;
1075
1153
  if (isOn) {
1076
- const activeNotes = this.getActiveNotes(channel);
1154
+ const now = this.audioContext.currentTime;
1155
+ const activeNotes = this.getActiveNotes(channel, now);
1077
1156
  channel.sostenutoNotes = new Map(activeNotes);
1078
1157
  }
1079
1158
  else {
@@ -1085,20 +1164,12 @@ export class Midy {
1085
1164
  channel.softPedal = softPedal / 127;
1086
1165
  }
1087
1166
  setVibratoRate(channelNumber, vibratoRate) {
1088
- const now = this.audioContext.currentTime;
1089
1167
  const channel = this.channels[channelNumber];
1090
1168
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1091
- channel.modulationEffect.lfo.frequency
1092
- .cancelScheduledValues(now)
1093
- .setValueAtTime(channel.vibratoRate, now);
1094
1169
  }
1095
1170
  setVibratoDepth(channelNumber, vibratoDepth) {
1096
- const now = this.audioContext.currentTime;
1097
1171
  const channel = this.channels[channelNumber];
1098
1172
  channel.vibratoDepth = vibratoDepth / 127;
1099
- channel.modulationEffect.lfoGain.gain
1100
- .cancelScheduledValues(now)
1101
- .setValueAtTime(channel.vibratoDepth, now);
1102
1173
  }
1103
1174
  setVibratoDelay(channelNumber, vibratoDelay) {
1104
1175
  // Access Virus: 0-10sec
@@ -1179,8 +1250,8 @@ export class Midy {
1179
1250
  const velocity = 0;
1180
1251
  const stopPedal = true;
1181
1252
  const promises = [];
1182
- channel.scheduledNotes.forEach((scheduledNotes) => {
1183
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1253
+ channel.scheduledNotes.forEach((noteList) => {
1254
+ const activeNote = this.getActiveNote(noteList, now);
1184
1255
  if (activeNote) {
1185
1256
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1186
1257
  promises.push(notePromise);
@@ -1197,8 +1268,8 @@ export class Midy {
1197
1268
  const velocity = 0;
1198
1269
  const stopPedal = false;
1199
1270
  const promises = [];
1200
- channel.scheduledNotes.forEach((scheduledNotes) => {
1201
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1271
+ channel.scheduledNotes.forEach((noteList) => {
1272
+ const activeNote = this.getActiveNote(noteList, now);
1202
1273
  if (activeNote) {
1203
1274
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1204
1275
  promises.push(notePromise);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
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",
@@ -12,7 +12,14 @@ declare class F {
12
12
  sampleName: any;
13
13
  sampleModes: number;
14
14
  playbackRate: (e: any) => number;
15
+ modLfoToPitch: number;
16
+ vibLfoToPitch: number;
15
17
  modEnvToPitch: number;
18
+ initialFilterFc: number;
19
+ initialFilterQ: number;
20
+ modLfoToFilterFc: number;
21
+ modEnvToFilterFc: number;
22
+ modLfoToVolume: number;
16
23
  scaleTuning: number;
17
24
  start: number;
18
25
  end: number;
@@ -32,12 +39,12 @@ declare class F {
32
39
  modRelease: number;
33
40
  keyRange: d;
34
41
  velRange: d;
35
- initialFilterFc: number;
36
- modEnvToFilterFc: number;
37
- initialFilterQ: number;
42
+ delayModLFO: number;
43
+ freqModLFO: number;
44
+ delayVibLFO: number;
45
+ freqVibLFO: number;
38
46
  initialAttenuation: number;
39
- freqVibLFO: number | undefined;
40
- pan: undefined;
47
+ pan: number;
41
48
  } | null;
42
49
  getPresetNames(): {};
43
50
  }
@@ -95,7 +102,7 @@ declare namespace T {
95
102
  let initialFilterQ: number;
96
103
  let initialFilterFc: number;
97
104
  let sampleModes: number;
98
- let pan: undefined;
105
+ let pan: number;
99
106
  }
100
107
  declare function b(e: any, n?: {}): {
101
108
  samples: any;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"+esm.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js"],"names":[],"mappings":";;AAMizX;IAAQ,oBAA2H;IAAd,YAAa;IAAC,qDAAoJ;IAAA,mCAAoP;IAAA,uCAAwQ;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAAkqE;IAAA,qBAA0I;CAAC;AAAA,mCAAwC;AAA9oI,+BAA8F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAA98D;;;;;;;;;;;;;;;;EAA27B;AAAr4I;IAAqQ,wBAAwD;IAArT,4BAAyN;IAApB,QAAS;IAAC,QAAS;IAAC,oBAAoC;CAAyD;AAAllG;IAAwjC,gCAA8d;CAAC"}
@@ -6,7 +6,7 @@ exports.createGeneratorObject = I;
6
6
  exports.parse = b;
7
7
  /**
8
8
  * Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.37.0.
9
- * Original file: /npm/@marmooo/soundfont-parser@0.0.1/esm/mod.js
9
+ * Original file: /npm/@marmooo/soundfont-parser@0.0.2/esm/mod.js
10
10
  *
11
11
  * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
12
12
  */
@@ -114,7 +114,7 @@ function I(e) { const t = {}; for (const r of e) {
114
114
  const e = r.type;
115
115
  void 0 !== e && (t[e] = r.value);
116
116
  } return t; }
117
- const T = { keynum: void 0, instrument: void 0, velocity: void 0, exclusiveClass: void 0, keyRange: new d(0, 127), velRange: new d(0, 127), sampleID: void 0, delayVolEnv: -12e3, attackVolEnv: -12e3, decayVolEnv: -12e3, holdVolEnv: -12e3, sustainVolEnv: 0, releaseVolEnv: -12e3, delayModEnv: -12e3, attackModEnv: -12e3, decayModEnv: -12e3, holdModEnv: -12e3, sustainModEnv: 0, releaseModEnv: -12e3, modEnvToPitch: 0, modEnvToFilterFc: 0, modLfoToFilterFc: 0, modLfoToPitch: 0, modLfoToVolume: 0, vibLfoToPitch: 0, chorusEffectsSend: 0, reverbEffectsSend: 0, delayModLFO: 0, freqModLFO: 0, delayVibLFO: 0, keynumToModEnvDecay: 0, keynumToModEnvHold: 0, keynumToVolEnvDecay: 0, keynumToVolEnvHold: 0, coarseTune: 0, fineTune: 0, scaleTuning: 100, freqVibLFO: 0, startAddrsOffset: 0, startAddrsCoarseOffset: 0, endAddrsOffset: 0, endAddrsCoarseOffset: 0, startloopAddrsOffset: 0, startloopAddrsCoarseOffset: 0, initialAttenuation: 0, endloopAddrsOffset: 0, endloopAddrsCoarseOffset: 0, overridingRootKey: void 0, initialFilterQ: 1, initialFilterFc: 13500, sampleModes: 0, pan: void 0 };
117
+ const T = { keynum: void 0, instrument: void 0, velocity: void 0, exclusiveClass: void 0, keyRange: new d(0, 127), velRange: new d(0, 127), sampleID: void 0, delayVolEnv: -12e3, attackVolEnv: -12e3, decayVolEnv: -12e3, holdVolEnv: -12e3, sustainVolEnv: 0, releaseVolEnv: -12e3, delayModEnv: -12e3, attackModEnv: -12e3, decayModEnv: -12e3, holdModEnv: -12e3, sustainModEnv: 0, releaseModEnv: -12e3, modEnvToPitch: 0, modEnvToFilterFc: 0, modLfoToFilterFc: 0, modLfoToPitch: 0, modLfoToVolume: 0, vibLfoToPitch: 0, chorusEffectsSend: 0, reverbEffectsSend: 0, delayModLFO: 0, freqModLFO: 0, delayVibLFO: 0, keynumToModEnvDecay: 0, keynumToModEnvHold: 0, keynumToVolEnvDecay: 0, keynumToVolEnvHold: 0, coarseTune: 0, fineTune: 0, scaleTuning: 100, freqVibLFO: 0, startAddrsOffset: 0, startAddrsCoarseOffset: 0, endAddrsOffset: 0, endAddrsCoarseOffset: 0, startloopAddrsOffset: 0, startloopAddrsCoarseOffset: 0, initialAttenuation: 0, endloopAddrsOffset: 0, endloopAddrsCoarseOffset: 0, overridingRootKey: void 0, initialFilterQ: 1, initialFilterFc: 13500, sampleModes: 0, pan: 0 };
118
118
  exports.defaultInstrumentZone = T;
119
119
  class F {
120
120
  constructor(e) { Object.defineProperty(this, "parsed", { enumerable: !0, configurable: !0, writable: !0, value: void 0 }), this.parsed = e; }
@@ -151,12 +151,12 @@ class F {
151
151
  break;
152
152
  } if (!l)
153
153
  return console.warn("instrument not found: bank=%s instrument=%s", e, t), null; if (void 0 === l.sampleID)
154
- throw new Error("Invalid SoundFont: sampleID not found"); const d = { ...T, ...A(o || {}), ...A(s || {}), ...A(l) }, u = this.parsed.samples[d.sampleID], c = this.parsed.sampleHeaders[d.sampleID], f = d.coarseTune + d.fineTune / 100 + c.pitchCorrection / 100 - (d.overridingRootKey || c.originalPitch), p = d.scaleTuning / 100; return { sample: u, sampleRate: c.sampleRate, sampleName: c.sampleName, sampleModes: d.sampleModes, playbackRate: e => Math.pow(Math.pow(2, 1 / 12), (e + f) * p), modEnvToPitch: d.modEnvToPitch / 100, scaleTuning: p, start: 32768 * d.startAddrsCoarseOffset + d.startAddrsOffset, end: 32768 * d.endAddrsCoarseOffset + d.endAddrsOffset, loopStart: c.loopStart + 32768 * d.startloopAddrsCoarseOffset + d.startloopAddrsOffset, loopEnd: c.loopEnd + 32768 * d.endloopAddrsCoarseOffset + d.endloopAddrsOffset, volDelay: M(d.delayVolEnv), volAttack: M(d.attackVolEnv), volHold: M(d.holdVolEnv), volDecay: M(d.decayVolEnv), volSustain: d.sustainVolEnv / 1e3, volRelease: M(d.releaseVolEnv), modDelay: M(d.delayModEnv), modAttack: M(d.attackModEnv), modHold: M(d.holdModEnv), modDecay: M(d.decayModEnv), modSustain: d.sustainModEnv / 1e3, modRelease: M(d.releaseModEnv), keyRange: d.keyRange, velRange: d.velRange, initialFilterFc: d.initialFilterFc, modEnvToFilterFc: d.modEnvToFilterFc, initialFilterQ: d.initialFilterQ, initialAttenuation: d.initialAttenuation, freqVibLFO: d.freqVibLFO ? 8.176 * M(d.freqVibLFO) : void 0, pan: d.pan }; }
154
+ throw new Error("Invalid SoundFont: sampleID not found"); const d = { ...T, ...L(o || {}), ...L(s || {}), ...L(l) }, u = this.parsed.samples[d.sampleID], c = this.parsed.sampleHeaders[d.sampleID], f = d.coarseTune + d.fineTune / 100 + c.pitchCorrection / 100 - (d.overridingRootKey || c.originalPitch), p = d.scaleTuning / 100; return { sample: u, sampleRate: c.sampleRate, sampleName: c.sampleName, sampleModes: d.sampleModes, playbackRate: e => Math.pow(Math.pow(2, 1 / 12), (e + f) * p), modLfoToPitch: d.modLfoToPitch, vibLfoToPitch: d.vibLfoToPitch, modEnvToPitch: d.modEnvToPitch, initialFilterFc: d.initialFilterFc, initialFilterQ: d.initialFilterQ, modLfoToFilterFc: d.modLfoToFilterFc, modEnvToFilterFc: d.modEnvToFilterFc, modLfoToVolume: d.modLfoToVolume, scaleTuning: p, start: 32768 * d.startAddrsCoarseOffset + d.startAddrsOffset, end: 32768 * d.endAddrsCoarseOffset + d.endAddrsOffset, loopStart: c.loopStart + 32768 * d.startloopAddrsCoarseOffset + d.startloopAddrsOffset, loopEnd: c.loopEnd + 32768 * d.endloopAddrsCoarseOffset + d.endloopAddrsOffset, volDelay: M(d.delayVolEnv), volAttack: M(d.attackVolEnv), volHold: M(d.holdVolEnv), volDecay: M(d.decayVolEnv), volSustain: d.sustainVolEnv / 1e3, volRelease: M(d.releaseVolEnv), modDelay: M(d.delayModEnv), modAttack: M(d.attackModEnv), modHold: M(d.holdModEnv), modDecay: M(d.decayModEnv), modSustain: d.sustainModEnv / 1e3, modRelease: M(d.releaseModEnv), keyRange: d.keyRange, velRange: d.velRange, delayModLFO: M(d.delayModLFO), freqModLFO: d.freqModLFO, delayVibLFO: M(d.delayVibLFO), freqVibLFO: d.freqVibLFO, initialAttenuation: d.initialAttenuation, pan: d.pan }; }
155
155
  getPresetNames() { const e = {}; return this.parsed.presetHeaders.forEach((t => { e[t.bank] || (e[t.bank] = {}), e[t.bank][t.preset] = t.presetName; })), e; }
156
156
  }
157
157
  exports.SoundFont = F;
158
158
  function M(e) { return Math.pow(2, e / 1200); }
159
- function A(e) { const t = {}; for (const r in e)
159
+ function L(e) { const t = {}; for (const r in e)
160
160
  void 0 !== e[r] && (t[r] = e[r]); return t; }
161
161
  exports.default = null;
162
- //# sourceMappingURL=/sm/f81f37edef0ee7c3c75f39a74df8b8e54ea144490d686e35430b76b426f1559d.map
162
+ //# sourceMappingURL=/sm/22d03ff23e99217677631c656a57eea14149d50ae4425cf6cd850760c3e93c1e.map