@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.MidyGM1 = 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 MidyGM1 {
7
57
  constructor(audioContext) {
8
58
  Object.defineProperty(this, "ticksPerBeat", {
@@ -154,14 +204,11 @@ class MidyGM1 {
154
204
  const pannerNode = new StereoPannerNode(audioContext, {
155
205
  pan: MidyGM1.channelSettings.pan,
156
206
  });
157
- const modulationEffect = this.createModulationEffect(audioContext);
158
- modulationEffect.lfo.start();
159
207
  pannerNode.connect(gainNode);
160
208
  gainNode.connect(this.masterGain);
161
209
  return {
162
210
  gainNode,
163
211
  pannerNode,
164
- modulationEffect,
165
212
  };
166
213
  }
167
214
  createChannels(audioContext) {
@@ -171,16 +218,15 @@ class MidyGM1 {
171
218
  ...MidyGM1.effectSettings,
172
219
  ...this.setChannelAudioNodes(audioContext),
173
220
  scheduledNotes: new Map(),
174
- sostenutoNotes: new Map(),
175
221
  };
176
222
  });
177
223
  return channels;
178
224
  }
179
- async createNoteBuffer(noteInfo, isSF3) {
180
- const sampleEnd = noteInfo.sample.length + noteInfo.end;
225
+ async createNoteBuffer(instrumentKey, isSF3) {
226
+ const sampleEnd = instrumentKey.sample.length + instrumentKey.end;
181
227
  if (isSF3) {
182
- const sample = new Uint8Array(noteInfo.sample.length);
183
- sample.set(noteInfo.sample);
228
+ const sample = new Uint8Array(instrumentKey.sample.length);
229
+ sample.set(instrumentKey.sample);
184
230
  const audioBuffer = await this.audioContext.decodeAudioData(sample.buffer);
185
231
  for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
186
232
  const channelData = audioBuffer.getChannelData(channel);
@@ -189,26 +235,27 @@ class MidyGM1 {
189
235
  return audioBuffer;
190
236
  }
191
237
  else {
192
- const sample = noteInfo.sample.subarray(0, sampleEnd);
238
+ const sample = instrumentKey.sample.subarray(0, sampleEnd);
193
239
  const floatSample = this.convertToFloat32Array(sample);
194
240
  const audioBuffer = new AudioBuffer({
195
241
  numberOfChannels: 1,
196
242
  length: sample.length,
197
- sampleRate: noteInfo.sampleRate,
243
+ sampleRate: instrumentKey.sampleRate,
198
244
  });
199
245
  const channelData = audioBuffer.getChannelData(0);
200
246
  channelData.set(floatSample);
201
247
  return audioBuffer;
202
248
  }
203
249
  }
204
- async createNoteBufferNode(noteInfo, isSF3) {
250
+ async createNoteBufferNode(instrumentKey, isSF3) {
205
251
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
206
- const audioBuffer = await this.createNoteBuffer(noteInfo, isSF3);
252
+ const audioBuffer = await this.createNoteBuffer(instrumentKey, isSF3);
207
253
  bufferSource.buffer = audioBuffer;
208
- bufferSource.loop = noteInfo.sampleModes % 2 !== 0;
254
+ bufferSource.loop = instrumentKey.sampleModes % 2 !== 0;
209
255
  if (bufferSource.loop) {
210
- bufferSource.loopStart = noteInfo.loopStart / noteInfo.sampleRate;
211
- bufferSource.loopEnd = noteInfo.loopEnd / noteInfo.sampleRate;
256
+ bufferSource.loopStart = instrumentKey.loopStart /
257
+ instrumentKey.sampleRate;
258
+ bufferSource.loopEnd = instrumentKey.loopEnd / instrumentKey.sampleRate;
212
259
  }
213
260
  return bufferSource;
214
261
  }
@@ -226,9 +273,6 @@ class MidyGM1 {
226
273
  if (event.startTime > t + this.lookAhead)
227
274
  break;
228
275
  switch (event.type) {
229
- case "controller":
230
- this.handleControlChange(event.channel, event.controllerType, event.value);
231
- break;
232
276
  case "noteOn":
233
277
  if (event.velocity !== 0) {
234
278
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -242,9 +286,15 @@ class MidyGM1 {
242
286
  }
243
287
  break;
244
288
  }
289
+ case "controller":
290
+ this.handleControlChange(event.channel, event.controllerType, event.value);
291
+ break;
245
292
  case "programChange":
246
293
  this.handleProgramChange(event.channel, event.programNumber);
247
294
  break;
295
+ case "pitchBend":
296
+ this.handlePitchBend(event.channel, event.value);
297
+ break;
248
298
  case "sysEx":
249
299
  this.handleSysEx(event.data);
250
300
  }
@@ -447,30 +497,26 @@ class MidyGM1 {
447
497
  const now = this.audioContext.currentTime;
448
498
  return this.resumeTime + now - this.startTime - this.startDelay;
449
499
  }
450
- getActiveNotes(channel) {
500
+ getActiveNotes(channel, time) {
451
501
  const activeNotes = new Map();
452
- channel.scheduledNotes.forEach((scheduledNotes) => {
453
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
502
+ channel.scheduledNotes.forEach((noteList) => {
503
+ const activeNote = this.getActiveNote(noteList, time);
454
504
  if (activeNote) {
455
505
  activeNotes.set(activeNote.noteNumber, activeNote);
456
506
  }
457
507
  });
458
508
  return activeNotes;
459
509
  }
460
- getActiveChannelNotes(scheduledNotes) {
461
- for (let i = 0; i < scheduledNotes; i++) {
462
- const scheduledNote = scheduledNotes[i];
463
- if (scheduledNote)
464
- return scheduledNote;
510
+ getActiveNote(noteList, time) {
511
+ for (let i = noteList.length - 1; i >= 0; i--) {
512
+ const note = noteList[i];
513
+ if (!note)
514
+ return;
515
+ if (time < note.startTime)
516
+ continue;
517
+ return (note.ending) ? null : note;
465
518
  }
466
- }
467
- createModulationEffect(audioContext) {
468
- const lfo = new OscillatorNode(audioContext, {
469
- frequency: 5,
470
- });
471
- return {
472
- lfo,
473
- };
519
+ return noteList[0];
474
520
  }
475
521
  connectNoteEffects(channel, gainNode) {
476
522
  gainNode.connect(channel.pannerNode);
@@ -481,71 +527,93 @@ class MidyGM1 {
481
527
  centToHz(cent) {
482
528
  return 8.176 * Math.pow(2, cent / 1200);
483
529
  }
484
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
530
+ calcSemitoneOffset(channel) {
485
531
  const tuning = channel.coarseTuning + channel.fineTuning;
486
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
487
- const playbackRate = noteInfo.playbackRate(noteNumber) *
532
+ return channel.pitchBend * channel.pitchBendRange + tuning;
533
+ }
534
+ calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset) {
535
+ return instrumentKey.playbackRate(noteNumber) *
488
536
  Math.pow(2, semitoneOffset / 12);
489
- const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
490
- bufferSource.playbackRate.value = playbackRate;
491
- // volume envelope
492
- const gainNode = new GainNode(this.audioContext, {
537
+ }
538
+ setVolumeEnvelope(channel, note) {
539
+ const { instrumentKey, startTime, velocity } = note;
540
+ note.gainNode = new GainNode(this.audioContext, {
493
541
  gain: 0,
494
542
  });
495
543
  let volume = (velocity / 127) * channel.volume * channel.expression;
496
544
  if (volume === 0)
497
545
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
498
- const attackVolume = this.cbToRatio(-noteInfo.initialAttenuation) * volume;
499
- const sustainVolume = attackVolume * (1 - noteInfo.volSustain);
500
- const volDelay = startTime + noteInfo.volDelay;
501
- const volAttack = volDelay + noteInfo.volAttack;
502
- const volHold = volAttack + noteInfo.volHold;
503
- const volDecay = volHold + noteInfo.volDecay;
504
- gainNode.gain
546
+ const attackVolume = this.cbToRatio(-instrumentKey.initialAttenuation) *
547
+ volume;
548
+ const sustainVolume = attackVolume * (1 - instrumentKey.volSustain);
549
+ const volDelay = startTime + instrumentKey.volDelay;
550
+ const volAttack = volDelay + instrumentKey.volAttack;
551
+ const volHold = volAttack + instrumentKey.volHold;
552
+ const volDecay = volHold + instrumentKey.volDecay;
553
+ note.gainNode.gain
505
554
  .setValueAtTime(1e-6, volDelay) // exponentialRampToValueAtTime() requires a non-zero value
506
555
  .exponentialRampToValueAtTime(attackVolume, volAttack)
507
556
  .setValueAtTime(attackVolume, volHold)
508
557
  .linearRampToValueAtTime(sustainVolume, volDecay);
509
- // filter envelope
558
+ }
559
+ setFilterEnvelope(channel, note) {
560
+ const { instrumentKey, startTime, noteNumber } = note;
561
+ const softPedalFactor = 1 -
562
+ (0.1 + (noteNumber / 127) * 0.2) * channel.softPedal;
510
563
  const maxFreq = this.audioContext.sampleRate / 2;
511
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
512
- const peekFreq = this.centToHz(noteInfo.initialFilterFc + noteInfo.modEnvToFilterFc);
513
- const sustainFreq = baseFreq +
514
- (peekFreq - baseFreq) * (1 - noteInfo.modSustain);
564
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc) *
565
+ softPedalFactor;
566
+ const peekFreq = this.centToHz(instrumentKey.initialFilterFc + instrumentKey.modEnvToFilterFc) * softPedalFactor;
567
+ const sustainFreq = (baseFreq +
568
+ (peekFreq - baseFreq) * (1 - instrumentKey.modSustain)) * softPedalFactor;
569
+ const modDelay = startTime + instrumentKey.modDelay;
570
+ const modAttack = modDelay + instrumentKey.modAttack;
571
+ const modHold = modAttack + instrumentKey.modHold;
572
+ const modDecay = modHold + instrumentKey.modDecay;
515
573
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
516
574
  const adjustedPeekFreq = Math.min(maxFreq, peekFreq);
517
575
  const adjustedSustainFreq = Math.min(maxFreq, sustainFreq);
518
- const filterNode = new BiquadFilterNode(this.audioContext, {
576
+ note.filterNode = new BiquadFilterNode(this.audioContext, {
519
577
  type: "lowpass",
520
- Q: noteInfo.initialFilterQ / 10, // dB
578
+ Q: instrumentKey.initialFilterQ / 10, // dB
521
579
  frequency: adjustedBaseFreq,
522
580
  });
523
- const modDelay = startTime + noteInfo.modDelay;
524
- const modAttack = modDelay + noteInfo.modAttack;
525
- const modHold = modAttack + noteInfo.modHold;
526
- const modDecay = modHold + noteInfo.modDecay;
527
- filterNode.frequency
581
+ note.filterNode.frequency
528
582
  .setValueAtTime(adjustedBaseFreq, modDelay)
529
583
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
530
584
  .setValueAtTime(adjustedPeekFreq, modHold)
531
585
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
532
- let lfoGain;
586
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modEnvToPitch, modDelay);
587
+ }
588
+ startModulation(channel, note, time) {
589
+ const { instrumentKey } = note;
590
+ note.modLFOGain = new GainNode(this.audioContext, {
591
+ gain: this.cbToRatio(instrumentKey.modLfoToVolume) * channel.modulation,
592
+ });
593
+ note.modLFO = new OscillatorNode(this.audioContext, {
594
+ frequency: this.centToHz(instrumentKey.freqModLFO),
595
+ });
596
+ note.modLFO.start(time);
597
+ note.filterNode.frequency.setValueAtTime(note.filterNode.frequency.value + instrumentKey.modLfoToFilterFc, time);
598
+ note.bufferSource.detune.setValueAtTime(note.bufferSource.detune.value + instrumentKey.modLfoToPitch, time);
599
+ note.modLFO.connect(note.modLFOGain);
600
+ note.modLFOGain.connect(note.bufferSource.detune);
601
+ }
602
+ async createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3) {
603
+ const semitoneOffset = this.calcSemitoneOffset(channel);
604
+ const note = new Note(noteNumber, velocity, startTime, instrumentKey);
605
+ note.bufferSource = await this.createNoteBufferNode(instrumentKey, isSF3);
606
+ note.bufferSource.playbackRate.value = this.calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
607
+ this.setVolumeEnvelope(channel, note);
608
+ this.setFilterEnvelope(channel, note);
533
609
  if (channel.modulation > 0) {
534
- const vibratoDelay = startTime + channel.vibratoDelay;
535
- const vibratoAttack = vibratoDelay + 0.1;
536
- lfoGain = new GainNode(this.audioContext, {
537
- gain: 0,
538
- });
539
- lfoGain.gain
540
- .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
541
- .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
542
- channel.modulationEffect.lfo.connect(lfoGain);
543
- lfoGain.connect(bufferSource.detune);
610
+ const delayModLFO = startTime + instrumentKey.delayModLFO;
611
+ this.startModulation(channel, note, delayModLFO);
544
612
  }
545
- bufferSource.connect(filterNode);
546
- filterNode.connect(gainNode);
547
- bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
548
- return { bufferSource, gainNode, filterNode, lfoGain };
613
+ note.bufferSource.connect(note.filterNode);
614
+ note.filterNode.connect(note.gainNode);
615
+ note.bufferSource.start(startTime, instrumentKey.start / instrumentKey.sampleRate);
616
+ return note;
549
617
  }
550
618
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
551
619
  const channel = this.channels[channelNumber];
@@ -555,27 +623,17 @@ class MidyGM1 {
555
623
  return;
556
624
  const soundFont = this.soundFonts[soundFontIndex];
557
625
  const isSF3 = soundFont.parsed.info.version.major === 3;
558
- const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
559
- if (!noteInfo)
626
+ const instrumentKey = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
627
+ if (!instrumentKey)
560
628
  return;
561
- const { bufferSource, gainNode, filterNode, lfoGain } = await this
562
- .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
563
- this.connectNoteEffects(channel, gainNode);
629
+ const note = await this.createNote(channel, instrumentKey, noteNumber, velocity, startTime, isSF3);
630
+ this.connectNoteEffects(channel, note.gainNode);
564
631
  const scheduledNotes = channel.scheduledNotes;
565
- const scheduledNote = {
566
- bufferSource,
567
- filterNode,
568
- gainNode,
569
- lfoGain,
570
- noteInfo,
571
- noteNumber,
572
- startTime,
573
- };
574
632
  if (scheduledNotes.has(noteNumber)) {
575
- scheduledNotes.get(noteNumber).push(scheduledNote);
633
+ scheduledNotes.get(noteNumber).push(note);
576
634
  }
577
635
  else {
578
- scheduledNotes.set(noteNumber, [scheduledNote]);
636
+ scheduledNotes.set(noteNumber, [note]);
579
637
  }
580
638
  }
581
639
  noteOn(channelNumber, noteNumber, velocity) {
@@ -595,15 +653,15 @@ class MidyGM1 {
595
653
  continue;
596
654
  if (targetNote.ending)
597
655
  continue;
598
- const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
656
+ const { bufferSource, filterNode, gainNode, modLFO, modLFOGain, instrumentKey, } = targetNote;
599
657
  const velocityRate = (velocity + 127) / 127;
600
- const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
658
+ const volEndTime = stopTime + instrumentKey.volRelease * velocityRate;
601
659
  gainNode.gain.cancelScheduledValues(stopTime);
602
660
  gainNode.gain.linearRampToValueAtTime(0, volEndTime);
603
661
  const maxFreq = this.audioContext.sampleRate / 2;
604
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
662
+ const baseFreq = this.centToHz(instrumentKey.initialFilterFc);
605
663
  const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
606
- const modEndTime = stopTime + noteInfo.modRelease * velocityRate;
664
+ const modEndTime = stopTime + instrumentKey.modRelease * velocityRate;
607
665
  filterNode.frequency
608
666
  .cancelScheduledValues(stopTime)
609
667
  .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
@@ -617,8 +675,10 @@ class MidyGM1 {
617
675
  bufferSource.disconnect(0);
618
676
  filterNode.disconnect(0);
619
677
  gainNode.disconnect(0);
620
- if (lfoGain)
621
- lfoGain.disconnect(0);
678
+ if (modLFOGain)
679
+ modLFOGain.disconnect(0);
680
+ if (modLFO)
681
+ modLFO.stop();
622
682
  resolve();
623
683
  };
624
684
  bufferSource.stop(volEndTime);
@@ -653,46 +713,37 @@ class MidyGM1 {
653
713
  return this.releaseNote(channelNumber, data1, data2);
654
714
  case 0x90:
655
715
  return this.noteOn(channelNumber, data1, data2);
656
- case 0xA0:
657
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
658
716
  case 0xB0:
659
717
  return this.handleControlChange(channelNumber, data1, data2);
660
718
  case 0xC0:
661
719
  return this.handleProgramChange(channelNumber, data1);
662
- case 0xD0:
663
- return this.handleChannelPressure(channelNumber, data1);
664
720
  case 0xE0:
665
- return this.handlePitchBend(channelNumber, data1, data2);
721
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
666
722
  default:
667
723
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
668
724
  }
669
725
  }
670
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
671
- const now = this.audioContext.currentTime;
672
- const channel = this.channels[channelNumber];
673
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
674
- pressure /= 127;
675
- if (scheduledNotes) {
676
- scheduledNotes.forEach((scheduledNote) => {
677
- if (scheduledNote) {
678
- const { initialAttenuation } = scheduledNote.noteInfo;
679
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
680
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
681
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
682
- }
683
- });
684
- }
685
- }
686
726
  handleProgramChange(channelNumber, program) {
687
727
  const channel = this.channels[channelNumber];
688
728
  channel.program = program;
689
729
  }
690
- handleChannelPressure(channelNumber, pressure) {
691
- this.channels[channelNumber].channelPressure = pressure;
730
+ handlePitchBendMessage(channelNumber, lsb, msb) {
731
+ const pitchBend = msb * 128 + lsb;
732
+ this.handlePitchBend(channelNumber, pitchBend);
692
733
  }
693
- handlePitchBend(channelNumber, lsb, msb) {
694
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
695
- this.channels[channelNumber].pitchBend = pitchBend;
734
+ handlePitchBend(channelNumber, pitchBend) {
735
+ const now = this.audioContext.currentTime;
736
+ const channel = this.channels[channelNumber];
737
+ channel.pitchBend = (pitchBend - 8192) / 8192;
738
+ const semitoneOffset = this.calcSemitoneOffset(channel);
739
+ const activeNotes = this.getActiveNotes(channel, now);
740
+ activeNotes.forEach((activeNote) => {
741
+ const { bufferSource, instrumentKey, noteNumber } = activeNote;
742
+ const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
743
+ bufferSource.playbackRate
744
+ .cancelScheduledValues(now)
745
+ .setValueAtTime(playbackRate * pressure, now);
746
+ });
696
747
  }
697
748
  handleControlChange(channelNumber, controller, value) {
698
749
  switch (controller) {
@@ -725,9 +776,20 @@ class MidyGM1 {
725
776
  }
726
777
  }
727
778
  setModulation(channelNumber, modulation) {
779
+ const now = this.audioContext.currentTime;
728
780
  const channel = this.channels[channelNumber];
729
781
  channel.modulation = (modulation / 127) *
730
782
  (channel.modulationDepthRange * 100);
783
+ const activeNotes = this.getActiveNotes(channel, now);
784
+ activeNotes.forEach((activeNote) => {
785
+ if (activeNote.modLFO) {
786
+ activeNote.gainNode.gain.setValueAtTime(this.cbToRatio(activeNote.instrumentKey.modLfoToVolume) *
787
+ channel.modulation, now);
788
+ }
789
+ else {
790
+ this.startModulation(channel, activeNote, now);
791
+ }
792
+ });
731
793
  }
732
794
  setVolume(channelNumber, volume) {
733
795
  const channel = this.channels[channelNumber];
@@ -790,8 +852,8 @@ class MidyGM1 {
790
852
  const velocity = 0;
791
853
  const stopPedal = true;
792
854
  const promises = [];
793
- channel.scheduledNotes.forEach((scheduledNotes) => {
794
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
855
+ channel.scheduledNotes.forEach((noteList) => {
856
+ const activeNote = this.getActiveNote(noteList, now);
795
857
  if (activeNote) {
796
858
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
797
859
  promises.push(notePromise);
@@ -808,8 +870,8 @@ class MidyGM1 {
808
870
  const velocity = 0;
809
871
  const stopPedal = false;
810
872
  const promises = [];
811
- channel.scheduledNotes.forEach((scheduledNotes) => {
812
- const activeNote = this.getActiveChannelNotes(scheduledNotes);
873
+ channel.scheduledNotes.forEach((noteList) => {
874
+ const activeNote = this.getActiveNote(noteList, now);
813
875
  if (activeNote) {
814
876
  const notePromise = this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now, stopPedal);
815
877
  promises.push(notePromise);
@@ -858,13 +920,18 @@ class MidyGM1 {
858
920
  }
859
921
  }
860
922
  handleMasterVolumeSysEx(data) {
861
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
923
+ const volume = (data[5] * 128 + data[4]) / 16383;
862
924
  this.handleMasterVolume(volume);
863
925
  }
864
926
  handleMasterVolume(volume) {
865
- const now = this.audioContext.currentTime;
866
- this.masterGain.gain.cancelScheduledValues(now);
867
- this.masterGain.gain.setValueAtTime(volume * volume, now);
927
+ if (volume < 0 && 1 < volume) {
928
+ console.error("Master Volume is out of range");
929
+ }
930
+ else {
931
+ const now = this.audioContext.currentTime;
932
+ this.masterGain.gain.cancelScheduledValues(now);
933
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
934
+ }
868
935
  }
869
936
  handleExclusiveMessage(data) {
870
937
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -899,9 +966,6 @@ Object.defineProperty(MidyGM1, "channelSettings", {
899
966
  value: {
900
967
  volume: 100 / 127,
901
968
  pan: 0,
902
- vibratoRate: 5,
903
- vibratoDepth: 0.5,
904
- vibratoDelay: 2.5,
905
969
  bank: 0,
906
970
  dataMSB: 0,
907
971
  dataLSB: 0,