@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/script/midy.js CHANGED
@@ -2,7 +2,57 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Midy = 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 Midy {
7
57
  constructor(audioContext) {
8
58
  Object.defineProperty(this, "ticksPerBeat", {
@@ -184,10 +234,8 @@ class Midy {
184
234
  const pannerNode = new StereoPannerNode(audioContext, {
185
235
  pan: Midy.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 Midy {
196
244
  return {
197
245
  gainNode,
198
246
  pannerNode,
199
- modulationEffect,
200
247
  reverbEffect,
201
248
  chorusEffect,
202
249
  };
@@ -209,15 +256,21 @@ class Midy {
209
256
  ...this.setChannelAudioNodes(audioContext),
210
257
  scheduledNotes: new Map(),
211
258
  sostenutoNotes: new Map(),
259
+ polyphonicKeyPressure: {
260
+ ...Midy.controllerDestinationSettings,
261
+ },
262
+ channelPressure: {
263
+ ...Midy.controllerDestinationSettings,
264
+ },
212
265
  };
213
266
  });
214
267
  return channels;
215
268
  }
216
- async createNoteBuffer(noteInfo, isSF3) {
217
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
269
+ async createNoteBuffer(instrumentKey, isSF3) {
270
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
218
271
  if (isSF3) {
219
- const sample = new Uint8Array(noteInfo.sample.length);
220
- sample.set(noteInfo.sample);
272
+ const sample = new Uint8Array(instrumentKey.sample.length);
273
+ sample.set(instrumentKey.sample);
221
274
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
222
275
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
223
276
  const channelData = audioBuffer.getChannelData(channel);
@@ -226,26 +279,27 @@ class Midy {
226
279
  return audioBuffer;
227
280
  }
228
281
  else {
229
- const sample = noteInfo.sample.subarray(0, sampleEnd);
282
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
230
283
  const floatSample = this.convertToFloat32Array(sample);
231
284
  const audioBuffer = new AudioBuffer({
232
285
  numberOfChannels: 1,
233
286
  length: sample.length,
234
- sampleRate: noteInfo.sampleRate,
287
+ sampleRate: instrumentKey.sampleRate,
235
288
  });
236
289
  const channelData = audioBuffer.getChannelData(0);
237
290
  channelData.set(floatSample);
238
291
  return audioBuffer;
239
292
  }
240
293
  }
241
- async createNoteBufferNode(noteInfo, isSF3) {
294
+ async createNoteBufferNode(instrumentKey, isSF3) {
242
295
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
243
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
296
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
244
297
  bufferSource.buffer = audioBuffer;
245
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
298
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
246
299
  if (bufferSource.loop) {
247
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
248
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
300
+ bufferSource.loopStart = instrumentKey.loopStart /
301
+ instrumentKey.sampleRate;
302
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
249
303
  }
250
304
  return bufferSource;
251
305
  }
@@ -263,9 +317,6 @@ class Midy {
263
317
  if (event.startTime > t + this.lookAhead)
264
318
  break;
265
319
  switch (event.type) {
266
- case "controller":
267
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
268
- break;
269
320
  case "noteOn":
270
321
  if (event.velocity !== 0) {
271
322
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -279,9 +330,21 @@ class Midy {
279
330
  }
280
331
  break;
281
332
  }
333
+ case "noteAftertouch":
334
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
335
+ break;
336
+ case "controller":
337
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
338
+ break;
282
339
  case "programChange":
283
340
  this.handleProgramChange(event.channel, event.programNumber);
284
341
  break;
342
+ case "channelAftertouch":
343
+ this.handleChannelPressure(event.channel, event.amount);
344
+ break;
345
+ case "pitchBend":
346
+ this.handlePitchBend(event.channel, event.value);
347
+ break;
285
348
  case "sysEx":
286
349
  this.handleSysEx(event.data);
287
350
  }
@@ -518,30 +581,26 @@ class Midy {
518
581
  const now = this.audioContext.currentTime;
519
582
  return this.resumeTime + now - this.startTime - this.startDelay;
520
583
  }
521
- getActiveNotes(channel) {
584
+ getActiveNotes(channel, time) {
522
585
  const activeNotes = new Map();
523
- channel.scheduledNotes.forEach((scheduledNotes) => {
524
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
586
+ channel.scheduledNotes.forEach((noteList) => {
587
+ const activeNote = this.getActiveNote(noteList, time);
525
588
  if (activeNote) {
526
589
  activeNotes.set(activeNote.noteNumber, activeNote);
527
590
  }
528
591
  });
529
592
  return activeNotes;
530
593
  }
531
- getActiveChannelNotes(scheduledNotes) {
532
- for (let i = 0; i < scheduledNotes; i++) {
533
- const scheduledNote = scheduledNotes[i];
534
- if (scheduledNote)
535
- return scheduledNote;
594
+ getActiveNote(noteList, time) {
595
+ for (let i = noteList.length - 1; i >= 0; i--) {
596
+ const note = noteList[i];
597
+ if (!note)
598
+ return;
599
+ if (time < note.startTime)
600
+ continue;
601
+ return (note.ending) ? null : note;
536
602
  }
537
- }
538
- createModulationEffect(audioContext) {
539
- const lfo = new OscillatorNode(audioContext, {
540
- frequency: 5,
541
- });
542
- return {
543
- lfo,
544
- };
603
+ return noteList[0];
545
604
  }
546
605
  createReverbEffect(audioContext, options = {}) {
547
606
  const { decay = 0.8, preDecay = 0, } = options;
@@ -645,79 +704,116 @@ class Midy {
645
704
  centToHz(cent) {
646
705
  return 8.176 * Math.pow(2, cent / 1200);
647
706
  }
648
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
707
+ calcSemitoneOffset(channel) {
649
708
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
650
709
  const channelTuning = channel.coarseTuning + channel.fineTuning;
651
710
  const tuning = masterTuning + channelTuning;
652
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
653
- const playbackRate = noteInfo.playbackRate(noteNumber) *
711
+ return channel.pitchBend * channel.pitchBendRange + tuning;
712
+ }
713
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
714
+ return instrumentKey.playbackRate(noteNumber) *
654
715
  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, {
716
+ }
717
+ setVolumeEnvelope(channel, note) {
718
+ const { instrumentKey, startTime, velocity } = note;
719
+ note.gainNode = new GainNode(this.audioContext, {
659
720
  gain: 0,
660
721
  });
661
722
  let volume = (velocity / 127) * channel.volume * channel.expression;
662
723
  if (volume === 0)
663
724
  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
725
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
726
+ volume;
727
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
728
+ const volDelay = startTime + instrumentKey.volDelay;
729
+ const volAttack = volDelay + instrumentKey.volAttack;
730
+ const volHold = volAttack + instrumentKey.volHold;
731
+ const volDecay = volHold + instrumentKey.volDecay;
732
+ note.gainNode.gain
671
733
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
672
734
  .exponentialRampToValueAtTime(attackVolume, volAttack)
673
735
  .setValueAtTime(attackVolume, volHold)
674
736
  .linearRampToValueAtTime(sustainVolume, volDecay);
675
- // filter envelope
737
+ }
738
+ setFilterEnvelope(channel, note) {
739
+ const { instrumentKey, startTime, noteNumber } = note;
676
740
  const softPedalFactor = 1 -
677
741
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
678
742
  const maxFreq = this.audioContext.sampleRate / 2;
679
- const baseFreq = this.centToHz(noteInfo.initialFilterFc) * softPedalFactor;
680
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc) * softPedalFactor;
743
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
744
+ softPedalFactor;
745
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
681
746
  const sustainFreq = (baseFreq +
682
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain)) * softPedalFactor;
747
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
748
+ const modDelay = startTime + instrumentKey.modDelay;
749
+ const modAttack = modDelay + instrumentKey.modAttack;
750
+ const modHold = modAttack + instrumentKey.modHold;
751
+ const modDecay = modHold + instrumentKey.modDecay;
683
752
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
684
753
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
685
754
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
686
- const filterNode = new BiquadFilterNode(this.audioContext, {
755
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
687
756
  type: "lowpass",
688
- Q: noteInfo.initialFilterQ / 10, // dB
757
+ Q: instrumentKey.initialFilterQ / 10, // dB
689
758
  frequency: adjustedBaseFreq,
690
759
  });
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
760
+ note.filterNode.frequency
696
761
  .setValueAtTime(adjustedBaseFreq, modDelay)
697
762
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
698
763
  .setValueAtTime(adjustedPeekFreq, modHold)
699
764
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
700
- let lfoGain;
765
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
766
+ }
767
+ startModulation(channel, note, time) {
768
+ const { instrumentKey } = note;
769
+ note.modLFOGain = new GainNode(this.audioContext, {
770
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
771
+ });
772
+ note.modLFO = new OscillatorNode(this.audioContext, {
773
+ frequency: this.centToHz(instrumentKey.freqModLFO),
774
+ });
775
+ note.modLFO.start(time);
776
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
777
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
778
+ note.modLFO.connect(note.modLFOGain);
779
+ note.modLFOGain.connect(note.bufferSource.detune);
780
+ }
781
+ startVibrato(channel, note, time) {
782
+ const { instrumentKey } = note;
783
+ note.vibLFOGain = new GainNode(this.audioContext, {
784
+ gain: channel.vibratoDepth,
785
+ });
786
+ note.vibLFO = new OscillatorNode(this.audioContext, {
787
+ frequency: this.centToHz(instrumentKey.freqModLFO) +
788
+ channel.vibratoRate,
789
+ });
790
+ note.vibLFO.start(time + channel.vibratoDelay);
791
+ note.vibLFO.connect(note.vibLFOGain);
792
+ note.vibLFOGain.connect(note.bufferSource.detune);
793
+ }
794
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
795
+ const semitoneOffset = this.calcSemitoneOffset(channel);
796
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
797
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
798
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
799
+ this.setVolumeEnvelope(channel, note);
800
+ this.setFilterEnvelope(channel, note);
701
801
  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);
802
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
803
+ this.startModulation(channel, note, delayModLFO);
804
+ }
805
+ if (channel.vibratoDepth > 0) {
806
+ const delayVibLFO = startTime + instrumentKey.delayVibLFO;
807
+ this.startVibrato(channel, note, delayVibLFO);
712
808
  }
713
- bufferSource.connect(filterNode);
714
- filterNode.connect(gainNode);
715
809
  if (this.mono && channel.currentBufferSource) {
716
810
  channel.currentBufferSource.stop(startTime);
717
- channel.currentBufferSource = bufferSource;
811
+ channel.currentBufferSource = note.bufferSource;
718
812
  }
719
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
720
- return { bufferSource, gainNode, filterNode };
813
+ note.bufferSource.connect(note.filterNode);
814
+ note.filterNode.connect(note.gainNode);
815
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
816
+ return note;
721
817
  }
722
818
  calcBank(channel, channelNumber) {
723
819
  if (channel.bankMSB === 121) {
@@ -736,36 +832,20 @@ class Midy {
736
832
  return;
737
833
  const soundFont = this.soundFonts[soundFontIndex];
738
834
  const isSF3 = soundFont.parsed.info.version.major === 3;
739
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
740
- if (!noteInfo)
835
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
836
+ if (!instrumentKey)
741
837
  return;
742
- const { bufferSource, gainNode, filterNode } = await this
743
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
744
- this.connectNoteEffects(channel, gainNode);
838
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
839
+ this.connectNoteEffects(channel, note.gainNode);
745
840
  if (channel.sostenutoPedal) {
746
- channel.sostenutoNotes.set(noteNumber, {
747
- gainNode,
748
- filterNode,
749
- bufferSource,
750
- noteNumber,
751
- noteInfo,
752
- });
841
+ channel.sostenutoNotes.set(noteNumber, note);
753
842
  }
754
843
  const scheduledNotes = channel.scheduledNotes;
755
- const scheduledNote = {
756
- bufferSource,
757
- filterNode,
758
- gainNode,
759
- lfoGain,
760
- noteInfo,
761
- noteNumber,
762
- startTime,
763
- };
764
844
  if (scheduledNotes.has(noteNumber)) {
765
- scheduledNotes.get(noteNumber).push(scheduledNote);
845
+ scheduledNotes.get(noteNumber).push(note);
766
846
  }
767
847
  else {
768
- scheduledNotes.set(noteNumber, [scheduledNote]);
848
+ scheduledNotes.set(noteNumber, [note]);
769
849
  }
770
850
  }
771
851
  noteOn(channelNumber, noteNumber, velocity) {
@@ -787,15 +867,15 @@ class Midy {
787
867
  continue;
788
868
  if (targetNote.ending)
789
869
  continue;
790
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
870
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
791
871
  const velocityRate = (velocity + 127) / 127;
792
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
872
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
793
873
  gainNode.gain.cancelScheduledValues(stopTime);
794
874
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
795
875
  const maxFreq = this.audioContext.sampleRate / 2;
796
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
876
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
797
877
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
798
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
878
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
799
879
  filterNode.frequency
800
880
  .cancelScheduledValues(stopTime)
801
881
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -809,8 +889,14 @@ class Midy {
809
889
  bufferSource.disconnect(0);
810
890
  filterNode.disconnect(0);
811
891
  gainNode.disconnect(0);
812
- if (lfoGain)
813
- lfoGain.disconnect(0);
892
+ if (modLFOGain)
893
+ modLFOGain.disconnect(0);
894
+ if (vibLFOGain)
895
+ vibLFOGain.disconnect(0);
896
+ if (modLFO)
897
+ modLFO.stop();
898
+ if (vibLFO)
899
+ vibLFO.stop();
814
900
  resolve();
815
901
  };
816
902
  bufferSource.stop(volEndTime);
@@ -859,7 +945,7 @@ class Midy {
859
945
  case 0x90:
860
946
  return this.noteOn(channelNumber, data1, data2);
861
947
  case 0xA0:
862
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
948
+ return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
863
949
  case 0xB0:
864
950
  return this.handleControlChange(channelNumber, data1, data2);
865
951
  case 0xC0:
@@ -867,7 +953,7 @@ class Midy {
867
953
  case 0xD0:
868
954
  return this.handleChannelPressure(channelNumber, data1);
869
955
  case 0xE0:
870
- return this.handlePitchBend(channelNumber, data1, data2);
956
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
871
957
  default:
872
958
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
873
959
  }
@@ -875,17 +961,16 @@ class Midy {
875
961
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
876
962
  const now = this.audioContext.currentTime;
877
963
  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
- });
964
+ pressure /= 64;
965
+ const activeNotes = this.getActiveNotes(channel, now);
966
+ if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
967
+ if (activeNotes.has(noteNumber)) {
968
+ const activeNote = activeNotes.get(noteNumber);
969
+ const gain = activeNote.gainNode.gain.value;
970
+ activeNote.gainNode.gain
971
+ .cancelScheduledValues(now)
972
+ .setValueAtTime(gain * pressure, now);
973
+ }
889
974
  }
890
975
  }
891
976
  handleProgramChange(channelNumber, program) {
@@ -894,11 +979,37 @@ class Midy {
894
979
  channel.program = program;
895
980
  }
896
981
  handleChannelPressure(channelNumber, pressure) {
897
- this.channels[channelNumber].channelPressure = pressure;
982
+ const now = this.audioContext.currentTime;
983
+ const channel = this.channels[channelNumber];
984
+ pressure /= 64;
985
+ channel.channelPressure = pressure;
986
+ const activeNotes = this.getActiveNotes(channel, now);
987
+ if (channel.channelPressure.amplitudeControl !== 1) {
988
+ activeNotes.forEach((activeNote) => {
989
+ const gain = activeNote.gainNode.gain.value;
990
+ activeNote.gainNode.gain
991
+ .cancelScheduledValues(now)
992
+ .setValueAtTime(gain * pressure, now);
993
+ });
994
+ }
995
+ }
996
+ handlePitchBendMessage(channelNumber, lsb, msb) {
997
+ const pitchBend = msb * 128 + lsb;
998
+ this.handlePitchBend(channelNumber, pitchBend);
898
999
  }
899
- handlePitchBend(channelNumber, lsb, msb) {
900
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
901
- this.channels[channelNumber].pitchBend = pitchBend;
1000
+ handlePitchBend(channelNumber, pitchBend) {
1001
+ const now = this.audioContext.currentTime;
1002
+ const channel = this.channels[channelNumber];
1003
+ channel.pitchBend = (pitchBend - 8192) / 8192;
1004
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1005
+ const activeNotes = this.getActiveNotes(channel, now);
1006
+ activeNotes.forEach((activeNote) => {
1007
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
1008
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
1009
+ bufferSource.playbackRate
1010
+ .cancelScheduledValues(now)
1011
+ .setValueAtTime(playbackRate * pressure, now);
1012
+ });
902
1013
  }
903
1014
  handleControlChange(channelNumber, controller, value) {
904
1015
  switch (controller) {
@@ -969,9 +1080,20 @@ class Midy {
969
1080
  this.channels[channelNumber].bankMSB = msb;
970
1081
  }
971
1082
  setModulation(channelNumber, modulation) {
1083
+ const now = this.audioContext.currentTime;
972
1084
  const channel = this.channels[channelNumber];
973
1085
  channel.modulation = (modulation / 127) *
974
1086
  (channel.modulationDepthRange * 100);
1087
+ const activeNotes = this.getActiveNotes(channel, now);
1088
+ activeNotes.forEach((activeNote) => {
1089
+ if (activeNote.modLFO) {
1090
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
1091
+ channel.modulation, now);
1092
+ }
1093
+ else {
1094
+ this.startModulation(channel, activeNote, now);
1095
+ }
1096
+ });
975
1097
  }
976
1098
  setPortamentoTime(channelNumber, portamentoTime) {
977
1099
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1032,7 +1154,8 @@ class Midy {
1032
1154
  const channel = this.channels[channelNumber];
1033
1155
  channel.sostenutoPedal = isOn;
1034
1156
  if (isOn) {
1035
- const activeNotes = this.getActiveNotes(channel);
1157
+ const now = this.audioContext.currentTime;
1158
+ const activeNotes = this.getActiveNotes(channel, now);
1036
1159
  channel.sostenutoNotes = new Map(activeNotes);
1037
1160
  }
1038
1161
  else {
@@ -1044,20 +1167,12 @@ class Midy {
1044
1167
  channel.softPedal = softPedal / 127;
1045
1168
  }
1046
1169
  setVibratoRate(channelNumber, vibratoRate) {
1047
- const now = this.audioContext.currentTime;
1048
1170
  const channel = this.channels[channelNumber];
1049
1171
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1050
- channel.modulationEffect.lfo.frequency
1051
- .cancelScheduledValues(now)
1052
- .setValueAtTime(channel.vibratoRate, now);
1053
1172
  }
1054
1173
  setVibratoDepth(channelNumber, vibratoDepth) {
1055
- const now = this.audioContext.currentTime;
1056
1174
  const channel = this.channels[channelNumber];
1057
1175
  channel.vibratoDepth = vibratoDepth / 127;
1058
- channel.modulationEffect.lfoGain.gain
1059
- .cancelScheduledValues(now)
1060
- .setValueAtTime(channel.vibratoDepth, now);
1061
1176
  }
1062
1177
  setVibratoDelay(channelNumber, vibratoDelay) {
1063
1178
  // Access Virus: 0-10sec
@@ -1138,8 +1253,8 @@ class Midy {
1138
1253
  const velocity = 0;
1139
1254
  const stopPedal = true;
1140
1255
  const promises = [];
1141
- channel.scheduledNotes.forEach((scheduledNotes) => {
1142
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1256
+ channel.scheduledNotes.forEach((noteList) => {
1257
+ const activeNote = this.getActiveNote(noteList, now);
1143
1258
  if (activeNote) {
1144
1259
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1145
1260
  promises.push(notePromise);
@@ -1156,8 +1271,8 @@ class Midy {
1156
1271
  const velocity = 0;
1157
1272
  const stopPedal = false;
1158
1273
  const promises = [];
1159
- channel.scheduledNotes.forEach((scheduledNotes) => {
1160
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1274
+ channel.scheduledNotes.forEach((noteList) => {
1275
+ const activeNote = this.getActiveNote(noteList, now);
1161
1276
  if (activeNote) {
1162
1277
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1163
1278
  promises.push(notePromise);
@@ -1243,10 +1358,10 @@ class Midy {
1243
1358
  switch (data[3]) {
1244
1359
  // case 1:
1245
1360
  // // TODO
1246
- // return this.handleChannelPressure();
1361
+ // return this.setChannelPressure();
1247
1362
  // case 3:
1248
1363
  // // TODO
1249
- // return this.handleControlChange();
1364
+ // return this.setControlChange();
1250
1365
  default:
1251
1366
  console.warn(`Unsupported Exclusive Message ${data}`);
1252
1367
  }
@@ -1265,20 +1380,25 @@ class Midy {
1265
1380
  }
1266
1381
  }
1267
1382
  handleMasterVolumeSysEx(data) {
1268
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
1383
+ const volume = (data[5] * 128 + data[4]) / 16383;
1269
1384
  this.handleMasterVolume(volume);
1270
1385
  }
1271
1386
  handleMasterVolume(volume) {
1272
- const now = this.audioContext.currentTime;
1273
- this.masterGain.gain.cancelScheduledValues(now);
1274
- this.masterGain.gain.setValueAtTime(volume * volume, now);
1387
+ if (volume < 0 && 1 < volume) {
1388
+ console.error("Master Volume is out of range");
1389
+ }
1390
+ else {
1391
+ const now = this.audioContext.currentTime;
1392
+ this.masterGain.gain.cancelScheduledValues(now);
1393
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
1394
+ }
1275
1395
  }
1276
1396
  handleMasterFineTuningSysEx(data) {
1277
1397
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1278
1398
  this.handleMasterFineTuning(fineTuning);
1279
1399
  }
1280
1400
  handleMasterFineTuning(fineTuning) {
1281
- if (fineTuning < 0 && 1 < fineTuning) {
1401
+ if (fineTuning < -1 && 1 < fineTuning) {
1282
1402
  console.error("Master Fine Tuning value is out of range");
1283
1403
  }
1284
1404
  else {
@@ -1366,3 +1486,16 @@ Object.defineProperty(Midy, "effectSettings", {
1366
1486
  pitchBendRange: 2,
1367
1487
  }
1368
1488
  });
1489
+ Object.defineProperty(Midy, "controllerDestinationSettings", {
1490
+ enumerable: true,
1491
+ configurable: true,
1492
+ writable: true,
1493
+ value: {
1494
+ pitchControl: 0,
1495
+ filterCutoffControl: 0,
1496
+ amplitudeControl: 1,
1497
+ lfoPitchDepth: 0,
1498
+ lfoFilterDepth: 0,
1499
+ lfoAmplitudeDepth: 0,
1500
+ }
1501
+ });
@@ -1 +0,0 @@
1
- {"version":3,"file":"+esm.d.ts","sourceRoot":"","sources":["../../../../../../src/deps/cdn.jsdelivr.net/npm/@marmooo/soundfont-parser@0.0.1/+esm.js"],"names":[],"mappings":";;AAMszX;IAAQ,oBAA2H;IAAd,YAAa;IAAC,qDAAoJ;IAAA,mCAAoP;IAAA,uCAAwQ;IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aAAi/D;IAAA,qBAA0I;CAAC;AAAA,mCAAwC;AAAl+H,+BAA8F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAA98D;;;;;;;;;;;;;;;;EAA27B;AAAr4I;IAAqQ,wBAAwD;IAArT,4BAAyN;IAApB,QAAS;IAAC,QAAS;IAAC,oBAAoC;CAAyD;AAAllG;IAAwjC,gCAA8d;CAAC"}