@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"}
@@ -106,6 +106,19 @@ class Note {
106
106
  this.voiceParams = voiceParams;
107
107
  }
108
108
  }
109
+ const drumExclusiveClasses = new Uint8Array(128);
110
+ drumExclusiveClasses[42] = 1;
111
+ drumExclusiveClasses[44] = 1;
112
+ drumExclusiveClasses[46] = 1, // HH
113
+ drumExclusiveClasses[71] = 2;
114
+ drumExclusiveClasses[72] = 2; // Whistle
115
+ drumExclusiveClasses[73] = 3;
116
+ drumExclusiveClasses[74] = 3; // Guiro
117
+ drumExclusiveClasses[78] = 4;
118
+ drumExclusiveClasses[79] = 4; // Cuica
119
+ drumExclusiveClasses[80] = 5;
120
+ drumExclusiveClasses[81] = 5; // Triangle
121
+ const drumExclusiveClassCount = 5;
109
122
  // normalized to 0-1 for use with the SF2 modulator model
110
123
  const defaultControllerState = {
111
124
  noteOnVelocity: { type: 2, defaultValue: 0 },
@@ -173,6 +186,18 @@ const volumeEnvelopeKeys = [
173
186
  const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
174
187
  class MidyGMLite {
175
188
  constructor(audioContext) {
189
+ Object.defineProperty(this, "mode", {
190
+ enumerable: true,
191
+ configurable: true,
192
+ writable: true,
193
+ value: "GM1"
194
+ });
195
+ Object.defineProperty(this, "numChannels", {
196
+ enumerable: true,
197
+ configurable: true,
198
+ writable: true,
199
+ value: 16
200
+ });
176
201
  Object.defineProperty(this, "ticksPerBeat", {
177
202
  enumerable: true,
178
203
  configurable: true,
@@ -287,18 +312,30 @@ class MidyGMLite {
287
312
  writable: true,
288
313
  value: []
289
314
  });
290
- Object.defineProperty(this, "exclusiveClassMap", {
315
+ Object.defineProperty(this, "exclusiveClassNotes", {
291
316
  enumerable: true,
292
317
  configurable: true,
293
318
  writable: true,
294
- value: new SparseMap(128)
319
+ value: new Array(128)
320
+ });
321
+ Object.defineProperty(this, "drumExclusiveClassNotes", {
322
+ enumerable: true,
323
+ configurable: true,
324
+ writable: true,
325
+ value: new Array(this.numChannels * drumExclusiveClassCount)
295
326
  });
296
327
  this.audioContext = audioContext;
297
328
  this.masterVolume = new GainNode(audioContext);
329
+ this.scheduler = new GainNode(audioContext, { gain: 0 });
330
+ this.schedulerBuffer = new AudioBuffer({
331
+ length: 1,
332
+ sampleRate: audioContext.sampleRate,
333
+ });
298
334
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
299
335
  this.controlChangeHandlers = this.createControlChangeHandlers();
300
336
  this.channels = this.createChannels(audioContext);
301
337
  this.masterVolume.connect(audioContext.destination);
338
+ this.scheduler.connect(audioContext.destination);
302
339
  this.GM1SystemOn();
303
340
  }
304
341
  initSoundFontTable() {
@@ -352,8 +389,10 @@ class MidyGMLite {
352
389
  };
353
390
  }
354
391
  createChannels(audioContext) {
355
- const channels = Array.from({ length: 16 }, () => {
392
+ const channels = Array.from({ length: this.numChannels }, () => {
356
393
  return {
394
+ currentBufferSource: null,
395
+ isDrum: false,
357
396
  ...this.constructor.channelSettings,
358
397
  state: new ControllerState(),
359
398
  ...this.setChannelAudioNodes(audioContext),
@@ -392,7 +431,7 @@ class MidyGMLite {
392
431
  return audioBuffer;
393
432
  }
394
433
  }
395
- createNoteBufferNode(audioBuffer, voiceParams) {
434
+ createBufferSource(voiceParams, audioBuffer) {
396
435
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
397
436
  bufferSource.buffer = audioBuffer;
398
437
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
@@ -458,7 +497,7 @@ class MidyGMLite {
458
497
  if (queueIndex >= this.timeline.length) {
459
498
  await Promise.all(this.notePromises);
460
499
  this.notePromises = [];
461
- this.exclusiveClassMap.clear();
500
+ this.exclusiveClassNotes.flll(undefined);
462
501
  this.audioBufferCache.clear();
463
502
  resolve();
464
503
  return;
@@ -477,7 +516,7 @@ class MidyGMLite {
477
516
  else if (this.isStopping) {
478
517
  await this.stopNotes(0, true, now);
479
518
  this.notePromises = [];
480
- this.exclusiveClassMap.clear();
519
+ this.exclusiveClassNotes.fill(undefined);
481
520
  this.audioBufferCache.clear();
482
521
  resolve();
483
522
  this.isStopping = false;
@@ -486,7 +525,7 @@ class MidyGMLite {
486
525
  }
487
526
  else if (this.isSeeking) {
488
527
  this.stopNotes(0, true, now);
489
- this.exclusiveClassMap.clear();
528
+ this.exclusiveClassNotes.fill(undefined);
490
529
  this.startTime = this.audioContext.currentTime;
491
530
  queueIndex = this.getQueueIndex(this.resumeTime);
492
531
  offset = this.resumeTime - this.startTime;
@@ -514,7 +553,7 @@ class MidyGMLite {
514
553
  extractMidiData(midi) {
515
554
  const instruments = new Set();
516
555
  const timeline = [];
517
- const tmpChannels = new Array(16);
556
+ const tmpChannels = new Array(this.channels.length);
518
557
  for (let i = 0; i < tmpChannels.length; i++) {
519
558
  tmpChannels[i] = {
520
559
  programNumber: -1,
@@ -606,6 +645,9 @@ class MidyGMLite {
606
645
  if (!this.isPlaying)
607
646
  return;
608
647
  this.isStopping = true;
648
+ for (let i = 0; i < this.channels.length; i++) {
649
+ this.resetAllStates(i);
650
+ }
609
651
  }
610
652
  pause() {
611
653
  if (!this.isPlaying || this.isPaused)
@@ -781,8 +823,8 @@ class MidyGMLite {
781
823
  note.modulationLFO.connect(note.volumeDepth);
782
824
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
783
825
  }
784
- async getAudioBuffer(program, noteNumber, velocity, voiceParams, isSF3) {
785
- const audioBufferId = this.getAudioBufferId(program, noteNumber, velocity);
826
+ async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
827
+ const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
786
828
  const cache = this.audioBufferCache.get(audioBufferId);
787
829
  if (cache) {
788
830
  cache.counter += 1;
@@ -805,8 +847,8 @@ class MidyGMLite {
805
847
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
806
848
  const voiceParams = voice.getAllParams(controllerState);
807
849
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
808
- const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
809
- note.bufferSource = this.createNoteBufferNode(audioBuffer, voiceParams);
850
+ const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
851
+ note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
810
852
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
811
853
  note.filterNode = new BiquadFilterNode(this.audioContext, {
812
854
  type: "lowpass",
@@ -823,14 +865,43 @@ class MidyGMLite {
823
865
  note.bufferSource.start(startTime);
824
866
  return note;
825
867
  }
868
+ handleExclusiveClass(note, channelNumber, startTime) {
869
+ const exclusiveClass = note.voiceParams.exclusiveClass;
870
+ if (exclusiveClass === 0)
871
+ return;
872
+ const prev = this.exclusiveClassNotes[exclusiveClass];
873
+ if (prev) {
874
+ const [prevNote, prevChannelNumber] = prev;
875
+ if (prevNote && !prevNote.ending) {
876
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
877
+ startTime, true);
878
+ }
879
+ }
880
+ this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
881
+ }
882
+ handleDrumExclusiveClass(note, channelNumber, startTime) {
883
+ const channel = this.channels[channelNumber];
884
+ if (!channel.isDrum)
885
+ return;
886
+ const drumExclusiveClass = drumExclusiveClasses[noteNumber];
887
+ if (drumExclusiveClass === 0)
888
+ return;
889
+ const index = drumExclusiveClass * this.channels.length + channelNumber;
890
+ const prevNote = this.drumExclusiveClassNotes[index];
891
+ if (prevNote && !prevNote.ending) {
892
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
893
+ startTime, true);
894
+ }
895
+ this.drumExclusiveClassNotes[index] = note;
896
+ }
826
897
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
827
898
  const channel = this.channels[channelNumber];
828
899
  const bankNumber = channel.bank;
829
- const soundFontIndex = this.soundFontTable[channel.program].get(bankNumber);
900
+ const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
830
901
  if (soundFontIndex === undefined)
831
902
  return;
832
903
  const soundFont = this.soundFonts[soundFontIndex];
833
- const voice = soundFont.getVoice(bankNumber, channel.program, noteNumber, velocity);
904
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
834
905
  if (!voice)
835
906
  return;
836
907
  const isSF3 = soundFont.parsed.info.version.major === 3;
@@ -840,30 +911,45 @@ class MidyGMLite {
840
911
  if (0.5 <= channel.state.sustainPedal) {
841
912
  channel.sustainNotes.push(note);
842
913
  }
843
- const exclusiveClass = note.voiceParams.exclusiveClass;
844
- if (exclusiveClass !== 0) {
845
- if (this.exclusiveClassMap.has(exclusiveClass)) {
846
- const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
847
- const [prevNote, prevChannelNumber] = prevEntry;
848
- if (!prevNote.ending) {
849
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
850
- startTime, true);
851
- }
852
- }
853
- this.exclusiveClassMap.set(exclusiveClass, [note, channelNumber]);
854
- }
914
+ this.handleExclusiveClass(note, channelNumber, startTime);
915
+ this.handleDrumExclusiveClass(note, channelNumber, startTime);
855
916
  const scheduledNotes = channel.scheduledNotes;
856
- if (scheduledNotes.has(noteNumber)) {
857
- scheduledNotes.get(noteNumber).push(note);
917
+ let notes = scheduledNotes.get(noteNumber);
918
+ if (notes) {
919
+ notes.push(note);
858
920
  }
859
921
  else {
860
- scheduledNotes.set(noteNumber, [note]);
922
+ notes = [note];
923
+ scheduledNotes.set(noteNumber, notes);
924
+ }
925
+ if (this.isDrumNoteOffException(channel, noteNumber)) {
926
+ const stopTime = startTime + note.bufferSource.buffer.duration;
927
+ const index = notes.length - 1;
928
+ const promise = new Promise((resolve) => {
929
+ note.bufferSource.onended = () => {
930
+ this.disconnectNote(note, scheduledNotes, index);
931
+ resolve();
932
+ };
933
+ note.bufferSource.stop(stopTime);
934
+ });
935
+ this.notePromises.push(promise);
861
936
  }
862
937
  }
863
938
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
864
939
  scheduleTime ??= this.audioContext.currentTime;
865
940
  return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime);
866
941
  }
942
+ disconnectNote(note, scheduledNotes, index) {
943
+ scheduledNotes[index] = null;
944
+ note.bufferSource.disconnect();
945
+ note.filterNode.disconnect();
946
+ note.volumeEnvelopeNode.disconnect();
947
+ if (note.modulationDepth) {
948
+ note.volumeDepth.disconnect();
949
+ note.modulationDepth.disconnect();
950
+ note.modulationLFO.stop();
951
+ }
952
+ }
867
953
  stopNote(endTime, stopTime, scheduledNotes, index) {
868
954
  const note = scheduledNotes[index];
869
955
  note.volumeEnvelopeNode.gain
@@ -875,15 +961,7 @@ class MidyGMLite {
875
961
  }, stopTime);
876
962
  return new Promise((resolve) => {
877
963
  note.bufferSource.onended = () => {
878
- scheduledNotes[index] = null;
879
- note.bufferSource.disconnect();
880
- note.filterNode.disconnect();
881
- note.volumeEnvelopeNode.disconnect();
882
- if (note.modulationDepth) {
883
- note.volumeDepth.disconnect();
884
- note.modulationDepth.disconnect();
885
- note.modulationLFO.stop();
886
- }
964
+ this.disconnectNote(note, scheduledNotes, index);
887
965
  resolve();
888
966
  };
889
967
  note.bufferSource.stop(stopTime);
@@ -891,6 +969,8 @@ class MidyGMLite {
891
969
  }
892
970
  scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
893
971
  const channel = this.channels[channelNumber];
972
+ if (channel.isDrum)
973
+ return;
894
974
  if (!force && 0.5 <= channel.state.sustainPedal)
895
975
  return;
896
976
  if (!channel.scheduledNotes.has(noteNumber))
@@ -944,17 +1024,17 @@ class MidyGMLite {
944
1024
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
945
1025
  }
946
1026
  }
947
- handleProgramChange(channelNumber, program, _scheduleTime) {
1027
+ handleProgramChange(channelNumber, programNumber, _scheduleTime) {
948
1028
  const channel = this.channels[channelNumber];
949
- channel.program = program;
1029
+ channel.programNumber = programNumber;
950
1030
  }
951
1031
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
952
1032
  const pitchBend = msb * 128 + lsb;
953
1033
  this.setPitchBend(channelNumber, pitchBend, scheduleTime);
954
1034
  }
955
1035
  setPitchBend(channelNumber, value, scheduleTime) {
956
- scheduleTime ??= this.audioContext.currentTime;
957
1036
  const channel = this.channels[channelNumber];
1037
+ scheduleTime ??= this.audioContext.currentTime;
958
1038
  const state = channel.state;
959
1039
  const prev = state.pitchWheel * 2 - 1;
960
1040
  const next = (value - 8192) / 8192;
@@ -1103,7 +1183,6 @@ class MidyGMLite {
1103
1183
  }
1104
1184
  }
1105
1185
  updateModulation(channel, scheduleTime) {
1106
- scheduleTime ??= this.audioContext.currentTime;
1107
1186
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1108
1187
  this.processScheduledNotes(channel, (note) => {
1109
1188
  if (note.modulationDepth) {
@@ -1116,8 +1195,8 @@ class MidyGMLite {
1116
1195
  });
1117
1196
  }
1118
1197
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1119
- scheduleTime ??= this.audioContext.currentTime;
1120
1198
  const channel = this.channels[channelNumber];
1199
+ scheduleTime ??= this.audioContext.currentTime;
1121
1200
  channel.state.modulationDepth = modulation / 127;
1122
1201
  this.updateModulation(channel, scheduleTime);
1123
1202
  }
@@ -1162,8 +1241,8 @@ class MidyGMLite {
1162
1241
  .setValueAtTime(volume * gainRight, scheduleTime);
1163
1242
  }
1164
1243
  setSustainPedal(channelNumber, value, scheduleTime) {
1165
- scheduleTime ??= this.audioContext.currentTime;
1166
1244
  const channel = this.channels[channelNumber];
1245
+ scheduleTime ??= this.audioContext.currentTime;
1167
1246
  channel.state.sustainPedal = value / 127;
1168
1247
  if (64 <= value) {
1169
1248
  this.processScheduledNotes(channel, (note) => {
@@ -1220,8 +1299,8 @@ class MidyGMLite {
1220
1299
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1221
1300
  }
1222
1301
  setPitchBendRange(channelNumber, value, scheduleTime) {
1223
- scheduleTime ??= this.audioContext.currentTime;
1224
1302
  const channel = this.channels[channelNumber];
1303
+ scheduleTime ??= this.audioContext.currentTime;
1225
1304
  const state = channel.state;
1226
1305
  const prev = state.pitchWheelSensitivity;
1227
1306
  const next = value / 128;
@@ -1234,12 +1313,24 @@ class MidyGMLite {
1234
1313
  scheduleTime ??= this.audioContext.currentTime;
1235
1314
  return this.stopChannelNotes(channelNumber, 0, true, scheduleTime);
1236
1315
  }
1316
+ resetAllStates(channelNumber) {
1317
+ const channel = this.channels[channelNumber];
1318
+ const state = channel.state;
1319
+ for (const type of Object.keys(defaultControllerState)) {
1320
+ state[type] = defaultControllerState[type].defaultValue;
1321
+ }
1322
+ for (const type of Object.keys(this.constructor.channelSettings)) {
1323
+ channel[type] = this.constructor.channelSettings[type];
1324
+ }
1325
+ this.mode = "GM1";
1326
+ }
1327
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
1237
1328
  resetAllControllers(channelNumber) {
1238
1329
  const stateTypes = [
1330
+ "pitchWheel",
1239
1331
  "expression",
1240
1332
  "modulationDepth",
1241
1333
  "sustainPedal",
1242
- "pitchWheelSensitivity",
1243
1334
  ];
1244
1335
  const channel = this.channels[channelNumber];
1245
1336
  const state = channel.state;
@@ -1260,12 +1351,12 @@ class MidyGMLite {
1260
1351
  scheduleTime ??= this.audioContext.currentTime;
1261
1352
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
1262
1353
  }
1263
- handleUniversalNonRealTimeExclusiveMessage(data, _scheduleTime) {
1354
+ handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
1264
1355
  switch (data[2]) {
1265
1356
  case 9:
1266
1357
  switch (data[3]) {
1267
1358
  case 1:
1268
- this.GM1SystemOn();
1359
+ this.GM1SystemOn(scheduleTime);
1269
1360
  break;
1270
1361
  case 2: // GM System Off
1271
1362
  break;
@@ -1277,12 +1368,17 @@ class MidyGMLite {
1277
1368
  console.warn(`Unsupported Exclusive Message: ${data}`);
1278
1369
  }
1279
1370
  }
1280
- GM1SystemOn() {
1371
+ GM1SystemOn(scheduleTime) {
1372
+ scheduleTime ??= this.audioContext.currentTime;
1373
+ this.mode = "GM1";
1281
1374
  for (let i = 0; i < this.channels.length; i++) {
1375
+ this.allSoundOff(i, 0, scheduleTime);
1282
1376
  const channel = this.channels[i];
1283
1377
  channel.bank = 0;
1378
+ channel.isDrum = false;
1284
1379
  }
1285
1380
  this.channels[9].bank = 128;
1381
+ this.channels[9].isDrum = true;
1286
1382
  }
1287
1383
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
1288
1384
  switch (data[2]) {
@@ -1323,15 +1419,23 @@ class MidyGMLite {
1323
1419
  console.warn(`Unsupported Exclusive Message: ${data}`);
1324
1420
  }
1325
1421
  }
1422
+ // https://github.com/marmooo/js-timer-benchmark
1326
1423
  scheduleTask(callback, scheduleTime) {
1327
1424
  return new Promise((resolve) => {
1328
- const bufferSource = new AudioBufferSourceNode(this.audioContext);
1425
+ const bufferSource = new AudioBufferSourceNode(this.audioContext, {
1426
+ buffer: this.schedulerBuffer,
1427
+ });
1428
+ bufferSource.connect(this.scheduler);
1329
1429
  bufferSource.onended = () => {
1330
- callback();
1331
- resolve();
1430
+ try {
1431
+ callback();
1432
+ }
1433
+ finally {
1434
+ bufferSource.disconnect();
1435
+ resolve();
1436
+ }
1332
1437
  };
1333
1438
  bufferSource.start(scheduleTime);
1334
- bufferSource.stop(scheduleTime);
1335
1439
  });
1336
1440
  }
1337
1441
  }
@@ -1341,13 +1445,13 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1341
1445
  configurable: true,
1342
1446
  writable: true,
1343
1447
  value: {
1344
- currentBufferSource: null,
1345
1448
  detune: 0,
1346
- program: 0,
1449
+ programNumber: 0,
1347
1450
  bank: 0,
1348
1451
  dataMSB: 0,
1349
1452
  dataLSB: 0,
1350
1453
  rpnMSB: 127,
1351
1454
  rpnLSB: 127,
1455
+ modulationDepthRange: 50, // cent
1352
1456
  }
1353
1457
  });
package/script/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;