@marmooo/midy 0.3.6 → 0.3.8

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.
@@ -3,7 +3,6 @@ export class MidyGMLite {
3
3
  scheduleIndex: number;
4
4
  detune: number;
5
5
  programNumber: number;
6
- bank: number;
7
6
  dataMSB: number;
8
7
  dataLSB: number;
9
8
  rpnMSB: number;
@@ -21,7 +20,7 @@ export class MidyGMLite {
21
20
  startTime: number;
22
21
  resumeTime: number;
23
22
  soundFonts: any[];
24
- soundFontTable: any[];
23
+ soundFontTable: never[][];
25
24
  voiceCounter: Map<any, any>;
26
25
  voiceCache: Map<any, any>;
27
26
  isPlaying: boolean;
@@ -29,6 +28,7 @@ export class MidyGMLite {
29
28
  isPaused: boolean;
30
29
  isStopping: boolean;
31
30
  isSeeking: boolean;
31
+ playPromise: any;
32
32
  timeline: any[];
33
33
  notePromises: any[];
34
34
  instruments: Set<any>;
@@ -38,21 +38,21 @@ export class MidyGMLite {
38
38
  masterVolume: any;
39
39
  scheduler: any;
40
40
  schedulerBuffer: any;
41
+ messageHandlers: any[];
41
42
  voiceParamsHandlers: {
42
- modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
43
- vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
44
- modLfoToFilterFc: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
45
- modLfoToVolume: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
46
- chorusEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
47
- reverbEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
48
- delayModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
49
- freqModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
50
- delayVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
51
- freqVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
43
+ modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
44
+ vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
45
+ modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
46
+ modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
47
+ chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
48
+ reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
49
+ delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
50
+ freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
51
+ delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
52
+ freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
52
53
  };
53
54
  controlChangeHandlers: any[];
54
55
  channels: any[];
55
- initSoundFontTable(): any[];
56
56
  addSoundFont(soundFont: any): void;
57
57
  toUint8Array(input: any): Promise<Uint8Array<ArrayBuffer>>;
58
58
  loadSoundFont(input: any): Promise<void>;
@@ -67,11 +67,14 @@ export class MidyGMLite {
67
67
  createChannels(audioContext: any): any[];
68
68
  createAudioBuffer(voiceParams: any): Promise<any>;
69
69
  createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
70
- scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
70
+ scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
71
71
  getQueueIndex(second: any): number;
72
- playNotes(): Promise<any>;
72
+ resetAllStates(): void;
73
+ updateStates(queueIndex: any, nextQueueIndex: any): void;
74
+ playNotes(): Promise<void>;
73
75
  ticksToSecond(ticks: any, secondsPerBeat: any): number;
74
76
  secondToTicks(second: any, secondsPerBeat: any): number;
77
+ getSoundFontId(channel: any): string;
75
78
  extractMidiData(midi: any): {
76
79
  instruments: Set<any>;
77
80
  timeline: any[];
@@ -80,8 +83,8 @@ export class MidyGMLite {
80
83
  stopChannelNotes(channelNumber: any, velocity: any, force: any, scheduleTime: any): Promise<any[]>;
81
84
  stopNotes(velocity: any, force: any, scheduleTime: any): Promise<any[]>;
82
85
  start(): Promise<void>;
83
- stop(): void;
84
- pause(): void;
86
+ stop(): Promise<void>;
87
+ pause(): Promise<void>;
85
88
  resume(): Promise<void>;
86
89
  seekTo(second: any): void;
87
90
  calcTotalTime(): number;
@@ -113,7 +116,9 @@ export class MidyGMLite {
113
116
  findNoteOffIndex(channel: any, noteNumber: any): any;
114
117
  noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
115
118
  releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
116
- handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
119
+ createMessageHandlers(): any[];
120
+ handleMessage(data: any, scheduleTime: any): void;
121
+ handleChannelMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
117
122
  setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
118
123
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
119
124
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -123,16 +128,16 @@ export class MidyGMLite {
123
128
  setDelayModLFO(note: any, scheduleTime: any): void;
124
129
  setFreqModLFO(note: any, scheduleTime: any): void;
125
130
  createVoiceParamsHandlers(): {
126
- modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
127
- vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
128
- modLfoToFilterFc: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
129
- modLfoToVolume: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
130
- chorusEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
131
- reverbEffectsSend: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
132
- delayModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
133
- freqModLFO: (_channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
134
- delayVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
135
- freqVibLFO: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
131
+ modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
132
+ vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
133
+ modLfoToFilterFc: (channel: any, note: any, scheduleTime: any) => void;
134
+ modLfoToVolume: (channel: any, note: any, scheduleTime: any) => void;
135
+ chorusEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
136
+ reverbEffectsSend: (_channel: any, _note: any, _scheduleTime: any) => void;
137
+ delayModLFO: (_channel: any, note: any, scheduleTime: any) => void;
138
+ freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
139
+ delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
140
+ freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
136
141
  };
137
142
  getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
138
143
  applyVoiceParams(channel: any, controllerType: any, scheduleTime: any): void;
@@ -158,14 +163,14 @@ export class MidyGMLite {
158
163
  handlePitchBendRangeRPN(channelNumber: any, scheduleTime: any): void;
159
164
  setPitchBendRange(channelNumber: any, value: any, scheduleTime: any): void;
160
165
  allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
161
- resetAllStates(channelNumber: any): void;
166
+ resetChannelStates(channelNumber: any): void;
162
167
  resetAllControllers(channelNumber: any, _value: any, scheduleTime: any): void;
163
168
  allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
164
169
  handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
165
170
  GM1SystemOn(scheduleTime: any): void;
166
171
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
167
172
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
168
- setMasterVolume(volume: any, scheduleTime: any): void;
173
+ setMasterVolume(value: any, scheduleTime: any): void;
169
174
  handleSysEx(data: any, scheduleTime: any): void;
170
175
  scheduleTask(callback: any, scheduleTime: any): Promise<any>;
171
176
  }
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA2BE;;;;;;;;;;MAUE;IAEF,+BAcC;IApDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEASC;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,6FAyBC;IAED,oGAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;IAED,qCAeC;IAED,+FAWC;IAED,wDASC;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,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAx/CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,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":"AA0GA;IA4BE;;;;;;;;;MASE;IAEF,+BAeC;IArDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAWC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,yEAqDC;IAED,mCAOC;IAED,uBAQC;IAED,yDA2BC;IAED,2BAwCC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,sBAKC;IAED,uBAQC;IAED,wBAKC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEASC;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,6FAyBC;IAED,oGA2CC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAiCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,uGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDASC;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,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCASC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA9hDD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -213,7 +213,7 @@ export class MidyGMLite {
213
213
  enumerable: true,
214
214
  configurable: true,
215
215
  writable: true,
216
- value: this.initSoundFontTable()
216
+ value: Array.from({ length: 128 }, () => [])
217
217
  });
218
218
  Object.defineProperty(this, "voiceCounter", {
219
219
  enumerable: true,
@@ -257,6 +257,12 @@ export class MidyGMLite {
257
257
  writable: true,
258
258
  value: false
259
259
  });
260
+ Object.defineProperty(this, "playPromise", {
261
+ enumerable: true,
262
+ configurable: true,
263
+ writable: true,
264
+ value: void 0
265
+ });
260
266
  Object.defineProperty(this, "timeline", {
261
267
  enumerable: true,
262
268
  configurable: true,
@@ -294,6 +300,7 @@ export class MidyGMLite {
294
300
  length: 1,
295
301
  sampleRate: audioContext.sampleRate,
296
302
  });
303
+ this.messageHandlers = this.createMessageHandlers();
297
304
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
298
305
  this.controlChangeHandlers = this.createControlChangeHandlers();
299
306
  this.channels = this.createChannels(audioContext);
@@ -301,21 +308,14 @@ export class MidyGMLite {
301
308
  this.scheduler.connect(audioContext.destination);
302
309
  this.GM1SystemOn();
303
310
  }
304
- initSoundFontTable() {
305
- const table = new Array(128);
306
- for (let i = 0; i < 128; i++) {
307
- table[i] = new Map();
308
- }
309
- return table;
310
- }
311
311
  addSoundFont(soundFont) {
312
312
  const index = this.soundFonts.length;
313
313
  this.soundFonts.push(soundFont);
314
314
  const presetHeaders = soundFont.parsed.presetHeaders;
315
+ const soundFontTable = this.soundFontTable;
315
316
  for (let i = 0; i < presetHeaders.length; i++) {
316
- const presetHeader = presetHeaders[i];
317
- const banks = this.soundFontTable[presetHeader.preset];
318
- banks.set(presetHeader.bank, index);
317
+ const { preset, bank } = presetHeaders[i];
318
+ soundFontTable[preset][bank] = index;
319
319
  }
320
320
  }
321
321
  async toUint8Array(input) {
@@ -393,13 +393,16 @@ export class MidyGMLite {
393
393
  this.GM1SystemOn();
394
394
  }
395
395
  getVoiceId(channel, noteNumber, velocity) {
396
- const bankNumber = this.calcBank(channel);
397
- const soundFontIndex = this.soundFontTable[channel.programNumber]
398
- .get(bankNumber);
396
+ const programNumber = channel.programNumber;
397
+ const bankTable = this.soundFontTable[programNumber];
398
+ if (!bankTable)
399
+ return;
400
+ const bank = channel.isDrum ? 128 : 0;
401
+ const soundFontIndex = bankTable[bank];
399
402
  if (soundFontIndex === undefined)
400
403
  return;
401
404
  const soundFont = this.soundFonts[soundFontIndex];
402
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
405
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
403
406
  const { instrument, sampleID } = voice.generators;
404
407
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
405
408
  }
@@ -450,13 +453,16 @@ export class MidyGMLite {
450
453
  }
451
454
  return bufferSource;
452
455
  }
453
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
454
- while (queueIndex < this.timeline.length) {
455
- const event = this.timeline[queueIndex];
456
- if (event.startTime > t + this.lookAhead)
456
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
457
+ const timeOffset = this.resumeTime - this.startTime;
458
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
459
+ const schedulingOffset = this.startDelay - timeOffset;
460
+ const timeline = this.timeline;
461
+ while (queueIndex < timeline.length) {
462
+ const event = timeline[queueIndex];
463
+ if (lookAheadCheckTime < event.startTime)
457
464
  break;
458
- const delay = this.startDelay - resumeTime;
459
- const startTime = event.startTime + delay;
465
+ const startTime = event.startTime + schedulingOffset;
460
466
  switch (event.type) {
461
467
  case "noteOn":
462
468
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
@@ -491,70 +497,77 @@ export class MidyGMLite {
491
497
  }
492
498
  return 0;
493
499
  }
494
- playNotes() {
495
- return new Promise((resolve) => {
496
- this.isPlaying = true;
497
- this.isPaused = false;
498
- this.startTime = this.audioContext.currentTime;
499
- let queueIndex = this.getQueueIndex(this.resumeTime);
500
- let resumeTime = this.resumeTime - this.startTime;
500
+ resetAllStates() {
501
+ this.exclusiveClassNotes.fill(undefined);
502
+ this.drumExclusiveClassNotes.fill(undefined);
503
+ this.voiceCache.clear();
504
+ for (let i = 0; i < this.channels.length; i++) {
505
+ this.channels[i].scheduledNotes = [];
506
+ this.resetChannelStates(i);
507
+ }
508
+ }
509
+ updateStates(queueIndex, nextQueueIndex) {
510
+ if (nextQueueIndex < queueIndex)
511
+ queueIndex = 0;
512
+ for (let i = queueIndex; i < nextQueueIndex; i++) {
513
+ const event = this.timeline[i];
514
+ switch (event.type) {
515
+ case "controller":
516
+ this.setControlChange(event.channel, event.controllerType, event.value, 0);
517
+ break;
518
+ case "programChange":
519
+ this.setProgramChange(event.channel, event.programNumber, 0);
520
+ break;
521
+ case "pitchBend":
522
+ this.setPitchBend(event.channel, event.value + 8192, 0);
523
+ break;
524
+ case "sysEx":
525
+ this.handleSysEx(event.data, 0);
526
+ }
527
+ }
528
+ }
529
+ async playNotes() {
530
+ if (this.audioContext.state === "suspended") {
531
+ await this.audioContext.resume();
532
+ }
533
+ this.isPlaying = true;
534
+ this.isPaused = false;
535
+ this.startTime = this.audioContext.currentTime;
536
+ let queueIndex = this.getQueueIndex(this.resumeTime);
537
+ let finished = false;
538
+ this.notePromises = [];
539
+ while (queueIndex < this.timeline.length) {
540
+ const now = this.audioContext.currentTime;
541
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
542
+ if (this.isPausing) {
543
+ await this.stopNotes(0, true, now);
544
+ await this.audioContext.suspend();
545
+ this.notePromises = [];
546
+ break;
547
+ }
548
+ else if (this.isStopping) {
549
+ await this.stopNotes(0, true, now);
550
+ await this.audioContext.suspend();
551
+ finished = true;
552
+ break;
553
+ }
554
+ else if (this.isSeeking) {
555
+ await this.stopNotes(0, true, now);
556
+ this.startTime = this.audioContext.currentTime;
557
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
558
+ this.updateStates(queueIndex, nextQueueIndex);
559
+ queueIndex = nextQueueIndex;
560
+ this.isSeeking = false;
561
+ continue;
562
+ }
563
+ const waitTime = now + this.noteCheckInterval;
564
+ await this.scheduleTask(() => { }, waitTime);
565
+ }
566
+ if (finished) {
501
567
  this.notePromises = [];
502
- const schedulePlayback = async () => {
503
- if (queueIndex >= this.timeline.length) {
504
- await Promise.all(this.notePromises);
505
- this.notePromises = [];
506
- this.exclusiveClassNotes.fill(undefined);
507
- this.drumExclusiveClassNotes.fill(undefined);
508
- this.voiceCache.clear();
509
- for (let i = 0; i < this.channels.length; i++) {
510
- this.resetAllStates(i);
511
- }
512
- resolve();
513
- return;
514
- }
515
- const now = this.audioContext.currentTime;
516
- const t = now + resumeTime;
517
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
518
- if (this.isPausing) {
519
- await this.stopNotes(0, true, now);
520
- this.notePromises = [];
521
- this.isPausing = false;
522
- this.isPaused = true;
523
- resolve();
524
- return;
525
- }
526
- else if (this.isStopping) {
527
- await this.stopNotes(0, true, now);
528
- this.notePromises = [];
529
- this.exclusiveClassNotes.fill(undefined);
530
- this.drumExclusiveClassNotes.fill(undefined);
531
- this.voiceCache.clear();
532
- for (let i = 0; i < this.channels.length; i++) {
533
- this.resetAllStates(i);
534
- }
535
- this.isStopping = false;
536
- this.isPaused = false;
537
- resolve();
538
- return;
539
- }
540
- else if (this.isSeeking) {
541
- this.stopNotes(0, true, now);
542
- this.exclusiveClassNotes.fill(undefined);
543
- this.drumExclusiveClassNotes.fill(undefined);
544
- this.startTime = this.audioContext.currentTime;
545
- queueIndex = this.getQueueIndex(this.resumeTime);
546
- resumeTime = this.resumeTime - this.startTime;
547
- this.isSeeking = false;
548
- await schedulePlayback();
549
- }
550
- else {
551
- const waitTime = now + this.noteCheckInterval;
552
- await this.scheduleTask(() => { }, waitTime);
553
- await schedulePlayback();
554
- }
555
- };
556
- schedulePlayback();
557
- });
568
+ this.resetAllStates();
569
+ }
570
+ this.isPlaying = false;
558
571
  }
559
572
  ticksToSecond(ticks, secondsPerBeat) {
560
573
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -562,16 +575,16 @@ export class MidyGMLite {
562
575
  secondToTicks(second, secondsPerBeat) {
563
576
  return second * this.ticksPerBeat / secondsPerBeat;
564
577
  }
578
+ getSoundFontId(channel) {
579
+ const programNumber = channel.programNumber;
580
+ const bank = channel.isDrum ? "128" : "000";
581
+ const program = programNumber.toString().padStart(3, "0");
582
+ return `${bank}:${program}`;
583
+ }
565
584
  extractMidiData(midi) {
566
585
  const instruments = new Set();
567
586
  const timeline = [];
568
- const tmpChannels = new Array(this.channels.length);
569
- for (let i = 0; i < tmpChannels.length; i++) {
570
- tmpChannels[i] = {
571
- programNumber: -1,
572
- bank: this.channels[i].bank,
573
- };
574
- }
587
+ const channels = this.channels;
575
588
  for (let i = 0; i < midi.tracks.length; i++) {
576
589
  const track = midi.tracks[i];
577
590
  let currentTicks = 0;
@@ -581,17 +594,15 @@ export class MidyGMLite {
581
594
  event.ticks = currentTicks;
582
595
  switch (event.type) {
583
596
  case "noteOn": {
584
- const channel = tmpChannels[event.channel];
585
- if (channel.programNumber < 0) {
586
- instruments.add(`${channel.bank}:0`);
587
- channel.programNumber = 0;
588
- }
597
+ const channel = channels[event.channel];
598
+ instruments.add(this.getSoundFontId(channel));
589
599
  break;
590
600
  }
591
601
  case "programChange": {
592
- const channel = tmpChannels[event.channel];
593
- channel.programNumber = event.programNumber;
594
- instruments.add(`${channel.bankNumber}:${channel.programNumber}`);
602
+ const channel = channels[event.channel];
603
+ this.setProgramChange(event.channel, event.programNumber);
604
+ instruments.add(this.getSoundFontId(channel));
605
+ break;
595
606
  }
596
607
  }
597
608
  delete event.deltaTime;
@@ -655,26 +666,32 @@ export class MidyGMLite {
655
666
  this.resumeTime = 0;
656
667
  if (this.voiceCounter.size === 0)
657
668
  this.cacheVoiceIds();
658
- await this.playNotes();
659
- this.isPlaying = false;
669
+ this.playPromise = this.playNotes();
670
+ await this.playPromise;
660
671
  }
661
- stop() {
672
+ async stop() {
662
673
  if (!this.isPlaying)
663
674
  return;
664
675
  this.isStopping = true;
676
+ await this.playPromise;
677
+ this.isStopping = false;
665
678
  }
666
- pause() {
679
+ async pause() {
667
680
  if (!this.isPlaying || this.isPaused)
668
681
  return;
669
682
  const now = this.audioContext.currentTime;
670
683
  this.resumeTime += now - this.startTime - this.startDelay;
671
684
  this.isPausing = true;
685
+ await this.playPromise;
686
+ this.isPausing = false;
687
+ this.isPaused = true;
672
688
  }
673
689
  async resume() {
674
690
  if (!this.isPaused)
675
691
  return;
676
- await this.playNotes();
677
- this.isPlaying = false;
692
+ this.playPromise = this.playNotes();
693
+ await this.playPromise;
694
+ this.isPaused = false;
678
695
  }
679
696
  seekTo(second) {
680
697
  this.resumeTime = second;
@@ -855,7 +872,7 @@ export class MidyGMLite {
855
872
  const voiceParams = voice.getAllParams(controllerState);
856
873
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
857
874
  const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
858
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
875
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
859
876
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
860
877
  note.filterNode = new BiquadFilterNode(this.audioContext, {
861
878
  type: "lowpass",
@@ -891,7 +908,7 @@ export class MidyGMLite {
891
908
  const channel = this.channels[channelNumber];
892
909
  if (!channel.isDrum)
893
910
  return;
894
- const drumExclusiveClass = drumExclusiveClasses[noteNumber];
911
+ const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
895
912
  if (drumExclusiveClass === 0)
896
913
  return;
897
914
  const index = drumExclusiveClass * this.channels.length + channelNumber;
@@ -904,13 +921,16 @@ export class MidyGMLite {
904
921
  }
905
922
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
906
923
  const channel = this.channels[channelNumber];
907
- const bankNumber = channel.bank;
908
- const soundFontIndex = this.soundFontTable[channel.programNumber]
909
- .get(bankNumber);
924
+ const programNumber = channel.programNumber;
925
+ const bankTable = this.soundFontTable[programNumber];
926
+ if (!bankTable)
927
+ return;
928
+ const bank = channel.isDrum ? 128 : 0;
929
+ const soundFontIndex = bankTable[bank];
910
930
  if (soundFontIndex === undefined)
911
931
  return;
912
932
  const soundFont = this.soundFonts[soundFontIndex];
913
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
933
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
914
934
  if (!voice)
915
935
  return;
916
936
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
@@ -1017,7 +1037,26 @@ export class MidyGMLite {
1017
1037
  channel.sustainNotes = [];
1018
1038
  return promises;
1019
1039
  }
1020
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1040
+ createMessageHandlers() {
1041
+ const handlers = new Array(256);
1042
+ // Channel Message
1043
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1044
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1045
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1046
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1047
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1048
+ return handlers;
1049
+ }
1050
+ handleMessage(data, scheduleTime) {
1051
+ const status = data[0];
1052
+ if (status === 0xF0) {
1053
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1054
+ }
1055
+ const handler = this.messageHandlers[status];
1056
+ if (handler)
1057
+ handler(data, scheduleTime);
1058
+ }
1059
+ handleChannelMessage(statusByte, data1, data2, scheduleTime) {
1021
1060
  const channelNumber = statusByte & 0x0F;
1022
1061
  const messageType = statusByte & 0xF0;
1023
1062
  switch (messageType) {
@@ -1097,28 +1136,36 @@ export class MidyGMLite {
1097
1136
  }
1098
1137
  createVoiceParamsHandlers() {
1099
1138
  return {
1100
- modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1139
+ modLfoToPitch: (channel, note, scheduleTime) => {
1101
1140
  if (0 < channel.state.modulationDepth) {
1102
1141
  this.setModLfoToPitch(channel, note, scheduleTime);
1103
1142
  }
1104
1143
  },
1105
- vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
1106
- modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
1144
+ vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
1145
+ modLfoToFilterFc: (channel, note, scheduleTime) => {
1107
1146
  if (0 < channel.state.modulationDepth) {
1108
1147
  this.setModLfoToFilterFc(note, scheduleTime);
1109
1148
  }
1110
1149
  },
1111
- modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
1150
+ modLfoToVolume: (channel, note, scheduleTime) => {
1112
1151
  if (0 < channel.state.modulationDepth) {
1113
1152
  this.setModLfoToVolume(note, scheduleTime);
1114
1153
  }
1115
1154
  },
1116
- chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1117
- reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1118
- delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
1119
- freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
1120
- delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1121
- freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1155
+ chorusEffectsSend: (_channel, _note, _scheduleTime) => { },
1156
+ reverbEffectsSend: (_channel, _note, _scheduleTime) => { },
1157
+ delayModLFO: (_channel, note, scheduleTime) => {
1158
+ if (0 < channel.state.modulationDepth) {
1159
+ this.setDelayModLFO(note, scheduleTime);
1160
+ }
1161
+ },
1162
+ freqModLFO: (_channel, note, scheduleTime) => {
1163
+ if (0 < channel.state.modulationDepth) {
1164
+ this.setFreqModLFO(note, scheduleTime);
1165
+ }
1166
+ },
1167
+ delayVibLFO: (_channel, _note, _scheduleTime) => { },
1168
+ freqVibLFO: (_channel, _note, _scheduleTime) => { },
1122
1169
  };
1123
1170
  }
1124
1171
  getControllerState(channel, noteNumber, velocity) {
@@ -1141,7 +1188,7 @@ export class MidyGMLite {
1141
1188
  continue;
1142
1189
  note.voiceParams[key] = value;
1143
1190
  if (key in this.voiceParamsHandlers) {
1144
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1191
+ this.voiceParamsHandlers[key](channel, note, scheduleTime);
1145
1192
  }
1146
1193
  else {
1147
1194
  if (volumeEnvelopeKeySet.has(key))
@@ -1298,8 +1345,8 @@ export class MidyGMLite {
1298
1345
  }
1299
1346
  handlePitchBendRangeRPN(channelNumber, scheduleTime) {
1300
1347
  const channel = this.channels[channelNumber];
1301
- this.limitData(channel, 0, 127, 0, 99);
1302
- const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1348
+ this.limitData(channel, 0, 127, 0, 127);
1349
+ const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
1303
1350
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1304
1351
  }
1305
1352
  setPitchBendRange(channelNumber, value, scheduleTime) {
@@ -1307,7 +1354,7 @@ export class MidyGMLite {
1307
1354
  scheduleTime ??= this.audioContext.currentTime;
1308
1355
  const state = channel.state;
1309
1356
  const prev = state.pitchWheelSensitivity;
1310
- const next = value / 128;
1357
+ const next = value / 12800;
1311
1358
  state.pitchWheelSensitivity = next;
1312
1359
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1313
1360
  this.updateChannelDetune(channel, scheduleTime);
@@ -1317,7 +1364,7 @@ export class MidyGMLite {
1317
1364
  scheduleTime ??= this.audioContext.currentTime;
1318
1365
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1319
1366
  }
1320
- resetAllStates(channelNumber) {
1367
+ resetChannelStates(channelNumber) {
1321
1368
  const scheduleTime = this.audioContext.currentTime;
1322
1369
  const channel = this.channels[channelNumber];
1323
1370
  const state = channel.state;
@@ -1392,10 +1439,8 @@ export class MidyGMLite {
1392
1439
  for (let i = 0; i < this.channels.length; i++) {
1393
1440
  this.allSoundOff(i, 0, scheduleTime);
1394
1441
  const channel = this.channels[i];
1395
- channel.bank = 0;
1396
1442
  channel.isDrum = false;
1397
1443
  }
1398
- this.channels[9].bank = 128;
1399
1444
  this.channels[9].isDrum = true;
1400
1445
  }
1401
1446
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -1416,16 +1461,11 @@ export class MidyGMLite {
1416
1461
  const volume = (data[5] * 128 + data[4]) / 16383;
1417
1462
  this.setMasterVolume(volume, scheduleTime);
1418
1463
  }
1419
- setMasterVolume(volume, scheduleTime) {
1464
+ setMasterVolume(value, scheduleTime) {
1420
1465
  scheduleTime ??= this.audioContext.currentTime;
1421
- if (volume < 0 && 1 < volume) {
1422
- console.error("Master Volume is out of range");
1423
- }
1424
- else {
1425
- this.masterVolume.gain
1426
- .cancelScheduledValues(scheduleTime)
1427
- .setValueAtTime(volume * volume, scheduleTime);
1428
- }
1466
+ this.masterVolume.gain
1467
+ .cancelScheduledValues(scheduleTime)
1468
+ .setValueAtTime(value * value, scheduleTime);
1429
1469
  }
1430
1470
  handleSysEx(data, scheduleTime) {
1431
1471
  switch (data[0]) {
@@ -1465,7 +1505,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1465
1505
  scheduleIndex: 0,
1466
1506
  detune: 0,
1467
1507
  programNumber: 0,
1468
- bank: 0,
1469
1508
  dataMSB: 0,
1470
1509
  dataLSB: 0,
1471
1510
  rpnMSB: 127,