@marmooo/midy 0.2.7 → 0.2.9

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.
@@ -1,6 +1,7 @@
1
1
  export class MidyGMLite {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ isDrum: boolean;
4
5
  detune: number;
5
6
  program: number;
6
7
  bank: number;
@@ -8,8 +9,10 @@ export class MidyGMLite {
8
9
  dataLSB: number;
9
10
  rpnMSB: number;
10
11
  rpnLSB: number;
12
+ modulationDepthRange: number;
11
13
  };
12
14
  constructor(audioContext: any);
15
+ mode: string;
13
16
  ticksPerBeat: number;
14
17
  totalTime: number;
15
18
  noteCheckInterval: number;
@@ -32,6 +35,8 @@ export class MidyGMLite {
32
35
  exclusiveClassMap: SparseMap;
33
36
  audioContext: any;
34
37
  masterVolume: any;
38
+ scheduler: any;
39
+ schedulerBuffer: any;
35
40
  voiceParamsHandlers: {
36
41
  modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
37
42
  vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
@@ -70,7 +75,8 @@ export class MidyGMLite {
70
75
  };
71
76
  createChannels(audioContext: any): any[];
72
77
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
73
- createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
78
+ calcLoopMode(channel: any, voiceParams: any): boolean;
79
+ createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
74
80
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
75
81
  getQueueIndex(second: any): number;
76
82
  playNotes(): Promise<any>;
@@ -90,7 +96,7 @@ export class MidyGMLite {
90
96
  seekTo(second: any): void;
91
97
  calcTotalTime(): number;
92
98
  currentTime(): number;
93
- processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
99
+ processScheduledNotes(channel: any, callback: any): void;
94
100
  getActiveNotes(channel: any, scheduleTime: any): SparseMap;
95
101
  getActiveNote(noteList: any, scheduleTime: any): any;
96
102
  cbToRatio(cb: any): number;
@@ -112,7 +118,7 @@ export class MidyGMLite {
112
118
  stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
113
119
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
114
120
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
115
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
121
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
116
122
  handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
117
123
  handleProgramChange(channelNumber: any, program: any, _scheduleTime: any): void;
118
124
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
@@ -173,8 +179,8 @@ export class MidyGMLite {
173
179
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
174
180
  resetAllControllers(channelNumber: any): void;
175
181
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
176
- handleUniversalNonRealTimeExclusiveMessage(data: any, _scheduleTime: any): void;
177
- GM1SystemOn(): void;
182
+ handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
183
+ GM1SystemOn(scheduleTime: any): void;
178
184
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
179
185
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
180
186
  setMasterVolume(volume: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAsBE;;;;;;;;;MASE;IAEF,+BAQC;IAxCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAcrC,kBAAgC;IAChC,kBAA8C;IAC9C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,8DASC;IAED,2EAqDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAoBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4EASC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHAwCC;IAED,kGAgDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,qFAUC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAkDC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAWC;IAED,iFAIC;IAED,oEAIC;IAED;;;MAMC;IAED,8DAIC;IAED,4EAIC;IAED,sEAGC;IAED,2DAUC;IAED,yEAMC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,gFAgBC;IAED,oBAMC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAUC;CACF;AA32CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAuBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAjDD,aAAa;IACb,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAgBrC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAWC;IAED,6DA2BC;IAED,sDAMC;IAED,0EASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHA4CC;IAED,kGAkDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAWC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAMC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAYC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAWC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAgBC;CACF;AAp5CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -170,6 +170,12 @@ const volumeEnvelopeKeys = [
170
170
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
171
171
  export class MidyGMLite {
172
172
  constructor(audioContext) {
173
+ Object.defineProperty(this, "mode", {
174
+ enumerable: true,
175
+ configurable: true,
176
+ writable: true,
177
+ value: "GM1"
178
+ });
173
179
  Object.defineProperty(this, "ticksPerBeat", {
174
180
  enumerable: true,
175
181
  configurable: true,
@@ -292,10 +298,16 @@ export class MidyGMLite {
292
298
  });
293
299
  this.audioContext = audioContext;
294
300
  this.masterVolume = new GainNode(audioContext);
301
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
302
+ this.schedulerBuffer = new AudioBuffer({
303
+ length: 1,
304
+ sampleRate: audioContext.sampleRate,
305
+ });
295
306
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
296
307
  this.controlChangeHandlers = this.createControlChangeHandlers();
297
308
  this.channels = this.createChannels(audioContext);
298
309
  this.masterVolume.connect(audioContext.destination);
310
+ this.scheduler.connect(audioContext.destination);
299
311
  this.GM1SystemOn();
300
312
  }
301
313
  initSoundFontTable() {
@@ -355,6 +367,7 @@ export class MidyGMLite {
355
367
  state: new ControllerState(),
356
368
  ...this.setChannelAudioNodes(audioContext),
357
369
  scheduledNotes: new SparseMap(128),
370
+ sustainNotes: [],
358
371
  };
359
372
  });
360
373
  return channels;
@@ -388,10 +401,18 @@ export class MidyGMLite {
388
401
  return audioBuffer;
389
402
  }
390
403
  }
391
- createNoteBufferNode(audioBuffer, voiceParams) {
404
+ calcLoopMode(channel, voiceParams) {
405
+ if (channel.isDrum) {
406
+ return false;
407
+ }
408
+ else {
409
+ return voiceParams.sampleModes % 2 !== 0;
410
+ }
411
+ }
412
+ createBufferSource(channel, voiceParams, audioBuffer) {
392
413
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
393
414
  bufferSource.buffer = audioBuffer;
394
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
415
+ bufferSource.loop = this.calcLoopMode(channel, voiceParams);
395
416
  if (bufferSource.loop) {
396
417
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
397
418
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -412,7 +433,7 @@ export class MidyGMLite {
412
433
  }
413
434
  /* falls through */
414
435
  case "noteOff": {
415
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
436
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
416
437
  if (notePromise) {
417
438
  this.notePromises.push(notePromise);
418
439
  }
@@ -576,15 +597,10 @@ export class MidyGMLite {
576
597
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
577
598
  const channel = this.channels[channelNumber];
578
599
  const promises = [];
579
- channel.scheduledNotes.forEach((noteList) => {
580
- for (let i = 0; i < noteList.length; i++) {
581
- const note = noteList[i];
582
- if (!note)
583
- continue;
584
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
585
- this.notePromises.push(promise);
586
- promises.push(promise);
587
- }
600
+ this.processScheduledNotes(channel, (note) => {
601
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
602
+ this.notePromises.push(promise);
603
+ promises.push(promise);
588
604
  });
589
605
  channel.scheduledNotes.clear();
590
606
  return Promise.all(promises);
@@ -640,14 +656,12 @@ export class MidyGMLite {
640
656
  const now = this.audioContext.currentTime;
641
657
  return this.resumeTime + now - this.startTime - this.startDelay;
642
658
  }
643
- processScheduledNotes(channel, scheduleTime, callback) {
659
+ processScheduledNotes(channel, callback) {
644
660
  channel.scheduledNotes.forEach((noteList) => {
645
661
  for (let i = 0; i < noteList.length; i++) {
646
662
  const note = noteList[i];
647
663
  if (!note)
648
664
  continue;
649
- if (scheduleTime < note.startTime)
650
- continue;
651
665
  callback(note);
652
666
  }
653
667
  });
@@ -691,7 +705,7 @@ export class MidyGMLite {
691
705
  return pitchWheel * pitchWheelSensitivity;
692
706
  }
693
707
  updateChannelDetune(channel, scheduleTime) {
694
- this.processScheduledNotes(channel, scheduleTime, (note) => {
708
+ this.processScheduledNotes(channel, (note) => {
695
709
  this.updateDetune(channel, note, scheduleTime);
696
710
  });
697
711
  }
@@ -809,7 +823,7 @@ export class MidyGMLite {
809
823
  const voiceParams = voice.getAllParams(controllerState);
810
824
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
811
825
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
812
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
826
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
813
827
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
814
828
  note.filterNode = new BiquadFilterNode(this.audioContext, {
815
829
  type: "lowpass",
@@ -840,15 +854,17 @@ export class MidyGMLite {
840
854
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
841
855
  note.volumeEnvelopeNode.connect(channel.gainL);
842
856
  note.volumeEnvelopeNode.connect(channel.gainR);
857
+ if (0.5 <= channel.state.sustainPedal) {
858
+ channel.sustainNotes.push(note);
859
+ }
843
860
  const exclusiveClass = note.voiceParams.exclusiveClass;
844
861
  if (exclusiveClass !== 0) {
845
862
  if (this.exclusiveClassMap.has(exclusiveClass)) {
846
863
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
847
864
  const [prevNote, prevChannelNumber] = prevEntry;
848
- if (!prevNote.ending) {
865
+ if (prevNote && !prevNote.ending) {
849
866
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
850
- startTime, undefined, // portamentoNoteNumber
851
- true);
867
+ startTime, true);
852
868
  }
853
869
  }
854
870
  this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
@@ -892,7 +908,7 @@ export class MidyGMLite {
892
908
  }
893
909
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
894
910
  const channel = this.channels[channelNumber];
895
- if (!force && 0.5 < channel.state.sustainPedal)
911
+ if (!force && 0.5 <= channel.state.sustainPedal)
896
912
  return;
897
913
  if (!channel.scheduledNotes.has(noteNumber))
898
914
  return;
@@ -920,11 +936,11 @@ export class MidyGMLite {
920
936
  const velocity = halfVelocity * 2;
921
937
  const channel = this.channels[channelNumber];
922
938
  const promises = [];
923
- this.processScheduledNotes(channel, scheduleTime, (note) => {
924
- const { noteNumber } = note;
925
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
939
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
940
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
926
941
  promises.push(promise);
927
- });
942
+ }
943
+ channel.sustainNotes = [];
928
944
  return promises;
929
945
  }
930
946
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -954,8 +970,10 @@ export class MidyGMLite {
954
970
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
955
971
  }
956
972
  setPitchBend(channelNumber, value, scheduleTime) {
957
- scheduleTime ??= this.audioContext.currentTime;
958
973
  const channel = this.channels[channelNumber];
974
+ if (channel.isDrum)
975
+ return;
976
+ scheduleTime ??= this.audioContext.currentTime;
959
977
  const state = channel.state;
960
978
  const prev = state.pitchWheel * 2 - 1;
961
979
  const next = (value - 8192) / 8192;
@@ -1035,48 +1053,43 @@ export class MidyGMLite {
1035
1053
  return state;
1036
1054
  }
1037
1055
  applyVoiceParams(channel, controllerType, scheduleTime) {
1038
- channel.scheduledNotes.forEach((noteList) => {
1039
- for (let i = 0; i < noteList.length; i++) {
1040
- const note = noteList[i];
1041
- if (!note)
1056
+ this.processScheduledNotes(channel, (note) => {
1057
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1058
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1059
+ let appliedFilterEnvelope = false;
1060
+ let appliedVolumeEnvelope = false;
1061
+ for (const [key, value] of Object.entries(voiceParams)) {
1062
+ const prevValue = note.voiceParams[key];
1063
+ if (value === prevValue)
1042
1064
  continue;
1043
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1044
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1045
- let appliedFilterEnvelope = false;
1046
- let appliedVolumeEnvelope = false;
1047
- for (const [key, value] of Object.entries(voiceParams)) {
1048
- const prevValue = note.voiceParams[key];
1049
- if (value === prevValue)
1065
+ note.voiceParams[key] = value;
1066
+ if (key in this.voiceParamsHandlers) {
1067
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1068
+ }
1069
+ else if (filterEnvelopeKeySet.has(key)) {
1070
+ if (appliedFilterEnvelope)
1050
1071
  continue;
1051
- note.voiceParams[key] = value;
1052
- if (key in this.voiceParamsHandlers) {
1053
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1054
- }
1055
- else if (filterEnvelopeKeySet.has(key)) {
1056
- if (appliedFilterEnvelope)
1057
- continue;
1058
- appliedFilterEnvelope = true;
1059
- const noteVoiceParams = note.voiceParams;
1060
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1061
- const key = filterEnvelopeKeys[i];
1062
- if (key in voiceParams)
1063
- noteVoiceParams[key] = voiceParams[key];
1064
- }
1065
- this.setFilterEnvelope(note, scheduleTime);
1066
- this.setPitchEnvelope(note, scheduleTime);
1072
+ appliedFilterEnvelope = true;
1073
+ const noteVoiceParams = note.voiceParams;
1074
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1075
+ const key = filterEnvelopeKeys[i];
1076
+ if (key in voiceParams)
1077
+ noteVoiceParams[key] = voiceParams[key];
1067
1078
  }
1068
- else if (volumeEnvelopeKeySet.has(key)) {
1069
- if (appliedVolumeEnvelope)
1070
- continue;
1071
- appliedVolumeEnvelope = true;
1072
- const noteVoiceParams = note.voiceParams;
1073
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1074
- const key = volumeEnvelopeKeys[i];
1075
- if (key in voiceParams)
1076
- noteVoiceParams[key] = voiceParams[key];
1077
- }
1078
- this.setVolumeEnvelope(channel, note, scheduleTime);
1079
+ this.setFilterEnvelope(note, scheduleTime);
1080
+ this.setPitchEnvelope(note, scheduleTime);
1081
+ }
1082
+ else if (volumeEnvelopeKeySet.has(key)) {
1083
+ if (appliedVolumeEnvelope)
1084
+ continue;
1085
+ appliedVolumeEnvelope = true;
1086
+ const noteVoiceParams = note.voiceParams;
1087
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1088
+ const key = volumeEnvelopeKeys[i];
1089
+ if (key in voiceParams)
1090
+ noteVoiceParams[key] = voiceParams[key];
1079
1091
  }
1092
+ this.setVolumeEnvelope(note, scheduleTime);
1080
1093
  }
1081
1094
  }
1082
1095
  });
@@ -1109,9 +1122,8 @@ export class MidyGMLite {
1109
1122
  }
1110
1123
  }
1111
1124
  updateModulation(channel, scheduleTime) {
1112
- scheduleTime ??= this.audioContext.currentTime;
1113
1125
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1114
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1126
+ this.processScheduledNotes(channel, (note) => {
1115
1127
  if (note.modulationDepth) {
1116
1128
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1117
1129
  }
@@ -1123,10 +1135,14 @@ export class MidyGMLite {
1123
1135
  }
1124
1136
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1125
1137
  const channel = this.channels[channelNumber];
1138
+ if (channel.isDrum)
1139
+ return;
1140
+ scheduleTime ??= this.audioContext.currentTime;
1126
1141
  channel.state.modulationDepth = modulation / 127;
1127
1142
  this.updateModulation(channel, scheduleTime);
1128
1143
  }
1129
1144
  setVolume(channelNumber, volume, scheduleTime) {
1145
+ scheduleTime ??= this.audioContext.currentTime;
1130
1146
  const channel = this.channels[channelNumber];
1131
1147
  channel.state.volume = volume / 127;
1132
1148
  this.updateChannelVolume(channel, scheduleTime);
@@ -1139,11 +1155,13 @@ export class MidyGMLite {
1139
1155
  };
1140
1156
  }
1141
1157
  setPan(channelNumber, pan, scheduleTime) {
1158
+ scheduleTime ??= this.audioContext.currentTime;
1142
1159
  const channel = this.channels[channelNumber];
1143
1160
  channel.state.pan = pan / 127;
1144
1161
  this.updateChannelVolume(channel, scheduleTime);
1145
1162
  }
1146
1163
  setExpression(channelNumber, expression, scheduleTime) {
1164
+ scheduleTime ??= this.audioContext.currentTime;
1147
1165
  const channel = this.channels[channelNumber];
1148
1166
  channel.state.expression = expression / 127;
1149
1167
  this.updateChannelVolume(channel, scheduleTime);
@@ -1164,9 +1182,17 @@ export class MidyGMLite {
1164
1182
  .setValueAtTime(volume * gainRight, scheduleTime);
1165
1183
  }
1166
1184
  setSustainPedal(channelNumber, value, scheduleTime) {
1185
+ const channel = this.channels[channelNumber];
1186
+ if (channel.isDrum)
1187
+ return;
1167
1188
  scheduleTime ??= this.audioContext.currentTime;
1168
- this.channels[channelNumber].state.sustainPedal = value / 127;
1169
- if (value < 64) {
1189
+ channel.state.sustainPedal = value / 127;
1190
+ if (64 <= value) {
1191
+ this.processScheduledNotes(channel, (note) => {
1192
+ channel.sustainNotes.push(note);
1193
+ });
1194
+ }
1195
+ else {
1170
1196
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1171
1197
  }
1172
1198
  }
@@ -1216,8 +1242,10 @@ export class MidyGMLite {
1216
1242
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1217
1243
  }
1218
1244
  setPitchBendRange(channelNumber, value, scheduleTime) {
1219
- scheduleTime ??= this.audioContext.currentTime;
1220
1245
  const channel = this.channels[channelNumber];
1246
+ if (channel.isDrum)
1247
+ return;
1248
+ scheduleTime ??= this.audioContext.currentTime;
1221
1249
  const state = channel.state;
1222
1250
  const prev = state.pitchWheelSensitivity;
1223
1251
  const next = value / 128;
@@ -1256,12 +1284,12 @@ export class MidyGMLite {
1256
1284
  scheduleTime ??= this.audioContext.currentTime;
1257
1285
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1258
1286
  }
1259
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1287
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1260
1288
  switch (data[2]) {
1261
1289
  case 9:
1262
1290
  switch (data[3]) {
1263
1291
  case 1:
1264
- this.GM1SystemOn();
1292
+ this.GM1SystemOn(scheduleTime);
1265
1293
  break;
1266
1294
  case 2: // GM System Off
1267
1295
  break;
@@ -1273,12 +1301,17 @@ export class MidyGMLite {
1273
1301
  console.warn(`Unsupported Exclusive Message: ${data}`);
1274
1302
  }
1275
1303
  }
1276
- GM1SystemOn() {
1304
+ GM1SystemOn(scheduleTime) {
1305
+ scheduleTime ??= this.audioContext.currentTime;
1306
+ this.mode = "GM1";
1277
1307
  for (let i = 0; i < this.channels.length; i++) {
1308
+ this.allSoundOff(i, 0, scheduleTime);
1278
1309
  const channel = this.channels[i];
1279
1310
  channel.bank = 0;
1311
+ channel.isDrum = false;
1280
1312
  }
1281
1313
  this.channels[9].bank = 128;
1314
+ this.channels[9].isDrum = true;
1282
1315
  }
1283
1316
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1284
1317
  switch (data[2]) {
@@ -1321,13 +1354,20 @@ export class MidyGMLite {
1321
1354
  }
1322
1355
  scheduleTask(callback, scheduleTime) {
1323
1356
  return new Promise((resolve) => {
1324
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1357
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1358
+ buffer: this.schedulerBuffer,
1359
+ });
1360
+ bufferSource.connect(this.scheduler);
1325
1361
  bufferSource.onended = () => {
1326
- callback();
1327
- resolve();
1362
+ try {
1363
+ callback();
1364
+ }
1365
+ finally {
1366
+ bufferSource.disconnect();
1367
+ resolve();
1368
+ }
1328
1369
  };
1329
1370
  bufferSource.start(scheduleTime);
1330
- bufferSource.stop(scheduleTime);
1331
1371
  });
1332
1372
  }
1333
1373
  }
@@ -1337,6 +1377,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1337
1377
  writable: true,
1338
1378
  value: {
1339
1379
  currentBufferSource: null,
1380
+ isDrum: false,
1340
1381
  detune: 0,
1341
1382
  program: 0,
1342
1383
  bank: 0,
@@ -1344,5 +1385,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1344
1385
  dataLSB: 0,
1345
1386
  rpnMSB: 127,
1346
1387
  rpnLSB: 127,
1388
+ modulationDepthRange: 50, // cent
1347
1389
  }
1348
1390
  });
package/esm/midy.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export class Midy {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ isDrum: boolean;
4
5
  detune: number;
5
6
  program: number;
6
7
  bank: number;
@@ -10,9 +11,10 @@ export class Midy {
10
11
  dataLSB: number;
11
12
  rpnMSB: number;
12
13
  rpnLSB: number;
14
+ mono: boolean;
15
+ modulationDepthRange: number;
13
16
  fineTuning: number;
14
17
  coarseTuning: number;
15
- modulationDepthRange: number;
16
18
  };
17
19
  constructor(audioContext: any, options?: {
18
20
  reverbAlgorithm: (audioContext: any) => {
@@ -20,6 +22,7 @@ export class Midy {
20
22
  output: any;
21
23
  };
22
24
  });
25
+ mode: string;
23
26
  ticksPerBeat: number;
24
27
  totalTime: number;
25
28
  masterFineTuning: number;
@@ -35,8 +38,6 @@ export class Midy {
35
38
  sendToReverb: number;
36
39
  delayTimes: any[];
37
40
  };
38
- mono: boolean;
39
- omni: boolean;
40
41
  noteCheckInterval: number;
41
42
  lookAhead: number;
42
43
  startDelay: number;
@@ -69,6 +70,8 @@ export class Midy {
69
70
  };
70
71
  };
71
72
  masterVolume: any;
73
+ scheduler: any;
74
+ schedulerBuffer: any;
72
75
  voiceParamsHandlers: {
73
76
  modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
74
77
  vibLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
@@ -94,7 +97,7 @@ export class Midy {
94
97
  64: (channelNumber: any, value: any, scheduleTime: any) => void;
95
98
  65: (channelNumber: any, value: any) => void;
96
99
  66: (channelNumber: any, value: any, scheduleTime: any) => void;
97
- 67: (channelNumber: any, softPedal: any, _scheduleTime: any) => void;
100
+ 67: (channelNumber: any, softPedal: any, scheduleTime: any) => void;
98
101
  71: (channelNumber: any, filterResonance: any, scheduleTime: any) => void;
99
102
  72: (channelNumber: any, releaseTime: any, _scheduleTime: any) => void;
100
103
  73: (channelNumber: any, attackTime: any, scheduleTime: any) => void;
@@ -112,10 +115,10 @@ export class Midy {
112
115
  120: (channelNumber: any, _value: any, scheduleTime: any) => Promise<any[]>;
113
116
  121: (channelNumber: any) => void;
114
117
  123: (channelNumber: any, _value: any, scheduleTime: any) => Promise<any[]>;
115
- 124: () => void;
116
- 125: () => void;
117
- 126: () => void;
118
- 127: () => void;
118
+ 124: (channelNumber: any, value: any, scheduleTime: any) => void;
119
+ 125: (channelNumber: any, value: any, scheduleTime: any) => void;
120
+ 126: (channelNumber: any, value: any, scheduleTime: any) => void;
121
+ 127: (channelNumber: any, value: any, scheduleTime: any) => void;
119
122
  };
120
123
  channels: any[];
121
124
  reverbEffect: {
@@ -142,7 +145,8 @@ export class Midy {
142
145
  };
143
146
  createChannels(audioContext: any): any[];
144
147
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
145
- createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
148
+ calcLoopMode(channel: any, note: any, voiceParams: any): boolean;
149
+ createBufferSource(channel: any, note: any, voiceParams: any, audioBuffer: any): any;
146
150
  findPortamentoTarget(queueIndex: any): any;
147
151
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
148
152
  getQueueIndex(second: any): number;
@@ -163,7 +167,7 @@ export class Midy {
163
167
  seekTo(second: any): void;
164
168
  calcTotalTime(): number;
165
169
  currentTime(): number;
166
- processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
170
+ processScheduledNotes(channel: any, callback: any): void;
167
171
  getActiveNotes(channel: any, scheduleTime: any): SparseMap;
168
172
  getActiveNote(noteList: any, scheduleTime: any): any;
169
173
  createConvolutionReverbImpulse(audioContext: any, decay: any, preDecay: any): any;
@@ -207,14 +211,14 @@ export class Midy {
207
211
  startVibrato(channel: any, note: any, scheduleTime: any): void;
208
212
  getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
209
213
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, portamento: any, isSF3: any): Promise<Note>;
210
- calcBank(channel: any, channelNumber: any): any;
214
+ calcBank(channel: any): any;
211
215
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, portamento: any): Promise<void>;
212
216
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
213
217
  stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
214
218
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any, portamentoNoteNumber: any): Promise<any> | undefined;
215
219
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
216
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
217
- releaseSostenutoPedal(channelNumber: any, halfVelocity: any): any[];
220
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
221
+ releaseSostenutoPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
218
222
  handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
219
223
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any, scheduleTime: any): void;
220
224
  handleProgramChange(channelNumber: any, program: any, _scheduleTime: any): void;
@@ -257,7 +261,7 @@ export class Midy {
257
261
  64: (channelNumber: any, value: any, scheduleTime: any) => void;
258
262
  65: (channelNumber: any, value: any) => void;
259
263
  66: (channelNumber: any, value: any, scheduleTime: any) => void;
260
- 67: (channelNumber: any, softPedal: any, _scheduleTime: any) => void;
264
+ 67: (channelNumber: any, softPedal: any, scheduleTime: any) => void;
261
265
  71: (channelNumber: any, filterResonance: any, scheduleTime: any) => void;
262
266
  72: (channelNumber: any, releaseTime: any, _scheduleTime: any) => void;
263
267
  73: (channelNumber: any, attackTime: any, scheduleTime: any) => void;
@@ -275,10 +279,10 @@ export class Midy {
275
279
  120: (channelNumber: any, _value: any, scheduleTime: any) => Promise<any[]>;
276
280
  121: (channelNumber: any) => void;
277
281
  123: (channelNumber: any, _value: any, scheduleTime: any) => Promise<any[]>;
278
- 124: () => void;
279
- 125: () => void;
280
- 126: () => void;
281
- 127: () => void;
282
+ 124: (channelNumber: any, value: any, scheduleTime: any) => void;
283
+ 125: (channelNumber: any, value: any, scheduleTime: any) => void;
284
+ 126: (channelNumber: any, value: any, scheduleTime: any) => void;
285
+ 127: (channelNumber: any, value: any, scheduleTime: any) => void;
282
286
  };
283
287
  handleControlChange(channelNumber: any, controllerType: any, value: any, scheduleTime: any): void;
284
288
  setBankMSB(channelNumber: any, msb: any): void;
@@ -300,7 +304,7 @@ export class Midy {
300
304
  setSustainPedal(channelNumber: any, value: any, scheduleTime: any): void;
301
305
  setPortamento(channelNumber: any, value: any): void;
302
306
  setSostenutoPedal(channelNumber: any, value: any, scheduleTime: any): void;
303
- setSoftPedal(channelNumber: any, softPedal: any, _scheduleTime: any): void;
307
+ setSoftPedal(channelNumber: any, softPedal: any, scheduleTime: any): void;
304
308
  setFilterResonance(channelNumber: any, filterResonance: any, scheduleTime: any): void;
305
309
  setReleaseTime(channelNumber: any, releaseTime: any, _scheduleTime: any): void;
306
310
  setAttackTime(channelNumber: any, attackTime: any, scheduleTime: any): void;
@@ -321,22 +325,22 @@ export class Midy {
321
325
  dataEntryMSB(channelNumber: any, value: any, scheduleTime: any): void;
322
326
  handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
323
327
  setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
324
- handleFineTuningRPN(channelNumber: any): void;
325
- setFineTuning(channelNumber: any, value: any): void;
326
- handleCoarseTuningRPN(channelNumber: any): void;
327
- setCoarseTuning(channelNumber: any, value: any): void;
328
- handleModulationDepthRangeRPN(channelNumber: any): void;
329
- setModulationDepthRange(channelNumber: any, modulationDepthRange: any): void;
328
+ handleFineTuningRPN(channelNumber: any, scheduleTime: any): void;
329
+ setFineTuning(channelNumber: any, value: any, scheduleTime: any): void;
330
+ handleCoarseTuningRPN(channelNumber: any, scheduleTime: any): void;
331
+ setCoarseTuning(channelNumber: any, value: any, scheduleTime: any): void;
332
+ handleModulationDepthRangeRPN(channelNumber: any, scheduleTime: any): void;
333
+ setModulationDepthRange(channelNumber: any, modulationDepthRange: any, scheduleTime: any): void;
330
334
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
331
335
  resetAllControllers(channelNumber: any): void;
332
336
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
333
- omniOff(): void;
334
- omniOn(): void;
335
- monoOn(): void;
336
- polyOn(): void;
337
+ omniOff(channelNumber: any, value: any, scheduleTime: any): void;
338
+ omniOn(channelNumber: any, value: any, scheduleTime: any): void;
339
+ monoOn(channelNumber: any, value: any, scheduleTime: any): void;
340
+ polyOn(channelNumber: any, value: any, scheduleTime: any): void;
337
341
  handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
338
- GM1SystemOn(): void;
339
- GM2SystemOn(): void;
342
+ GM1SystemOn(scheduleTime: any): void;
343
+ GM2SystemOn(scheduleTime: any): void;
340
344
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
341
345
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
342
346
  setMasterVolume(volume: any, scheduleTime: any): void;