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