@marmooo/midy 0.0.5 → 0.0.6

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.
@@ -35,18 +35,6 @@ class Note {
35
35
  writable: true,
36
36
  value: void 0
37
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
38
  this.noteNumber = noteNumber;
51
39
  this.velocity = velocity;
52
40
  this.startTime = startTime;
@@ -198,17 +186,16 @@ class MidyGM1 {
198
186
  this.totalTime = this.calcTotalTime();
199
187
  }
200
188
  setChannelAudioNodes(audioContext) {
201
- const gainNode = new GainNode(audioContext, {
202
- gain: MidyGM1.channelSettings.volume,
203
- });
204
- const pannerNode = new StereoPannerNode(audioContext, {
205
- pan: MidyGM1.channelSettings.pan,
206
- });
207
- pannerNode.connect(gainNode);
208
- gainNode.connect(this.masterGain);
189
+ const { gainLeft, gainRight } = this.panToGain(MidyGM1.channelSettings.pan);
190
+ const gainL = new GainNode(audioContext, { gain: gainLeft });
191
+ const gainR = new GainNode(audioContext, { gain: gainRight });
192
+ const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
193
+ gainL.connect(merger, 0, 0);
194
+ gainR.connect(merger, 0, 1);
195
+ merger.connect(this.masterGain);
209
196
  return {
210
- gainNode,
211
- pannerNode,
197
+ gainL,
198
+ gainR,
212
199
  };
213
200
  }
214
201
  createChannels(audioContext) {
@@ -293,7 +280,7 @@ class MidyGM1 {
293
280
  this.handleProgramChange(event.channel, event.programNumber);
294
281
  break;
295
282
  case "pitchBend":
296
- this.handlePitchBend(event.channel, event.value);
283
+ this.setPitchBend(event.channel, event.value);
297
284
  break;
298
285
  case "sysEx":
299
286
  this.handleSysEx(event.data);
@@ -373,7 +360,6 @@ class MidyGM1 {
373
360
  const tmpChannels = new Array(16);
374
361
  for (let i = 0; i < tmpChannels.length; i++) {
375
362
  tmpChannels[i] = {
376
- durationTicks: new Map(),
377
363
  programNumber: -1,
378
364
  bank: this.channels[i].bank,
379
365
  };
@@ -390,16 +376,6 @@ class MidyGM1 {
390
376
  instruments.add(`${channel.bank}:0`);
391
377
  channel.programNumber = 0;
392
378
  }
393
- channel.durationTicks.set(event.noteNumber, {
394
- ticks: event.ticks,
395
- noteOn: event,
396
- });
397
- break;
398
- }
399
- case "noteOff": {
400
- const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
401
- .get(event.noteNumber);
402
- noteOn.durationTicks = event.ticks - ticks;
403
379
  break;
404
380
  }
405
381
  case "programChange": {
@@ -413,8 +389,8 @@ class MidyGM1 {
413
389
  });
414
390
  });
415
391
  const priority = {
416
- setTempo: 0,
417
- controller: 1,
392
+ controller: 0,
393
+ sysEx: 1,
418
394
  };
419
395
  timeline.sort((a, b) => {
420
396
  if (a.ticks !== b.ticks)
@@ -537,9 +513,7 @@ class MidyGM1 {
537
513
  }
538
514
  setVolumeEnvelope(channel, note) {
539
515
  const { instrumentKey, startTime, velocity } = note;
540
- note.gainNode = new GainNode(this.audioContext, {
541
- gain: 0,
542
- });
516
+ note.gainNode = new GainNode(this.audioContext, { gain: 0 });
543
517
  let volume = (velocity / 127) * channel.volume * channel.expression;
544
518
  if (volume === 0)
545
519
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
@@ -729,20 +703,22 @@ class MidyGM1 {
729
703
  }
730
704
  handlePitchBendMessage(channelNumber, lsb, msb) {
731
705
  const pitchBend = msb * 128 + lsb;
732
- this.handlePitchBend(channelNumber, pitchBend);
706
+ this.setPitchBend(channelNumber, pitchBend);
733
707
  }
734
- handlePitchBend(channelNumber, pitchBend) {
708
+ setPitchBend(channelNumber, pitchBend) {
735
709
  const now = this.audioContext.currentTime;
736
710
  const channel = this.channels[channelNumber];
711
+ const prevPitchBend = channel.pitchBend;
737
712
  channel.pitchBend = (pitchBend - 8192) / 8192;
738
- const semitoneOffset = this.calcSemitoneOffset(channel);
713
+ const detuneChange = (channel.pitchBend - prevPitchBend) *
714
+ channel.pitchBendRange * 100;
739
715
  const activeNotes = this.getActiveNotes(channel, now);
740
716
  activeNotes.forEach((activeNote) => {
741
- const { bufferSource, instrumentKey, noteNumber } = activeNote;
742
- const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
743
- bufferSource.playbackRate
717
+ const { bufferSource } = activeNote;
718
+ const detune = bufferSource.detune.value + detuneChange;
719
+ bufferSource.detune
744
720
  .cancelScheduledValues(now)
745
- .setValueAtTime(playbackRate * pressure, now);
721
+ .setValueAtTime(detune, now);
746
722
  });
747
723
  }
748
724
  handleControlChange(channelNumber, controller, value) {
@@ -796,12 +772,17 @@ class MidyGM1 {
796
772
  channel.volume = volume / 127;
797
773
  this.updateChannelGain(channel);
798
774
  }
775
+ panToGain(pan) {
776
+ const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
777
+ return {
778
+ gainLeft: Math.cos(theta),
779
+ gainRight: Math.sin(theta),
780
+ };
781
+ }
799
782
  setPan(channelNumber, pan) {
800
- const now = this.audioContext.currentTime;
801
783
  const channel = this.channels[channelNumber];
802
- channel.pan = pan / 127 * 2 - 1; // -1 (left) - +1 (right)
803
- channel.pannerNode.pan.cancelScheduledValues(now);
804
- channel.pannerNode.pan.setValueAtTime(channel.pan, now);
784
+ channel.pan = pan;
785
+ this.updateChannelGain(channel);
805
786
  }
806
787
  setExpression(channelNumber, expression) {
807
788
  const channel = this.channels[channelNumber];
@@ -811,8 +792,13 @@ class MidyGM1 {
811
792
  updateChannelGain(channel) {
812
793
  const now = this.audioContext.currentTime;
813
794
  const volume = channel.volume * channel.expression;
814
- channel.gainNode.gain.cancelScheduledValues(now);
815
- channel.gainNode.gain.setValueAtTime(volume, now);
795
+ const { gainLeft, gainRight } = this.panToGain(channel.pan);
796
+ channel.gainL.gain
797
+ .cancelScheduledValues(now)
798
+ .setValueAtTime(volume * gainLeft, now);
799
+ channel.gainR.gain
800
+ .cancelScheduledValues(now)
801
+ .setValueAtTime(volume * gainRight, now);
816
802
  }
817
803
  setSustainPedal(channelNumber, value) {
818
804
  const isOn = value >= 64;
@@ -834,8 +820,7 @@ class MidyGM1 {
834
820
  const { dataMSB, dataLSB } = channel;
835
821
  switch (rpn) {
836
822
  case 0:
837
- channel.pitchBendRange = dataMSB + dataLSB / 100;
838
- break;
823
+ return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
839
824
  case 1:
840
825
  channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
841
826
  break;
@@ -846,6 +831,26 @@ class MidyGM1 {
846
831
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
847
832
  }
848
833
  }
834
+ handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
835
+ const pitchBendRange = dataMSB + dataLSB / 100;
836
+ this.setPitchBendRange(channelNumber, pitchBendRange);
837
+ }
838
+ setPitchBendRange(channelNumber, pitchBendRange) {
839
+ const now = this.audioContext.currentTime;
840
+ const channel = this.channels[channelNumber];
841
+ const prevPitchBendRange = channel.pitchBendRange;
842
+ channel.pitchBendRange = pitchBendRange;
843
+ const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
844
+ channel.pitchBend * 100;
845
+ const activeNotes = this.getActiveNotes(channel, now);
846
+ activeNotes.forEach((activeNote) => {
847
+ const { bufferSource } = activeNote;
848
+ const detune = bufferSource.detune.value + detuneChange;
849
+ bufferSource.detune
850
+ .cancelScheduledValues(now)
851
+ .setValueAtTime(detune, now);
852
+ });
853
+ }
849
854
  allSoundOff(channelNumber) {
850
855
  const now = this.audioContext.currentTime;
851
856
  const channel = this.channels[channelNumber];
@@ -921,9 +926,9 @@ class MidyGM1 {
921
926
  }
922
927
  handleMasterVolumeSysEx(data) {
923
928
  const volume = (data[5] * 128 + data[4]) / 16383;
924
- this.handleMasterVolume(volume);
929
+ this.setMasterVolume(volume);
925
930
  }
926
- handleMasterVolume(volume) {
931
+ setMasterVolume(volume) {
927
932
  if (volume < 0 && 1 < volume) {
928
933
  console.error("Master Volume is out of range");
929
934
  }
@@ -965,7 +970,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
965
970
  writable: true,
966
971
  value: {
967
972
  volume: 100 / 127,
968
- pan: 0,
973
+ pan: 64,
969
974
  bank: 0,
970
975
  dataMSB: 0,
971
976
  dataLSB: 0,
@@ -234,12 +234,16 @@ export class MidyGM2 {
234
234
  handleProgramChange(channelNumber: any, program: any): void;
235
235
  handleChannelPressure(channelNumber: any, pressure: any): void;
236
236
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
237
- handlePitchBend(channelNumber: any, pitchBend: any): void;
237
+ setPitchBend(channelNumber: any, pitchBend: any): void;
238
238
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
239
239
  setBankMSB(channelNumber: any, msb: any): void;
240
240
  setModulation(channelNumber: any, modulation: any): void;
241
241
  setPortamentoTime(channelNumber: any, portamentoTime: any): void;
242
242
  setVolume(channelNumber: any, volume: any): void;
243
+ panToGain(pan: any): {
244
+ gainLeft: number;
245
+ gainRight: number;
246
+ };
243
247
  setPan(channelNumber: any, pan: any): void;
244
248
  setExpression(channelNumber: any, expression: any): void;
245
249
  setBankLSB(channelNumber: any, lsb: any): void;
@@ -253,6 +257,8 @@ export class MidyGM2 {
253
257
  setRPNMSB(channelNumber: any, value: any): void;
254
258
  setRPNLSB(channelNumber: any, value: any): void;
255
259
  setDataEntry(channelNumber: any, value: any, isMSB: any): void;
260
+ handlePitchBendRangeMessage(channelNumber: any, dataMSB: any, dataLSB: any): void;
261
+ setPitchBendRange(channelNumber: any, pitchBendRange: any): void;
256
262
  allSoundOff(channelNumber: any): any[];
257
263
  resetAllControllers(channelNumber: any): void;
258
264
  allNotesOff(channelNumber: any): any[];
@@ -265,11 +271,11 @@ export class MidyGM2 {
265
271
  GM2SystemOn(): void;
266
272
  handleUniversalRealTimeExclusiveMessage(data: any): void;
267
273
  handleMasterVolumeSysEx(data: any): void;
268
- handleMasterVolume(volume: any): void;
274
+ setMasterVolume(volume: any): void;
269
275
  handleMasterFineTuningSysEx(data: any): void;
270
- handleMasterFineTuning(fineTuning: any): void;
276
+ setMasterFineTuning(fineTuning: any): void;
271
277
  handleMasterCoarseTuningSysEx(data: any): void;
272
- handleMasterCoarseTuning(coarseTuning: any): void;
278
+ setMasterCoarseTuning(coarseTuning: any): void;
273
279
  handleExclusiveMessage(data: any): void;
274
280
  handleSysEx(data: any): void;
275
281
  scheduleTask(callback: any, startTime: any): Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"AAuBA;IAwBE;;;;;;;;;;;;;;;;;MAiBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAtED,qBAAmB;IACnB,kBAAc;IACd,qBAAmB;IACnB,yBAAqB;IACrB,2BAAuB;IACvB,cAAa;IACb,cAAa;IACb,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;IA4ChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;MAoBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAcC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAkDC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA2GC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED;;;;MAoCC;IAED;;;;;MA2CC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED,sCAKC;IAED,mFAGC;IAED,iDAmBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHAiCC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,sIAoDC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,wFAmBC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,0DAiBC;IAED,mFAuDC;IAED,+CAEC;IAED,yDAiBC;IAED,iEAEC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,+CAEC;IAED,sCAKC;IAED,sDAMC;IAED,oDAEC;IAED,iDASC;IAED,iDAIC;IAED,wDAUC;IAED,uDAGC;IAED,gDAEC;IAED,gDAEC;IAED,+DAuBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAgDC;IAED,yCAGC;IAED,sCAQC;IAED,6CAGC;IAED,8CAMC;IAED,+CAGC;IAED,kDAMC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAv3CD;IASE,gFAKC;IAbD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
1
+ {"version":3,"file":"midy-GM2.d.ts","sourceRoot":"","sources":["../src/midy-GM2.js"],"names":[],"mappings":"AAuBA;IAwBE;;;;;;;;;;;;;;;;;MAiBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAtED,qBAAmB;IACnB,kBAAc;IACd,qBAAmB;IACnB,yBAAqB;IACrB,2BAAuB;IACvB,cAAa;IACb,cAAa;IACb,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;IA4ChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;MAoBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAcC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAkDC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgGC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED;;;;MAoCC;IAED;;;;;MAqCC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED,sCAKC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHAiCC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,sIAoDC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,wFAmBC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,uDAeC;IAED,mFAuDC;IAED,+CAEC;IAED,yDAiBC;IAED,iEAEC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,+CAEC;IAED,sCAUC;IAED,sDAMC;IAED,oDAEC;IAED,iDASC;IAED,iDAIC;IAED,wDAWC;IAED,uDAGC;IAED,gDAEC;IAED,gDAEC;IAED,+DA0BC;IAED,kFAGC;IAED,iEAeC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,gBAEC;IAED,eAEC;IAED,eAEC;IAED,eAEC;IAED,4DAmBC;IAED,oBAQC;IAED,oBAQC;IAED,yDAgDC;IAED,yCAGC;IAED,mCAQC;IAED,6CAGC;IAED,2CAMC;IAED,+CAGC;IAED,+CAMC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAv4CD;IASE,gFAKC;IAbD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
@@ -337,7 +337,7 @@ class MidyGM2 {
337
337
  this.handleChannelPressure(event.channel, event.amount);
338
338
  break;
339
339
  case "pitchBend":
340
- this.handlePitchBend(event.channel, event.value);
340
+ this.setPitchBend(event.channel, event.value);
341
341
  break;
342
342
  case "sysEx":
343
343
  this.handleSysEx(event.data);
@@ -417,7 +417,6 @@ class MidyGM2 {
417
417
  const tmpChannels = new Array(16);
418
418
  for (let i = 0; i < tmpChannels.length; i++) {
419
419
  tmpChannels[i] = {
420
- durationTicks: new Map(),
421
420
  programNumber: -1,
422
421
  bankMSB: this.channels[i].bankMSB,
423
422
  bankLSB: this.channels[i].bankLSB,
@@ -447,16 +446,6 @@ class MidyGM2 {
447
446
  }
448
447
  channel.programNumber = 0;
449
448
  }
450
- channel.durationTicks.set(event.noteNumber, {
451
- ticks: event.ticks,
452
- noteOn: event,
453
- });
454
- break;
455
- }
456
- case "noteOff": {
457
- const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
458
- .get(event.noteNumber);
459
- noteOn.durationTicks = event.ticks - ticks;
460
449
  break;
461
450
  }
462
451
  case "controller":
@@ -491,8 +480,8 @@ class MidyGM2 {
491
480
  });
492
481
  });
493
482
  const priority = {
494
- setTempo: 0,
495
- controller: 1,
483
+ controller: 0,
484
+ sysEx: 1,
496
485
  };
497
486
  timeline.sort((a, b) => {
498
487
  if (a.ticks !== b.ticks)
@@ -630,12 +619,8 @@ class MidyGM2 {
630
619
  }
631
620
  createChorusEffect(audioContext, options = {}) {
632
621
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
633
- const lfo = new OscillatorNode(audioContext, {
634
- frequency: chorusRate,
635
- });
636
- const lfoGain = new GainNode(audioContext, {
637
- gain: chorusDepth,
638
- });
622
+ const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
623
+ const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
639
624
  const chorusGains = [];
640
625
  const delayNodes = [];
641
626
  const baseGain = 1 / chorusCount;
@@ -646,9 +631,7 @@ class MidyGM2 {
646
631
  maxDelayTime: delayTime,
647
632
  });
648
633
  delayNodes.push(delayNode);
649
- const chorusGain = new GainNode(audioContext, {
650
- gain: baseGain,
651
- });
634
+ const chorusGain = new GainNode(audioContext, { gain: baseGain });
652
635
  chorusGains.push(chorusGain);
653
636
  lfo.connect(lfoGain);
654
637
  lfoGain.connect(delayNode.delayTime);
@@ -710,9 +693,7 @@ class MidyGM2 {
710
693
  }
711
694
  setVolumeEnvelope(channel, note) {
712
695
  const { instrumentKey, startTime, velocity } = note;
713
- note.gainNode = new GainNode(this.audioContext, {
714
- gain: 0,
715
- });
696
+ note.gainNode = new GainNode(this.audioContext, { gain: 0 });
716
697
  let volume = (velocity / 127) * channel.volume * channel.expression;
717
698
  if (volume === 0)
718
699
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
@@ -951,20 +932,22 @@ class MidyGM2 {
951
932
  }
952
933
  handlePitchBendMessage(channelNumber, lsb, msb) {
953
934
  const pitchBend = msb * 128 + lsb;
954
- this.handlePitchBend(channelNumber, pitchBend);
935
+ this.setPitchBend(channelNumber, pitchBend);
955
936
  }
956
- handlePitchBend(channelNumber, pitchBend) {
937
+ setPitchBend(channelNumber, pitchBend) {
957
938
  const now = this.audioContext.currentTime;
958
939
  const channel = this.channels[channelNumber];
940
+ const prevPitchBend = channel.pitchBend;
959
941
  channel.pitchBend = (pitchBend - 8192) / 8192;
960
- const semitoneOffset = this.calcSemitoneOffset(channel);
942
+ const detuneChange = (channel.pitchBend - prevPitchBend) *
943
+ channel.pitchBendRange * 100;
961
944
  const activeNotes = this.getActiveNotes(channel, now);
962
945
  activeNotes.forEach((activeNote) => {
963
- const { bufferSource, instrumentKey, noteNumber } = activeNote;
964
- const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
965
- bufferSource.playbackRate
946
+ const { bufferSource } = activeNote;
947
+ const detune = bufferSource.detune.value + detuneChange;
948
+ bufferSource.detune
966
949
  .cancelScheduledValues(now)
967
- .setValueAtTime(playbackRate * pressure, now);
950
+ .setValueAtTime(detune, now);
968
951
  });
969
952
  }
970
953
  handleControlChange(channelNumber, controller, value) {
@@ -1048,12 +1031,17 @@ class MidyGM2 {
1048
1031
  channel.volume = volume / 127;
1049
1032
  this.updateChannelGain(channel);
1050
1033
  }
1034
+ panToGain(pan) {
1035
+ const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1036
+ return {
1037
+ gainLeft: Math.cos(theta),
1038
+ gainRight: Math.sin(theta),
1039
+ };
1040
+ }
1051
1041
  setPan(channelNumber, pan) {
1052
- const now = this.audioContext.currentTime;
1053
1042
  const channel = this.channels[channelNumber];
1054
- channel.pan = pan / 127 * 2 - 1; // -1 (left) - +1 (right)
1055
- channel.pannerNode.pan.cancelScheduledValues(now);
1056
- channel.pannerNode.pan.setValueAtTime(channel.pan, now);
1043
+ channel.pan = pan;
1044
+ this.updateChannelGain(channel);
1057
1045
  }
1058
1046
  setExpression(channelNumber, expression) {
1059
1047
  const channel = this.channels[channelNumber];
@@ -1066,8 +1054,13 @@ class MidyGM2 {
1066
1054
  updateChannelGain(channel) {
1067
1055
  const now = this.audioContext.currentTime;
1068
1056
  const volume = channel.volume * channel.expression;
1069
- channel.gainNode.gain.cancelScheduledValues(now);
1070
- channel.gainNode.gain.setValueAtTime(volume, now);
1057
+ const { gainLeft, gainRight } = this.panToGain(channel.pan);
1058
+ channel.gainL.gain
1059
+ .cancelScheduledValues(now)
1060
+ .setValueAtTime(volume * gainLeft, now);
1061
+ channel.gainR.gain
1062
+ .cancelScheduledValues(now)
1063
+ .setValueAtTime(volume * gainRight, now);
1071
1064
  }
1072
1065
  setSustainPedal(channelNumber, value) {
1073
1066
  const isOn = value >= 64;
@@ -1099,7 +1092,8 @@ class MidyGM2 {
1099
1092
  const channel = this.channels[channelNumber];
1100
1093
  channel.sostenutoPedal = isOn;
1101
1094
  if (isOn) {
1102
- const activeNotes = this.getActiveNotes(channel);
1095
+ const now = this.audioContext.currentTime;
1096
+ const activeNotes = this.getActiveNotes(channel, now);
1103
1097
  channel.sostenutoNotes = new Map(activeNotes);
1104
1098
  }
1105
1099
  else {
@@ -1123,8 +1117,7 @@ class MidyGM2 {
1123
1117
  const { dataMSB, dataLSB } = channel;
1124
1118
  switch (rpn) {
1125
1119
  case 0:
1126
- channel.pitchBendRange = dataMSB + dataLSB / 100;
1127
- break;
1120
+ return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
1128
1121
  case 1:
1129
1122
  channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
1130
1123
  break;
@@ -1138,6 +1131,26 @@ class MidyGM2 {
1138
1131
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
1139
1132
  }
1140
1133
  }
1134
+ handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
1135
+ const pitchBendRange = dataMSB + dataLSB / 100;
1136
+ this.setPitchBendRange(channelNumber, pitchBendRange);
1137
+ }
1138
+ setPitchBendRange(channelNumber, pitchBendRange) {
1139
+ const now = this.audioContext.currentTime;
1140
+ const channel = this.channels[channelNumber];
1141
+ const prevPitchBendRange = channel.pitchBendRange;
1142
+ channel.pitchBendRange = pitchBendRange;
1143
+ const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1144
+ channel.pitchBend * 100;
1145
+ const activeNotes = this.getActiveNotes(channel, now);
1146
+ activeNotes.forEach((activeNote) => {
1147
+ const { bufferSource } = activeNote;
1148
+ const detune = bufferSource.detune.value + detuneChange;
1149
+ bufferSource.detune
1150
+ .cancelScheduledValues(now)
1151
+ .setValueAtTime(detune, now);
1152
+ });
1153
+ }
1141
1154
  allSoundOff(channelNumber) {
1142
1155
  const now = this.audioContext.currentTime;
1143
1156
  const channel = this.channels[channelNumber];
@@ -1228,9 +1241,9 @@ class MidyGM2 {
1228
1241
  case 1:
1229
1242
  return this.handleMasterVolumeSysEx(data);
1230
1243
  case 3:
1231
- return this.handleMasterFineTuning(data);
1244
+ return this.handleMasterFineTuningSysEx(data);
1232
1245
  case 4:
1233
- return this.handleMasterCoarseTuning(data);
1246
+ return this.handleMasterCoarseTuningSysEx(data);
1234
1247
  // case 5: // TODO: Global Parameter Control
1235
1248
  default:
1236
1249
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -1272,9 +1285,9 @@ class MidyGM2 {
1272
1285
  }
1273
1286
  handleMasterVolumeSysEx(data) {
1274
1287
  const volume = (data[5] * 128 + data[4]) / 16383;
1275
- this.handleMasterVolume(volume);
1288
+ this.setMasterVolume(volume);
1276
1289
  }
1277
- handleMasterVolume(volume) {
1290
+ setMasterVolume(volume) {
1278
1291
  if (volume < 0 && 1 < volume) {
1279
1292
  console.error("Master Volume is out of range");
1280
1293
  }
@@ -1286,9 +1299,9 @@ class MidyGM2 {
1286
1299
  }
1287
1300
  handleMasterFineTuningSysEx(data) {
1288
1301
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1289
- this.handleMasterFineTuning(fineTuning);
1302
+ this.setMasterFineTuning(fineTuning);
1290
1303
  }
1291
- handleMasterFineTuning(fineTuning) {
1304
+ setMasterFineTuning(fineTuning) {
1292
1305
  if (fineTuning < -1 && 1 < fineTuning) {
1293
1306
  console.error("Master Fine Tuning value is out of range");
1294
1307
  }
@@ -1298,9 +1311,9 @@ class MidyGM2 {
1298
1311
  }
1299
1312
  handleMasterCoarseTuningSysEx(data) {
1300
1313
  const coarseTuning = data[4];
1301
- this.handleMasterCoarseTuning(coarseTuning);
1314
+ this.setMasterCoarseTuning(coarseTuning);
1302
1315
  }
1303
- handleMasterCoarseTuning(coarseTuning) {
1316
+ setMasterCoarseTuning(coarseTuning) {
1304
1317
  if (coarseTuning < 0 && 127 < coarseTuning) {
1305
1318
  console.error("Master Coarse Tuning value is out of range");
1306
1319
  }
@@ -39,8 +39,8 @@ export class MidyGMLite {
39
39
  masterGain: any;
40
40
  channels: {
41
41
  scheduledNotes: Map<any, any>;
42
- gainNode: any;
43
- pannerNode: any;
42
+ gainL: any;
43
+ gainR: any;
44
44
  expression: number;
45
45
  modulation: number;
46
46
  sustainPedal: boolean;
@@ -61,13 +61,13 @@ export class MidyGMLite {
61
61
  loadSoundFont(soundFontUrl: any): Promise<void>;
62
62
  loadMIDI(midiUrl: any): Promise<void>;
63
63
  setChannelAudioNodes(audioContext: any): {
64
- gainNode: any;
65
- pannerNode: any;
64
+ gainL: any;
65
+ gainR: any;
66
66
  };
67
67
  createChannels(audioContext: any): {
68
68
  scheduledNotes: Map<any, any>;
69
- gainNode: any;
70
- pannerNode: any;
69
+ gainL: any;
70
+ gainR: any;
71
71
  expression: number;
72
72
  modulation: number;
73
73
  sustainPedal: boolean;
@@ -122,10 +122,14 @@ export class MidyGMLite {
122
122
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
123
123
  handleProgramChange(channelNumber: any, program: any): void;
124
124
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
125
- handlePitchBend(channelNumber: any, pitchBend: any): void;
125
+ setPitchBend(channelNumber: any, pitchBend: any): void;
126
126
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
127
127
  setModulation(channelNumber: any, modulation: any): void;
128
128
  setVolume(channelNumber: any, volume: any): void;
129
+ panToGain(pan: any): {
130
+ gainLeft: number;
131
+ gainRight: number;
132
+ };
129
133
  setPan(channelNumber: any, pan: any): void;
130
134
  setExpression(channelNumber: any, expression: any): void;
131
135
  updateChannelGain(channel: any): void;
@@ -133,6 +137,8 @@ export class MidyGMLite {
133
137
  setRPNMSB(channelNumber: any, value: any): void;
134
138
  setRPNLSB(channelNumber: any, value: any): void;
135
139
  setDataEntry(channelNumber: any, value: any, isMSB: any): void;
140
+ handlePitchBendRangeMessage(channelNumber: any, dataMSB: any, dataLSB: any): void;
141
+ setPitchBendRange(channelNumber: any, pitchBendRange: any): void;
136
142
  allSoundOff(channelNumber: any): any[];
137
143
  resetAllControllers(channelNumber: any): void;
138
144
  allNotesOff(channelNumber: any): any[];
@@ -140,7 +146,7 @@ export class MidyGMLite {
140
146
  GM1SystemOn(): void;
141
147
  handleUniversalRealTimeExclusiveMessage(data: any): void;
142
148
  handleMasterVolumeSysEx(data: any): void;
143
- handleMasterVolume(volume: any): void;
149
+ setMasterVolume(volume: any): void;
144
150
  handleExclusiveMessage(data: any): void;
145
151
  handleSysEx(data: any): void;
146
152
  scheduleTask(callback: any, startTime: any): Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,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;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAaC;IAED;;;;;;;;;;;;;;;;;;QAUC;IAED,+DAyBC;IAED,mEAWC;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,uDASC;IAED,6CAQC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAmBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIAmDC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,0DAiBC;IAED,mFA+BC;IAED,yDAiBC;IAED,iDAIC;IAED,2CAMC;IAED,yDAIC;IAED,sCAKC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAcC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AA59BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;MASE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA5CD,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;IAuBhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAcC;IAED;;;;;;;;;;;;;;;;;;QAUC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EA+CC;IAED,mCAOC;IAED,0BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MA8DC;IAED,4BAsBC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,sDAEC;IAED,2BAEC;IAED,4BAEC;IAED,yCAEC;IAED,mFAGC;IAED,iDAiBC;IAED,iDAiCC;IAED,0DAmBC;IAED,wHA6BC;IAED,kGA6BC;IAED,0EAGC;IAED,sIAmDC;IAED,0FAGC;IAED,kEAeC;IAED,wFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,uDAeC;IAED,mFA+BC;IAED,yDAiBC;IAED,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,sCAUC;IAED,sDAMC;IAED,gDAEC;IAED,gDAEC;IAED,+DAiBC;IAED,kFAGC;IAED,iEAeC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAl/BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}