@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.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
  };
@@ -206,15 +253,21 @@ export class Midy {
206
253
  ...this.setChannelAudioNodes(audioContext),
207
254
  scheduledNotes: new Map(),
208
255
  sostenutoNotes: new Map(),
256
+ polyphonicKeyPressure: {
257
+ ...Midy.controllerDestinationSettings,
258
+ },
259
+ channelPressure: {
260
+ ...Midy.controllerDestinationSettings,
261
+ },
209
262
  };
210
263
  });
211
264
  return channels;
212
265
  }
213
- async createNoteBuffer(noteInfo, isSF3) {
214
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
266
+ async createNoteBuffer(instrumentKey, isSF3) {
267
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
215
268
  if (isSF3) {
216
- const sample = new Uint8Array(noteInfo.sample.length);
217
- sample.set(noteInfo.sample);
269
+ const sample = new Uint8Array(instrumentKey.sample.length);
270
+ sample.set(instrumentKey.sample);
218
271
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
219
272
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
220
273
  const channelData = audioBuffer.getChannelData(channel);
@@ -223,26 +276,27 @@ export class Midy {
223
276
  return audioBuffer;
224
277
  }
225
278
  else {
226
- const sample = noteInfo.sample.subarray(0, sampleEnd);
279
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
227
280
  const floatSample = this.convertToFloat32Array(sample);
228
281
  const audioBuffer = new AudioBuffer({
229
282
  numberOfChannels: 1,
230
283
  length: sample.length,
231
- sampleRate: noteInfo.sampleRate,
284
+ sampleRate: instrumentKey.sampleRate,
232
285
  });
233
286
  const channelData = audioBuffer.getChannelData(0);
234
287
  channelData.set(floatSample);
235
288
  return audioBuffer;
236
289
  }
237
290
  }
238
- async createNoteBufferNode(noteInfo, isSF3) {
291
+ async createNoteBufferNode(instrumentKey, isSF3) {
239
292
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
240
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
293
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
241
294
  bufferSource.buffer = audioBuffer;
242
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
295
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
243
296
  if (bufferSource.loop) {
244
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
245
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
297
+ bufferSource.loopStart = instrumentKey.loopStart /
298
+ instrumentKey.sampleRate;
299
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
246
300
  }
247
301
  return bufferSource;
248
302
  }
@@ -260,9 +314,6 @@ export class Midy {
260
314
  if (event.startTime > t + this.lookAhead)
261
315
  break;
262
316
  switch (event.type) {
263
- case "controller":
264
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
265
- break;
266
317
  case "noteOn":
267
318
  if (event.velocity !== 0) {
268
319
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -276,9 +327,21 @@ export class Midy {
276
327
  }
277
328
  break;
278
329
  }
330
+ case "noteAftertouch":
331
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
332
+ break;
333
+ case "controller":
334
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
335
+ break;
279
336
  case "programChange":
280
337
  this.handleProgramChange(event.channel, event.programNumber);
281
338
  break;
339
+ case "channelAftertouch":
340
+ this.handleChannelPressure(event.channel, event.amount);
341
+ break;
342
+ case "pitchBend":
343
+ this.handlePitchBend(event.channel, event.value);
344
+ break;
282
345
  case "sysEx":
283
346
  this.handleSysEx(event.data);
284
347
  }
@@ -515,30 +578,26 @@ export class Midy {
515
578
  const now = this.audioContext.currentTime;
516
579
  return this.resumeTime + now - this.startTime - this.startDelay;
517
580
  }
518
- getActiveNotes(channel) {
581
+ getActiveNotes(channel, time) {
519
582
  const activeNotes = new Map();
520
- channel.scheduledNotes.forEach((scheduledNotes) => {
521
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
583
+ channel.scheduledNotes.forEach((noteList) => {
584
+ const activeNote = this.getActiveNote(noteList, time);
522
585
  if (activeNote) {
523
586
  activeNotes.set(activeNote.noteNumber, activeNote);
524
587
  }
525
588
  });
526
589
  return activeNotes;
527
590
  }
528
- getActiveChannelNotes(scheduledNotes) {
529
- for (let i = 0; i < scheduledNotes; i++) {
530
- const scheduledNote = scheduledNotes[i];
531
- if (scheduledNote)
532
- 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;
533
599
  }
534
- }
535
- createModulationEffect(audioContext) {
536
- const lfo = new OscillatorNode(audioContext, {
537
- frequency: 5,
538
- });
539
- return {
540
- lfo,
541
- };
600
+ return noteList[0];
542
601
  }
543
602
  createReverbEffect(audioContext, options = {}) {
544
603
  const { decay = 0.8, preDecay = 0, } = options;
@@ -642,79 +701,116 @@ export class Midy {
642
701
  centToHz(cent) {
643
702
  return 8.176 * Math.pow(2, cent / 1200);
644
703
  }
645
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
704
+ calcSemitoneOffset(channel) {
646
705
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
647
706
  const channelTuning = channel.coarseTuning + channel.fineTuning;
648
707
  const tuning = masterTuning + channelTuning;
649
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
650
- const playbackRate = noteInfo.playbackRate(noteNumber) *
708
+ return channel.pitchBend * channel.pitchBendRange + tuning;
709
+ }
710
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
711
+ return instrumentKey.playbackRate(noteNumber) *
651
712
  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, {
713
+ }
714
+ setVolumeEnvelope(channel, note) {
715
+ const { instrumentKey, startTime, velocity } = note;
716
+ note.gainNode = new GainNode(this.audioContext, {
656
717
  gain: 0,
657
718
  });
658
719
  let volume = (velocity / 127) * channel.volume * channel.expression;
659
720
  if (volume === 0)
660
721
  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
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
668
730
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
669
731
  .exponentialRampToValueAtTime(attackVolume, volAttack)
670
732
  .setValueAtTime(attackVolume, volHold)
671
733
  .linearRampToValueAtTime(sustainVolume, volDecay);
672
- // filter envelope
734
+ }
735
+ setFilterEnvelope(channel, note) {
736
+ const { instrumentKey, startTime, noteNumber } = note;
673
737
  const softPedalFactor = 1 -
674
738
  (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
675
739
  const maxFreq = this.audioContext.sampleRate / 2;
676
- const baseFreq = this.centToHz(noteInfo.initialFilterFc) * softPedalFactor;
677
- 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;
678
743
  const sustainFreq = (baseFreq +
679
- (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;
680
749
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
681
750
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
682
751
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
683
- const filterNode = new BiquadFilterNode(this.audioContext, {
752
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
684
753
  type: "lowpass",
685
- Q: noteInfo.initialFilterQ / 10, // dB
754
+ Q: instrumentKey.initialFilterQ / 10, // dB
686
755
  frequency: adjustedBaseFreq,
687
756
  });
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
757
+ note.filterNode.frequency
693
758
  .setValueAtTime(adjustedBaseFreq, modDelay)
694
759
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
695
760
  .setValueAtTime(adjustedPeekFreq, modHold)
696
761
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
697
- 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);
698
798
  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);
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);
709
805
  }
710
- bufferSource.connect(filterNode);
711
- filterNode.connect(gainNode);
712
806
  if (this.mono && channel.currentBufferSource) {
713
807
  channel.currentBufferSource.stop(startTime);
714
- channel.currentBufferSource = bufferSource;
808
+ channel.currentBufferSource = note.bufferSource;
715
809
  }
716
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
717
- return { bufferSource, gainNode, filterNode };
810
+ note.bufferSource.connect(note.filterNode);
811
+ note.filterNode.connect(note.gainNode);
812
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
813
+ return note;
718
814
  }
719
815
  calcBank(channel, channelNumber) {
720
816
  if (channel.bankMSB === 121) {
@@ -733,36 +829,20 @@ export class Midy {
733
829
  return;
734
830
  const soundFont = this.soundFonts[soundFontIndex];
735
831
  const isSF3 = soundFont.parsed.info.version.major === 3;
736
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
737
- if (!noteInfo)
832
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
833
+ if (!instrumentKey)
738
834
  return;
739
- const { bufferSource, gainNode, filterNode } = await this
740
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
741
- this.connectNoteEffects(channel, gainNode);
835
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
836
+ this.connectNoteEffects(channel, note.gainNode);
742
837
  if (channel.sostenutoPedal) {
743
- channel.sostenutoNotes.set(noteNumber, {
744
- gainNode,
745
- filterNode,
746
- bufferSource,
747
- noteNumber,
748
- noteInfo,
749
- });
838
+ channel.sostenutoNotes.set(noteNumber, note);
750
839
  }
751
840
  const scheduledNotes = channel.scheduledNotes;
752
- const scheduledNote = {
753
- bufferSource,
754
- filterNode,
755
- gainNode,
756
- lfoGain,
757
- noteInfo,
758
- noteNumber,
759
- startTime,
760
- };
761
841
  if (scheduledNotes.has(noteNumber)) {
762
- scheduledNotes.get(noteNumber).push(scheduledNote);
842
+ scheduledNotes.get(noteNumber).push(note);
763
843
  }
764
844
  else {
765
- scheduledNotes.set(noteNumber, [scheduledNote]);
845
+ scheduledNotes.set(noteNumber, [note]);
766
846
  }
767
847
  }
768
848
  noteOn(channelNumber, noteNumber, velocity) {
@@ -784,15 +864,15 @@ export class Midy {
784
864
  continue;
785
865
  if (targetNote.ending)
786
866
  continue;
787
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
867
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, vibLFO, vibLFOGain, instrumentKey, } = targetNote;
788
868
  const velocityRate = (velocity + 127) / 127;
789
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
869
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
790
870
  gainNode.gain.cancelScheduledValues(stopTime);
791
871
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
792
872
  const maxFreq = this.audioContext.sampleRate / 2;
793
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
873
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
794
874
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
795
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
875
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
796
876
  filterNode.frequency
797
877
  .cancelScheduledValues(stopTime)
798
878
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -806,8 +886,14 @@ export class Midy {
806
886
  bufferSource.disconnect(0);
807
887
  filterNode.disconnect(0);
808
888
  gainNode.disconnect(0);
809
- if (lfoGain)
810
- 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();
811
897
  resolve();
812
898
  };
813
899
  bufferSource.stop(volEndTime);
@@ -856,7 +942,7 @@ export class Midy {
856
942
  case 0x90:
857
943
  return this.noteOn(channelNumber, data1, data2);
858
944
  case 0xA0:
859
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
945
+ return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
860
946
  case 0xB0:
861
947
  return this.handleControlChange(channelNumber, data1, data2);
862
948
  case 0xC0:
@@ -864,7 +950,7 @@ export class Midy {
864
950
  case 0xD0:
865
951
  return this.handleChannelPressure(channelNumber, data1);
866
952
  case 0xE0:
867
- return this.handlePitchBend(channelNumber, data1, data2);
953
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
868
954
  default:
869
955
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
870
956
  }
@@ -872,17 +958,16 @@ export class Midy {
872
958
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
873
959
  const now = this.audioContext.currentTime;
874
960
  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
- });
961
+ pressure /= 64;
962
+ const activeNotes = this.getActiveNotes(channel, now);
963
+ if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
964
+ if (activeNotes.has(noteNumber)) {
965
+ const activeNote = activeNotes.get(noteNumber);
966
+ const gain = activeNote.gainNode.gain.value;
967
+ activeNote.gainNode.gain
968
+ .cancelScheduledValues(now)
969
+ .setValueAtTime(gain * pressure, now);
970
+ }
886
971
  }
887
972
  }
888
973
  handleProgramChange(channelNumber, program) {
@@ -891,11 +976,37 @@ export class Midy {
891
976
  channel.program = program;
892
977
  }
893
978
  handleChannelPressure(channelNumber, pressure) {
894
- this.channels[channelNumber].channelPressure = pressure;
979
+ const now = this.audioContext.currentTime;
980
+ const channel = this.channels[channelNumber];
981
+ pressure /= 64;
982
+ channel.channelPressure = pressure;
983
+ const activeNotes = this.getActiveNotes(channel, now);
984
+ if (channel.channelPressure.amplitudeControl !== 1) {
985
+ activeNotes.forEach((activeNote) => {
986
+ const gain = activeNote.gainNode.gain.value;
987
+ activeNote.gainNode.gain
988
+ .cancelScheduledValues(now)
989
+ .setValueAtTime(gain * pressure, now);
990
+ });
991
+ }
992
+ }
993
+ handlePitchBendMessage(channelNumber, lsb, msb) {
994
+ const pitchBend = msb * 128 + lsb;
995
+ this.handlePitchBend(channelNumber, pitchBend);
895
996
  }
896
- handlePitchBend(channelNumber, lsb, msb) {
897
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
898
- this.channels[channelNumber].pitchBend = pitchBend;
997
+ handlePitchBend(channelNumber, pitchBend) {
998
+ const now = this.audioContext.currentTime;
999
+ const channel = this.channels[channelNumber];
1000
+ channel.pitchBend = (pitchBend - 8192) / 8192;
1001
+ const semitoneOffset = this.calcSemitoneOffset(channel);
1002
+ const activeNotes = this.getActiveNotes(channel, now);
1003
+ activeNotes.forEach((activeNote) => {
1004
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
1005
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
1006
+ bufferSource.playbackRate
1007
+ .cancelScheduledValues(now)
1008
+ .setValueAtTime(playbackRate * pressure, now);
1009
+ });
899
1010
  }
900
1011
  handleControlChange(channelNumber, controller, value) {
901
1012
  switch (controller) {
@@ -966,9 +1077,20 @@ export class Midy {
966
1077
  this.channels[channelNumber].bankMSB = msb;
967
1078
  }
968
1079
  setModulation(channelNumber, modulation) {
1080
+ const now = this.audioContext.currentTime;
969
1081
  const channel = this.channels[channelNumber];
970
1082
  channel.modulation = (modulation / 127) *
971
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
+ });
972
1094
  }
973
1095
  setPortamentoTime(channelNumber, portamentoTime) {
974
1096
  this.channels[channelNumber].portamentoTime = portamentoTime / 127;
@@ -1029,7 +1151,8 @@ export class Midy {
1029
1151
  const channel = this.channels[channelNumber];
1030
1152
  channel.sostenutoPedal = isOn;
1031
1153
  if (isOn) {
1032
- const activeNotes = this.getActiveNotes(channel);
1154
+ const now = this.audioContext.currentTime;
1155
+ const activeNotes = this.getActiveNotes(channel, now);
1033
1156
  channel.sostenutoNotes = new Map(activeNotes);
1034
1157
  }
1035
1158
  else {
@@ -1041,20 +1164,12 @@ export class Midy {
1041
1164
  channel.softPedal = softPedal / 127;
1042
1165
  }
1043
1166
  setVibratoRate(channelNumber, vibratoRate) {
1044
- const now = this.audioContext.currentTime;
1045
1167
  const channel = this.channels[channelNumber];
1046
1168
  channel.vibratoRate = vibratoRate / 127 * 4 + 3; // 3-7Hz
1047
- channel.modulationEffect.lfo.frequency
1048
- .cancelScheduledValues(now)
1049
- .setValueAtTime(channel.vibratoRate, now);
1050
1169
  }
1051
1170
  setVibratoDepth(channelNumber, vibratoDepth) {
1052
- const now = this.audioContext.currentTime;
1053
1171
  const channel = this.channels[channelNumber];
1054
1172
  channel.vibratoDepth = vibratoDepth / 127;
1055
- channel.modulationEffect.lfoGain.gain
1056
- .cancelScheduledValues(now)
1057
- .setValueAtTime(channel.vibratoDepth, now);
1058
1173
  }
1059
1174
  setVibratoDelay(channelNumber, vibratoDelay) {
1060
1175
  // Access Virus: 0-10sec
@@ -1135,8 +1250,8 @@ export class Midy {
1135
1250
  const velocity = 0;
1136
1251
  const stopPedal = true;
1137
1252
  const promises = [];
1138
- channel.scheduledNotes.forEach((scheduledNotes) => {
1139
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1253
+ channel.scheduledNotes.forEach((noteList) => {
1254
+ const activeNote = this.getActiveNote(noteList, now);
1140
1255
  if (activeNote) {
1141
1256
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1142
1257
  promises.push(notePromise);
@@ -1153,8 +1268,8 @@ export class Midy {
1153
1268
  const velocity = 0;
1154
1269
  const stopPedal = false;
1155
1270
  const promises = [];
1156
- channel.scheduledNotes.forEach((scheduledNotes) => {
1157
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
1271
+ channel.scheduledNotes.forEach((noteList) => {
1272
+ const activeNote = this.getActiveNote(noteList, now);
1158
1273
  if (activeNote) {
1159
1274
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
1160
1275
  promises.push(notePromise);
@@ -1240,10 +1355,10 @@ export class Midy {
1240
1355
  switch (data[3]) {
1241
1356
  // case 1:
1242
1357
  // // TODO
1243
- // return this.handleChannelPressure();
1358
+ // return this.setChannelPressure();
1244
1359
  // case 3:
1245
1360
  // // TODO
1246
- // return this.handleControlChange();
1361
+ // return this.setControlChange();
1247
1362
  default:
1248
1363
  console.warn(`Unsupported Exclusive Message ${data}`);
1249
1364
  }
@@ -1262,20 +1377,25 @@ export class Midy {
1262
1377
  }
1263
1378
  }
1264
1379
  handleMasterVolumeSysEx(data) {
1265
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
1380
+ const volume = (data[5] * 128 + data[4]) / 16383;
1266
1381
  this.handleMasterVolume(volume);
1267
1382
  }
1268
1383
  handleMasterVolume(volume) {
1269
- const now = this.audioContext.currentTime;
1270
- this.masterGain.gain.cancelScheduledValues(now);
1271
- this.masterGain.gain.setValueAtTime(volume * volume, now);
1384
+ if (volume < 0 && 1 < volume) {
1385
+ console.error("Master Volume is out of range");
1386
+ }
1387
+ else {
1388
+ const now = this.audioContext.currentTime;
1389
+ this.masterGain.gain.cancelScheduledValues(now);
1390
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
1391
+ }
1272
1392
  }
1273
1393
  handleMasterFineTuningSysEx(data) {
1274
1394
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1275
1395
  this.handleMasterFineTuning(fineTuning);
1276
1396
  }
1277
1397
  handleMasterFineTuning(fineTuning) {
1278
- if (fineTuning < 0 && 1 < fineTuning) {
1398
+ if (fineTuning < -1 && 1 < fineTuning) {
1279
1399
  console.error("Master Fine Tuning value is out of range");
1280
1400
  }
1281
1401
  else {
@@ -1362,3 +1482,16 @@ Object.defineProperty(Midy, "effectSettings", {
1362
1482
  pitchBendRange: 2,
1363
1483
  }
1364
1484
  });
1485
+ Object.defineProperty(Midy, "controllerDestinationSettings", {
1486
+ enumerable: true,
1487
+ configurable: true,
1488
+ writable: true,
1489
+ value: {
1490
+ pitchControl: 0,
1491
+ filterCutoffControl: 0,
1492
+ amplitudeControl: 1,
1493
+ lfoPitchDepth: 0,
1494
+ lfoFilterDepth: 0,
1495
+ lfoAmplitudeDepth: 0,
1496
+ }
1497
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.3",
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",