@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"}
@@ -216,7 +216,7 @@ class MidyGMLite {
216
216
  enumerable: true,
217
217
  configurable: true,
218
218
  writable: true,
219
- value: this.initSoundFontTable()
219
+ value: Array.from({ length: 128 }, () => [])
220
220
  });
221
221
  Object.defineProperty(this, "voiceCounter", {
222
222
  enumerable: true,
@@ -260,6 +260,12 @@ class MidyGMLite {
260
260
  writable: true,
261
261
  value: false
262
262
  });
263
+ Object.defineProperty(this, "playPromise", {
264
+ enumerable: true,
265
+ configurable: true,
266
+ writable: true,
267
+ value: void 0
268
+ });
263
269
  Object.defineProperty(this, "timeline", {
264
270
  enumerable: true,
265
271
  configurable: true,
@@ -297,6 +303,7 @@ class MidyGMLite {
297
303
  length: 1,
298
304
  sampleRate: audioContext.sampleRate,
299
305
  });
306
+ this.messageHandlers = this.createMessageHandlers();
300
307
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
301
308
  this.controlChangeHandlers = this.createControlChangeHandlers();
302
309
  this.channels = this.createChannels(audioContext);
@@ -304,21 +311,14 @@ class MidyGMLite {
304
311
  this.scheduler.connect(audioContext.destination);
305
312
  this.GM1SystemOn();
306
313
  }
307
- initSoundFontTable() {
308
- const table = new Array(128);
309
- for (let i = 0; i < 128; i++) {
310
- table[i] = new Map();
311
- }
312
- return table;
313
- }
314
314
  addSoundFont(soundFont) {
315
315
  const index = this.soundFonts.length;
316
316
  this.soundFonts.push(soundFont);
317
317
  const presetHeaders = soundFont.parsed.presetHeaders;
318
+ const soundFontTable = this.soundFontTable;
318
319
  for (let i = 0; i < presetHeaders.length; i++) {
319
- const presetHeader = presetHeaders[i];
320
- const banks = this.soundFontTable[presetHeader.preset];
321
- banks.set(presetHeader.bank, index);
320
+ const { preset, bank } = presetHeaders[i];
321
+ soundFontTable[preset][bank] = index;
322
322
  }
323
323
  }
324
324
  async toUint8Array(input) {
@@ -396,13 +396,16 @@ class MidyGMLite {
396
396
  this.GM1SystemOn();
397
397
  }
398
398
  getVoiceId(channel, noteNumber, velocity) {
399
- const bankNumber = this.calcBank(channel);
400
- const soundFontIndex = this.soundFontTable[channel.programNumber]
401
- .get(bankNumber);
399
+ const programNumber = channel.programNumber;
400
+ const bankTable = this.soundFontTable[programNumber];
401
+ if (!bankTable)
402
+ return;
403
+ const bank = channel.isDrum ? 128 : 0;
404
+ const soundFontIndex = bankTable[bank];
402
405
  if (soundFontIndex === undefined)
403
406
  return;
404
407
  const soundFont = this.soundFonts[soundFontIndex];
405
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
408
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
406
409
  const { instrument, sampleID } = voice.generators;
407
410
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
408
411
  }
@@ -453,13 +456,16 @@ class MidyGMLite {
453
456
  }
454
457
  return bufferSource;
455
458
  }
456
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
457
- while (queueIndex < this.timeline.length) {
458
- const event = this.timeline[queueIndex];
459
- if (event.startTime > t + this.lookAhead)
459
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
460
+ const timeOffset = this.resumeTime - this.startTime;
461
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
462
+ const schedulingOffset = this.startDelay - timeOffset;
463
+ const timeline = this.timeline;
464
+ while (queueIndex < timeline.length) {
465
+ const event = timeline[queueIndex];
466
+ if (lookAheadCheckTime < event.startTime)
460
467
  break;
461
- const delay = this.startDelay - resumeTime;
462
- const startTime = event.startTime + delay;
468
+ const startTime = event.startTime + schedulingOffset;
463
469
  switch (event.type) {
464
470
  case "noteOn":
465
471
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
@@ -494,70 +500,77 @@ class MidyGMLite {
494
500
  }
495
501
  return 0;
496
502
  }
497
- playNotes() {
498
- return new Promise((resolve) => {
499
- this.isPlaying = true;
500
- this.isPaused = false;
501
- this.startTime = this.audioContext.currentTime;
502
- let queueIndex = this.getQueueIndex(this.resumeTime);
503
- let resumeTime = this.resumeTime - this.startTime;
503
+ resetAllStates() {
504
+ this.exclusiveClassNotes.fill(undefined);
505
+ this.drumExclusiveClassNotes.fill(undefined);
506
+ this.voiceCache.clear();
507
+ for (let i = 0; i < this.channels.length; i++) {
508
+ this.channels[i].scheduledNotes = [];
509
+ this.resetChannelStates(i);
510
+ }
511
+ }
512
+ updateStates(queueIndex, nextQueueIndex) {
513
+ if (nextQueueIndex < queueIndex)
514
+ queueIndex = 0;
515
+ for (let i = queueIndex; i < nextQueueIndex; i++) {
516
+ const event = this.timeline[i];
517
+ switch (event.type) {
518
+ case "controller":
519
+ this.setControlChange(event.channel, event.controllerType, event.value, 0);
520
+ break;
521
+ case "programChange":
522
+ this.setProgramChange(event.channel, event.programNumber, 0);
523
+ break;
524
+ case "pitchBend":
525
+ this.setPitchBend(event.channel, event.value + 8192, 0);
526
+ break;
527
+ case "sysEx":
528
+ this.handleSysEx(event.data, 0);
529
+ }
530
+ }
531
+ }
532
+ async playNotes() {
533
+ if (this.audioContext.state === "suspended") {
534
+ await this.audioContext.resume();
535
+ }
536
+ this.isPlaying = true;
537
+ this.isPaused = false;
538
+ this.startTime = this.audioContext.currentTime;
539
+ let queueIndex = this.getQueueIndex(this.resumeTime);
540
+ let finished = false;
541
+ this.notePromises = [];
542
+ while (queueIndex < this.timeline.length) {
543
+ const now = this.audioContext.currentTime;
544
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
545
+ if (this.isPausing) {
546
+ await this.stopNotes(0, true, now);
547
+ await this.audioContext.suspend();
548
+ this.notePromises = [];
549
+ break;
550
+ }
551
+ else if (this.isStopping) {
552
+ await this.stopNotes(0, true, now);
553
+ await this.audioContext.suspend();
554
+ finished = true;
555
+ break;
556
+ }
557
+ else if (this.isSeeking) {
558
+ await this.stopNotes(0, true, now);
559
+ this.startTime = this.audioContext.currentTime;
560
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
561
+ this.updateStates(queueIndex, nextQueueIndex);
562
+ queueIndex = nextQueueIndex;
563
+ this.isSeeking = false;
564
+ continue;
565
+ }
566
+ const waitTime = now + this.noteCheckInterval;
567
+ await this.scheduleTask(() => { }, waitTime);
568
+ }
569
+ if (finished) {
504
570
  this.notePromises = [];
505
- const schedulePlayback = async () => {
506
- if (queueIndex >= this.timeline.length) {
507
- await Promise.all(this.notePromises);
508
- this.notePromises = [];
509
- this.exclusiveClassNotes.fill(undefined);
510
- this.drumExclusiveClassNotes.fill(undefined);
511
- this.voiceCache.clear();
512
- for (let i = 0; i < this.channels.length; i++) {
513
- this.resetAllStates(i);
514
- }
515
- resolve();
516
- return;
517
- }
518
- const now = this.audioContext.currentTime;
519
- const t = now + resumeTime;
520
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
521
- if (this.isPausing) {
522
- await this.stopNotes(0, true, now);
523
- this.notePromises = [];
524
- this.isPausing = false;
525
- this.isPaused = true;
526
- resolve();
527
- return;
528
- }
529
- else if (this.isStopping) {
530
- await this.stopNotes(0, true, now);
531
- this.notePromises = [];
532
- this.exclusiveClassNotes.fill(undefined);
533
- this.drumExclusiveClassNotes.fill(undefined);
534
- this.voiceCache.clear();
535
- for (let i = 0; i < this.channels.length; i++) {
536
- this.resetAllStates(i);
537
- }
538
- this.isStopping = false;
539
- this.isPaused = false;
540
- resolve();
541
- return;
542
- }
543
- else if (this.isSeeking) {
544
- this.stopNotes(0, true, now);
545
- this.exclusiveClassNotes.fill(undefined);
546
- this.drumExclusiveClassNotes.fill(undefined);
547
- this.startTime = this.audioContext.currentTime;
548
- queueIndex = this.getQueueIndex(this.resumeTime);
549
- resumeTime = this.resumeTime - this.startTime;
550
- this.isSeeking = false;
551
- await schedulePlayback();
552
- }
553
- else {
554
- const waitTime = now + this.noteCheckInterval;
555
- await this.scheduleTask(() => { }, waitTime);
556
- await schedulePlayback();
557
- }
558
- };
559
- schedulePlayback();
560
- });
571
+ this.resetAllStates();
572
+ }
573
+ this.isPlaying = false;
561
574
  }
562
575
  ticksToSecond(ticks, secondsPerBeat) {
563
576
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -565,16 +578,16 @@ class MidyGMLite {
565
578
  secondToTicks(second, secondsPerBeat) {
566
579
  return second * this.ticksPerBeat / secondsPerBeat;
567
580
  }
581
+ getSoundFontId(channel) {
582
+ const programNumber = channel.programNumber;
583
+ const bank = channel.isDrum ? "128" : "000";
584
+ const program = programNumber.toString().padStart(3, "0");
585
+ return `${bank}:${program}`;
586
+ }
568
587
  extractMidiData(midi) {
569
588
  const instruments = new Set();
570
589
  const timeline = [];
571
- const tmpChannels = new Array(this.channels.length);
572
- for (let i = 0; i < tmpChannels.length; i++) {
573
- tmpChannels[i] = {
574
- programNumber: -1,
575
- bank: this.channels[i].bank,
576
- };
577
- }
590
+ const channels = this.channels;
578
591
  for (let i = 0; i < midi.tracks.length; i++) {
579
592
  const track = midi.tracks[i];
580
593
  let currentTicks = 0;
@@ -584,17 +597,15 @@ class MidyGMLite {
584
597
  event.ticks = currentTicks;
585
598
  switch (event.type) {
586
599
  case "noteOn": {
587
- const channel = tmpChannels[event.channel];
588
- if (channel.programNumber < 0) {
589
- instruments.add(`${channel.bank}:0`);
590
- channel.programNumber = 0;
591
- }
600
+ const channel = channels[event.channel];
601
+ instruments.add(this.getSoundFontId(channel));
592
602
  break;
593
603
  }
594
604
  case "programChange": {
595
- const channel = tmpChannels[event.channel];
596
- channel.programNumber = event.programNumber;
597
- instruments.add(`${channel.bankNumber}:${channel.programNumber}`);
605
+ const channel = channels[event.channel];
606
+ this.setProgramChange(event.channel, event.programNumber);
607
+ instruments.add(this.getSoundFontId(channel));
608
+ break;
598
609
  }
599
610
  }
600
611
  delete event.deltaTime;
@@ -658,26 +669,32 @@ class MidyGMLite {
658
669
  this.resumeTime = 0;
659
670
  if (this.voiceCounter.size === 0)
660
671
  this.cacheVoiceIds();
661
- await this.playNotes();
662
- this.isPlaying = false;
672
+ this.playPromise = this.playNotes();
673
+ await this.playPromise;
663
674
  }
664
- stop() {
675
+ async stop() {
665
676
  if (!this.isPlaying)
666
677
  return;
667
678
  this.isStopping = true;
679
+ await this.playPromise;
680
+ this.isStopping = false;
668
681
  }
669
- pause() {
682
+ async pause() {
670
683
  if (!this.isPlaying || this.isPaused)
671
684
  return;
672
685
  const now = this.audioContext.currentTime;
673
686
  this.resumeTime += now - this.startTime - this.startDelay;
674
687
  this.isPausing = true;
688
+ await this.playPromise;
689
+ this.isPausing = false;
690
+ this.isPaused = true;
675
691
  }
676
692
  async resume() {
677
693
  if (!this.isPaused)
678
694
  return;
679
- await this.playNotes();
680
- this.isPlaying = false;
695
+ this.playPromise = this.playNotes();
696
+ await this.playPromise;
697
+ this.isPaused = false;
681
698
  }
682
699
  seekTo(second) {
683
700
  this.resumeTime = second;
@@ -858,7 +875,7 @@ class MidyGMLite {
858
875
  const voiceParams = voice.getAllParams(controllerState);
859
876
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
860
877
  const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
861
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
878
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
862
879
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
863
880
  note.filterNode = new BiquadFilterNode(this.audioContext, {
864
881
  type: "lowpass",
@@ -894,7 +911,7 @@ class MidyGMLite {
894
911
  const channel = this.channels[channelNumber];
895
912
  if (!channel.isDrum)
896
913
  return;
897
- const drumExclusiveClass = drumExclusiveClasses[noteNumber];
914
+ const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
898
915
  if (drumExclusiveClass === 0)
899
916
  return;
900
917
  const index = drumExclusiveClass * this.channels.length + channelNumber;
@@ -907,13 +924,16 @@ class MidyGMLite {
907
924
  }
908
925
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
909
926
  const channel = this.channels[channelNumber];
910
- const bankNumber = channel.bank;
911
- const soundFontIndex = this.soundFontTable[channel.programNumber]
912
- .get(bankNumber);
927
+ const programNumber = channel.programNumber;
928
+ const bankTable = this.soundFontTable[programNumber];
929
+ if (!bankTable)
930
+ return;
931
+ const bank = channel.isDrum ? 128 : 0;
932
+ const soundFontIndex = bankTable[bank];
913
933
  if (soundFontIndex === undefined)
914
934
  return;
915
935
  const soundFont = this.soundFonts[soundFontIndex];
916
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
936
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
917
937
  if (!voice)
918
938
  return;
919
939
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
@@ -1020,7 +1040,26 @@ class MidyGMLite {
1020
1040
  channel.sustainNotes = [];
1021
1041
  return promises;
1022
1042
  }
1023
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1043
+ createMessageHandlers() {
1044
+ const handlers = new Array(256);
1045
+ // Channel Message
1046
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1047
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1048
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1049
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1050
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1051
+ return handlers;
1052
+ }
1053
+ handleMessage(data, scheduleTime) {
1054
+ const status = data[0];
1055
+ if (status === 0xF0) {
1056
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1057
+ }
1058
+ const handler = this.messageHandlers[status];
1059
+ if (handler)
1060
+ handler(data, scheduleTime);
1061
+ }
1062
+ handleChannelMessage(statusByte, data1, data2, scheduleTime) {
1024
1063
  const channelNumber = statusByte & 0x0F;
1025
1064
  const messageType = statusByte & 0xF0;
1026
1065
  switch (messageType) {
@@ -1100,28 +1139,36 @@ class MidyGMLite {
1100
1139
  }
1101
1140
  createVoiceParamsHandlers() {
1102
1141
  return {
1103
- modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1142
+ modLfoToPitch: (channel, note, scheduleTime) => {
1104
1143
  if (0 < channel.state.modulationDepth) {
1105
1144
  this.setModLfoToPitch(channel, note, scheduleTime);
1106
1145
  }
1107
1146
  },
1108
- vibLfoToPitch: (_channel, _note, _prevValue, _scheduleTime) => { },
1109
- modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
1147
+ vibLfoToPitch: (_channel, _note, _scheduleTime) => { },
1148
+ modLfoToFilterFc: (channel, note, scheduleTime) => {
1110
1149
  if (0 < channel.state.modulationDepth) {
1111
1150
  this.setModLfoToFilterFc(note, scheduleTime);
1112
1151
  }
1113
1152
  },
1114
- modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
1153
+ modLfoToVolume: (channel, note, scheduleTime) => {
1115
1154
  if (0 < channel.state.modulationDepth) {
1116
1155
  this.setModLfoToVolume(note, scheduleTime);
1117
1156
  }
1118
1157
  },
1119
- chorusEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1120
- reverbEffectsSend: (_channel, _note, _prevValue, _scheduleTime) => { },
1121
- delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
1122
- freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
1123
- delayVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1124
- freqVibLFO: (_channel, _note, _prevValue, _scheduleTime) => { },
1158
+ chorusEffectsSend: (_channel, _note, _scheduleTime) => { },
1159
+ reverbEffectsSend: (_channel, _note, _scheduleTime) => { },
1160
+ delayModLFO: (_channel, note, scheduleTime) => {
1161
+ if (0 < channel.state.modulationDepth) {
1162
+ this.setDelayModLFO(note, scheduleTime);
1163
+ }
1164
+ },
1165
+ freqModLFO: (_channel, note, scheduleTime) => {
1166
+ if (0 < channel.state.modulationDepth) {
1167
+ this.setFreqModLFO(note, scheduleTime);
1168
+ }
1169
+ },
1170
+ delayVibLFO: (_channel, _note, _scheduleTime) => { },
1171
+ freqVibLFO: (_channel, _note, _scheduleTime) => { },
1125
1172
  };
1126
1173
  }
1127
1174
  getControllerState(channel, noteNumber, velocity) {
@@ -1144,7 +1191,7 @@ class MidyGMLite {
1144
1191
  continue;
1145
1192
  note.voiceParams[key] = value;
1146
1193
  if (key in this.voiceParamsHandlers) {
1147
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1194
+ this.voiceParamsHandlers[key](channel, note, scheduleTime);
1148
1195
  }
1149
1196
  else {
1150
1197
  if (volumeEnvelopeKeySet.has(key))
@@ -1301,8 +1348,8 @@ class MidyGMLite {
1301
1348
  }
1302
1349
  handlePitchBendRangeRPN(channelNumber, scheduleTime) {
1303
1350
  const channel = this.channels[channelNumber];
1304
- this.limitData(channel, 0, 127, 0, 99);
1305
- const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1351
+ this.limitData(channel, 0, 127, 0, 127);
1352
+ const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
1306
1353
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
1307
1354
  }
1308
1355
  setPitchBendRange(channelNumber, value, scheduleTime) {
@@ -1310,7 +1357,7 @@ class MidyGMLite {
1310
1357
  scheduleTime ??= this.audioContext.currentTime;
1311
1358
  const state = channel.state;
1312
1359
  const prev = state.pitchWheelSensitivity;
1313
- const next = value / 128;
1360
+ const next = value / 12800;
1314
1361
  state.pitchWheelSensitivity = next;
1315
1362
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1316
1363
  this.updateChannelDetune(channel, scheduleTime);
@@ -1320,7 +1367,7 @@ class MidyGMLite {
1320
1367
  scheduleTime ??= this.audioContext.currentTime;
1321
1368
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
1322
1369
  }
1323
- resetAllStates(channelNumber) {
1370
+ resetChannelStates(channelNumber) {
1324
1371
  const scheduleTime = this.audioContext.currentTime;
1325
1372
  const channel = this.channels[channelNumber];
1326
1373
  const state = channel.state;
@@ -1395,10 +1442,8 @@ class MidyGMLite {
1395
1442
  for (let i = 0; i < this.channels.length; i++) {
1396
1443
  this.allSoundOff(i, 0, scheduleTime);
1397
1444
  const channel = this.channels[i];
1398
- channel.bank = 0;
1399
1445
  channel.isDrum = false;
1400
1446
  }
1401
- this.channels[9].bank = 128;
1402
1447
  this.channels[9].isDrum = true;
1403
1448
  }
1404
1449
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -1419,16 +1464,11 @@ class MidyGMLite {
1419
1464
  const volume = (data[5] * 128 + data[4]) / 16383;
1420
1465
  this.setMasterVolume(volume, scheduleTime);
1421
1466
  }
1422
- setMasterVolume(volume, scheduleTime) {
1467
+ setMasterVolume(value, scheduleTime) {
1423
1468
  scheduleTime ??= this.audioContext.currentTime;
1424
- if (volume < 0 && 1 < volume) {
1425
- console.error("Master Volume is out of range");
1426
- }
1427
- else {
1428
- this.masterVolume.gain
1429
- .cancelScheduledValues(scheduleTime)
1430
- .setValueAtTime(volume * volume, scheduleTime);
1431
- }
1469
+ this.masterVolume.gain
1470
+ .cancelScheduledValues(scheduleTime)
1471
+ .setValueAtTime(value * value, scheduleTime);
1432
1472
  }
1433
1473
  handleSysEx(data, scheduleTime) {
1434
1474
  switch (data[0]) {
@@ -1469,7 +1509,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1469
1509
  scheduleIndex: 0,
1470
1510
  detune: 0,
1471
1511
  programNumber: 0,
1472
- bank: 0,
1473
1512
  dataMSB: 0,
1474
1513
  dataLSB: 0,
1475
1514
  rpnMSB: 127,