@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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",
@@ -1,6 +1,7 @@
1
1
  export class MidyGM1 {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ isDrum: boolean;
4
5
  detune: number;
5
6
  program: number;
6
7
  bank: number;
@@ -8,11 +9,12 @@ export class MidyGM1 {
8
9
  dataLSB: number;
9
10
  rpnMSB: number;
10
11
  rpnLSB: number;
12
+ modulationDepthRange: number;
11
13
  fineTuning: number;
12
14
  coarseTuning: number;
13
- modulationDepthRange: number;
14
15
  };
15
16
  constructor(audioContext: any);
17
+ mode: string;
16
18
  ticksPerBeat: number;
17
19
  totalTime: number;
18
20
  noteCheckInterval: number;
@@ -35,6 +37,8 @@ export class MidyGM1 {
35
37
  exclusiveClassMap: SparseMap;
36
38
  audioContext: any;
37
39
  masterVolume: any;
40
+ scheduler: any;
41
+ schedulerBuffer: any;
38
42
  voiceParamsHandlers: {
39
43
  modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
40
44
  vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
@@ -73,7 +77,7 @@ export class MidyGM1 {
73
77
  };
74
78
  createChannels(audioContext: any): any[];
75
79
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
76
- createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
80
+ createBufferSource(audioBuffer: any, voiceParams: any): any;
77
81
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
78
82
  getQueueIndex(second: any): number;
79
83
  playNotes(): Promise<any>;
@@ -93,7 +97,7 @@ export class MidyGM1 {
93
97
  seekTo(second: any): void;
94
98
  calcTotalTime(): number;
95
99
  currentTime(): number;
96
- processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
100
+ processScheduledNotes(channel: any, callback: any): void;
97
101
  getActiveNotes(channel: any, scheduleTime: any): SparseMap;
98
102
  getActiveNote(noteList: any, scheduleTime: any): any;
99
103
  cbToRatio(cb: any): number;
@@ -115,7 +119,7 @@ export class MidyGM1 {
115
119
  stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
116
120
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
117
121
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
118
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
122
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
119
123
  handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
120
124
  handleProgramChange(channelNumber: any, program: any, _scheduleTime: any): void;
121
125
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
@@ -174,15 +178,15 @@ export class MidyGM1 {
174
178
  dataEntryMSB(channelNumber: any, value: any, scheduleTime: any): void;
175
179
  handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
176
180
  setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
177
- handleFineTuningRPN(channelNumber: any): void;
178
- setFineTuning(channelNumber: any, value: any): void;
179
- handleCoarseTuningRPN(channelNumber: any): void;
180
- setCoarseTuning(channelNumber: any, value: any): void;
181
+ handleFineTuningRPN(channelNumber: any, scheduleTime: any): void;
182
+ setFineTuning(channelNumber: any, value: any, scheduleTime: any): void;
183
+ handleCoarseTuningRPN(channelNumber: any, scheduleTime: any): void;
184
+ setCoarseTuning(channelNumber: any, value: any, scheduleTime: any): void;
181
185
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
182
186
  resetAllControllers(channelNumber: any): void;
183
187
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
184
- handleUniversalNonRealTimeExclusiveMessage(data: any, _scheduleTime: any): void;
185
- GM1SystemOn(): void;
188
+ handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
189
+ GM1SystemOn(scheduleTime: any): void;
186
190
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
187
191
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
188
192
  setMasterVolume(volume: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAiJA;IAsBE;;;;;;;;;;;;MAYE;IAEF,+BAQC;IA3CD,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;IAiBrC,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,qCAMC;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,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,8CAKC;IAED,oDAOC;IAED,gDAKC;IAED,sDAOC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,gFAgBC;IAED,oBAMC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAUC;CACF;AA95CD;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-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAiJA;IAuBE;;;;;;;;;;;;;MAaE;IAEF,+BAcC;IAnDD,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;IAkBrC,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,4DASC;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,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHAwCC;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,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAWC;IAED,iEAKC;IAED,uEASC;IAED,mEAKC;IAED,yEASC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAgBC;CACF;AA97CD;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 MidyGM1 {
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 MidyGM1 {
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 MidyGM1 {
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,7 +404,7 @@ class MidyGM1 {
391
404
  return audioBuffer;
392
405
  }
393
406
  }
394
- createNoteBufferNode(audioBuffer, voiceParams) {
407
+ createBufferSource(audioBuffer, voiceParams) {
395
408
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
396
409
  bufferSource.buffer = audioBuffer;
397
410
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
@@ -415,7 +428,7 @@ class MidyGM1 {
415
428
  }
416
429
  /* falls through */
417
430
  case "noteOff": {
418
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime);
431
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
419
432
  if (notePromise) {
420
433
  this.notePromises.push(notePromise);
421
434
  }
@@ -579,15 +592,10 @@ class MidyGM1 {
579
592
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
580
593
  const channel = this.channels[channelNumber];
581
594
  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
- }
595
+ this.processScheduledNotes(channel, (note) => {
596
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
597
+ this.notePromises.push(promise);
598
+ promises.push(promise);
591
599
  });
592
600
  channel.scheduledNotes.clear();
593
601
  return Promise.all(promises);
@@ -643,14 +651,12 @@ class MidyGM1 {
643
651
  const now = this.audioContext.currentTime;
644
652
  return this.resumeTime + now - this.startTime - this.startDelay;
645
653
  }
646
- processScheduledNotes(channel, scheduleTime, callback) {
654
+ processScheduledNotes(channel, callback) {
647
655
  channel.scheduledNotes.forEach((noteList) => {
648
656
  for (let i = 0; i < noteList.length; i++) {
649
657
  const note = noteList[i];
650
658
  if (!note)
651
659
  continue;
652
- if (scheduleTime < note.startTime)
653
- continue;
654
660
  callback(note);
655
661
  }
656
662
  });
@@ -696,7 +702,7 @@ class MidyGM1 {
696
702
  return tuning + pitch;
697
703
  }
698
704
  updateChannelDetune(channel, scheduleTime) {
699
- this.processScheduledNotes(channel, scheduleTime, (note) => {
705
+ this.processScheduledNotes(channel, (note) => {
700
706
  this.updateDetune(channel, note, scheduleTime);
701
707
  });
702
708
  }
@@ -814,7 +820,7 @@ class MidyGM1 {
814
820
  const voiceParams = voice.getAllParams(controllerState);
815
821
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
816
822
  const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
817
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
823
+ note.bufferSource = this.createBufferSource(audioBuffer, voiceParams);
818
824
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
819
825
  note.filterNode = new BiquadFilterNode(this.audioContext, {
820
826
  type: "lowpass",
@@ -845,15 +851,17 @@ class MidyGM1 {
845
851
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
846
852
  note.volumeEnvelopeNode.connect(channel.gainL);
847
853
  note.volumeEnvelopeNode.connect(channel.gainR);
854
+ if (0.5 <= channel.state.sustainPedal) {
855
+ channel.sustainNotes.push(note);
856
+ }
848
857
  const exclusiveClass = note.voiceParams.exclusiveClass;
849
858
  if (exclusiveClass !== 0) {
850
859
  if (this.exclusiveClassMap.has(exclusiveClass)) {
851
860
  const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
852
861
  const [prevNote, prevChannelNumber] = prevEntry;
853
- if (!prevNote.ending) {
862
+ if (prevNote && !prevNote.ending) {
854
863
  this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
855
- startTime, undefined, // portamentoNoteNumber
856
- true);
864
+ startTime, true);
857
865
  }
858
866
  }
859
867
  this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
@@ -897,7 +905,7 @@ class MidyGM1 {
897
905
  }
898
906
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
899
907
  const channel = this.channels[channelNumber];
900
- if (!force && 0.5 < channel.state.sustainPedal)
908
+ if (!force && 0.5 <= channel.state.sustainPedal)
901
909
  return;
902
910
  if (!channel.scheduledNotes.has(noteNumber))
903
911
  return;
@@ -925,11 +933,11 @@ class MidyGM1 {
925
933
  const velocity = halfVelocity * 2;
926
934
  const channel = this.channels[channelNumber];
927
935
  const promises = [];
928
- this.processScheduledNotes(channel, scheduleTime, (note) => {
929
- const { noteNumber } = note;
930
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
936
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
937
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
931
938
  promises.push(promise);
932
- });
939
+ }
940
+ channel.sustainNotes = [];
933
941
  return promises;
934
942
  }
935
943
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
@@ -959,8 +967,10 @@ class MidyGM1 {
959
967
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
960
968
  }
961
969
  setPitchBend(channelNumber, value, scheduleTime) {
962
- scheduleTime ??= this.audioContext.currentTime;
963
970
  const channel = this.channels[channelNumber];
971
+ if (channel.isDrum)
972
+ return;
973
+ scheduleTime ??= this.audioContext.currentTime;
964
974
  const state = channel.state;
965
975
  const prev = state.pitchWheel * 2 - 1;
966
976
  const next = (value - 8192) / 8192;
@@ -1040,48 +1050,43 @@ class MidyGM1 {
1040
1050
  return state;
1041
1051
  }
1042
1052
  applyVoiceParams(channel, controllerType, scheduleTime) {
1043
- channel.scheduledNotes.forEach((noteList) => {
1044
- for (let i = 0; i < noteList.length; i++) {
1045
- const note = noteList[i];
1046
- if (!note)
1053
+ this.processScheduledNotes(channel, (note) => {
1054
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1055
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1056
+ let appliedFilterEnvelope = false;
1057
+ let appliedVolumeEnvelope = false;
1058
+ for (const [key, value] of Object.entries(voiceParams)) {
1059
+ const prevValue = note.voiceParams[key];
1060
+ if (value === prevValue)
1047
1061
  continue;
1048
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1049
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1050
- let appliedFilterEnvelope = false;
1051
- let appliedVolumeEnvelope = false;
1052
- for (const [key, value] of Object.entries(voiceParams)) {
1053
- const prevValue = note.voiceParams[key];
1054
- if (value === prevValue)
1062
+ note.voiceParams[key] = value;
1063
+ if (key in this.voiceParamsHandlers) {
1064
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1065
+ }
1066
+ else if (filterEnvelopeKeySet.has(key)) {
1067
+ if (appliedFilterEnvelope)
1055
1068
  continue;
1056
- note.voiceParams[key] = value;
1057
- if (key in this.voiceParamsHandlers) {
1058
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1059
- }
1060
- else if (filterEnvelopeKeySet.has(key)) {
1061
- if (appliedFilterEnvelope)
1062
- continue;
1063
- appliedFilterEnvelope = true;
1064
- const noteVoiceParams = note.voiceParams;
1065
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1066
- const key = filterEnvelopeKeys[i];
1067
- if (key in voiceParams)
1068
- noteVoiceParams[key] = voiceParams[key];
1069
- }
1070
- this.setFilterEnvelope(note, scheduleTime);
1071
- this.setPitchEnvelope(note, scheduleTime);
1069
+ appliedFilterEnvelope = true;
1070
+ const noteVoiceParams = note.voiceParams;
1071
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1072
+ const key = filterEnvelopeKeys[i];
1073
+ if (key in voiceParams)
1074
+ noteVoiceParams[key] = voiceParams[key];
1072
1075
  }
1073
- else if (volumeEnvelopeKeySet.has(key)) {
1074
- if (appliedVolumeEnvelope)
1075
- continue;
1076
- appliedVolumeEnvelope = true;
1077
- const noteVoiceParams = note.voiceParams;
1078
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1079
- const key = volumeEnvelopeKeys[i];
1080
- if (key in voiceParams)
1081
- noteVoiceParams[key] = voiceParams[key];
1082
- }
1083
- this.setVolumeEnvelope(channel, note, scheduleTime);
1076
+ this.setFilterEnvelope(note, scheduleTime);
1077
+ this.setPitchEnvelope(note, scheduleTime);
1078
+ }
1079
+ else if (volumeEnvelopeKeySet.has(key)) {
1080
+ if (appliedVolumeEnvelope)
1081
+ continue;
1082
+ appliedVolumeEnvelope = true;
1083
+ const noteVoiceParams = note.voiceParams;
1084
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1085
+ const key = volumeEnvelopeKeys[i];
1086
+ if (key in voiceParams)
1087
+ noteVoiceParams[key] = voiceParams[key];
1084
1088
  }
1089
+ this.setVolumeEnvelope(note, scheduleTime);
1085
1090
  }
1086
1091
  }
1087
1092
  });
@@ -1114,9 +1119,8 @@ class MidyGM1 {
1114
1119
  }
1115
1120
  }
1116
1121
  updateModulation(channel, scheduleTime) {
1117
- scheduleTime ??= this.audioContext.currentTime;
1118
1122
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1119
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1123
+ this.processScheduledNotes(channel, (note) => {
1120
1124
  if (note.modulationDepth) {
1121
1125
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1122
1126
  }
@@ -1128,10 +1132,14 @@ class MidyGM1 {
1128
1132
  }
1129
1133
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1130
1134
  const channel = this.channels[channelNumber];
1135
+ if (channel.isDrum)
1136
+ return;
1137
+ scheduleTime ??= this.audioContext.currentTime;
1131
1138
  channel.state.modulationDepth = modulation / 127;
1132
1139
  this.updateModulation(channel, scheduleTime);
1133
1140
  }
1134
1141
  setVolume(channelNumber, volume, scheduleTime) {
1142
+ scheduleTime ??= this.audioContext.currentTime;
1135
1143
  const channel = this.channels[channelNumber];
1136
1144
  channel.state.volume = volume / 127;
1137
1145
  this.updateChannelVolume(channel, scheduleTime);
@@ -1144,11 +1152,13 @@ class MidyGM1 {
1144
1152
  };
1145
1153
  }
1146
1154
  setPan(channelNumber, pan, scheduleTime) {
1155
+ scheduleTime ??= this.audioContext.currentTime;
1147
1156
  const channel = this.channels[channelNumber];
1148
1157
  channel.state.pan = pan / 127;
1149
1158
  this.updateChannelVolume(channel, scheduleTime);
1150
1159
  }
1151
1160
  setExpression(channelNumber, expression, scheduleTime) {
1161
+ scheduleTime ??= this.audioContext.currentTime;
1152
1162
  const channel = this.channels[channelNumber];
1153
1163
  channel.state.expression = expression / 127;
1154
1164
  this.updateChannelVolume(channel, scheduleTime);
@@ -1169,9 +1179,17 @@ class MidyGM1 {
1169
1179
  .setValueAtTime(volume * gainRight, scheduleTime);
1170
1180
  }
1171
1181
  setSustainPedal(channelNumber, value, scheduleTime) {
1182
+ const channel = this.channels[channelNumber];
1183
+ if (channel.isDrum)
1184
+ return;
1172
1185
  scheduleTime ??= this.audioContext.currentTime;
1173
- this.channels[channelNumber].state.sustainPedal = value / 127;
1174
- if (value < 64) {
1186
+ channel.state.sustainPedal = value / 127;
1187
+ if (64 <= value) {
1188
+ this.processScheduledNotes(channel, (note) => {
1189
+ channel.sustainNotes.push(note);
1190
+ });
1191
+ }
1192
+ else {
1175
1193
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1176
1194
  }
1177
1195
  }
@@ -1209,10 +1227,10 @@ class MidyGM1 {
1209
1227
  this.handlePitchBendRangeRPN(channelNumber, scheduleTime);
1210
1228
  break;
1211
1229
  case 1:
1212
- this.handleFineTuningRPN(channelNumber);
1230
+ this.handleFineTuningRPN(channelNumber, scheduleTime);
1213
1231
  break;
1214
1232
  case 2:
1215
- this.handleCoarseTuningRPN(channelNumber);
1233
+ this.handleCoarseTuningRPN(channelNumber, scheduleTime);
1216
1234
  break;
1217
1235
  default:
1218
1236
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -1235,8 +1253,10 @@ class MidyGM1 {
1235
1253
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1236
1254
  }
1237
1255
  setPitchBendRange(channelNumber, value, scheduleTime) {
1238
- scheduleTime ??= this.audioContext.currentTime;
1239
1256
  const channel = this.channels[channelNumber];
1257
+ if (channel.isDrum)
1258
+ return;
1259
+ scheduleTime ??= this.audioContext.currentTime;
1240
1260
  const state = channel.state;
1241
1261
  const prev = state.pitchWheelSensitivity;
1242
1262
  const next = value / 128;
@@ -1245,33 +1265,39 @@ class MidyGM1 {
1245
1265
  this.updateChannelDetune(channel, scheduleTime);
1246
1266
  this.applyVoiceParams(channel, 16, scheduleTime);
1247
1267
  }
1248
- handleFineTuningRPN(channelNumber) {
1268
+ handleFineTuningRPN(channelNumber, scheduleTime) {
1249
1269
  const channel = this.channels[channelNumber];
1250
1270
  this.limitData(channel, 0, 127, 0, 127);
1251
1271
  const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1252
- this.setFineTuning(channelNumber, fineTuning);
1272
+ this.setFineTuning(channelNumber, fineTuning, scheduleTime);
1253
1273
  }
1254
- setFineTuning(channelNumber, value) {
1274
+ setFineTuning(channelNumber, value, scheduleTime) {
1255
1275
  const channel = this.channels[channelNumber];
1276
+ if (channel.isDrum)
1277
+ return;
1278
+ scheduleTime ??= this.audioContext.currentTime;
1256
1279
  const prev = channel.fineTuning;
1257
1280
  const next = (value - 8192) / 8.192; // cent
1258
1281
  channel.fineTuning = next;
1259
1282
  channel.detune += next - prev;
1260
- this.updateChannelDetune(channel);
1283
+ this.updateChannelDetune(channel, scheduleTime);
1261
1284
  }
1262
- handleCoarseTuningRPN(channelNumber) {
1285
+ handleCoarseTuningRPN(channelNumber, scheduleTime) {
1263
1286
  const channel = this.channels[channelNumber];
1264
1287
  this.limitDataMSB(channel, 0, 127);
1265
1288
  const coarseTuning = channel.dataMSB;
1266
- this.setCoarseTuning(channelNumber, coarseTuning);
1289
+ this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
1267
1290
  }
1268
- setCoarseTuning(channelNumber, value) {
1291
+ setCoarseTuning(channelNumber, value, scheduleTime) {
1269
1292
  const channel = this.channels[channelNumber];
1293
+ if (channel.isDrum)
1294
+ return;
1295
+ scheduleTime ??= this.audioContext.currentTime;
1270
1296
  const prev = channel.coarseTuning;
1271
1297
  const next = (value - 64) * 100; // cent
1272
1298
  channel.coarseTuning = next;
1273
1299
  channel.detune += next - prev;
1274
- this.updateChannelDetune(channel);
1300
+ this.updateChannelDetune(channel, scheduleTime);
1275
1301
  }
1276
1302
  allSoundOff(channelNumber, _value, scheduleTime) {
1277
1303
  scheduleTime ??= this.audioContext.currentTime;
@@ -1303,12 +1329,12 @@ class MidyGM1 {
1303
1329
  scheduleTime ??= this.audioContext.currentTime;
1304
1330
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1305
1331
  }
1306
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1332
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1307
1333
  switch (data[2]) {
1308
1334
  case 9:
1309
1335
  switch (data[3]) {
1310
1336
  case 1:
1311
- this.GM1SystemOn();
1337
+ this.GM1SystemOn(scheduleTime);
1312
1338
  break;
1313
1339
  case 2: // GM System Off
1314
1340
  break;
@@ -1320,12 +1346,17 @@ class MidyGM1 {
1320
1346
  console.warn(`Unsupported Exclusive Message: ${data}`);
1321
1347
  }
1322
1348
  }
1323
- GM1SystemOn() {
1349
+ GM1SystemOn(scheduleTime) {
1350
+ scheduleTime ??= this.audioContext.currentTime;
1351
+ this.mode = "GM1";
1324
1352
  for (let i = 0; i < this.channels.length; i++) {
1353
+ this.allSoundOff(i, 0, scheduleTime);
1325
1354
  const channel = this.channels[i];
1326
1355
  channel.bank = 0;
1356
+ channel.isDrum = false;
1327
1357
  }
1328
1358
  this.channels[9].bank = 128;
1359
+ this.channels[9].isDrum = true;
1329
1360
  }
1330
1361
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1331
1362
  switch (data[2]) {
@@ -1368,13 +1399,20 @@ class MidyGM1 {
1368
1399
  }
1369
1400
  scheduleTask(callback, scheduleTime) {
1370
1401
  return new Promise((resolve) => {
1371
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1402
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1403
+ buffer: this.schedulerBuffer,
1404
+ });
1405
+ bufferSource.connect(this.scheduler);
1372
1406
  bufferSource.onended = () => {
1373
- callback();
1374
- resolve();
1407
+ try {
1408
+ callback();
1409
+ }
1410
+ finally {
1411
+ bufferSource.disconnect();
1412
+ resolve();
1413
+ }
1375
1414
  };
1376
1415
  bufferSource.start(scheduleTime);
1377
- bufferSource.stop(scheduleTime);
1378
1416
  });
1379
1417
  }
1380
1418
  }
@@ -1385,6 +1423,7 @@ Object.defineProperty(MidyGM1, "channelSettings", {
1385
1423
  writable: true,
1386
1424
  value: {
1387
1425
  currentBufferSource: null,
1426
+ isDrum: false,
1388
1427
  detune: 0,
1389
1428
  program: 0,
1390
1429
  bank: 0,
@@ -1392,8 +1431,8 @@ Object.defineProperty(MidyGM1, "channelSettings", {
1392
1431
  dataLSB: 0,
1393
1432
  rpnMSB: 127,
1394
1433
  rpnLSB: 127,
1434
+ modulationDepthRange: 50, // cent
1395
1435
  fineTuning: 0, // cb
1396
1436
  coarseTuning: 0, // cb
1397
- modulationDepthRange: 50, // cent
1398
1437
  }
1399
1438
  });