@marmooo/midy 0.2.8 → 0.3.0

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,15 +1,17 @@
1
1
  export class MidyGMLite {
2
2
  static channelSettings: {
3
- currentBufferSource: null;
4
3
  detune: number;
5
- program: number;
4
+ programNumber: number;
6
5
  bank: number;
7
6
  dataMSB: number;
8
7
  dataLSB: number;
9
8
  rpnMSB: number;
10
9
  rpnLSB: number;
10
+ modulationDepthRange: number;
11
11
  };
12
12
  constructor(audioContext: any);
13
+ mode: string;
14
+ numChannels: number;
13
15
  ticksPerBeat: number;
14
16
  totalTime: number;
15
17
  noteCheckInterval: number;
@@ -29,9 +31,12 @@ export class MidyGMLite {
29
31
  timeline: any[];
30
32
  instruments: any[];
31
33
  notePromises: any[];
32
- exclusiveClassMap: SparseMap;
34
+ exclusiveClassNotes: any[];
35
+ drumExclusiveClassNotes: any[];
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,7 @@ 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
+ createBufferSource(voiceParams: any, audioBuffer: any): any;
74
79
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
75
80
  getQueueIndex(second: any): number;
76
81
  playNotes(): Promise<any>;
@@ -105,16 +110,19 @@ export class MidyGMLite {
105
110
  clampCutoffFrequency(frequency: any): number;
106
111
  setFilterEnvelope(note: any, scheduleTime: any): void;
107
112
  startModulation(channel: any, note: any, scheduleTime: any): void;
108
- getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
113
+ getAudioBuffer(programNumber: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
109
114
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
115
+ handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
116
+ handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
110
117
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
111
118
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
119
+ disconnectNote(note: any, scheduledNotes: any, index: any): void;
112
120
  stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
113
121
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
114
122
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
115
123
  releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
116
124
  handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
117
- handleProgramChange(channelNumber: any, program: any, _scheduleTime: any): void;
125
+ handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
118
126
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
119
127
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
120
128
  setModLfoToPitch(channel: any, note: any, scheduleTime: any): void;
@@ -171,10 +179,11 @@ export class MidyGMLite {
171
179
  handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
172
180
  setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
173
181
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
182
+ resetAllStates(channelNumber: any): void;
174
183
  resetAllControllers(channelNumber: any): void;
175
184
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
176
- handleUniversalNonRealTimeExclusiveMessage(data: any, _scheduleTime: any): void;
177
- GM1SystemOn(): void;
185
+ handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
186
+ GM1SystemOn(scheduleTime: any): void;
178
187
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
179
188
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
180
189
  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,yCAWC;IAED,6DA2BC;IAED,8DASC;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,gHAwCC;IAED,kGAkDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAWC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;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;AAj3CD;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":"AA+JA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,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,2BAAqC;IACrC,+BAEE;IAcA,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,yCAaC;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,aAMC;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,+GA0BC;IAED,gHAwCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAmDC;IAED,6FAQC;IAED,iEAUC;IAED,qFAgBC;IAED,yHAwBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAUC;IAGD,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA79CD;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"}
@@ -103,6 +103,19 @@ class Note {
103
103
  this.voiceParams = voiceParams;
104
104
  }
105
105
  }
106
+ const drumExclusiveClasses = new Uint8Array(128);
107
+ drumExclusiveClasses[42] = 1;
108
+ drumExclusiveClasses[44] = 1;
109
+ drumExclusiveClasses[46] = 1, // HH
110
+ drumExclusiveClasses[71] = 2;
111
+ drumExclusiveClasses[72] = 2; // Whistle
112
+ drumExclusiveClasses[73] = 3;
113
+ drumExclusiveClasses[74] = 3; // Guiro
114
+ drumExclusiveClasses[78] = 4;
115
+ drumExclusiveClasses[79] = 4; // Cuica
116
+ drumExclusiveClasses[80] = 5;
117
+ drumExclusiveClasses[81] = 5; // Triangle
118
+ const drumExclusiveClassCount = 5;
106
119
  // normalized to 0-1 for use with the SF2 modulator model
107
120
  const defaultControllerState = {
108
121
  noteOnVelocity: { type: 2, defaultValue: 0 },
@@ -170,6 +183,18 @@ const volumeEnvelopeKeys = [
170
183
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
171
184
  export class MidyGMLite {
172
185
  constructor(audioContext) {
186
+ Object.defineProperty(this, "mode", {
187
+ enumerable: true,
188
+ configurable: true,
189
+ writable: true,
190
+ value: "GM1"
191
+ });
192
+ Object.defineProperty(this, "numChannels", {
193
+ enumerable: true,
194
+ configurable: true,
195
+ writable: true,
196
+ value: 16
197
+ });
173
198
  Object.defineProperty(this, "ticksPerBeat", {
174
199
  enumerable: true,
175
200
  configurable: true,
@@ -284,18 +309,30 @@ export class MidyGMLite {
284
309
  writable: true,
285
310
  value: []
286
311
  });
287
- Object.defineProperty(this, "exclusiveClassMap", {
312
+ Object.defineProperty(this, "exclusiveClassNotes", {
288
313
  enumerable: true,
289
314
  configurable: true,
290
315
  writable: true,
291
- value: new SparseMap(128)
316
+ value: new Array(128)
317
+ });
318
+ Object.defineProperty(this, "drumExclusiveClassNotes", {
319
+ enumerable: true,
320
+ configurable: true,
321
+ writable: true,
322
+ value: new Array(this.numChannels * drumExclusiveClassCount)
292
323
  });
293
324
  this.audioContext = audioContext;
294
325
  this.masterVolume = new GainNode(audioContext);
326
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
327
+ this.schedulerBuffer = new AudioBuffer({
328
+ length: 1,
329
+ sampleRate: audioContext.sampleRate,
330
+ });
295
331
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
296
332
  this.controlChangeHandlers = this.createControlChangeHandlers();
297
333
  this.channels = this.createChannels(audioContext);
298
334
  this.masterVolume.connect(audioContext.destination);
335
+ this.scheduler.connect(audioContext.destination);
299
336
  this.GM1SystemOn();
300
337
  }
301
338
  initSoundFontTable() {
@@ -349,8 +386,10 @@ export class MidyGMLite {
349
386
  };
350
387
  }
351
388
  createChannels(audioContext) {
352
- const channels = Array.from({ length: 16 }, () => {
389
+ const channels = Array.from({ length: this.numChannels }, () => {
353
390
  return {
391
+ currentBufferSource: null,
392
+ isDrum: false,
354
393
  ...this.constructor.channelSettings,
355
394
  state: new ControllerState(),
356
395
  ...this.setChannelAudioNodes(audioContext),
@@ -389,7 +428,7 @@ export class MidyGMLite {
389
428
  return audioBuffer;
390
429
  }
391
430
  }
392
- createNoteBufferNode(audioBuffer, voiceParams) {
431
+ createBufferSource(voiceParams, audioBuffer) {
393
432
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
394
433
  bufferSource.buffer = audioBuffer;
395
434
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
@@ -455,7 +494,7 @@ export class MidyGMLite {
455
494
  if (queueIndex >= this.timeline.length) {
456
495
  await Promise.all(this.notePromises);
457
496
  this.notePromises = [];
458
- this.exclusiveClassMap.clear();
497
+ this.exclusiveClassNotes.flll(undefined);
459
498
  this.audioBufferCache.clear();
460
499
  resolve();
461
500
  return;
@@ -474,7 +513,7 @@ export class MidyGMLite {
474
513
  else if (this.isStopping) {
475
514
  await this.stopNotes(0, true, now);
476
515
  this.notePromises = [];
477
- this.exclusiveClassMap.clear();
516
+ this.exclusiveClassNotes.fill(undefined);
478
517
  this.audioBufferCache.clear();
479
518
  resolve();
480
519
  this.isStopping = false;
@@ -483,7 +522,7 @@ export class MidyGMLite {
483
522
  }
484
523
  else if (this.isSeeking) {
485
524
  this.stopNotes(0, true, now);
486
- this.exclusiveClassMap.clear();
525
+ this.exclusiveClassNotes.fill(undefined);
487
526
  this.startTime = this.audioContext.currentTime;
488
527
  queueIndex = this.getQueueIndex(this.resumeTime);
489
528
  offset = this.resumeTime - this.startTime;
@@ -511,7 +550,7 @@ export class MidyGMLite {
511
550
  extractMidiData(midi) {
512
551
  const instruments = new Set();
513
552
  const timeline = [];
514
- const tmpChannels = new Array(16);
553
+ const tmpChannels = new Array(this.channels.length);
515
554
  for (let i = 0; i < tmpChannels.length; i++) {
516
555
  tmpChannels[i] = {
517
556
  programNumber: -1,
@@ -603,6 +642,9 @@ export class MidyGMLite {
603
642
  if (!this.isPlaying)
604
643
  return;
605
644
  this.isStopping = true;
645
+ for (let i = 0; i < this.channels.length; i++) {
646
+ this.resetAllStates(i);
647
+ }
606
648
  }
607
649
  pause() {
608
650
  if (!this.isPlaying || this.isPaused)
@@ -778,8 +820,8 @@ export class MidyGMLite {
778
820
  note.modulationLFO.connect(note.volumeDepth);
779
821
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
780
822
  }
781
- async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
782
- const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
823
+ async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
824
+ const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
783
825
  const cache = this.audioBufferCache.get(audioBufferId);
784
826
  if (cache) {
785
827
  cache.counter += 1;
@@ -802,8 +844,8 @@ export class MidyGMLite {
802
844
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
803
845
  const voiceParams = voice.getAllParams(controllerState);
804
846
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
805
- const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
806
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
847
+ const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
848
+ note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
807
849
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
808
850
  note.filterNode = new BiquadFilterNode(this.audioContext, {
809
851
  type: "lowpass",
@@ -820,14 +862,43 @@ export class MidyGMLite {
820
862
  note.bufferSource.start(startTime);
821
863
  return note;
822
864
  }
865
+ handleExclusiveClass(note, channelNumber, startTime) {
866
+ const exclusiveClass = note.voiceParams.exclusiveClass;
867
+ if (exclusiveClass === 0)
868
+ return;
869
+ const prev = this.exclusiveClassNotes[exclusiveClass];
870
+ if (prev) {
871
+ const [prevNote, prevChannelNumber] = prev;
872
+ if (prevNote && !prevNote.ending) {
873
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
874
+ startTime, true);
875
+ }
876
+ }
877
+ this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
878
+ }
879
+ handleDrumExclusiveClass(note, channelNumber, startTime) {
880
+ const channel = this.channels[channelNumber];
881
+ if (!channel.isDrum)
882
+ return;
883
+ const drumExclusiveClass = drumExclusiveClasses[noteNumber];
884
+ if (drumExclusiveClass === 0)
885
+ return;
886
+ const index = drumExclusiveClass * this.channels.length + channelNumber;
887
+ const prevNote = this.drumExclusiveClassNotes[index];
888
+ if (prevNote && !prevNote.ending) {
889
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
890
+ startTime, true);
891
+ }
892
+ this.drumExclusiveClassNotes[index] = note;
893
+ }
823
894
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
824
895
  const channel = this.channels[channelNumber];
825
896
  const bankNumber = channel.bank;
826
- const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
897
+ const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
827
898
  if (soundFontIndex === undefined)
828
899
  return;
829
900
  const soundFont = this.soundFonts[soundFontIndex];
830
- const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
901
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
831
902
  if (!voice)
832
903
  return;
833
904
  const isSF3 = soundFont.parsed.info.version.major === 3;
@@ -837,30 +908,45 @@ export class MidyGMLite {
837
908
  if (0.5 <= channel.state.sustainPedal) {
838
909
  channel.sustainNotes.push(note);
839
910
  }
840
- const exclusiveClass = note.voiceParams.exclusiveClass;
841
- if (exclusiveClass !== 0) {
842
- if (this.exclusiveClassMap.has(exclusiveClass)) {
843
- const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
844
- const [prevNote, prevChannelNumber] = prevEntry;
845
- if (!prevNote.ending) {
846
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
847
- startTime, true);
848
- }
849
- }
850
- this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
851
- }
911
+ this.handleExclusiveClass(note, channelNumber, startTime);
912
+ this.handleDrumExclusiveClass(note, channelNumber, startTime);
852
913
  const scheduledNotes = channel.scheduledNotes;
853
- if (scheduledNotes.has(noteNumber)) {
854
- scheduledNotes.get(noteNumber).push(note);
914
+ let notes = scheduledNotes.get(noteNumber);
915
+ if (notes) {
916
+ notes.push(note);
855
917
  }
856
918
  else {
857
- scheduledNotes.set(noteNumber, [note]);
919
+ notes = [note];
920
+ scheduledNotes.set(noteNumber, notes);
921
+ }
922
+ if (this.isDrumNoteOffException(channel, noteNumber)) {
923
+ const stopTime = startTime + note.bufferSource.buffer.duration;
924
+ const index = notes.length - 1;
925
+ const promise = new Promise((resolve) => {
926
+ note.bufferSource.onended = () => {
927
+ this.disconnectNote(note, scheduledNotes, index);
928
+ resolve();
929
+ };
930
+ note.bufferSource.stop(stopTime);
931
+ });
932
+ this.notePromises.push(promise);
858
933
  }
859
934
  }
860
935
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
861
936
  scheduleTime ??= this.audioContext.currentTime;
862
937
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
863
938
  }
939
+ disconnectNote(note, scheduledNotes, index) {
940
+ scheduledNotes[index] = null;
941
+ note.bufferSource.disconnect();
942
+ note.filterNode.disconnect();
943
+ note.volumeEnvelopeNode.disconnect();
944
+ if (note.modulationDepth) {
945
+ note.volumeDepth.disconnect();
946
+ note.modulationDepth.disconnect();
947
+ note.modulationLFO.stop();
948
+ }
949
+ }
864
950
  stopNote(endTime, stopTime, scheduledNotes, index) {
865
951
  const note = scheduledNotes[index];
866
952
  note.volumeEnvelopeNode.gain
@@ -872,15 +958,7 @@ export class MidyGMLite {
872
958
  }, stopTime);
873
959
  return new Promise((resolve) => {
874
960
  note.bufferSource.onended = () => {
875
- scheduledNotes[index] = null;
876
- note.bufferSource.disconnect();
877
- note.filterNode.disconnect();
878
- note.volumeEnvelopeNode.disconnect();
879
- if (note.modulationDepth) {
880
- note.volumeDepth.disconnect();
881
- note.modulationDepth.disconnect();
882
- note.modulationLFO.stop();
883
- }
961
+ this.disconnectNote(note, scheduledNotes, index);
884
962
  resolve();
885
963
  };
886
964
  note.bufferSource.stop(stopTime);
@@ -888,6 +966,8 @@ export class MidyGMLite {
888
966
  }
889
967
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
890
968
  const channel = this.channels[channelNumber];
969
+ if (channel.isDrum)
970
+ return;
891
971
  if (!force && 0.5 <= channel.state.sustainPedal)
892
972
  return;
893
973
  if (!channel.scheduledNotes.has(noteNumber))
@@ -941,17 +1021,17 @@ export class MidyGMLite {
941
1021
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
942
1022
  }
943
1023
  }
944
- handleProgramChange(channelNumber, program, _scheduleTime) {
1024
+ handleProgramChange(channelNumber, programNumber, _scheduleTime) {
945
1025
  const channel = this.channels[channelNumber];
946
- channel.program = program;
1026
+ channel.programNumber = programNumber;
947
1027
  }
948
1028
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
949
1029
  const pitchBend = msb * 128 + lsb;
950
1030
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
951
1031
  }
952
1032
  setPitchBend(channelNumber, value, scheduleTime) {
953
- scheduleTime ??= this.audioContext.currentTime;
954
1033
  const channel = this.channels[channelNumber];
1034
+ scheduleTime ??= this.audioContext.currentTime;
955
1035
  const state = channel.state;
956
1036
  const prev = state.pitchWheel * 2 - 1;
957
1037
  const next = (value - 8192) / 8192;
@@ -1100,7 +1180,6 @@ export class MidyGMLite {
1100
1180
  }
1101
1181
  }
1102
1182
  updateModulation(channel, scheduleTime) {
1103
- scheduleTime ??= this.audioContext.currentTime;
1104
1183
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1105
1184
  this.processScheduledNotes(channel, (note) => {
1106
1185
  if (note.modulationDepth) {
@@ -1113,8 +1192,8 @@ export class MidyGMLite {
1113
1192
  });
1114
1193
  }
1115
1194
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1116
- scheduleTime ??= this.audioContext.currentTime;
1117
1195
  const channel = this.channels[channelNumber];
1196
+ scheduleTime ??= this.audioContext.currentTime;
1118
1197
  channel.state.modulationDepth = modulation / 127;
1119
1198
  this.updateModulation(channel, scheduleTime);
1120
1199
  }
@@ -1159,8 +1238,8 @@ export class MidyGMLite {
1159
1238
  .setValueAtTime(volume * gainRight, scheduleTime);
1160
1239
  }
1161
1240
  setSustainPedal(channelNumber, value, scheduleTime) {
1162
- scheduleTime ??= this.audioContext.currentTime;
1163
1241
  const channel = this.channels[channelNumber];
1242
+ scheduleTime ??= this.audioContext.currentTime;
1164
1243
  channel.state.sustainPedal = value / 127;
1165
1244
  if (64 <= value) {
1166
1245
  this.processScheduledNotes(channel, (note) => {
@@ -1217,8 +1296,8 @@ export class MidyGMLite {
1217
1296
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1218
1297
  }
1219
1298
  setPitchBendRange(channelNumber, value, scheduleTime) {
1220
- scheduleTime ??= this.audioContext.currentTime;
1221
1299
  const channel = this.channels[channelNumber];
1300
+ scheduleTime ??= this.audioContext.currentTime;
1222
1301
  const state = channel.state;
1223
1302
  const prev = state.pitchWheelSensitivity;
1224
1303
  const next = value / 128;
@@ -1231,12 +1310,24 @@ export class MidyGMLite {
1231
1310
  scheduleTime ??= this.audioContext.currentTime;
1232
1311
  return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
1233
1312
  }
1313
+ resetAllStates(channelNumber) {
1314
+ const channel = this.channels[channelNumber];
1315
+ const state = channel.state;
1316
+ for (const type of Object.keys(defaultControllerState)) {
1317
+ state[type] = defaultControllerState[type].defaultValue;
1318
+ }
1319
+ for (const type of Object.keys(this.constructor.channelSettings)) {
1320
+ channel[type] = this.constructor.channelSettings[type];
1321
+ }
1322
+ this.mode = "GM1";
1323
+ }
1324
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
1234
1325
  resetAllControllers(channelNumber) {
1235
1326
  const stateTypes = [
1327
+ "pitchWheel",
1236
1328
  "expression",
1237
1329
  "modulationDepth",
1238
1330
  "sustainPedal",
1239
- "pitchWheelSensitivity",
1240
1331
  ];
1241
1332
  const channel = this.channels[channelNumber];
1242
1333
  const state = channel.state;
@@ -1257,12 +1348,12 @@ export class MidyGMLite {
1257
1348
  scheduleTime ??= this.audioContext.currentTime;
1258
1349
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1259
1350
  }
1260
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1351
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1261
1352
  switch (data[2]) {
1262
1353
  case 9:
1263
1354
  switch (data[3]) {
1264
1355
  case 1:
1265
- this.GM1SystemOn();
1356
+ this.GM1SystemOn(scheduleTime);
1266
1357
  break;
1267
1358
  case 2: // GM System Off
1268
1359
  break;
@@ -1274,12 +1365,17 @@ export class MidyGMLite {
1274
1365
  console.warn(`Unsupported Exclusive Message: ${data}`);
1275
1366
  }
1276
1367
  }
1277
- GM1SystemOn() {
1368
+ GM1SystemOn(scheduleTime) {
1369
+ scheduleTime ??= this.audioContext.currentTime;
1370
+ this.mode = "GM1";
1278
1371
  for (let i = 0; i < this.channels.length; i++) {
1372
+ this.allSoundOff(i, 0, scheduleTime);
1279
1373
  const channel = this.channels[i];
1280
1374
  channel.bank = 0;
1375
+ channel.isDrum = false;
1281
1376
  }
1282
1377
  this.channels[9].bank = 128;
1378
+ this.channels[9].isDrum = true;
1283
1379
  }
1284
1380
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1285
1381
  switch (data[2]) {
@@ -1320,15 +1416,23 @@ export class MidyGMLite {
1320
1416
  console.warn(`Unsupported Exclusive Message: ${data}`);
1321
1417
  }
1322
1418
  }
1419
+ // https://github.com/marmooo/js-timer-benchmark
1323
1420
  scheduleTask(callback, scheduleTime) {
1324
1421
  return new Promise((resolve) => {
1325
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1422
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1423
+ buffer: this.schedulerBuffer,
1424
+ });
1425
+ bufferSource.connect(this.scheduler);
1326
1426
  bufferSource.onended = () => {
1327
- callback();
1328
- resolve();
1427
+ try {
1428
+ callback();
1429
+ }
1430
+ finally {
1431
+ bufferSource.disconnect();
1432
+ resolve();
1433
+ }
1329
1434
  };
1330
1435
  bufferSource.start(scheduleTime);
1331
- bufferSource.stop(scheduleTime);
1332
1436
  });
1333
1437
  }
1334
1438
  }
@@ -1337,13 +1441,13 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1337
1441
  configurable: true,
1338
1442
  writable: true,
1339
1443
  value: {
1340
- currentBufferSource: null,
1341
1444
  detune: 0,
1342
- program: 0,
1445
+ programNumber: 0,
1343
1446
  bank: 0,
1344
1447
  dataMSB: 0,
1345
1448
  dataLSB: 0,
1346
1449
  rpnMSB: 127,
1347
1450
  rpnLSB: 127,
1451
+ modulationDepthRange: 50, // cent
1348
1452
  }
1349
1453
  });
package/esm/midy.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  export class Midy {
2
2
  static channelSettings: {
3
- currentBufferSource: null;
4
3
  detune: number;
5
- program: number;
4
+ programNumber: number;
6
5
  bank: number;
7
6
  bankMSB: number;
8
7
  bankLSB: number;
@@ -11,9 +10,9 @@ export class Midy {
11
10
  rpnMSB: number;
12
11
  rpnLSB: number;
13
12
  mono: boolean;
13
+ modulationDepthRange: number;
14
14
  fineTuning: number;
15
15
  coarseTuning: number;
16
- modulationDepthRange: number;
17
16
  };
18
17
  constructor(audioContext: any, options?: {
19
18
  reverbAlgorithm: (audioContext: any) => {
@@ -21,8 +20,7 @@ export class Midy {
21
20
  output: any;
22
21
  };
23
22
  });
24
- ticksPerBeat: number;
25
- totalTime: number;
23
+ mode: string;
26
24
  masterFineTuning: number;
27
25
  masterCoarseTuning: number;
28
26
  reverb: {
@@ -36,6 +34,9 @@ export class Midy {
36
34
  sendToReverb: number;
37
35
  delayTimes: any[];
38
36
  };
37
+ numChannels: number;
38
+ ticksPerBeat: number;
39
+ totalTime: number;
39
40
  noteCheckInterval: number;
40
41
  lookAhead: number;
41
42
  startDelay: number;
@@ -53,7 +54,8 @@ export class Midy {
53
54
  timeline: any[];
54
55
  instruments: any[];
55
56
  notePromises: any[];
56
- exclusiveClassMap: SparseMap;
57
+ exclusiveClassNotes: any[];
58
+ drumExclusiveClassNotes: any[];
57
59
  defaultOptions: {
58
60
  reverbAlgorithm: (audioContext: any) => {
59
61
  input: any;
@@ -68,6 +70,8 @@ export class Midy {
68
70
  };
69
71
  };
70
72
  masterVolume: any;
73
+ scheduler: any;
74
+ schedulerBuffer: any;
71
75
  voiceParamsHandlers: {
72
76
  modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
73
77
  vibLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
@@ -93,7 +97,7 @@ export class Midy {
93
97
  64: (channelNumber: any, value: any, scheduleTime: any) => void;
94
98
  65: (channelNumber: any, value: any) => void;
95
99
  66: (channelNumber: any, value: any, scheduleTime: any) => void;
96
- 67: (channelNumber: any, softPedal: any, _scheduleTime: any) => void;
100
+ 67: (channelNumber: any, softPedal: any, scheduleTime: any) => void;
97
101
  71: (channelNumber: any, filterResonance: any, scheduleTime: any) => void;
98
102
  72: (channelNumber: any, releaseTime: any, _scheduleTime: any) => void;
99
103
  73: (channelNumber: any, attackTime: any, scheduleTime: any) => void;
@@ -141,7 +145,7 @@ export class Midy {
141
145
  };
142
146
  createChannels(audioContext: any): any[];
143
147
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
144
- createNoteBufferNode(audioBuffer: any, voiceParams: any): any;
148
+ createBufferSource(voiceParams: any, audioBuffer: any): any;
145
149
  findPortamentoTarget(queueIndex: any): any;
146
150
  scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
147
151
  getQueueIndex(second: any): number;
@@ -204,11 +208,15 @@ export class Midy {
204
208
  setFilterEnvelope(channel: any, note: any, scheduleTime: any): void;
205
209
  startModulation(channel: any, note: any, scheduleTime: any): void;
206
210
  startVibrato(channel: any, note: any, scheduleTime: any): void;
207
- getAudioBuffer(program: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
211
+ getAudioBuffer(programNumber: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
208
212
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, portamento: any, isSF3: any): Promise<Note>;
209
- calcBank(channel: any, channelNumber: any): any;
213
+ calcBank(channel: any): any;
214
+ handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
215
+ handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
216
+ isDrumNoteOffException(channel: any, noteNumber: any): boolean;
210
217
  scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, portamento: any): Promise<void>;
211
218
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
219
+ disconnectNote(note: any, scheduledNotes: any, index: any): void;
212
220
  stopNote(endTime: any, stopTime: any, scheduledNotes: any, index: any): Promise<any>;
213
221
  scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any, portamentoNoteNumber: any): Promise<any> | undefined;
214
222
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
@@ -216,7 +224,7 @@ export class Midy {
216
224
  releaseSostenutoPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): any[];
217
225
  handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
218
226
  handlePolyphonicKeyPressure(channelNumber: any, noteNumber: any, pressure: any, scheduleTime: any): void;
219
- handleProgramChange(channelNumber: any, program: any, _scheduleTime: any): void;
227
+ handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
220
228
  handleChannelPressure(channelNumber: any, value: any, scheduleTime: any): void;
221
229
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
222
230
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -256,7 +264,7 @@ export class Midy {
256
264
  64: (channelNumber: any, value: any, scheduleTime: any) => void;
257
265
  65: (channelNumber: any, value: any) => void;
258
266
  66: (channelNumber: any, value: any, scheduleTime: any) => void;
259
- 67: (channelNumber: any, softPedal: any, _scheduleTime: any) => void;
267
+ 67: (channelNumber: any, softPedal: any, scheduleTime: any) => void;
260
268
  71: (channelNumber: any, filterResonance: any, scheduleTime: any) => void;
261
269
  72: (channelNumber: any, releaseTime: any, _scheduleTime: any) => void;
262
270
  73: (channelNumber: any, attackTime: any, scheduleTime: any) => void;
@@ -299,7 +307,7 @@ export class Midy {
299
307
  setSustainPedal(channelNumber: any, value: any, scheduleTime: any): void;
300
308
  setPortamento(channelNumber: any, value: any): void;
301
309
  setSostenutoPedal(channelNumber: any, value: any, scheduleTime: any): void;
302
- setSoftPedal(channelNumber: any, softPedal: any, _scheduleTime: any): void;
310
+ setSoftPedal(channelNumber: any, softPedal: any, scheduleTime: any): void;
303
311
  setFilterResonance(channelNumber: any, filterResonance: any, scheduleTime: any): void;
304
312
  setReleaseTime(channelNumber: any, releaseTime: any, _scheduleTime: any): void;
305
313
  setAttackTime(channelNumber: any, attackTime: any, scheduleTime: any): void;
@@ -327,6 +335,7 @@ export class Midy {
327
335
  handleModulationDepthRangeRPN(channelNumber: any, scheduleTime: any): void;
328
336
  setModulationDepthRange(channelNumber: any, modulationDepthRange: any, scheduleTime: any): void;
329
337
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
338
+ resetAllStates(channelNumber: any): void;
330
339
  resetAllControllers(channelNumber: any): void;
331
340
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
332
341
  omniOff(channelNumber: any, value: any, scheduleTime: any): void;
@@ -334,8 +343,8 @@ export class Midy {
334
343
  monoOn(channelNumber: any, value: any, scheduleTime: any): void;
335
344
  polyOn(channelNumber: any, value: any, scheduleTime: any): void;
336
345
  handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
337
- GM1SystemOn(): void;
338
- GM2SystemOn(): void;
346
+ GM1SystemOn(scheduleTime: any): void;
347
+ GM2SystemOn(scheduleTime: any): void;
339
348
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
340
349
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
341
350
  setMasterVolume(volume: any, scheduleTime: any): void;