@marmooo/midy 0.0.2 → 0.0.4

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.
package/esm/midy-GM1.d.ts CHANGED
@@ -24,7 +24,6 @@ export class MidyGM1 {
24
24
  };
25
25
  constructor(audioContext: any);
26
26
  ticksPerBeat: number;
27
- secondsPerBeat: number;
28
27
  totalTime: number;
29
28
  noteCheckInterval: number;
30
29
  lookAhead: number;
@@ -50,7 +49,6 @@ export class MidyGM1 {
50
49
  pannerNode: any;
51
50
  modulationEffect: {
52
51
  lfo: any;
53
- lfoGain: any;
54
52
  };
55
53
  expression: number;
56
54
  modulation: number;
@@ -81,7 +79,6 @@ export class MidyGM1 {
81
79
  pannerNode: any;
82
80
  modulationEffect: {
83
81
  lfo: any;
84
- lfoGain: any;
85
82
  };
86
83
  };
87
84
  createChannels(audioContext: any): {
@@ -91,7 +88,6 @@ export class MidyGM1 {
91
88
  pannerNode: any;
92
89
  modulationEffect: {
93
90
  lfo: any;
94
- lfoGain: any;
95
91
  };
96
92
  expression: number;
97
93
  modulation: number;
@@ -137,31 +133,27 @@ export class MidyGM1 {
137
133
  getActiveChannelNotes(scheduledNotes: any): any;
138
134
  createModulationEffect(audioContext: any): {
139
135
  lfo: any;
140
- lfoGain: any;
141
- };
142
- createReverbEffect(audioContext: any, options?: {}): {
143
- convolverNode: any;
144
- dryGain: any;
145
- wetGain: any;
146
136
  };
147
137
  connectNoteEffects(channel: any, gainNode: any): void;
148
138
  cbToRatio(cb: any): number;
149
139
  centToHz(cent: any): number;
140
+ calcSemitoneOffset(channel: any): any;
141
+ calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
150
142
  createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
151
143
  bufferSource: any;
152
144
  gainNode: any;
153
145
  filterNode: any;
146
+ lfoGain: any;
154
147
  }>;
155
148
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
156
149
  noteOn(channelNumber: any, noteNumber: any, velocity: any): Promise<void>;
157
150
  scheduleNoteRelease(channelNumber: any, noteNumber: any, velocity: any, stopTime: any, stopPedal?: boolean): Promise<any> | undefined;
158
151
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
159
- releaseSustainPedal(channelNumber: any): void;
152
+ releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
160
153
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
161
- handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
162
154
  handleProgramChange(channelNumber: any, program: any): void;
163
- handleChannelPressure(channelNumber: any, pressure: any): void;
164
- handlePitchBend(channelNumber: any, lsb: any, msb: any): void;
155
+ handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
156
+ handlePitchBend(channelNumber: any, pitchBend: any): void;
165
157
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
166
158
  setModulation(channelNumber: any, modulation: any): void;
167
159
  setVolume(channelNumber: any, volume: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAoBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAlDD,qBAAmB;IACnB,uBAAqB;IACrB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA6CC;IAED,mCAQC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA0DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBA2BC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;;MAUC;IAED;;;;MAoCC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAwEC;IAED,kGAsCC;IAED,0EAGC;IAED,sIA0CC;IAED,0FAGC;IAED,8CAuBC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;IAED,mFA+BC;IAED,yDAQC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAIC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAMA;IAmBE;;;;;;;;;;;;;;MAcE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAjDD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IA4BhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;MAgBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAyEC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4CASC;IAED,gDAKC;IAED;;MAOC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,sCAGC;IAED,8EAEC;IAED;;;;;OA8EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAIC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
package/esm/midy-GM1.js CHANGED
@@ -8,12 +8,6 @@ export class MidyGM1 {
8
8
  writable: true,
9
9
  value: 120
10
10
  });
11
- Object.defineProperty(this, "secondsPerBeat", {
12
- enumerable: true,
13
- configurable: true,
14
- writable: true,
15
- value: 0.5
16
- });
17
11
  Object.defineProperty(this, "totalTime", {
18
12
  enumerable: true,
19
13
  configurable: true,
@@ -144,10 +138,10 @@ export class MidyGM1 {
144
138
  const response = await fetch(midiUrl);
145
139
  const arrayBuffer = await response.arrayBuffer();
146
140
  const midi = parseMidi(new Uint8Array(arrayBuffer));
141
+ this.ticksPerBeat = midi.header.ticksPerBeat;
147
142
  const midiData = this.extractMidiData(midi);
148
143
  this.instruments = midiData.instruments;
149
144
  this.timeline = midiData.timeline;
150
- this.ticksPerBeat = midi.header.ticksPerBeat;
151
145
  this.totalTime = this.calcTotalTime();
152
146
  }
153
147
  setChannelAudioNodes(audioContext) {
@@ -226,28 +220,30 @@ export class MidyGM1 {
226
220
  async scheduleTimelineEvents(t, offset, queueIndex) {
227
221
  while (queueIndex < this.timeline.length) {
228
222
  const event = this.timeline[queueIndex];
229
- const time = this.ticksToSecond(event.ticks, this.secondsPerBeat);
230
- if (time > t + this.lookAhead)
223
+ if (event.startTime > t + this.lookAhead)
231
224
  break;
232
225
  switch (event.type) {
233
- case "controller":
234
- this.handleControlChange(event.channel, event.controllerType, event.value);
235
- break;
236
226
  case "noteOn":
237
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
238
- break;
227
+ if (event.velocity !== 0) {
228
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
229
+ break;
230
+ }
231
+ /* falls through */
239
232
  case "noteOff": {
240
- const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, time + this.startDelay - offset);
233
+ const notePromise = this.scheduleNoteRelease(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
241
234
  if (notePromise) {
242
235
  this.notePromises.push(notePromise);
243
236
  }
244
237
  break;
245
238
  }
239
+ case "controller":
240
+ this.handleControlChange(event.channel, event.controllerType, event.value);
241
+ break;
246
242
  case "programChange":
247
243
  this.handleProgramChange(event.channel, event.programNumber);
248
244
  break;
249
- case "setTempo":
250
- this.secondsPerBeat = event.microsecondsPerBeat / 1000000;
245
+ case "pitchBend":
246
+ this.handlePitchBend(event.channel, event.value);
251
247
  break;
252
248
  case "sysEx":
253
249
  this.handleSysEx(event.data);
@@ -257,9 +253,8 @@ export class MidyGM1 {
257
253
  return queueIndex;
258
254
  }
259
255
  getQueueIndex(second) {
260
- const ticks = this.secondToTicks(second, this.secondsPerBeat);
261
256
  for (let i = 0; i < this.timeline.length; i++) {
262
- if (ticks <= this.timeline[i].ticks) {
257
+ if (second <= this.timeline[i].startTime) {
263
258
  return i;
264
259
  }
265
260
  }
@@ -367,18 +362,28 @@ export class MidyGM1 {
367
362
  timeline.push(event);
368
363
  });
369
364
  });
365
+ const priority = {
366
+ setTempo: 0,
367
+ controller: 1,
368
+ };
370
369
  timeline.sort((a, b) => {
371
- if (a.ticks !== b.ticks) {
370
+ if (a.ticks !== b.ticks)
372
371
  return a.ticks - b.ticks;
373
- }
374
- if (a.type !== "controller" && b.type === "controller") {
375
- return -1;
376
- }
377
- if (a.type === "controller" && b.type !== "controller") {
378
- return 1;
379
- }
380
- return 0;
372
+ return (priority[a.type] || 2) - (priority[b.type] || 2);
381
373
  });
374
+ let prevTempoTime = 0;
375
+ let prevTempoTicks = 0;
376
+ let secondsPerBeat = 0.5;
377
+ for (let i = 0; i < timeline.length; i++) {
378
+ const event = timeline[i];
379
+ const timeFromPrevTempo = this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
380
+ event.startTime = prevTempoTime + timeFromPrevTempo;
381
+ if (event.type === "setTempo") {
382
+ prevTempoTime += this.ticksToSecond(event.ticks - prevTempoTicks, secondsPerBeat);
383
+ secondsPerBeat = event.microsecondsPerBeat / 1000000;
384
+ prevTempoTicks = event.ticks;
385
+ }
386
+ }
382
387
  return { instruments, timeline };
383
388
  }
384
389
  stopNotes() {
@@ -430,32 +435,12 @@ export class MidyGM1 {
430
435
  }
431
436
  }
432
437
  calcTotalTime() {
433
- const endOfTracks = [];
434
- let prevTicks = 0;
435
438
  let totalTime = 0;
436
- let secondsPerBeat = 0.5;
437
439
  for (let i = 0; i < this.timeline.length; i++) {
438
440
  const event = this.timeline[i];
439
- switch (event.type) {
440
- case "setTempo": {
441
- const durationTicks = event.ticks - prevTicks;
442
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
443
- secondsPerBeat = event.microsecondsPerBeat / 1000000;
444
- prevTicks = event.ticks;
445
- break;
446
- }
447
- case "endOfTrack":
448
- endOfTracks.push(event);
449
- }
450
- }
451
- let maxTicks = 0;
452
- for (let i = 0; i < endOfTracks.length; i++) {
453
- const event = endOfTracks[i];
454
- if (maxTicks < event.ticks)
455
- maxTicks = event.ticks;
441
+ if (totalTime < event.startTime)
442
+ totalTime = event.startTime;
456
443
  }
457
- const durationTicks = maxTicks - prevTicks;
458
- totalTime += this.ticksToSecond(durationTicks, secondsPerBeat);
459
444
  return totalTime;
460
445
  }
461
446
  currentTime() {
@@ -483,43 +468,8 @@ export class MidyGM1 {
483
468
  const lfo = new OscillatorNode(audioContext, {
484
469
  frequency: 5,
485
470
  });
486
- const lfoGain = new GainNode(audioContext);
487
- lfo.connect(lfoGain);
488
471
  return {
489
472
  lfo,
490
- lfoGain,
491
- };
492
- }
493
- createReverbEffect(audioContext, options = {}) {
494
- const { decay = 0.8, preDecay = 0, } = options;
495
- const sampleRate = audioContext.sampleRate;
496
- const length = sampleRate * decay;
497
- const impulse = new AudioBuffer({
498
- numberOfChannels: 2,
499
- length,
500
- sampleRate,
501
- });
502
- const preDecayLength = Math.min(sampleRate * preDecay, length);
503
- for (let channel = 0; channel < impulse.numberOfChannels; channel++) {
504
- const channelData = impulse.getChannelData(channel);
505
- for (let i = 0; i < preDecayLength; i++) {
506
- channelData[i] = Math.random() * 2 - 1;
507
- }
508
- for (let i = preDecayLength; i < length; i++) {
509
- const attenuation = Math.exp(-(i - preDecayLength) / sampleRate / decay);
510
- channelData[i] = (Math.random() * 2 - 1) * attenuation;
511
- }
512
- }
513
- const convolverNode = new ConvolverNode(audioContext, {
514
- buffer: impulse,
515
- });
516
- const dryGain = new GainNode(audioContext);
517
- const wetGain = new GainNode(audioContext);
518
- convolverNode.connect(wetGain);
519
- return {
520
- convolverNode,
521
- dryGain,
522
- wetGain,
523
473
  };
524
474
  }
525
475
  connectNoteEffects(channel, gainNode) {
@@ -531,13 +481,17 @@ export class MidyGM1 {
531
481
  centToHz(cent) {
532
482
  return 8.176 * Math.pow(2, cent / 1200);
533
483
  }
534
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
484
+ calcSemitoneOffset(channel) {
535
485
  const tuning = channel.coarseTuning + channel.fineTuning;
536
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
537
- const playbackRate = noteInfo.playbackRate(noteNumber) *
538
- Math.pow(2, semitoneOffset / 12);
486
+ return channel.pitchBend * channel.pitchBendRange + tuning;
487
+ }
488
+ calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
489
+ return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
490
+ }
491
+ async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
539
492
  const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
540
- bufferSource.playbackRate.value = playbackRate;
493
+ const semitoneOffset = this.calcSemitoneOffset(channel);
494
+ bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
541
495
  // volume envelope
542
496
  const gainNode = new GainNode(this.audioContext, {
543
497
  gain: 0,
@@ -556,12 +510,6 @@ export class MidyGM1 {
556
510
  .exponentialRampToValueAtTime(attackVolume, volAttack)
557
511
  .setValueAtTime(attackVolume, volHold)
558
512
  .linearRampToValueAtTime(sustainVolume, volDecay);
559
- if (channel.modulation > 0) {
560
- const lfoGain = channel.modulationEffect.lfoGain;
561
- lfoGain.connect(bufferSource.detune);
562
- lfoGain.gain.cancelScheduledValues(startTime + channel.vibratoDelay);
563
- lfoGain.gain.setValueAtTime(channel.modulation, startTime + channel.vibratoDelay);
564
- }
565
513
  // filter envelope
566
514
  const maxFreq = this.audioContext.sampleRate / 2;
567
515
  const baseFreq = this.centToHz(noteInfo.initialFilterFc);
@@ -585,10 +533,23 @@ export class MidyGM1 {
585
533
  .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
586
534
  .setValueAtTime(adjustedPeekFreq, modHold)
587
535
  .linearRampToValueAtTime(adjustedSustainFreq, modDecay);
536
+ let lfoGain;
537
+ if (channel.modulation > 0) {
538
+ const vibratoDelay = startTime + channel.vibratoDelay;
539
+ const vibratoAttack = vibratoDelay + 0.1;
540
+ lfoGain = new GainNode(this.audioContext, {
541
+ gain: 0,
542
+ });
543
+ lfoGain.gain
544
+ .setValueAtTime(1e-6, vibratoDelay) // exponentialRampToValueAtTime() requires a non-zero value
545
+ .exponentialRampToValueAtTime(channel.modulation, vibratoAttack);
546
+ channel.modulationEffect.lfo.connect(lfoGain);
547
+ lfoGain.connect(bufferSource.detune);
548
+ }
588
549
  bufferSource.connect(filterNode);
589
550
  filterNode.connect(gainNode);
590
551
  bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
591
- return { bufferSource, gainNode, filterNode };
552
+ return { bufferSource, gainNode, filterNode, lfoGain };
592
553
  }
593
554
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
594
555
  const channel = this.channels[channelNumber];
@@ -601,16 +562,17 @@ export class MidyGM1 {
601
562
  const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
602
563
  if (!noteInfo)
603
564
  return;
604
- const { bufferSource, gainNode, filterNode } = await this
565
+ const { bufferSource, gainNode, filterNode, lfoGain } = await this
605
566
  .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
606
567
  this.connectNoteEffects(channel, gainNode);
607
568
  const scheduledNotes = channel.scheduledNotes;
608
569
  const scheduledNote = {
609
- gainNode,
610
- filterNode,
611
570
  bufferSource,
612
- noteNumber,
571
+ filterNode,
572
+ gainNode,
573
+ lfoGain,
613
574
  noteInfo,
575
+ noteNumber,
614
576
  startTime,
615
577
  };
616
578
  if (scheduledNotes.has(noteNumber)) {
@@ -637,7 +599,7 @@ export class MidyGM1 {
637
599
  continue;
638
600
  if (targetNote.ending)
639
601
  continue;
640
- const { bufferSource, filterNode, gainNode, noteInfo } = targetNote;
602
+ const { bufferSource, filterNode, gainNode, lfoGain, noteInfo } = targetNote;
641
603
  const velocityRate = (velocity + 127) / 127;
642
604
  const volEndTime = stopTime + noteInfo.volRelease * velocityRate;
643
605
  gainNode.gain.cancelScheduledValues(stopTime);
@@ -659,6 +621,8 @@ export class MidyGM1 {
659
621
  bufferSource.disconnect(0);
660
622
  filterNode.disconnect(0);
661
623
  gainNode.disconnect(0);
624
+ if (lfoGain)
625
+ lfoGain.disconnect(0);
662
626
  resolve();
663
627
  };
664
628
  bufferSource.stop(volEndTime);
@@ -669,28 +633,21 @@ export class MidyGM1 {
669
633
  const now = this.audioContext.currentTime;
670
634
  return this.scheduleNoteRelease(channelNumber, noteNumber, velocity, now);
671
635
  }
672
- releaseSustainPedal(channelNumber) {
673
- const now = this.audioContext.currentTime;
636
+ releaseSustainPedal(channelNumber, halfVelocity) {
637
+ const velocity = halfVelocity * 2;
674
638
  const channel = this.channels[channelNumber];
639
+ const promises = [];
675
640
  channel.sustainPedal = false;
676
641
  channel.scheduledNotes.forEach((scheduledNotes) => {
677
642
  scheduledNotes.forEach((scheduledNote) => {
678
643
  if (scheduledNote) {
679
- const { bufferSource, gainNode, filterNode, noteInfo } = scheduledNote;
680
- const volEndTime = now + noteInfo.volRelease;
681
- gainNode.gain.cancelScheduledValues(now);
682
- gainNode.gain.linearRampToValueAtTime(0, volEndTime);
683
- const maxFreq = this.audioContext.sampleRate / 2;
684
- const baseFreq = this.centToHz(noteInfo.initialFilterFc);
685
- const adjustedBaseFreq = Math.min(maxFreq, baseFreq);
686
- const modEndTime = now + noteInfo.modRelease;
687
- filterNode.frequency
688
- .cancelScheduledValues(stopTime)
689
- .linearRampToValueAtTime(adjustedBaseFreq, modEndTime);
690
- bufferSource.stop(volEndTime);
644
+ const { noteNumber } = scheduledNote;
645
+ const promise = this.releaseNote(channelNumber, noteNumber, velocity);
646
+ promises.push(promise);
691
647
  }
692
648
  });
693
649
  });
650
+ return promises;
694
651
  }
695
652
  handleMIDIMessage(statusByte, data1, data2) {
696
653
  const channelNumber = statusByte & 0x0F;
@@ -700,46 +657,37 @@ export class MidyGM1 {
700
657
  return this.releaseNote(channelNumber, data1, data2);
701
658
  case 0x90:
702
659
  return this.noteOn(channelNumber, data1, data2);
703
- case 0xA0:
704
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
705
660
  case 0xB0:
706
661
  return this.handleControlChange(channelNumber, data1, data2);
707
662
  case 0xC0:
708
663
  return this.handleProgramChange(channelNumber, data1);
709
- case 0xD0:
710
- return this.handleChannelPressure(channelNumber, data1);
711
664
  case 0xE0:
712
- return this.handlePitchBend(channelNumber, data1, data2);
665
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
713
666
  default:
714
667
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
715
668
  }
716
669
  }
717
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
718
- const now = this.audioContext.currentTime;
719
- const channel = this.channels[channelNumber];
720
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
721
- pressure /= 127;
722
- if (scheduledNotes) {
723
- scheduledNotes.forEach((scheduledNote) => {
724
- if (scheduledNote) {
725
- const { initialAttenuation } = scheduledNote.noteInfo;
726
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
727
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
728
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
729
- }
730
- });
731
- }
732
- }
733
670
  handleProgramChange(channelNumber, program) {
734
671
  const channel = this.channels[channelNumber];
735
672
  channel.program = program;
736
673
  }
737
- handleChannelPressure(channelNumber, pressure) {
738
- this.channels[channelNumber].channelPressure = pressure;
674
+ handlePitchBendMessage(channelNumber, lsb, msb) {
675
+ const pitchBend = msb * 128 + lsb;
676
+ this.handlePitchBend(channelNumber, pitchBend);
739
677
  }
740
- handlePitchBend(channelNumber, lsb, msb) {
741
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
742
- this.channels[channelNumber].pitchBend = pitchBend;
678
+ handlePitchBend(channelNumber, pitchBend) {
679
+ const now = this.audioContext.currentTime;
680
+ const channel = this.channels[channelNumber];
681
+ channel.pitchBend = (pitchBend - 8192) / 8192;
682
+ const semitoneOffset = this.calcSemitoneOffset(channel);
683
+ const activeNotes = this.getActiveNotes(channel);
684
+ activeNotes.forEach((activeNote) => {
685
+ const { bufferSource, noteInfo, noteNumber } = activeNote;
686
+ const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
687
+ bufferSource.playbackRate
688
+ .cancelScheduledValues(now)
689
+ .setValueAtTime(playbackRate * pressure, now);
690
+ });
743
691
  }
744
692
  handleControlChange(channelNumber, controller, value) {
745
693
  switch (controller) {
@@ -772,13 +720,9 @@ export class MidyGM1 {
772
720
  }
773
721
  }
774
722
  setModulation(channelNumber, modulation) {
775
- const now = this.audioContext.currentTime;
776
723
  const channel = this.channels[channelNumber];
777
- channel.modulation = (modulation * 100 / 127) *
778
- channel.modulationDepthRange;
779
- const lfoGain = channel.modulationEffect.lfoGain;
780
- lfoGain.gain.cancelScheduledValues(now);
781
- lfoGain.gain.setValueAtTime(channel.modulation, now);
724
+ channel.modulation = (modulation / 127) *
725
+ (channel.modulationDepthRange * 100);
782
726
  }
783
727
  setVolume(channelNumber, volume) {
784
728
  const channel = this.channels[channelNumber];
@@ -807,7 +751,7 @@ export class MidyGM1 {
807
751
  const isOn = value >= 64;
808
752
  this.channels[channelNumber].sustainPedal = isOn;
809
753
  if (!isOn) {
810
- this.releaseSustainPedal(channelNumber);
754
+ this.releaseSustainPedal(channelNumber, value);
811
755
  }
812
756
  }
813
757
  setRPNMSB(channelNumber, value) {
@@ -909,13 +853,18 @@ export class MidyGM1 {
909
853
  }
910
854
  }
911
855
  handleMasterVolumeSysEx(data) {
912
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
856
+ const volume = (data[5] * 128 + data[4]) / 16383;
913
857
  this.handleMasterVolume(volume);
914
858
  }
915
859
  handleMasterVolume(volume) {
916
- const now = this.audioContext.currentTime;
917
- this.masterGain.gain.cancelScheduledValues(now);
918
- this.masterGain.gain.setValueAtTime(volume * volume, now);
860
+ if (volume < 0 && 1 < volume) {
861
+ console.error("Master Volume is out of range");
862
+ }
863
+ else {
864
+ const now = this.audioContext.currentTime;
865
+ this.masterGain.gain.cancelScheduledValues(now);
866
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
867
+ }
919
868
  }
920
869
  handleExclusiveMessage(data) {
921
870
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -947,7 +896,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
947
896
  configurable: true,
948
897
  writable: true,
949
898
  value: {
950
- volume: 1,
899
+ volume: 100 / 127,
951
900
  pan: 0,
952
901
  vibratoRate: 5,
953
902
  vibratoDepth: 0.5,
@@ -959,7 +908,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
959
908
  pitchBend: 0,
960
909
  fineTuning: 0,
961
910
  coarseTuning: 0,
962
- modulationDepthRange: 2,
911
+ modulationDepthRange: 0.5,
963
912
  }
964
913
  });
965
914
  Object.defineProperty(MidyGM1, "effectSettings", {