@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"}
@@ -173,6 +173,12 @@ const volumeEnvelopeKeys = [
173
173
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
174
174
  class MidyGMLite {
175
175
  constructor(audioContext) {
176
+ Object.defineProperty(this, "mode", {
177
+ enumerable: true,
178
+ configurable: true,
179
+ writable: true,
180
+ value: "GM1"
181
+ });
176
182
  Object.defineProperty(this, "ticksPerBeat", {
177
183
  enumerable: true,
178
184
  configurable: true,
@@ -295,10 +301,16 @@ class MidyGMLite {
295
301
  });
296
302
  this.audioContext = audioContext;
297
303
  this.masterVolume = new GainNode(audioContext);
304
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
305
+ this.schedulerBuffer = new AudioBuffer({
306
+ length: 1,
307
+ sampleRate: audioContext.sampleRate,
308
+ });
298
309
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
299
310
  this.controlChangeHandlers = this.createControlChangeHandlers();
300
311
  this.channels = this.createChannels(audioContext);
301
312
  this.masterVolume.connect(audioContext.destination);
313
+ this.scheduler.connect(audioContext.destination);
302
314
  this.GM1SystemOn();
303
315
  }
304
316
  initSoundFontTable() {
@@ -358,6 +370,7 @@ class MidyGMLite {
358
370
  state: new ControllerState(),
359
371
  ...this.setChannelAudioNodes(audioContext),
360
372
  scheduledNotes: new SparseMap(128),
373
+ sustainNotes: [],
361
374
  };
362
375
  });
363
376
  return channels;
@@ -391,10 +404,18 @@ class MidyGMLite {
391
404
  return audioBuffer;
392
405
  }
393
406
  }
394
- createNoteBufferNode(audioBuffer, voiceParams) {
407
+ calcLoopMode(channel, voiceParams) {
408
+ if (channel.isDrum) {
409
+ return false;
410
+ }
411
+ else {
412
+ return voiceParams.sampleModes % 2 !== 0;
413
+ }
414
+ }
415
+ createBufferSource(channel, voiceParams, audioBuffer) {
395
416
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
396
417
  bufferSource.buffer = audioBuffer;
397
- bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
418
+ bufferSource.loop = this.calcLoopMode(channel, voiceParams);
398
419
  if (bufferSource.loop) {
399
420
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
400
421
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -415,7 +436,7 @@ class MidyGMLite {
415
436
  }
416
437
  /* falls through */
417
438
  case "noteOff": {
418
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
439
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
419
440
  if (notePromise) {
420
441
  this.notePromises.push(notePromise);
421
442
  }
@@ -579,15 +600,10 @@ class MidyGMLite {
579
600
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
580
601
  const channel = this.channels[channelNumber];
581
602
  const promises = [];
582
- channel.scheduledNotes.forEach((noteList) => {
583
- for (let i = 0; i < noteList.length; i++) {
584
- const note = noteList[i];
585
- if (!note)
586
- continue;
587
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
588
- this.notePromises.push(promise);
589
- promises.push(promise);
590
- }
603
+ this.processScheduledNotes(channel, (note) => {
604
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
605
+ this.notePromises.push(promise);
606
+ promises.push(promise);
591
607
  });
592
608
  channel.scheduledNotes.clear();
593
609
  return Promise.all(promises);
@@ -643,14 +659,12 @@ class MidyGMLite {
643
659
  const now = this.audioContext.currentTime;
644
660
  return this.resumeTime + now - this.startTime - this.startDelay;
645
661
  }
646
- processScheduledNotes(channel, scheduleTime, callback) {
662
+ processScheduledNotes(channel, callback) {
647
663
  channel.scheduledNotes.forEach((noteList) => {
648
664
  for (let i = 0; i < noteList.length; i++) {
649
665
  const note = noteList[i];
650
666
  if (!note)
651
667
  continue;
652
- if (scheduleTime < note.startTime)
653
- continue;
654
668
  callback(note);
655
669
  }
656
670
  });
@@ -694,7 +708,7 @@ class MidyGMLite {
694
708
  return pitchWheel * pitchWheelSensitivity;
695
709
  }
696
710
  updateChannelDetune(channel, scheduleTime) {
697
- this.processScheduledNotes(channel, scheduleTime, (note) => {
711
+ this.processScheduledNotes(channel, (note) => {
698
712
  this.updateDetune(channel, note, scheduleTime);
699
713
  });
700
714
  }
@@ -812,7 +826,7 @@ class MidyGMLite {
812
826
  const voiceParams = voice.getAllParams(controllerState);
813
827
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
814
828
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
815
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
829
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
816
830
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
817
831
  note.filterNode = new BiquadFilterNode(this.audioContext, {
818
832
  type: "lowpass",
@@ -843,15 +857,17 @@ class MidyGMLite {
843
857
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
844
858
  note.volumeEnvelopeNode.connect(channel.gainL);
845
859
  note.volumeEnvelopeNode.connect(channel.gainR);
860
+ if (0.5 <= channel.state.sustainPedal) {
861
+ channel.sustainNotes.push(note);
862
+ }
846
863
  const exclusiveClass = note.voiceParams.exclusiveClass;
847
864
  if (exclusiveClass !== 0) {
848
865
  if (this.exclusiveClassMap.has(exclusiveClass)) {
849
866
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
850
867
  const [prevNote, prevChannelNumber] = prevEntry;
851
- if (!prevNote.ending) {
868
+ if (prevNote && !prevNote.ending) {
852
869
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
853
- startTime, undefined, // portamentoNoteNumber
854
- true);
870
+ startTime, true);
855
871
  }
856
872
  }
857
873
  this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
@@ -895,7 +911,7 @@ class MidyGMLite {
895
911
  }
896
912
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
897
913
  const channel = this.channels[channelNumber];
898
- if (!force && 0.5 < channel.state.sustainPedal)
914
+ if (!force && 0.5 <= channel.state.sustainPedal)
899
915
  return;
900
916
  if (!channel.scheduledNotes.has(noteNumber))
901
917
  return;
@@ -923,11 +939,11 @@ class MidyGMLite {
923
939
  const velocity = halfVelocity * 2;
924
940
  const channel = this.channels[channelNumber];
925
941
  const promises = [];
926
- this.processScheduledNotes(channel, scheduleTime, (note) => {
927
- const { noteNumber } = note;
928
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
942
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
943
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
929
944
  promises.push(promise);
930
- });
945
+ }
946
+ channel.sustainNotes = [];
931
947
  return promises;
932
948
  }
933
949
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -957,8 +973,10 @@ class MidyGMLite {
957
973
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
958
974
  }
959
975
  setPitchBend(channelNumber, value, scheduleTime) {
960
- scheduleTime ??= this.audioContext.currentTime;
961
976
  const channel = this.channels[channelNumber];
977
+ if (channel.isDrum)
978
+ return;
979
+ scheduleTime ??= this.audioContext.currentTime;
962
980
  const state = channel.state;
963
981
  const prev = state.pitchWheel * 2 - 1;
964
982
  const next = (value - 8192) / 8192;
@@ -1038,48 +1056,43 @@ class MidyGMLite {
1038
1056
  return state;
1039
1057
  }
1040
1058
  applyVoiceParams(channel, controllerType, scheduleTime) {
1041
- channel.scheduledNotes.forEach((noteList) => {
1042
- for (let i = 0; i < noteList.length; i++) {
1043
- const note = noteList[i];
1044
- if (!note)
1059
+ this.processScheduledNotes(channel, (note) => {
1060
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1061
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1062
+ let appliedFilterEnvelope = false;
1063
+ let appliedVolumeEnvelope = false;
1064
+ for (const [key, value] of Object.entries(voiceParams)) {
1065
+ const prevValue = note.voiceParams[key];
1066
+ if (value === prevValue)
1045
1067
  continue;
1046
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1047
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1048
- let appliedFilterEnvelope = false;
1049
- let appliedVolumeEnvelope = false;
1050
- for (const [key, value] of Object.entries(voiceParams)) {
1051
- const prevValue = note.voiceParams[key];
1052
- if (value === prevValue)
1068
+ note.voiceParams[key] = value;
1069
+ if (key in this.voiceParamsHandlers) {
1070
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1071
+ }
1072
+ else if (filterEnvelopeKeySet.has(key)) {
1073
+ if (appliedFilterEnvelope)
1053
1074
  continue;
1054
- note.voiceParams[key] = value;
1055
- if (key in this.voiceParamsHandlers) {
1056
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1057
- }
1058
- else if (filterEnvelopeKeySet.has(key)) {
1059
- if (appliedFilterEnvelope)
1060
- continue;
1061
- appliedFilterEnvelope = true;
1062
- const noteVoiceParams = note.voiceParams;
1063
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1064
- const key = filterEnvelopeKeys[i];
1065
- if (key in voiceParams)
1066
- noteVoiceParams[key] = voiceParams[key];
1067
- }
1068
- this.setFilterEnvelope(note, scheduleTime);
1069
- this.setPitchEnvelope(note, scheduleTime);
1075
+ appliedFilterEnvelope = true;
1076
+ const noteVoiceParams = note.voiceParams;
1077
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1078
+ const key = filterEnvelopeKeys[i];
1079
+ if (key in voiceParams)
1080
+ noteVoiceParams[key] = voiceParams[key];
1070
1081
  }
1071
- else if (volumeEnvelopeKeySet.has(key)) {
1072
- if (appliedVolumeEnvelope)
1073
- continue;
1074
- appliedVolumeEnvelope = true;
1075
- const noteVoiceParams = note.voiceParams;
1076
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1077
- const key = volumeEnvelopeKeys[i];
1078
- if (key in voiceParams)
1079
- noteVoiceParams[key] = voiceParams[key];
1080
- }
1081
- this.setVolumeEnvelope(channel, note, scheduleTime);
1082
+ this.setFilterEnvelope(note, scheduleTime);
1083
+ this.setPitchEnvelope(note, scheduleTime);
1084
+ }
1085
+ else if (volumeEnvelopeKeySet.has(key)) {
1086
+ if (appliedVolumeEnvelope)
1087
+ continue;
1088
+ appliedVolumeEnvelope = true;
1089
+ const noteVoiceParams = note.voiceParams;
1090
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1091
+ const key = volumeEnvelopeKeys[i];
1092
+ if (key in voiceParams)
1093
+ noteVoiceParams[key] = voiceParams[key];
1082
1094
  }
1095
+ this.setVolumeEnvelope(note, scheduleTime);
1083
1096
  }
1084
1097
  }
1085
1098
  });
@@ -1112,9 +1125,8 @@ class MidyGMLite {
1112
1125
  }
1113
1126
  }
1114
1127
  updateModulation(channel, scheduleTime) {
1115
- scheduleTime ??= this.audioContext.currentTime;
1116
1128
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1117
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1129
+ this.processScheduledNotes(channel, (note) => {
1118
1130
  if (note.modulationDepth) {
1119
1131
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1120
1132
  }
@@ -1126,10 +1138,14 @@ class MidyGMLite {
1126
1138
  }
1127
1139
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1128
1140
  const channel = this.channels[channelNumber];
1141
+ if (channel.isDrum)
1142
+ return;
1143
+ scheduleTime ??= this.audioContext.currentTime;
1129
1144
  channel.state.modulationDepth = modulation / 127;
1130
1145
  this.updateModulation(channel, scheduleTime);
1131
1146
  }
1132
1147
  setVolume(channelNumber, volume, scheduleTime) {
1148
+ scheduleTime ??= this.audioContext.currentTime;
1133
1149
  const channel = this.channels[channelNumber];
1134
1150
  channel.state.volume = volume / 127;
1135
1151
  this.updateChannelVolume(channel, scheduleTime);
@@ -1142,11 +1158,13 @@ class MidyGMLite {
1142
1158
  };
1143
1159
  }
1144
1160
  setPan(channelNumber, pan, scheduleTime) {
1161
+ scheduleTime ??= this.audioContext.currentTime;
1145
1162
  const channel = this.channels[channelNumber];
1146
1163
  channel.state.pan = pan / 127;
1147
1164
  this.updateChannelVolume(channel, scheduleTime);
1148
1165
  }
1149
1166
  setExpression(channelNumber, expression, scheduleTime) {
1167
+ scheduleTime ??= this.audioContext.currentTime;
1150
1168
  const channel = this.channels[channelNumber];
1151
1169
  channel.state.expression = expression / 127;
1152
1170
  this.updateChannelVolume(channel, scheduleTime);
@@ -1167,9 +1185,17 @@ class MidyGMLite {
1167
1185
  .setValueAtTime(volume * gainRight, scheduleTime);
1168
1186
  }
1169
1187
  setSustainPedal(channelNumber, value, scheduleTime) {
1188
+ const channel = this.channels[channelNumber];
1189
+ if (channel.isDrum)
1190
+ return;
1170
1191
  scheduleTime ??= this.audioContext.currentTime;
1171
- this.channels[channelNumber].state.sustainPedal = value / 127;
1172
- if (value < 64) {
1192
+ channel.state.sustainPedal = value / 127;
1193
+ if (64 <= value) {
1194
+ this.processScheduledNotes(channel, (note) => {
1195
+ channel.sustainNotes.push(note);
1196
+ });
1197
+ }
1198
+ else {
1173
1199
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1174
1200
  }
1175
1201
  }
@@ -1219,8 +1245,10 @@ class MidyGMLite {
1219
1245
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1220
1246
  }
1221
1247
  setPitchBendRange(channelNumber, value, scheduleTime) {
1222
- scheduleTime ??= this.audioContext.currentTime;
1223
1248
  const channel = this.channels[channelNumber];
1249
+ if (channel.isDrum)
1250
+ return;
1251
+ scheduleTime ??= this.audioContext.currentTime;
1224
1252
  const state = channel.state;
1225
1253
  const prev = state.pitchWheelSensitivity;
1226
1254
  const next = value / 128;
@@ -1259,12 +1287,12 @@ class MidyGMLite {
1259
1287
  scheduleTime ??= this.audioContext.currentTime;
1260
1288
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1261
1289
  }
1262
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1290
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1263
1291
  switch (data[2]) {
1264
1292
  case 9:
1265
1293
  switch (data[3]) {
1266
1294
  case 1:
1267
- this.GM1SystemOn();
1295
+ this.GM1SystemOn(scheduleTime);
1268
1296
  break;
1269
1297
  case 2: // GM System Off
1270
1298
  break;
@@ -1276,12 +1304,17 @@ class MidyGMLite {
1276
1304
  console.warn(`Unsupported Exclusive Message: ${data}`);
1277
1305
  }
1278
1306
  }
1279
- GM1SystemOn() {
1307
+ GM1SystemOn(scheduleTime) {
1308
+ scheduleTime ??= this.audioContext.currentTime;
1309
+ this.mode = "GM1";
1280
1310
  for (let i = 0; i < this.channels.length; i++) {
1311
+ this.allSoundOff(i, 0, scheduleTime);
1281
1312
  const channel = this.channels[i];
1282
1313
  channel.bank = 0;
1314
+ channel.isDrum = false;
1283
1315
  }
1284
1316
  this.channels[9].bank = 128;
1317
+ this.channels[9].isDrum = true;
1285
1318
  }
1286
1319
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1287
1320
  switch (data[2]) {
@@ -1324,13 +1357,20 @@ class MidyGMLite {
1324
1357
  }
1325
1358
  scheduleTask(callback, scheduleTime) {
1326
1359
  return new Promise((resolve) => {
1327
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1360
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1361
+ buffer: this.schedulerBuffer,
1362
+ });
1363
+ bufferSource.connect(this.scheduler);
1328
1364
  bufferSource.onended = () => {
1329
- callback();
1330
- resolve();
1365
+ try {
1366
+ callback();
1367
+ }
1368
+ finally {
1369
+ bufferSource.disconnect();
1370
+ resolve();
1371
+ }
1331
1372
  };
1332
1373
  bufferSource.start(scheduleTime);
1333
- bufferSource.stop(scheduleTime);
1334
1374
  });
1335
1375
  }
1336
1376
  }
@@ -1341,6 +1381,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1341
1381
  writable: true,
1342
1382
  value: {
1343
1383
  currentBufferSource: null,
1384
+ isDrum: false,
1344
1385
  detune: 0,
1345
1386
  program: 0,
1346
1387
  bank: 0,
@@ -1348,5 +1389,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1348
1389
  dataLSB: 0,
1349
1390
  rpnMSB: 127,
1350
1391
  rpnLSB: 127,
1392
+ modulationDepthRange: 50, // cent
1351
1393
  }
1352
1394
  });
package/script/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;