@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.
@@ -183,17 +183,16 @@ export class MidyGMLite {
183
183
  this.totalTime = this.calcTotalTime();
184
184
  }
185
185
  setChannelAudioNodes(audioContext) {
186
- const gainNode = new GainNode(audioContext, {
187
- gain: MidyGMLite.channelSettings.volume,
188
- });
189
- const pannerNode = new StereoPannerNode(audioContext, {
190
- pan: MidyGMLite.channelSettings.pan,
191
- });
192
- pannerNode.connect(gainNode);
193
- gainNode.connect(this.masterGain);
186
+ const { gainLeft, gainRight } = this.panToGain(MidyGMLite.channelSettings.pan);
187
+ const gainL = new GainNode(audioContext, { gain: gainLeft });
188
+ const gainR = new GainNode(audioContext, { gain: gainRight });
189
+ const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
190
+ gainL.connect(merger, 0, 0);
191
+ gainR.connect(merger, 0, 1);
192
+ merger.connect(this.masterGain);
194
193
  return {
195
- gainNode,
196
- pannerNode,
194
+ gainL,
195
+ gainR,
197
196
  };
198
197
  }
199
198
  createChannels(audioContext) {
@@ -278,7 +277,7 @@ export class MidyGMLite {
278
277
  this.handleProgramChange(event.channel, event.programNumber);
279
278
  break;
280
279
  case "pitchBend":
281
- this.handlePitchBend(event.channel, event.value);
280
+ this.setPitchBend(event.channel, event.value);
282
281
  break;
283
282
  case "sysEx":
284
283
  this.handleSysEx(event.data);
@@ -358,7 +357,6 @@ export class MidyGMLite {
358
357
  const tmpChannels = new Array(16);
359
358
  for (let i = 0; i < tmpChannels.length; i++) {
360
359
  tmpChannels[i] = {
361
- durationTicks: new Map(),
362
360
  programNumber: -1,
363
361
  bank: this.channels[i].bank,
364
362
  };
@@ -375,16 +373,6 @@ export class MidyGMLite {
375
373
  instruments.add(`${channel.bank}:0`);
376
374
  channel.programNumber = 0;
377
375
  }
378
- channel.durationTicks.set(event.noteNumber, {
379
- ticks: event.ticks,
380
- noteOn: event,
381
- });
382
- break;
383
- }
384
- case "noteOff": {
385
- const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
386
- .get(event.noteNumber);
387
- noteOn.durationTicks = event.ticks - ticks;
388
376
  break;
389
377
  }
390
378
  case "programChange": {
@@ -398,8 +386,8 @@ export class MidyGMLite {
398
386
  });
399
387
  });
400
388
  const priority = {
401
- setTempo: 0,
402
- controller: 1,
389
+ controller: 0,
390
+ sysEx: 1,
403
391
  };
404
392
  timeline.sort((a, b) => {
405
393
  if (a.ticks !== b.ticks)
@@ -521,9 +509,7 @@ export class MidyGMLite {
521
509
  }
522
510
  setVolumeEnvelope(channel, note) {
523
511
  const { instrumentKey, startTime, velocity } = note;
524
- note.gainNode = new GainNode(this.audioContext, {
525
- gain: 0,
526
- });
512
+ note.gainNode = new GainNode(this.audioContext, { gain: 0 });
527
513
  let volume = (velocity / 127) * channel.volume * channel.expression;
528
514
  if (volume === 0)
529
515
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
@@ -713,20 +699,22 @@ export class MidyGMLite {
713
699
  }
714
700
  handlePitchBendMessage(channelNumber, lsb, msb) {
715
701
  const pitchBend = msb * 128 + lsb;
716
- this.handlePitchBend(channelNumber, pitchBend);
702
+ this.setPitchBend(channelNumber, pitchBend);
717
703
  }
718
- handlePitchBend(channelNumber, pitchBend) {
704
+ setPitchBend(channelNumber, pitchBend) {
719
705
  const now = this.audioContext.currentTime;
720
706
  const channel = this.channels[channelNumber];
707
+ const prevPitchBend = channel.pitchBend;
721
708
  channel.pitchBend = (pitchBend - 8192) / 8192;
722
- const semitoneOffset = this.calcSemitoneOffset(channel);
709
+ const detuneChange = (channel.pitchBend - prevPitchBend) *
710
+ channel.pitchBendRange * 100;
723
711
  const activeNotes = this.getActiveNotes(channel, now);
724
712
  activeNotes.forEach((activeNote) => {
725
- const { bufferSource, instrumentKey, noteNumber } = activeNote;
726
- const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
727
- bufferSource.playbackRate
713
+ const { bufferSource } = activeNote;
714
+ const detune = bufferSource.detune.value + detuneChange;
715
+ bufferSource.detune
728
716
  .cancelScheduledValues(now)
729
- .setValueAtTime(playbackRate * pressure, now);
717
+ .setValueAtTime(detune, now);
730
718
  });
731
719
  }
732
720
  handleControlChange(channelNumber, controller, value) {
@@ -780,12 +768,17 @@ export class MidyGMLite {
780
768
  channel.volume = volume / 127;
781
769
  this.updateChannelGain(channel);
782
770
  }
771
+ panToGain(pan) {
772
+ const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
773
+ return {
774
+ gainLeft: Math.cos(theta),
775
+ gainRight: Math.sin(theta),
776
+ };
777
+ }
783
778
  setPan(channelNumber, pan) {
784
- const now = this.audioContext.currentTime;
785
779
  const channel = this.channels[channelNumber];
786
- channel.pan = pan / 127 * 2 - 1; // -1 (left) - +1 (right)
787
- channel.pannerNode.pan.cancelScheduledValues(now);
788
- channel.pannerNode.pan.setValueAtTime(channel.pan, now);
780
+ channel.pan = pan;
781
+ this.updateChannelGain(channel);
789
782
  }
790
783
  setExpression(channelNumber, expression) {
791
784
  const channel = this.channels[channelNumber];
@@ -795,8 +788,13 @@ export class MidyGMLite {
795
788
  updateChannelGain(channel) {
796
789
  const now = this.audioContext.currentTime;
797
790
  const volume = channel.volume * channel.expression;
798
- channel.gainNode.gain.cancelScheduledValues(now);
799
- channel.gainNode.gain.setValueAtTime(volume, now);
791
+ const { gainLeft, gainRight } = this.panToGain(channel.pan);
792
+ channel.gainL.gain
793
+ .cancelScheduledValues(now)
794
+ .setValueAtTime(volume * gainLeft, now);
795
+ channel.gainR.gain
796
+ .cancelScheduledValues(now)
797
+ .setValueAtTime(volume * gainRight, now);
800
798
  }
801
799
  setSustainPedal(channelNumber, value) {
802
800
  const isOn = value >= 64;
@@ -818,12 +816,31 @@ export class MidyGMLite {
818
816
  const { dataMSB, dataLSB } = channel;
819
817
  switch (rpn) {
820
818
  case 0:
821
- channel.pitchBendRange = dataMSB + dataLSB / 100;
822
- break;
819
+ return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
823
820
  default:
824
821
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
825
822
  }
826
823
  }
824
+ handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
825
+ const pitchBendRange = dataMSB + dataLSB / 100;
826
+ this.setPitchBendRange(channelNumber, pitchBendRange);
827
+ }
828
+ setPitchBendRange(channelNumber, pitchBendRange) {
829
+ const now = this.audioContext.currentTime;
830
+ const channel = this.channels[channelNumber];
831
+ const prevPitchBendRange = channel.pitchBendRange;
832
+ channel.pitchBendRange = pitchBendRange;
833
+ const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
834
+ channel.pitchBend * 100;
835
+ const activeNotes = this.getActiveNotes(channel, now);
836
+ activeNotes.forEach((activeNote) => {
837
+ const { bufferSource } = activeNote;
838
+ const detune = bufferSource.detune.value + detuneChange;
839
+ bufferSource.detune
840
+ .cancelScheduledValues(now)
841
+ .setValueAtTime(detune, now);
842
+ });
843
+ }
827
844
  allSoundOff(channelNumber) {
828
845
  const now = this.audioContext.currentTime;
829
846
  const channel = this.channels[channelNumber];
@@ -899,9 +916,9 @@ export class MidyGMLite {
899
916
  }
900
917
  handleMasterVolumeSysEx(data) {
901
918
  const volume = (data[5] * 128 + data[4]) / 16383;
902
- this.handleMasterVolume(volume);
919
+ this.setMasterVolume(volume);
903
920
  }
904
- handleMasterVolume(volume) {
921
+ setMasterVolume(volume) {
905
922
  if (volume < 0 && 1 < volume) {
906
923
  console.error("Master Volume is out of range");
907
924
  }
@@ -942,7 +959,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
942
959
  writable: true,
943
960
  value: {
944
961
  volume: 100 / 127,
945
- pan: 0,
962
+ pan: 64,
946
963
  bank: 0,
947
964
  dataMSB: 0,
948
965
  dataLSB: 0,
package/esm/midy.d.ts CHANGED
@@ -84,8 +84,8 @@ export class Midy {
84
84
  lfoFilterDepth: number;
85
85
  lfoAmplitudeDepth: number;
86
86
  };
87
- gainNode: any;
88
- pannerNode: any;
87
+ gainL: any;
88
+ gainR: any;
89
89
  reverbEffect: {
90
90
  convolverNode: any;
91
91
  dryGain: any;
@@ -131,8 +131,8 @@ export class Midy {
131
131
  loadSoundFont(soundFontUrl: any): Promise<void>;
132
132
  loadMIDI(midiUrl: any): Promise<void>;
133
133
  setChannelAudioNodes(audioContext: any): {
134
- gainNode: any;
135
- pannerNode: any;
134
+ gainL: any;
135
+ gainR: any;
136
136
  reverbEffect: {
137
137
  convolverNode: any;
138
138
  dryGain: any;
@@ -164,8 +164,8 @@ export class Midy {
164
164
  lfoFilterDepth: number;
165
165
  lfoAmplitudeDepth: number;
166
166
  };
167
- gainNode: any;
168
- pannerNode: any;
167
+ gainL: any;
168
+ gainR: any;
169
169
  reverbEffect: {
170
170
  convolverNode: any;
171
171
  dryGain: any;
@@ -261,12 +261,16 @@ export class Midy {
261
261
  handleProgramChange(channelNumber: any, program: any): void;
262
262
  handleChannelPressure(channelNumber: any, pressure: any): void;
263
263
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
264
- handlePitchBend(channelNumber: any, pitchBend: any): void;
264
+ setPitchBend(channelNumber: any, pitchBend: any): void;
265
265
  handleControlChange(channelNumber: any, controller: any, value: any): any;
266
266
  setBankMSB(channelNumber: any, msb: any): void;
267
267
  setModulation(channelNumber: any, modulation: any): void;
268
268
  setPortamentoTime(channelNumber: any, portamentoTime: any): void;
269
269
  setVolume(channelNumber: any, volume: any): void;
270
+ panToGain(pan: any): {
271
+ gainLeft: number;
272
+ gainRight: number;
273
+ };
270
274
  setPan(channelNumber: any, pan: any): void;
271
275
  setExpression(channelNumber: any, expression: any): void;
272
276
  setBankLSB(channelNumber: any, lsb: any): void;
@@ -285,6 +289,8 @@ export class Midy {
285
289
  setRPNMSB(channelNumber: any, value: any): void;
286
290
  setRPNLSB(channelNumber: any, value: any): void;
287
291
  setDataEntry(channelNumber: any, value: any, isMSB: any): void;
292
+ handlePitchBendRangeMessage(channelNumber: any, dataMSB: any, dataLSB: any): void;
293
+ setPitchBendRange(channelNumber: any, pitchBendRange: any): void;
288
294
  allSoundOff(channelNumber: any): any[];
289
295
  resetAllControllers(channelNumber: any): void;
290
296
  allNotesOff(channelNumber: any): any[];
@@ -297,11 +303,11 @@ export class Midy {
297
303
  GM2SystemOn(): void;
298
304
  handleUniversalRealTimeExclusiveMessage(data: any): void;
299
305
  handleMasterVolumeSysEx(data: any): void;
300
- handleMasterVolume(volume: any): void;
306
+ setMasterVolume(volume: any): void;
301
307
  handleMasterFineTuningSysEx(data: any): void;
302
- handleMasterFineTuning(fineTuning: any): void;
308
+ setMasterFineTuning(fineTuning: any): void;
303
309
  handleMasterCoarseTuningSysEx(data: any): void;
304
- handleMasterCoarseTuning(coarseTuning: any): void;
310
+ setMasterCoarseTuning(coarseTuning: any): void;
305
311
  handleExclusiveMessage(data: any): void;
306
312
  handleSysEx(data: any): void;
307
313
  scheduleTask(callback: any, startTime: any): Promise<any>;
package/esm/midy.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAuBA;IAwBE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAzED,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;IA+ChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;MAoBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiBC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAyDC;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,uDAcC;IAED,wHAqCC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,sIAwDC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gEAqBC;IAED,sFAcC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,0DAiBC;IAED,0EAkEC;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,wDAWC;IAED,uDAGC;IAED,2DAGC;IAED,6DAGC;IAED,6DASC;IAED,4CAkBC;IAED,4CAkBC;IAED,gDAEC;IAED,gDAEC;IAGD,+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;AAx/CD;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.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAuBA;IAwBE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IAzED,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;IA+ChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;MAqBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiBC;IAED,+DAyBC;IAED,mEAWC;IAED,qDAOC;IAED,2EAyDC;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,uDAcC;IAED,wHAqCC;IAED,gDAQC;IAED,kGAgCC;IAED,0EAGC;IAED,sIAwDC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gEAqBC;IAED,sFAcC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,uDAeC;IAED,0EAkEC;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,2DAGC;IAED,6DAGC;IAED,6DASC;IAED,4CAkBC;IAED,4CAkBC;IAED,gDAEC;IAED,gDAEC;IAGD,+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;AAxgDD;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"}
package/esm/midy.js CHANGED
@@ -225,22 +225,23 @@ export class Midy {
225
225
  this.totalTime = this.calcTotalTime();
226
226
  }
227
227
  setChannelAudioNodes(audioContext) {
228
- const gainNode = new GainNode(audioContext, {
229
- gain: Midy.channelSettings.volume,
230
- });
231
- const pannerNode = new StereoPannerNode(audioContext, {
232
- pan: Midy.channelSettings.pan,
233
- });
228
+ const { gainLeft, gainRight } = this.panToGain(Midy.channelSettings.pan);
229
+ const gainL = new GainNode(audioContext, { gain: gainLeft });
230
+ const gainR = new GainNode(audioContext, { gain: gainRight });
231
+ const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
232
+ gainL.connect(merger, 0, 0);
233
+ gainR.connect(merger, 0, 1);
234
+ merger.connect(this.masterGain);
234
235
  const reverbEffect = this.createReverbEffect(audioContext);
235
236
  const chorusEffect = this.createChorusEffect(audioContext);
236
237
  chorusEffect.lfo.start();
237
- reverbEffect.dryGain.connect(pannerNode);
238
- reverbEffect.wetGain.connect(pannerNode);
239
- pannerNode.connect(gainNode);
240
- gainNode.connect(this.masterGain);
238
+ reverbEffect.dryGain.connect(gainL);
239
+ reverbEffect.dryGain.connect(gainR);
240
+ reverbEffect.wetGain.connect(gainL);
241
+ reverbEffect.wetGain.connect(gainR);
241
242
  return {
242
- gainNode,
243
- pannerNode,
243
+ gainL,
244
+ gainR,
244
245
  reverbEffect,
245
246
  chorusEffect,
246
247
  };
@@ -340,7 +341,7 @@ export class Midy {
340
341
  this.handleChannelPressure(event.channel, event.amount);
341
342
  break;
342
343
  case "pitchBend":
343
- this.handlePitchBend(event.channel, event.value);
344
+ this.setPitchBend(event.channel, event.value);
344
345
  break;
345
346
  case "sysEx":
346
347
  this.handleSysEx(event.data);
@@ -420,7 +421,6 @@ export class Midy {
420
421
  const tmpChannels = new Array(16);
421
422
  for (let i = 0; i < tmpChannels.length; i++) {
422
423
  tmpChannels[i] = {
423
- durationTicks: new Map(),
424
424
  programNumber: -1,
425
425
  bankMSB: this.channels[i].bankMSB,
426
426
  bankLSB: this.channels[i].bankLSB,
@@ -450,16 +450,6 @@ export class Midy {
450
450
  }
451
451
  channel.programNumber = 0;
452
452
  }
453
- channel.durationTicks.set(event.noteNumber, {
454
- ticks: event.ticks,
455
- noteOn: event,
456
- });
457
- break;
458
- }
459
- case "noteOff": {
460
- const { ticks, noteOn } = tmpChannels[event.channel].durationTicks
461
- .get(event.noteNumber);
462
- noteOn.durationTicks = event.ticks - ticks;
463
453
  break;
464
454
  }
465
455
  case "controller":
@@ -494,8 +484,8 @@ export class Midy {
494
484
  });
495
485
  });
496
486
  const priority = {
497
- setTempo: 0,
498
- controller: 1,
487
+ controller: 0,
488
+ sysEx: 1,
499
489
  };
500
490
  timeline.sort((a, b) => {
501
491
  if (a.ticks !== b.ticks)
@@ -633,12 +623,8 @@ export class Midy {
633
623
  }
634
624
  createChorusEffect(audioContext, options = {}) {
635
625
  const { chorusCount = 2, chorusRate = 0.6, chorusDepth = 0.15, delay = 0.01, variance = delay * 0.1, } = options;
636
- const lfo = new OscillatorNode(audioContext, {
637
- frequency: chorusRate,
638
- });
639
- const lfoGain = new GainNode(audioContext, {
640
- gain: chorusDepth,
641
- });
626
+ const lfo = new OscillatorNode(audioContext, { frequency: chorusRate });
627
+ const lfoGain = new GainNode(audioContext, { gain: chorusDepth });
642
628
  const chorusGains = [];
643
629
  const delayNodes = [];
644
630
  const baseGain = 1 / chorusCount;
@@ -649,9 +635,7 @@ export class Midy {
649
635
  maxDelayTime: delayTime,
650
636
  });
651
637
  delayNodes.push(delayNode);
652
- const chorusGain = new GainNode(audioContext, {
653
- gain: baseGain,
654
- });
638
+ const chorusGain = new GainNode(audioContext, { gain: baseGain });
655
639
  chorusGains.push(chorusGain);
656
640
  lfo.connect(lfoGain);
657
641
  lfoGain.connect(delayNode.delayTime);
@@ -713,9 +697,7 @@ export class Midy {
713
697
  }
714
698
  setVolumeEnvelope(channel, note) {
715
699
  const { instrumentKey, startTime, velocity } = note;
716
- note.gainNode = new GainNode(this.audioContext, {
717
- gain: 0,
718
- });
700
+ note.gainNode = new GainNode(this.audioContext, { gain: 0 });
719
701
  let volume = (velocity / 127) * channel.volume * channel.expression;
720
702
  if (volume === 0)
721
703
  volume = 1e-6; // exponentialRampToValueAtTime() requires a non-zero value
@@ -992,20 +974,22 @@ export class Midy {
992
974
  }
993
975
  handlePitchBendMessage(channelNumber, lsb, msb) {
994
976
  const pitchBend = msb * 128 + lsb;
995
- this.handlePitchBend(channelNumber, pitchBend);
977
+ this.setPitchBend(channelNumber, pitchBend);
996
978
  }
997
- handlePitchBend(channelNumber, pitchBend) {
979
+ setPitchBend(channelNumber, pitchBend) {
998
980
  const now = this.audioContext.currentTime;
999
981
  const channel = this.channels[channelNumber];
982
+ const prevPitchBend = channel.pitchBend;
1000
983
  channel.pitchBend = (pitchBend - 8192) / 8192;
1001
- const semitoneOffset = this.calcSemitoneOffset(channel);
984
+ const detuneChange = (channel.pitchBend - prevPitchBend) *
985
+ channel.pitchBendRange * 100;
1002
986
  const activeNotes = this.getActiveNotes(channel, now);
1003
987
  activeNotes.forEach((activeNote) => {
1004
- const { bufferSource, instrumentKey, noteNumber } = activeNote;
1005
- const playbackRate = calcPlaybackRate(instrumentKey, noteNumber, semitoneOffset);
1006
- bufferSource.playbackRate
988
+ const { bufferSource } = activeNote;
989
+ const detune = bufferSource.detune.value + detuneChange;
990
+ bufferSource.detune
1007
991
  .cancelScheduledValues(now)
1008
- .setValueAtTime(playbackRate * pressure, now);
992
+ .setValueAtTime(detune, now);
1009
993
  });
1010
994
  }
1011
995
  handleControlChange(channelNumber, controller, value) {
@@ -1100,12 +1084,17 @@ export class Midy {
1100
1084
  channel.volume = volume / 127;
1101
1085
  this.updateChannelGain(channel);
1102
1086
  }
1087
+ panToGain(pan) {
1088
+ const theta = Math.PI / 2 * Math.max(0, pan - 1) / 126;
1089
+ return {
1090
+ gainLeft: Math.cos(theta),
1091
+ gainRight: Math.sin(theta),
1092
+ };
1093
+ }
1103
1094
  setPan(channelNumber, pan) {
1104
- const now = this.audioContext.currentTime;
1105
1095
  const channel = this.channels[channelNumber];
1106
- channel.pan = pan / 127 * 2 - 1; // -1 (left) - +1 (right)
1107
- channel.pannerNode.pan.cancelScheduledValues(now);
1108
- channel.pannerNode.pan.setValueAtTime(channel.pan, now);
1096
+ channel.pan = pan;
1097
+ this.updateChannelGain(channel);
1109
1098
  }
1110
1099
  setExpression(channelNumber, expression) {
1111
1100
  const channel = this.channels[channelNumber];
@@ -1118,8 +1107,13 @@ export class Midy {
1118
1107
  updateChannelGain(channel) {
1119
1108
  const now = this.audioContext.currentTime;
1120
1109
  const volume = channel.volume * channel.expression;
1121
- channel.gainNode.gain.cancelScheduledValues(now);
1122
- channel.gainNode.gain.setValueAtTime(volume, now);
1110
+ const { gainLeft, gainRight } = this.panToGain(channel.pan);
1111
+ channel.gainL.gain
1112
+ .cancelScheduledValues(now)
1113
+ .setValueAtTime(volume * gainLeft, now);
1114
+ channel.gainR.gain
1115
+ .cancelScheduledValues(now)
1116
+ .setValueAtTime(volume * gainRight, now);
1123
1117
  }
1124
1118
  setSustainPedal(channelNumber, value) {
1125
1119
  const isOn = value >= 64;
@@ -1229,8 +1223,7 @@ export class Midy {
1229
1223
  const { dataMSB, dataLSB } = channel;
1230
1224
  switch (rpn) {
1231
1225
  case 0:
1232
- channel.pitchBendRange = dataMSB + dataLSB / 100;
1233
- break;
1226
+ return this.handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB);
1234
1227
  case 1:
1235
1228
  channel.fineTuning = (dataMSB * 128 + dataLSB - 8192) / 8192;
1236
1229
  break;
@@ -1244,6 +1237,26 @@ export class Midy {
1244
1237
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
1245
1238
  }
1246
1239
  }
1240
+ handlePitchBendRangeMessage(channelNumber, dataMSB, dataLSB) {
1241
+ const pitchBendRange = dataMSB + dataLSB / 100;
1242
+ this.setPitchBendRange(channelNumber, pitchBendRange);
1243
+ }
1244
+ setPitchBendRange(channelNumber, pitchBendRange) {
1245
+ const now = this.audioContext.currentTime;
1246
+ const channel = this.channels[channelNumber];
1247
+ const prevPitchBendRange = channel.pitchBendRange;
1248
+ channel.pitchBendRange = pitchBendRange;
1249
+ const detuneChange = (channel.pitchBendRange - prevPitchBendRange) *
1250
+ channel.pitchBend * 100;
1251
+ const activeNotes = this.getActiveNotes(channel, now);
1252
+ activeNotes.forEach((activeNote) => {
1253
+ const { bufferSource } = activeNote;
1254
+ const detune = bufferSource.detune.value + detuneChange;
1255
+ bufferSource.detune
1256
+ .cancelScheduledValues(now)
1257
+ .setValueAtTime(detune, now);
1258
+ });
1259
+ }
1247
1260
  allSoundOff(channelNumber) {
1248
1261
  const now = this.audioContext.currentTime;
1249
1262
  const channel = this.channels[channelNumber];
@@ -1334,9 +1347,9 @@ export class Midy {
1334
1347
  case 1:
1335
1348
  return this.handleMasterVolumeSysEx(data);
1336
1349
  case 3:
1337
- return this.handleMasterFineTuning(data);
1350
+ return this.handleMasterFineTuningSysEx(data);
1338
1351
  case 4:
1339
- return this.handleMasterCoarseTuning(data);
1352
+ return this.handleMasterCoarseTuningSysEx(data);
1340
1353
  // case 5: // TODO: Global Parameter Control
1341
1354
  default:
1342
1355
  console.warn(`Unsupported Exclusive Message ${data}`);
@@ -1378,9 +1391,9 @@ export class Midy {
1378
1391
  }
1379
1392
  handleMasterVolumeSysEx(data) {
1380
1393
  const volume = (data[5] * 128 + data[4]) / 16383;
1381
- this.handleMasterVolume(volume);
1394
+ this.setMasterVolume(volume);
1382
1395
  }
1383
- handleMasterVolume(volume) {
1396
+ setMasterVolume(volume) {
1384
1397
  if (volume < 0 && 1 < volume) {
1385
1398
  console.error("Master Volume is out of range");
1386
1399
  }
@@ -1392,9 +1405,9 @@ export class Midy {
1392
1405
  }
1393
1406
  handleMasterFineTuningSysEx(data) {
1394
1407
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1395
- this.handleMasterFineTuning(fineTuning);
1408
+ this.setMasterFineTuning(fineTuning);
1396
1409
  }
1397
- handleMasterFineTuning(fineTuning) {
1410
+ setMasterFineTuning(fineTuning) {
1398
1411
  if (fineTuning < -1 && 1 < fineTuning) {
1399
1412
  console.error("Master Fine Tuning value is out of range");
1400
1413
  }
@@ -1404,9 +1417,9 @@ export class Midy {
1404
1417
  }
1405
1418
  handleMasterCoarseTuningSysEx(data) {
1406
1419
  const coarseTuning = data[4];
1407
- this.handleMasterCoarseTuning(coarseTuning);
1420
+ this.setMasterCoarseTuning(coarseTuning);
1408
1421
  }
1409
- handleMasterCoarseTuning(coarseTuning) {
1422
+ setMasterCoarseTuning(coarseTuning) {
1410
1423
  if (coarseTuning < 0 && 127 < coarseTuning) {
1411
1424
  console.error("Master Coarse Tuning value is out of range");
1412
1425
  }
@@ -1446,7 +1459,7 @@ Object.defineProperty(Midy, "channelSettings", {
1446
1459
  value: {
1447
1460
  currentBufferSource: null,
1448
1461
  volume: 100 / 127,
1449
- pan: 0,
1462
+ pan: 64,
1450
1463
  portamentoTime: 0,
1451
1464
  reverb: 0,
1452
1465
  chorus: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -41,8 +41,8 @@ export class MidyGM1 {
41
41
  masterGain: any;
42
42
  channels: {
43
43
  scheduledNotes: Map<any, any>;
44
- gainNode: any;
45
- pannerNode: any;
44
+ gainL: any;
45
+ gainR: any;
46
46
  expression: number;
47
47
  modulation: number;
48
48
  sustainPedal: boolean;
@@ -65,13 +65,13 @@ export class MidyGM1 {
65
65
  loadSoundFont(soundFontUrl: any): Promise<void>;
66
66
  loadMIDI(midiUrl: any): Promise<void>;
67
67
  setChannelAudioNodes(audioContext: any): {
68
- gainNode: any;
69
- pannerNode: any;
68
+ gainL: any;
69
+ gainR: any;
70
70
  };
71
71
  createChannels(audioContext: any): {
72
72
  scheduledNotes: Map<any, any>;
73
- gainNode: any;
74
- pannerNode: any;
73
+ gainL: any;
74
+ gainR: any;
75
75
  expression: number;
76
76
  modulation: number;
77
77
  sustainPedal: boolean;
@@ -128,10 +128,14 @@ export class MidyGM1 {
128
128
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
129
129
  handleProgramChange(channelNumber: any, program: any): void;
130
130
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
131
- handlePitchBend(channelNumber: any, pitchBend: any): void;
131
+ setPitchBend(channelNumber: any, pitchBend: any): void;
132
132
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
133
133
  setModulation(channelNumber: any, modulation: any): void;
134
134
  setVolume(channelNumber: any, volume: any): void;
135
+ panToGain(pan: any): {
136
+ gainLeft: number;
137
+ gainRight: number;
138
+ };
135
139
  setPan(channelNumber: any, pan: any): void;
136
140
  setExpression(channelNumber: any, expression: any): void;
137
141
  updateChannelGain(channel: any): void;
@@ -139,6 +143,8 @@ export class MidyGM1 {
139
143
  setRPNMSB(channelNumber: any, value: any): void;
140
144
  setRPNLSB(channelNumber: any, value: any): void;
141
145
  setDataEntry(channelNumber: any, value: any, isMSB: any): void;
146
+ handlePitchBendRangeMessage(channelNumber: any, dataMSB: any, dataLSB: any): void;
147
+ setPitchBendRange(channelNumber: any, pitchBendRange: any): void;
142
148
  allSoundOff(channelNumber: any): any[];
143
149
  resetAllControllers(channelNumber: any): void;
144
150
  allNotesOff(channelNumber: any): any[];
@@ -146,7 +152,7 @@ export class MidyGM1 {
146
152
  GM1SystemOn(): void;
147
153
  handleUniversalRealTimeExclusiveMessage(data: any): void;
148
154
  handleMasterVolumeSysEx(data: any): void;
149
- handleMasterVolume(volume: any): void;
155
+ setMasterVolume(volume: any): void;
150
156
  handleExclusiveMessage(data: any): void;
151
157
  handleSysEx(data: any): void;
152
158
  scheduleTask(callback: any, startTime: any): Promise<any>;
@@ -158,8 +164,6 @@ declare class Note {
158
164
  filterNode: any;
159
165
  modLFO: any;
160
166
  modLFOGain: any;
161
- vibLFO: any;
162
- vibLFOGain: any;
163
167
  noteNumber: any;
164
168
  velocity: any;
165
169
  startTime: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAuBA;IAmBE;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA9CD,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;IAyBhB,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,sCAGC;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,+DAoBC;IAED,uCAoBC;IAED,8CAEC;IAED,uCAoBC;IAED,4DAgBC;IAED,oBAQC;IAED,yDAaC;IAED,yCAGC;IAED,sCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAv+BD;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-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqBA;IAmBE;;;;;;;;;;;MAWE;IAEF;;;;;;;MAOE;IAEF,+BAMC;IA9CD,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;IAyBhB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;MAYC;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,sCAGC;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,+DAuBC;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;AAz/BD;IAOE,gFAKC;IAXD,kBAAa;IACb,cAAS;IACT,gBAAW;IACX,YAAO;IACP,gBAAW;IAGT,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,mBAAkC;CAErC"}