@marmooo/midy 0.0.3 → 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 +27 -36
  5. package/esm/midy-GM1.d.ts.map +1 -1
  6. package/esm/midy-GM1.js +199 -135
  7. package/esm/midy-GM2.d.ts +51 -35
  8. package/esm/midy-GM2.d.ts.map +1 -1
  9. package/esm/midy-GM2.js +234 -141
  10. package/esm/midy-GMLite.d.ts +25 -36
  11. package/esm/midy-GMLite.d.ts.map +1 -1
  12. package/esm/midy-GMLite.js +187 -135
  13. package/esm/midy.d.ts +68 -24
  14. package/esm/midy.d.ts.map +1 -1
  15. package/esm/midy.js +274 -141
  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 +27 -36
  21. package/script/midy-GM1.d.ts.map +1 -1
  22. package/script/midy-GM1.js +199 -135
  23. package/script/midy-GM2.d.ts +51 -35
  24. package/script/midy-GM2.d.ts.map +1 -1
  25. package/script/midy-GM2.js +234 -141
  26. package/script/midy-GMLite.d.ts +25 -36
  27. package/script/midy-GMLite.d.ts.map +1 -1
  28. package/script/midy-GMLite.js +187 -135
  29. package/script/midy.d.ts +68 -24
  30. package/script/midy.d.ts.map +1 -1
  31. package/script/midy.js +274 -141
  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
@@ -2,7 +2,57 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MidyGM2 = void 0;
4
4
  const _esm_js_1 = require("./deps/cdn.jsdelivr.net/npm/midi-file@1.2.4/+esm.js");
5
- const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.js");
5
+ const _esm_js_2 = require("./deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.2/+esm.js");
6
+ class Note {
7
+ constructor(noteNumber, velocity, startTime, instrumentKey) {
8
+ Object.defineProperty(this, "bufferSource", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ Object.defineProperty(this, "gainNode", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
20
+ Object.defineProperty(this, "filterNode", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
26
+ Object.defineProperty(this, "modLFO", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: void 0
31
+ });
32
+ Object.defineProperty(this, "modLFOGain", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "vibLFO", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: void 0
43
+ });
44
+ Object.defineProperty(this, "vibLFOGain", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: void 0
49
+ });
50
+ this.noteNumber = noteNumber;
51
+ this.velocity = velocity;
52
+ this.startTime = startTime;
53
+ this.instrumentKey = instrumentKey;
54
+ }
55
+ }
6
56
  class MidyGM2 {
7
57
  constructor(audioContext) {
8
58
  Object.defineProperty(this, "ticksPerBeat", {
@@ -184,10 +234,8 @@ class MidyGM2 {
184
234
  const pannerNode = new StereoPannerNode(audioContext, {
185
235
  pan: MidyGM2.channelSettings.pan,
186
236
  });
187
- const modulationEffect = this.createModulationEffect(audioContext);
188
237
  const reverbEffect = this.createReverbEffect(audioContext);
189
238
  const chorusEffect = this.createChorusEffect(audioContext);
190
- modulationEffect.lfo.start();
191
239
  chorusEffect.lfo.start();
192
240
  reverbEffect.dryGain.connect(pannerNode);
193
241
  reverbEffect.wetGain.connect(pannerNode);
@@ -196,7 +244,6 @@ class MidyGM2 {
196
244
  return {
197
245
  gainNode,
198
246
  pannerNode,
199
- modulationEffect,
200
247
  reverbEffect,
201
248
  chorusEffect,
202
249
  };
@@ -209,15 +256,18 @@ class MidyGM2 {
209
256
  ...this.setChannelAudioNodes(audioContext),
210
257
  scheduledNotes: new Map(),
211
258
  sostenutoNotes: new Map(),
259
+ channelPressure: {
260
+ ...MidyGM2.controllerDestinationSettings,
261
+ },
212
262
  };
213
263
  });
214
264
  return channels;
215
265
  }
216
- async createNoteBuffer(noteInfo, isSF3) {
217
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
266
+ async createNoteBuffer(instrumentKey, isSF3) {
267
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
218
268
  if (isSF3) {
219
- const sample = new Uint8Array(noteInfo.sample.length);
220
- sample.set(noteInfo.sample);
269
+ const sample = new Uint8Array(instrumentKey.sample.length);
270
+ sample.set(instrumentKey.sample);
221
271
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
222
272
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
223
273
  const channelData = audioBuffer.getChannelData(channel);
@@ -226,26 +276,27 @@ class MidyGM2 {
226
276
  return audioBuffer;
227
277
  }
228
278
  else {
229
- const sample = noteInfo.sample.subarray(0, sampleEnd);
279
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
230
280
  const floatSample = this.convertToFloat32Array(sample);
231
281
  const audioBuffer = new AudioBuffer({
232
282
  numberOfChannels: 1,
233
283
  length: sample.length,
234
- sampleRate: noteInfo.sampleRate,
284
+ sampleRate: instrumentKey.sampleRate,
235
285
  });
236
286
  const channelData = audioBuffer.getChannelData(0);
237
287
  channelData.set(floatSample);
238
288
  return audioBuffer;
239
289
  }
240
290
  }
241
- async createNoteBufferNode(noteInfo, isSF3) {
291
+ async createNoteBufferNode(instrumentKey, isSF3) {
242
292
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
243
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
293
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
244
294
  bufferSource.buffer = audioBuffer;
245
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
295
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
246
296
  if (bufferSource.loop) {
247
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
248
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
297
+ bufferSource.loopStart = instrumentKey.loopStart /
298
+ instrumentKey.sampleRate;
299
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
249
300
  }
250
301
  return bufferSource;
251
302
  }
@@ -263,9 +314,6 @@ class MidyGM2 {
263
314
  if (event.startTime > t + this.lookAhead)
264
315
  break;
265
316
  switch (event.type) {
266
- case "controller":
267
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
268
- break;
269
317
  case "noteOn":
270
318
  if (event.velocity !== 0) {
271
319
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -279,9 +327,18 @@ class MidyGM2 {
279
327
  }
280
328
  break;
281
329
  }
330
+ case "controller":
331
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
332
+ break;
282
333
  case "programChange":
283
334
  this.handleProgramChange(event.channel, event.programNumber);
284
335
  break;
336
+ case "channelAftertouch":
337
+ this.handleChannelPressure(event.channel, event.amount);
338
+ break;
339
+ case "pitchBend":
340
+ this.handlePitchBend(event.channel, event.value);
341
+ break;
285
342
  case "sysEx":
286
343
  this.handleSysEx(event.data);
287
344
  }
@@ -518,30 +575,26 @@ class MidyGM2 {
518
575
  const now = this.audioContext.currentTime;
519
576
  return this.resumeTime + now - this.startTime - this.startDelay;
520
577
  }
521
- getActiveNotes(channel) {
578
+ getActiveNotes(channel, time) {
522
579
  const activeNotes = new Map();
523
- channel.scheduledNotes.forEach((scheduledNotes) => {
524
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
580
+ channel.scheduledNotes.forEach((noteList) => {
581
+ const activeNote = this.getActiveNote(noteList, time);
525
582
  if (activeNote) {
526
583
  activeNotes.set(activeNote.noteNumber, activeNote);
527
584
  }
528
585
  });
529
586
  return activeNotes;
530
587
  }
531
- getActiveChannelNotes(scheduledNotes) {
532
- for (let i = 0; i < scheduledNotes; i++) {
533
- const scheduledNote = scheduledNotes[i];
534
- if (scheduledNote)
535
- return scheduledNote;
588
+ getActiveNote(noteList, time) {
589
+ for (let i = noteList.length - 1; i >= 0; i--) {
590
+ const note = noteList[i];
591
+ if (!note)
592
+ return;
593
+ if (time < note.startTime)
594
+ continue;
595
+ return (note.ending) ? null : note;
536
596
  }
537
- }
538
- createModulationEffect(audioContext) {
539
- const lfo = new OscillatorNode(audioContext, {
540
- frequency: 5,
541
- });
542
- return {
543
- lfo,
544
- };
597
+ return noteList[0];
545
598
  }
546
599
  createReverbEffect(audioContext, options = {}) {
547
600
  const { decay = 0.8, preDecay = 0, } = options;
@@ -645,79 +698,99 @@ class MidyGM2 {
645
698
  centToHz(cent) {
646
699
  return 8.176 * Math.pow(2, cent / 1200);
647
700
  }
648
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
701
+ calcSemitoneOffset(channel) {
649
702
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
650
703
  const channelTuning = channel.coarseTuning + channel.fineTuning;
651
704
  const tuning = masterTuning + channelTuning;
652
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
653
- const playbackRate = noteInfo.playbackRate(noteNumber) *
705
+ return channel.pitchBend * channel.pitchBendRange + tuning;
706
+ }
707
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
708
+ return instrumentKey.playbackRate(noteNumber) *
654
709
  Math.pow(2, semitoneOffset / 12);
655
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
656
- bufferSource.playbackRate.value = playbackRate;
657
- // volume envelope
658
- const gainNode = new GainNode(this.audioContext, {
710
+ }
711
+ setVolumeEnvelope(channel, note) {
712
+ const { instrumentKey, startTime, velocity } = note;
713
+ note.gainNode = new GainNode(this.audioContext, {
659
714
  gain: 0,
660
715
  });
661
716
  let volume = (velocity / 127) * channel.volume * channel.expression;
662
717
  if (volume === 0)
663
718
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
664
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
665
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
666
- const volDelay = startTime + noteInfo.volDelay;
667
- const volAttack = volDelay + noteInfo.volAttack;
668
- const volHold = volAttack + noteInfo.volHold;
669
- const volDecay = volHold + noteInfo.volDecay;
670
- gainNode.gain
719
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
720
+ volume;
721
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
722
+ const volDelay = startTime + instrumentKey.volDelay;
723
+ const volAttack = volDelay + instrumentKey.volAttack;
724
+ const volHold = volAttack + instrumentKey.volHold;
725
+ const volDecay = volHold + instrumentKey.volDecay;
726
+ note.gainNode.gain
671
727
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
672
728
  .exponentialRampToValueAtTime(attackVolume, volAttack)
673
729
  .setValueAtTime(attackVolume, volHold)
674
730
  .linearRampToValueAtTime(sustainVolume, volDecay);
675
- // filter envelope
731
+ }
732
+ setFilterEnvelope(channel, note) {
733
+ const { instrumentKey, startTime, noteNumber } = note;
676
734
  const softPedalFactor = 1 -
677
735
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
678
736
  const maxFreq = this.audioContext.sampleRate / 2;
679
- const baseFreq = this.centToHz(noteInfo.initialFilterFc) * softPedalFactor;
680
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc) * softPedalFactor;
737
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
738
+ softPedalFactor;
739
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
681
740
  const sustainFreq = (baseFreq +
682
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain)) * softPedalFactor;
741
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
742
+ const modDelay = startTime + instrumentKey.modDelay;
743
+ const modAttack = modDelay + instrumentKey.modAttack;
744
+ const modHold = modAttack + instrumentKey.modHold;
745
+ const modDecay = modHold + instrumentKey.modDecay;
683
746
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
684
747
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
685
748
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
686
- const filterNode = new BiquadFilterNode(this.audioContext, {
749
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
687
750
  type: "lowpass",
688
- Q: noteInfo.initialFilterQ / 10, // dB
751
+ Q: instrumentKey.initialFilterQ / 10, // dB
689
752
  frequency: adjustedBaseFreq,
690
753
  });
691
- const modDelay = startTime + noteInfo.modDelay;
692
- const modAttack = modDelay + noteInfo.modAttack;
693
- const modHold = modAttack + noteInfo.modHold;
694
- const modDecay = modHold + noteInfo.modDecay;
695
- filterNode.frequency
754
+ note.filterNode.frequency
696
755
  .setValueAtTime(adjustedBaseFreq, modDelay)
697
756
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
698
757
  .setValueAtTime(adjustedPeekFreq, modHold)
699
758
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
700
- let lfoGain;
759
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
760
+ }
761
+ startModulation(channel, note, time) {
762
+ const { instrumentKey } = note;
763
+ note.modLFOGain = new GainNode(this.audioContext, {
764
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
765
+ });
766
+ note.modLFO = new OscillatorNode(this.audioContext, {
767
+ frequency: this.centToHz(instrumentKey.freqModLFO),
768
+ });
769
+ note.modLFO.start(time);
770
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
771
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
772
+ note.modLFO.connect(note.modLFOGain);
773
+ note.modLFOGain.connect(note.bufferSource.detune);
774
+ }
775
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
776
+ const semitoneOffset = this.calcSemitoneOffset(channel);
777
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
778
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
779
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
780
+ this.setVolumeEnvelope(channel, note);
781
+ this.setFilterEnvelope(channel, note);
701
782
  if (channel.modulation > 0) {
702
- const vibratoDelay = startTime + channel.vibratoDelay;
703
- const vibratoAttack = vibratoDelay + 0.1;
704
- lfoGain = new GainNode(this.audioContext, {
705
- gain: 0,
706
- });
707
- lfoGain.gain
708
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
709
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
710
- channel.modulationEffect.lfo.connect(lfoGain);
711
- lfoGain.connect(bufferSource.detune);
783
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
784
+ this.startModulation(channel, note, delayModLFO);
712
785
  }
713
- bufferSource.connect(filterNode);
714
- filterNode.connect(gainNode);
715
786
  if (this.mono && channel.currentBufferSource) {
716
787
  channel.currentBufferSource.stop(startTime);
717
- channel.currentBufferSource = bufferSource;
788
+ channel.currentBufferSource = note.bufferSource;
718
789
  }
719
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
720
- return { bufferSource, gainNode, filterNode, lfoGain };
790
+ note.bufferSource.connect(note.filterNode);
791
+ note.filterNode.connect(note.gainNode);
792
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
793
+ return note;
721
794
  }
722
795
  calcBank(channel, channelNumber) {
723
796
  if (channel.bankMSB === 121) {
@@ -736,36 +809,20 @@ class MidyGM2 {
736
809
  return;
737
810
  const soundFont = this.soundFonts[soundFontIndex];
738
811
  const isSF3 = soundFont.parsed.info.version.major === 3;
739
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
740
- if (!noteInfo)
812
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
813
+ if (!instrumentKey)
741
814
  return;
742
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
743
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
744
- this.connectNoteEffects(channel, gainNode);
815
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
816
+ this.connectNoteEffects(channel, note.gainNode);
745
817
  if (channel.sostenutoPedal) {
746
- channel.sostenutoNotes.set(noteNumber, {
747
- gainNode,
748
- filterNode,
749
- bufferSource,
750
- noteNumber,
751
- noteInfo,
752
- });
818
+ channel.sostenutoNotes.set(noteNumber, note);
753
819
  }
754
820
  const scheduledNotes = channel.scheduledNotes;
755
- const scheduledNote = {
756
- bufferSource,
757
- filterNode,
758
- gainNode,
759
- lfoGain,
760
- noteInfo,
761
- noteNumber,
762
- startTime,
763
- };
764
821
  if (scheduledNotes.has(noteNumber)) {
765
- scheduledNotes.get(noteNumber).push(scheduledNote);
822
+ scheduledNotes.get(noteNumber).push(note);
766
823
  }
767
824
  else {
768
- scheduledNotes.set(noteNumber, [scheduledNote]);
825
+ scheduledNotes.set(noteNumber, [note]);
769
826
  }
770
827
  }
771
828
  noteOn(channelNumber, noteNumber, velocity) {
@@ -787,15 +844,15 @@ class MidyGM2 {
787
844
  continue;
788
845
  if (targetNote.ending)
789
846
  continue;
790
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
847
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
791
848
  const velocityRate = (velocity + 127) / 127;
792
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
849
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
793
850
  gainNode.gain.cancelScheduledValues(stopTime);
794
851
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
795
852
  const maxFreq = this.audioContext.sampleRate / 2;
796
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
853
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
797
854
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
798
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
855
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
799
856
  filterNode.frequency
800
857
  .cancelScheduledValues(stopTime)
801
858
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -809,8 +866,10 @@ class MidyGM2 {
809
866
  bufferSource.disconnect(0);
810
867
  filterNode.disconnect(0);
811
868
  gainNode.disconnect(0);
812
- if (lfoGain)
813
- lfoGain.disconnect(0);
869
+ if (modLFOGain)
870
+ modLFOGain.disconnect(0);
871
+ if (modLFO)
872
+ modLFO.stop();
814
873
  resolve();
815
874
  };
816
875
  bufferSource.stop(volEndTime);
@@ -858,8 +917,6 @@ class MidyGM2 {
858
917
  return this.releaseNote(channelNumber, data1, data2);
859
918
  case 0x90:
860
919
  return this.noteOn(channelNumber, data1, data2);
861
- case 0xA0:
862
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
863
920
  case 0xB0:
864
921
  return this.handleControlChange(channelNumber, data1, data2);
865
922
  case 0xC0:
@@ -867,38 +924,48 @@ class MidyGM2 {
867
924
  case 0xD0:
868
925
  return this.handleChannelPressure(channelNumber, data1);
869
926
  case 0xE0:
870
- return this.handlePitchBend(channelNumber, data1, data2);
927
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
871
928
  default:
872
929
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
873
930
  }
874
931
  }
875
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
876
- const now = this.audioContext.currentTime;
877
- const channel = this.channels[channelNumber];
878
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
879
- pressure /= 127;
880
- if (scheduledNotes) {
881
- scheduledNotes.forEach((scheduledNote) => {
882
- if (scheduledNote) {
883
- const { initialAttenuation } = scheduledNote.noteInfo;
884
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
885
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
886
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
887
- }
888
- });
889
- }
890
- }
891
932
  handleProgramChange(channelNumber, program) {
892
933
  const channel = this.channels[channelNumber];
893
934
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
894
935
  channel.program = program;
895
936
  }
896
937
  handleChannelPressure(channelNumber, pressure) {
897
- this.channels[channelNumber].channelPressure = pressure;
938
+ const now = this.audioContext.currentTime;
939
+ const channel = this.channels[channelNumber];
940
+ pressure /= 64;
941
+ channel.channelPressure = pressure;
942
+ const activeNotes = this.getActiveNotes(channel, now);
943
+ if (channel.channelPressure.amplitudeControl !== 1) {
944
+ activeNotes.forEach((activeNote) => {
945
+ const gain = activeNote.gainNode.gain.value;
946
+ activeNote.gainNode.gain
947
+ .cancelScheduledValues(now)
948
+ .setValueAtTime(gain * pressure, now);
949
+ });
950
+ }
951
+ }
952
+ handlePitchBendMessage(channelNumber, lsb, msb) {
953
+ const pitchBend = msb * 128 + lsb;
954
+ this.handlePitchBend(channelNumber, pitchBend);
898
955
  }
899
- handlePitchBend(channelNumber, lsb, msb) {
900
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
901
- this.channels[channelNumber].pitchBend = pitchBend;
956
+ handlePitchBend(channelNumber, pitchBend) {
957
+ const now = this.audioContext.currentTime;
958
+ const channel = this.channels[channelNumber];
959
+ channel.pitchBend = (pitchBend - 8192) / 8192;
960
+ const semitoneOffset = this.calcSemitoneOffset(channel);
961
+ const activeNotes = this.getActiveNotes(channel, now);
962
+ activeNotes.forEach((activeNote) => {
963
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
964
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
965
+ bufferSource.playbackRate
966
+ .cancelScheduledValues(now)
967
+ .setValueAtTime(playbackRate * pressure, now);
968
+ });
902
969
  }
903
970
  handleControlChange(channelNumber, controller, value) {
904
971
  switch (controller) {
@@ -958,9 +1025,20 @@ class MidyGM2 {
958
1025
  this.channels[channelNumber].bankMSB = msb;
959
1026
  }
960
1027
  setModulation(channelNumber, modulation) {
1028
+ const now = this.audioContext.currentTime;
961
1029
  const channel = this.channels[channelNumber];
962
1030
  channel.modulation = (modulation / 127) *
963
1031
  (channel.modulationDepthRange * 100);
1032
+ const activeNotes = this.getActiveNotes(channel, now);
1033
+ activeNotes.forEach((activeNote) => {
1034
+ if (activeNote.modLFO) {
1035
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
1036
+ channel.modulation, now);
1037
+ }
1038
+ else {
1039
+ this.startModulation(channel, activeNote, now);
1040
+ }
1041
+ });
964
1042
  }
965
1043
  setPortamentoTime(channelNumber, portamentoTime) {
966
1044
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1066,8 +1144,8 @@ class MidyGM2 {
1066
1144
  const velocity = 0;
1067
1145
  const stopPedal = true;
1068
1146
  const promises = [];
1069
- channel.scheduledNotes.forEach((scheduledNotes) => {
1070
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1147
+ channel.scheduledNotes.forEach((noteList) => {
1148
+ const activeNote = this.getActiveNote(noteList, now);
1071
1149
  if (activeNote) {
1072
1150
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1073
1151
  promises.push(notePromise);
@@ -1084,8 +1162,8 @@ class MidyGM2 {
1084
1162
  const velocity = 0;
1085
1163
  const stopPedal = false;
1086
1164
  const promises = [];
1087
- channel.scheduledNotes.forEach((scheduledNotes) => {
1088
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1165
+ channel.scheduledNotes.forEach((noteList) => {
1166
+ const activeNote = this.getActiveNote(noteList, now);
1089
1167
  if (activeNote) {
1090
1168
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1091
1169
  promises.push(notePromise);
@@ -1171,10 +1249,10 @@ class MidyGM2 {
1171
1249
  switch (data[3]) {
1172
1250
  // case 1:
1173
1251
  // // TODO
1174
- // return this.handleChannelPressure();
1252
+ // return this.setChannelPressure();
1175
1253
  // case 3:
1176
1254
  // // TODO
1177
- // return this.handleControlChange();
1255
+ // return this.setControlChange();
1178
1256
  default:
1179
1257
  console.warn(`Unsupported Exclusive Message ${data}`);
1180
1258
  }
@@ -1193,20 +1271,25 @@ class MidyGM2 {
1193
1271
  }
1194
1272
  }
1195
1273
  handleMasterVolumeSysEx(data) {
1196
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
1274
+ const volume = (data[5] * 128 + data[4]) / 16383;
1197
1275
  this.handleMasterVolume(volume);
1198
1276
  }
1199
1277
  handleMasterVolume(volume) {
1200
- const now = this.audioContext.currentTime;
1201
- this.masterGain.gain.cancelScheduledValues(now);
1202
- this.masterGain.gain.setValueAtTime(volume * volume, now);
1278
+ if (volume < 0 && 1 < volume) {
1279
+ console.error("Master Volume is out of range");
1280
+ }
1281
+ else {
1282
+ const now = this.audioContext.currentTime;
1283
+ this.masterGain.gain.cancelScheduledValues(now);
1284
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
1285
+ }
1203
1286
  }
1204
1287
  handleMasterFineTuningSysEx(data) {
1205
1288
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1206
1289
  this.handleMasterFineTuning(fineTuning);
1207
1290
  }
1208
1291
  handleMasterFineTuning(fineTuning) {
1209
- if (fineTuning < 0 && 1 < fineTuning) {
1292
+ if (fineTuning < -1 && 1 < fineTuning) {
1210
1293
  console.error("Master Fine Tuning value is out of range");
1211
1294
  }
1212
1295
  else {
@@ -1262,9 +1345,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
1262
1345
  portamentoTime: 0,
1263
1346
  reverb: 0,
1264
1347
  chorus: 0,
1265
- vibratoRate: 5,
1266
- vibratoDepth: 0.5,
1267
- vibratoDelay: 2.5,
1268
1348
  bank: 121 * 128,
1269
1349
  bankMSB: 121,
1270
1350
  bankLSB: 0,
@@ -1294,3 +1374,16 @@ Object.defineProperty(MidyGM2, "effectSettings", {
1294
1374
  pitchBendRange: 2,
1295
1375
  }
1296
1376
  });
1377
+ Object.defineProperty(MidyGM2, "controllerDestinationSettings", {
1378
+ enumerable: true,
1379
+ configurable: true,
1380
+ writable: true,
1381
+ value: {
1382
+ pitchControl: 0,
1383
+ filterCutoffControl: 0,
1384
+ amplitudeControl: 1,
1385
+ lfoPitchDepth: 0,
1386
+ lfoFilterDepth: 0,
1387
+ lfoAmplitudeDepth: 0,
1388
+ }
1389
+ });