@marmooo/midy 0.0.3 → 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.
@@ -223,9 +223,6 @@ export class MidyGMLite {
223
223
  if (event.startTime > t + this.lookAhead)
224
224
  break;
225
225
  switch (event.type) {
226
- case "controller":
227
- this.handleControlChange(event.channel, event.controllerType, event.value);
228
- break;
229
226
  case "noteOn":
230
227
  if (event.velocity !== 0) {
231
228
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -239,9 +236,15 @@ export class MidyGMLite {
239
236
  }
240
237
  break;
241
238
  }
239
+ case "controller":
240
+ this.handleControlChange(event.channel, event.controllerType, event.value);
241
+ break;
242
242
  case "programChange":
243
243
  this.handleProgramChange(event.channel, event.programNumber);
244
244
  break;
245
+ case "pitchBend":
246
+ this.handlePitchBend(event.channel, event.value);
247
+ break;
245
248
  case "sysEx":
246
249
  this.handleSysEx(event.data);
247
250
  }
@@ -478,12 +481,16 @@ export class MidyGMLite {
478
481
  centToHz(cent) {
479
482
  return 8.176 * Math.pow(2, cent / 1200);
480
483
  }
484
+ calcSemitoneOffset(channel) {
485
+ return channel.pitchBend * channel.pitchBendRange + tuning;
486
+ }
487
+ calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
488
+ return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
489
+ }
481
490
  async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
482
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange;
483
- const playbackRate = noteInfo.playbackRate(noteNumber) *
484
- Math.pow(2, semitoneOffset / 12);
485
491
  const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
486
- bufferSource.playbackRate.value = playbackRate;
492
+ const semitoneOffset = this.calcSemitoneOffset(channel);
493
+ bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
487
494
  // volume envelope
488
495
  const gainNode = new GainNode(this.audioContext, {
489
496
  gain: 0,
@@ -649,46 +656,37 @@ export class MidyGMLite {
649
656
  return this.releaseNote(channelNumber, data1, data2);
650
657
  case 0x90:
651
658
  return this.noteOn(channelNumber, data1, data2);
652
- case 0xA0:
653
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
654
659
  case 0xB0:
655
660
  return this.handleControlChange(channelNumber, data1, data2);
656
661
  case 0xC0:
657
662
  return this.handleProgramChange(channelNumber, data1);
658
- case 0xD0:
659
- return this.handleChannelPressure(channelNumber, data1);
660
663
  case 0xE0:
661
- return this.handlePitchBend(channelNumber, data1, data2);
664
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
662
665
  default:
663
666
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
664
667
  }
665
668
  }
666
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
667
- const now = this.audioContext.currentTime;
668
- const channel = this.channels[channelNumber];
669
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
670
- pressure /= 127;
671
- if (scheduledNotes) {
672
- scheduledNotes.forEach((scheduledNote) => {
673
- if (scheduledNote) {
674
- const { initialAttenuation } = scheduledNote.noteInfo;
675
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
676
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
677
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
678
- }
679
- });
680
- }
681
- }
682
669
  handleProgramChange(channelNumber, program) {
683
670
  const channel = this.channels[channelNumber];
684
671
  channel.program = program;
685
672
  }
686
- handleChannelPressure(channelNumber, pressure) {
687
- this.channels[channelNumber].channelPressure = pressure;
673
+ handlePitchBendMessage(channelNumber, lsb, msb) {
674
+ const pitchBend = msb * 128 + lsb;
675
+ this.handlePitchBend(channelNumber, pitchBend);
688
676
  }
689
- handlePitchBend(channelNumber, lsb, msb) {
690
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
691
- this.channels[channelNumber].pitchBend = pitchBend;
677
+ handlePitchBend(channelNumber, pitchBend) {
678
+ const now = this.audioContext.currentTime;
679
+ const channel = this.channels[channelNumber];
680
+ channel.pitchBend = (pitchBend - 8192) / 8192;
681
+ const semitoneOffset = this.calcSemitoneOffset(channel);
682
+ const activeNotes = this.getActiveNotes(channel);
683
+ activeNotes.forEach((activeNote) => {
684
+ const { bufferSource, noteInfo, noteNumber } = activeNote;
685
+ const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
686
+ bufferSource.playbackRate
687
+ .cancelScheduledValues(now)
688
+ .setValueAtTime(playbackRate * pressure, now);
689
+ });
692
690
  }
693
691
  handleControlChange(channelNumber, controller, value) {
694
692
  switch (controller) {
@@ -848,13 +846,18 @@ export class MidyGMLite {
848
846
  }
849
847
  }
850
848
  handleMasterVolumeSysEx(data) {
851
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
849
+ const volume = (data[5] * 128 + data[4]) / 16383;
852
850
  this.handleMasterVolume(volume);
853
851
  }
854
852
  handleMasterVolume(volume) {
855
- const now = this.audioContext.currentTime;
856
- this.masterGain.gain.cancelScheduledValues(now);
857
- this.masterGain.gain.setValueAtTime(volume * volume, now);
853
+ if (volume < 0 && 1 < volume) {
854
+ console.error("Master Volume is out of range");
855
+ }
856
+ else {
857
+ const now = this.audioContext.currentTime;
858
+ this.masterGain.gain.cancelScheduledValues(now);
859
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
860
+ }
858
861
  }
859
862
  handleExclusiveMessage(data) {
860
863
  console.warn(`Unsupported Exclusive Message ${data}`);
package/esm/midy.d.ts CHANGED
@@ -32,6 +32,14 @@ export class Midy {
32
32
  channelPressure: number;
33
33
  pitchBendRange: number;
34
34
  };
35
+ static controllerDestinationSettings: {
36
+ pitchControl: number;
37
+ filterCutoffControl: number;
38
+ amplitudeControl: number;
39
+ lfoPitchDepth: number;
40
+ lfoFilterDepth: number;
41
+ lfoAmplitudeDepth: number;
42
+ };
35
43
  constructor(audioContext: any);
36
44
  ticksPerBeat: number;
37
45
  totalTime: number;
@@ -60,6 +68,22 @@ export class Midy {
60
68
  channels: {
61
69
  scheduledNotes: Map<any, any>;
62
70
  sostenutoNotes: Map<any, any>;
71
+ polyphonicKeyPressure: {
72
+ pitchControl: number;
73
+ filterCutoffControl: number;
74
+ amplitudeControl: number;
75
+ lfoPitchDepth: number;
76
+ lfoFilterDepth: number;
77
+ lfoAmplitudeDepth: number;
78
+ };
79
+ channelPressure: {
80
+ pitchControl: number;
81
+ filterCutoffControl: number;
82
+ amplitudeControl: number;
83
+ lfoPitchDepth: number;
84
+ lfoFilterDepth: number;
85
+ lfoAmplitudeDepth: number;
86
+ };
63
87
  gainNode: any;
64
88
  pannerNode: any;
65
89
  modulationEffect: {
@@ -84,7 +108,6 @@ export class Midy {
84
108
  softPedal: number;
85
109
  rpnMSB: number;
86
110
  rpnLSB: number;
87
- channelPressure: number;
88
111
  pitchBendRange: number;
89
112
  currentBufferSource: null;
90
113
  volume: number;
@@ -131,6 +154,22 @@ export class Midy {
131
154
  createChannels(audioContext: any): {
132
155
  scheduledNotes: Map<any, any>;
133
156
  sostenutoNotes: Map<any, any>;
157
+ polyphonicKeyPressure: {
158
+ pitchControl: number;
159
+ filterCutoffControl: number;
160
+ amplitudeControl: number;
161
+ lfoPitchDepth: number;
162
+ lfoFilterDepth: number;
163
+ lfoAmplitudeDepth: number;
164
+ };
165
+ channelPressure: {
166
+ pitchControl: number;
167
+ filterCutoffControl: number;
168
+ amplitudeControl: number;
169
+ lfoPitchDepth: number;
170
+ lfoFilterDepth: number;
171
+ lfoAmplitudeDepth: number;
172
+ };
134
173
  gainNode: any;
135
174
  pannerNode: any;
136
175
  modulationEffect: {
@@ -155,7 +194,6 @@ export class Midy {
155
194
  softPedal: number;
156
195
  rpnMSB: number;
157
196
  rpnLSB: number;
158
- channelPressure: number;
159
197
  pitchBendRange: number;
160
198
  currentBufferSource: null;
161
199
  volume: number;
@@ -216,10 +254,13 @@ export class Midy {
216
254
  connectNoteEffects(channel: any, gainNode: any): void;
217
255
  cbToRatio(cb: any): number;
218
256
  centToHz(cent: any): number;
257
+ calcSemitoneOffset(channel: any): any;
258
+ calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
219
259
  createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
220
260
  bufferSource: any;
221
261
  gainNode: any;
222
262
  filterNode: any;
263
+ lfoGain: any;
223
264
  }>;
224
265
  calcBank(channel: any, channelNumber: any): any;
225
266
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
@@ -232,7 +273,8 @@ export class Midy {
232
273
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
233
274
  handleProgramChange(channelNumber: any, program: any): void;
234
275
  handleChannelPressure(channelNumber: any, pressure: any): void;
235
- handlePitchBend(channelNumber: any, lsb: any, msb: any): void;
276
+ handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
277
+ handlePitchBend(channelNumber: any, pitchBend: any): void;
236
278
  handleControlChange(channelNumber: any, controller: any, value: any): any;
237
279
  setBankMSB(channelNumber: any, msb: any): void;
238
280
  setModulation(channelNumber: any, modulation: any): void;
package/esm/midy.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAMA;IAwBE;;;;;;;;;;;;;;;;;;;;MAoBE;IAEF;;;;;;;;;;;MAWE;IAEF,+BAMC;IAhED,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;IAsChB,kBAAgC;IAChC,gBAA4C;IAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAAiD;IAInD,4BAMC;IAED,mCASC;IAED,gDAMC;IAED,sCASC;IAED;;;;;;;;;;;;;;;;;MAuBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAWC;IAED,0DAyBC;IAED,8DAUC;IAED,qDAOC;IAED,2EA4CC;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,4CASC;IAED,gDAKC;IAED;;MAOC;IAED;;;;MAoCC;IAED;;;;;MA2CC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED;;;;OAsFC;IAED,gDAQC;IAED,kGAgDC;IAED,0EAGC;IAED,sIA6CC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gEAqBC;IAED,sFAeC;IAED,4DAIC;IAED,+DAEC;IAED,8DAGC;IAED,0EAkEC;IAED,+CAEC;IAED,yDAIC;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,2DAOC;IAED,6DAOC;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,sCAIC;IAED,6CAGC;IAED,8CAMC;IAED,+CAGC;IAED,kDAMC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF"}
1
+ {"version":3,"file":"midy.d.ts","sourceRoot":"","sources":["../src/midy.js"],"names":[],"mappings":"AAMA;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;;;;;;;;;;;;;;;;;MAuBC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiBC;IAED,0DAyBC;IAED,8DAUC;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,4CASC;IAED,gDAKC;IAED;;MAOC;IAED;;;;MAoCC;IAED;;;;;MA2CC;IAED,sDA2BC;IAED,2BAEC;IAED,4BAEC;IAED,sCAKC;IAED,8EAEC;IAED;;;;;OAqFC;IAED,gDAQC;IAED,kGAgDC;IAED,0EAGC;IAED,sIA6CC;IAED,0FAGC;IAED,kEAeC;IAED,oEAYC;IAED,gEAqBC;IAED,sFAcC;IAED,4DAIC;IAED,+DAcC;IAED,qEAGC;IAED,0DAiBC;IAED,0EAkEC;IAED,+CAEC;IAED,yDAIC;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,2DAOC;IAED,6DAOC;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"}
package/esm/midy.js CHANGED
@@ -206,6 +206,12 @@ export class Midy {
206
206
  ...this.setChannelAudioNodes(audioContext),
207
207
  scheduledNotes: new Map(),
208
208
  sostenutoNotes: new Map(),
209
+ polyphonicKeyPressure: {
210
+ ...Midy.controllerDestinationSettings,
211
+ },
212
+ channelPressure: {
213
+ ...Midy.controllerDestinationSettings,
214
+ },
209
215
  };
210
216
  });
211
217
  return channels;
@@ -260,9 +266,6 @@ export class Midy {
260
266
  if (event.startTime > t + this.lookAhead)
261
267
  break;
262
268
  switch (event.type) {
263
- case "controller":
264
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
265
- break;
266
269
  case "noteOn":
267
270
  if (event.velocity !== 0) {
268
271
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -276,9 +279,21 @@ export class Midy {
276
279
  }
277
280
  break;
278
281
  }
282
+ case "noteAftertouch":
283
+ this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount);
284
+ break;
285
+ case "controller":
286
+ this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value);
287
+ break;
279
288
  case "programChange":
280
289
  this.handleProgramChange(event.channel, event.programNumber);
281
290
  break;
291
+ case "channelAftertouch":
292
+ this.handleChannelPressure(event.channel, event.amount);
293
+ break;
294
+ case "pitchBend":
295
+ this.handlePitchBend(event.channel, event.value);
296
+ break;
282
297
  case "sysEx":
283
298
  this.handleSysEx(event.data);
284
299
  }
@@ -642,15 +657,19 @@ export class Midy {
642
657
  centToHz(cent) {
643
658
  return 8.176 * Math.pow(2, cent / 1200);
644
659
  }
645
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
660
+ calcSemitoneOffset(channel) {
646
661
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
647
662
  const channelTuning = channel.coarseTuning + channel.fineTuning;
648
663
  const tuning = masterTuning + channelTuning;
649
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
650
- const playbackRate = noteInfo.playbackRate(noteNumber) *
651
- Math.pow(2, semitoneOffset / 12);
664
+ return channel.pitchBend * channel.pitchBendRange + tuning;
665
+ }
666
+ calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
667
+ return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
668
+ }
669
+ async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
652
670
  const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
653
- bufferSource.playbackRate.value = playbackRate;
671
+ const semitoneOffset = this.calcSemitoneOffset(channel);
672
+ bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
654
673
  // volume envelope
655
674
  const gainNode = new GainNode(this.audioContext, {
656
675
  gain: 0,
@@ -714,7 +733,7 @@ export class Midy {
714
733
  channel.currentBufferSource = bufferSource;
715
734
  }
716
735
  bufferSource.start(startTime, noteInfo.start / noteInfo.sampleRate);
717
- return { bufferSource, gainNode, filterNode };
736
+ return { bufferSource, gainNode, filterNode, lfoGain };
718
737
  }
719
738
  calcBank(channel, channelNumber) {
720
739
  if (channel.bankMSB === 121) {
@@ -736,7 +755,7 @@ export class Midy {
736
755
  const noteInfo = soundFont.getInstrumentKey(bankNumber, channel.program, noteNumber);
737
756
  if (!noteInfo)
738
757
  return;
739
- const { bufferSource, gainNode, filterNode } = await this
758
+ const { bufferSource, gainNode, filterNode, lfoGain } = await this
740
759
  .createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3);
741
760
  this.connectNoteEffects(channel, gainNode);
742
761
  if (channel.sostenutoPedal) {
@@ -856,7 +875,7 @@ export class Midy {
856
875
  case 0x90:
857
876
  return this.noteOn(channelNumber, data1, data2);
858
877
  case 0xA0:
859
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
878
+ return; // this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
860
879
  case 0xB0:
861
880
  return this.handleControlChange(channelNumber, data1, data2);
862
881
  case 0xC0:
@@ -864,7 +883,7 @@ export class Midy {
864
883
  case 0xD0:
865
884
  return this.handleChannelPressure(channelNumber, data1);
866
885
  case 0xE0:
867
- return this.handlePitchBend(channelNumber, data1, data2);
886
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
868
887
  default:
869
888
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
870
889
  }
@@ -872,17 +891,16 @@ export class Midy {
872
891
  handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
873
892
  const now = this.audioContext.currentTime;
874
893
  const channel = this.channels[channelNumber];
875
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
876
- pressure /= 127;
877
- if (scheduledNotes) {
878
- scheduledNotes.forEach((scheduledNote) => {
879
- if (scheduledNote) {
880
- const { initialAttenuation } = scheduledNote.noteInfo;
881
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
882
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
883
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
884
- }
885
- });
894
+ pressure /= 64;
895
+ const activeNotes = this.getActiveNotes(channel);
896
+ if (channel.polyphonicKeyPressure.amplitudeControl !== 1) {
897
+ if (activeNotes.has(noteNumber)) {
898
+ const activeNote = activeNotes.get(noteNumber);
899
+ const gain = activeNote.gainNode.gain.value;
900
+ activeNote.gainNode.gain
901
+ .cancelScheduledValues(now)
902
+ .setValueAtTime(gain * pressure, now);
903
+ }
886
904
  }
887
905
  }
888
906
  handleProgramChange(channelNumber, program) {
@@ -891,11 +909,37 @@ export class Midy {
891
909
  channel.program = program;
892
910
  }
893
911
  handleChannelPressure(channelNumber, pressure) {
894
- this.channels[channelNumber].channelPressure = pressure;
912
+ const now = this.audioContext.currentTime;
913
+ const channel = this.channels[channelNumber];
914
+ pressure /= 64;
915
+ channel.channelPressure = pressure;
916
+ const activeNotes = this.getActiveNotes(channel);
917
+ if (channel.channelPressure.amplitudeControl !== 1) {
918
+ activeNotes.forEach((activeNote) => {
919
+ const gain = activeNote.gainNode.gain.value;
920
+ activeNote.gainNode.gain
921
+ .cancelScheduledValues(now)
922
+ .setValueAtTime(gain * pressure, now);
923
+ });
924
+ }
895
925
  }
896
- handlePitchBend(channelNumber, lsb, msb) {
897
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
898
- this.channels[channelNumber].pitchBend = pitchBend;
926
+ handlePitchBendMessage(channelNumber, lsb, msb) {
927
+ const pitchBend = msb * 128 + lsb;
928
+ this.handlePitchBend(channelNumber, pitchBend);
929
+ }
930
+ handlePitchBend(channelNumber, pitchBend) {
931
+ const now = this.audioContext.currentTime;
932
+ const channel = this.channels[channelNumber];
933
+ channel.pitchBend = (pitchBend - 8192) / 8192;
934
+ const semitoneOffset = this.calcSemitoneOffset(channel);
935
+ const activeNotes = this.getActiveNotes(channel);
936
+ activeNotes.forEach((activeNote) => {
937
+ const { bufferSource, noteInfo, noteNumber } = activeNote;
938
+ const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
939
+ bufferSource.playbackRate
940
+ .cancelScheduledValues(now)
941
+ .setValueAtTime(playbackRate * pressure, now);
942
+ });
899
943
  }
900
944
  handleControlChange(channelNumber, controller, value) {
901
945
  switch (controller) {
@@ -1240,10 +1284,10 @@ export class Midy {
1240
1284
  switch (data[3]) {
1241
1285
  // case 1:
1242
1286
  // // TODO
1243
- // return this.handleChannelPressure();
1287
+ // return this.setChannelPressure();
1244
1288
  // case 3:
1245
1289
  // // TODO
1246
- // return this.handleControlChange();
1290
+ // return this.setControlChange();
1247
1291
  default:
1248
1292
  console.warn(`Unsupported Exclusive Message ${data}`);
1249
1293
  }
@@ -1262,20 +1306,25 @@ export class Midy {
1262
1306
  }
1263
1307
  }
1264
1308
  handleMasterVolumeSysEx(data) {
1265
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
1309
+ const volume = (data[5] * 128 + data[4]) / 16383;
1266
1310
  this.handleMasterVolume(volume);
1267
1311
  }
1268
1312
  handleMasterVolume(volume) {
1269
- const now = this.audioContext.currentTime;
1270
- this.masterGain.gain.cancelScheduledValues(now);
1271
- this.masterGain.gain.setValueAtTime(volume * volume, now);
1313
+ if (volume < 0 && 1 < volume) {
1314
+ console.error("Master Volume is out of range");
1315
+ }
1316
+ else {
1317
+ const now = this.audioContext.currentTime;
1318
+ this.masterGain.gain.cancelScheduledValues(now);
1319
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
1320
+ }
1272
1321
  }
1273
1322
  handleMasterFineTuningSysEx(data) {
1274
1323
  const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
1275
1324
  this.handleMasterFineTuning(fineTuning);
1276
1325
  }
1277
1326
  handleMasterFineTuning(fineTuning) {
1278
- if (fineTuning < 0 && 1 < fineTuning) {
1327
+ if (fineTuning < -1 && 1 < fineTuning) {
1279
1328
  console.error("Master Fine Tuning value is out of range");
1280
1329
  }
1281
1330
  else {
@@ -1362,3 +1411,16 @@ Object.defineProperty(Midy, "effectSettings", {
1362
1411
  pitchBendRange: 2,
1363
1412
  }
1364
1413
  });
1414
+ Object.defineProperty(Midy, "controllerDestinationSettings", {
1415
+ enumerable: true,
1416
+ configurable: true,
1417
+ writable: true,
1418
+ value: {
1419
+ pitchControl: 0,
1420
+ filterCutoffControl: 0,
1421
+ amplitudeControl: 1,
1422
+ lfoPitchDepth: 0,
1423
+ lfoFilterDepth: 0,
1424
+ lfoAmplitudeDepth: 0,
1425
+ }
1426
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
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",
@@ -137,6 +137,8 @@ export class MidyGM1 {
137
137
  connectNoteEffects(channel: any, gainNode: any): void;
138
138
  cbToRatio(cb: any): number;
139
139
  centToHz(cent: any): number;
140
+ calcSemitoneOffset(channel: any): any;
141
+ calcPlaybackRate(noteInfo: any, noteNumber: any, semitoneOffset: any): number;
140
142
  createNoteAudioChain(channel: any, noteInfo: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<{
141
143
  bufferSource: any;
142
144
  gainNode: any;
@@ -149,10 +151,9 @@ export class MidyGM1 {
149
151
  releaseNote(channelNumber: any, noteNumber: any, velocity: any): Promise<any> | undefined;
150
152
  releaseSustainPedal(channelNumber: any, halfVelocity: any): any[];
151
153
  handleMIDIMessage(statusByte: any, data1: any, data2: any): void | any[] | Promise<any>;
152
- handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any): void;
153
154
  handleProgramChange(channelNumber: any, program: any): void;
154
- handleChannelPressure(channelNumber: any, pressure: any): void;
155
- handlePitchBend(channelNumber: any, lsb: any, msb: any): void;
155
+ handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
156
+ handlePitchBend(channelNumber: any, pitchBend: any): void;
156
157
  handleControlChange(channelNumber: any, controller: any, value: any): void | any[];
157
158
  setModulation(channelNumber: any, modulation: any): void;
158
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;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,2EA4CC;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;;;;;OA6EC;IAED,kGAuCC;IAED,0EAGC;IAED,sIA4CC;IAED,0FAGC;IAED,kEAeC;IAED,wFAqBC;IAED,sFAeC;IAED,4DAGC;IAED,+DAEC;IAED,8DAGC;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,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"}
@@ -226,9 +226,6 @@ class MidyGM1 {
226
226
  if (event.startTime > t + this.lookAhead)
227
227
  break;
228
228
  switch (event.type) {
229
- case "controller":
230
- this.handleControlChange(event.channel, event.controllerType, event.value);
231
- break;
232
229
  case "noteOn":
233
230
  if (event.velocity !== 0) {
234
231
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, event.startTime + this.startDelay - offset);
@@ -242,9 +239,15 @@ class MidyGM1 {
242
239
  }
243
240
  break;
244
241
  }
242
+ case "controller":
243
+ this.handleControlChange(event.channel, event.controllerType, event.value);
244
+ break;
245
245
  case "programChange":
246
246
  this.handleProgramChange(event.channel, event.programNumber);
247
247
  break;
248
+ case "pitchBend":
249
+ this.handlePitchBend(event.channel, event.value);
250
+ break;
248
251
  case "sysEx":
249
252
  this.handleSysEx(event.data);
250
253
  }
@@ -481,13 +484,17 @@ class MidyGM1 {
481
484
  centToHz(cent) {
482
485
  return 8.176 * Math.pow(2, cent / 1200);
483
486
  }
484
- async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
487
+ calcSemitoneOffset(channel) {
485
488
  const tuning = channel.coarseTuning + channel.fineTuning;
486
- const semitoneOffset = channel.pitchBend * channel.pitchBendRange + tuning;
487
- const playbackRate = noteInfo.playbackRate(noteNumber) *
488
- Math.pow(2, semitoneOffset / 12);
489
+ return channel.pitchBend * channel.pitchBendRange + tuning;
490
+ }
491
+ calcPlaybackRate(noteInfo, noteNumber, semitoneOffset) {
492
+ return noteInfo.playbackRate(noteNumber) * Math.pow(2, semitoneOffset / 12);
493
+ }
494
+ async createNoteAudioChain(channel, noteInfo, noteNumber, velocity, startTime, isSF3) {
489
495
  const bufferSource = await this.createNoteBufferNode(noteInfo, isSF3);
490
- bufferSource.playbackRate.value = playbackRate;
496
+ const semitoneOffset = this.calcSemitoneOffset(channel);
497
+ bufferSource.playbackRate.value = this.calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
491
498
  // volume envelope
492
499
  const gainNode = new GainNode(this.audioContext, {
493
500
  gain: 0,
@@ -653,46 +660,37 @@ class MidyGM1 {
653
660
  return this.releaseNote(channelNumber, data1, data2);
654
661
  case 0x90:
655
662
  return this.noteOn(channelNumber, data1, data2);
656
- case 0xA0:
657
- return this.handlePolyphonicKeyPressure(channelNumber, data1, data2);
658
663
  case 0xB0:
659
664
  return this.handleControlChange(channelNumber, data1, data2);
660
665
  case 0xC0:
661
666
  return this.handleProgramChange(channelNumber, data1);
662
- case 0xD0:
663
- return this.handleChannelPressure(channelNumber, data1);
664
667
  case 0xE0:
665
- return this.handlePitchBend(channelNumber, data1, data2);
668
+ return this.handlePitchBendMessage(channelNumber, data1, data2);
666
669
  default:
667
670
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
668
671
  }
669
672
  }
670
- handlePolyphonicKeyPressure(channelNumber, noteNumber, pressure) {
671
- const now = this.audioContext.currentTime;
672
- const channel = this.channels[channelNumber];
673
- const scheduledNotes = channel.scheduledNotes.get(noteNumber);
674
- pressure /= 127;
675
- if (scheduledNotes) {
676
- scheduledNotes.forEach((scheduledNote) => {
677
- if (scheduledNote) {
678
- const { initialAttenuation } = scheduledNote.noteInfo;
679
- const gain = this.cbToRatio(-initialAttenuation) * pressure;
680
- scheduledNote.gainNode.gain.cancelScheduledValues(now);
681
- scheduledNote.gainNode.gain.setValueAtTime(gain, now);
682
- }
683
- });
684
- }
685
- }
686
673
  handleProgramChange(channelNumber, program) {
687
674
  const channel = this.channels[channelNumber];
688
675
  channel.program = program;
689
676
  }
690
- handleChannelPressure(channelNumber, pressure) {
691
- this.channels[channelNumber].channelPressure = pressure;
677
+ handlePitchBendMessage(channelNumber, lsb, msb) {
678
+ const pitchBend = msb * 128 + lsb;
679
+ this.handlePitchBend(channelNumber, pitchBend);
692
680
  }
693
- handlePitchBend(channelNumber, lsb, msb) {
694
- const pitchBend = (msb * 128 + lsb - 8192) / 8192;
695
- this.channels[channelNumber].pitchBend = pitchBend;
681
+ handlePitchBend(channelNumber, pitchBend) {
682
+ const now = this.audioContext.currentTime;
683
+ const channel = this.channels[channelNumber];
684
+ channel.pitchBend = (pitchBend - 8192) / 8192;
685
+ const semitoneOffset = this.calcSemitoneOffset(channel);
686
+ const activeNotes = this.getActiveNotes(channel);
687
+ activeNotes.forEach((activeNote) => {
688
+ const { bufferSource, noteInfo, noteNumber } = activeNote;
689
+ const playbackRate = calcPlaybackRate(noteInfo, noteNumber, semitoneOffset);
690
+ bufferSource.playbackRate
691
+ .cancelScheduledValues(now)
692
+ .setValueAtTime(playbackRate * pressure, now);
693
+ });
696
694
  }
697
695
  handleControlChange(channelNumber, controller, value) {
698
696
  switch (controller) {
@@ -858,13 +856,18 @@ class MidyGM1 {
858
856
  }
859
857
  }
860
858
  handleMasterVolumeSysEx(data) {
861
- const volume = (data[5] * 128 + data[4] - 8192) / 8192;
859
+ const volume = (data[5] * 128 + data[4]) / 16383;
862
860
  this.handleMasterVolume(volume);
863
861
  }
864
862
  handleMasterVolume(volume) {
865
- const now = this.audioContext.currentTime;
866
- this.masterGain.gain.cancelScheduledValues(now);
867
- this.masterGain.gain.setValueAtTime(volume * volume, now);
863
+ if (volume < 0 && 1 < volume) {
864
+ console.error("Master Volume is out of range");
865
+ }
866
+ else {
867
+ const now = this.audioContext.currentTime;
868
+ this.masterGain.gain.cancelScheduledValues(now);
869
+ this.masterGain.gain.setValueAtTime(volume * volume, now);
870
+ }
868
871
  }
869
872
  handleExclusiveMessage(data) {
870
873
  console.warn(`Unsupported Exclusive Message ${data}`);