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