@marmooo/midy 0.3.7 → 0.4.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.
@@ -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,9 +20,10 @@ 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>;
26
+ realtimeVoiceCache: Map<any, any>;
27
27
  isPlaying: boolean;
28
28
  isPausing: boolean;
29
29
  isPaused: boolean;
@@ -39,6 +39,7 @@ export class MidyGMLite {
39
39
  masterVolume: any;
40
40
  scheduler: any;
41
41
  schedulerBuffer: any;
42
+ messageHandlers: any[];
42
43
  voiceParamsHandlers: {
43
44
  modLfoToPitch: (channel: any, note: any, scheduleTime: any) => void;
44
45
  vibLfoToPitch: (_channel: any, _note: any, _scheduleTime: any) => void;
@@ -53,7 +54,6 @@ export class MidyGMLite {
53
54
  };
54
55
  controlChangeHandlers: any[];
55
56
  channels: any[];
56
- initSoundFontTable(): any[];
57
57
  addSoundFont(soundFont: any): void;
58
58
  toUint8Array(input: any): Promise<Uint8Array<ArrayBuffer>>;
59
59
  loadSoundFont(input: any): Promise<void>;
@@ -68,13 +68,14 @@ export class MidyGMLite {
68
68
  createChannels(audioContext: any): any[];
69
69
  createAudioBuffer(voiceParams: any): Promise<any>;
70
70
  createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
71
- scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
71
+ scheduleTimelineEvents(scheduleTime: any, queueIndex: any): Promise<any>;
72
72
  getQueueIndex(second: any): number;
73
73
  resetAllStates(): void;
74
74
  updateStates(queueIndex: any, nextQueueIndex: any): void;
75
75
  playNotes(): Promise<void>;
76
76
  ticksToSecond(ticks: any, secondsPerBeat: any): number;
77
77
  secondToTicks(second: any, secondsPerBeat: any): number;
78
+ getSoundFontId(channel: any): string;
78
79
  extractMidiData(midi: any): {
79
80
  instruments: Set<any>;
80
81
  timeline: any[];
@@ -103,20 +104,21 @@ export class MidyGMLite {
103
104
  clampCutoffFrequency(frequency: any): number;
104
105
  setFilterEnvelope(note: any, scheduleTime: any): void;
105
106
  startModulation(channel: any, note: any, scheduleTime: any): void;
106
- getAudioBuffer(channel: any, noteNumber: any, velocity: any, voiceParams: any): Promise<any>;
107
- createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any): Promise<Note>;
107
+ getAudioBuffer(channel: any, noteNumber: any, velocity: any, voiceParams: any, realtime: any): Promise<any>;
108
+ setNoteAudioNode(channel: any, note: any, realtime: any): Promise<any>;
108
109
  handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
109
110
  handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
110
- scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
111
- noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
111
+ setNoteRouting(channelNumber: any, note: any, startTime: any): void;
112
+ noteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
112
113
  disconnectNote(note: any): void;
113
114
  releaseNote(channel: any, note: any, endTime: any): Promise<any>;
114
- scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): void;
115
+ noteOff(channelNumber: any, noteNumber: any, velocity: any, endTime: any, force: any): void;
115
116
  setNoteIndex(channel: any, index: any): void;
116
117
  findNoteOffIndex(channel: any, noteNumber: any): any;
117
- noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
118
118
  releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
119
- 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>;
120
122
  setProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
121
123
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
122
124
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -168,26 +170,8 @@ export class MidyGMLite {
168
170
  GM1SystemOn(scheduleTime: any): void;
169
171
  handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
170
172
  handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
171
- setMasterVolume(volume: any, scheduleTime: any): void;
173
+ setMasterVolume(value: any, scheduleTime: any): void;
172
174
  handleSysEx(data: any, scheduleTime: any): void;
173
175
  scheduleTask(callback: any, scheduleTime: any): Promise<any>;
174
176
  }
175
- declare class Note {
176
- constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
177
- index: number;
178
- ending: boolean;
179
- bufferSource: any;
180
- filterNode: any;
181
- filterDepth: any;
182
- volumeEnvelopeNode: any;
183
- volumeDepth: any;
184
- modulationLFO: any;
185
- modulationDepth: any;
186
- noteNumber: any;
187
- velocity: any;
188
- startTime: any;
189
- voice: any;
190
- voiceParams: any;
191
- }
192
- export {};
193
177
  //# sourceMappingURL=midy-GMLite.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA4BE;;;;;;;;;;MAUE;IAEF,+BAcC;IArDD,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,iBAAY;IACZ,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,uBAQC;IAED,yDA2BC;IAED,2BA+CC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;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,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;;;;;;;;;;;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,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AArhDD;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":"AA2GA;IA6BE;;;;;;;;;MASE;IAEF,+BAeC;IAtDD,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,kCAA+B;IAC/B,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,uBASC;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,sBAIC;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,4GAkCC;IAED,uEAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,oEAUC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAsBC;IAED,6CAUC;IAED,qDAUC;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"}
@@ -1,7 +1,19 @@
1
1
  import { parseMidi } from "midi-file";
2
2
  import { parse, SoundFont } from "@marmooo/soundfont-parser";
3
3
  class Note {
4
- constructor(noteNumber, velocity, startTime, voice, voiceParams) {
4
+ constructor(noteNumber, velocity, startTime) {
5
+ Object.defineProperty(this, "voice", {
6
+ enumerable: true,
7
+ configurable: true,
8
+ writable: true,
9
+ value: void 0
10
+ });
11
+ Object.defineProperty(this, "voiceParams", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: void 0
16
+ });
5
17
  Object.defineProperty(this, "index", {
6
18
  enumerable: true,
7
19
  configurable: true,
@@ -14,6 +26,12 @@ class Note {
14
26
  writable: true,
15
27
  value: false
16
28
  });
29
+ Object.defineProperty(this, "pending", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: true
34
+ });
17
35
  Object.defineProperty(this, "bufferSource", {
18
36
  enumerable: true,
19
37
  configurable: true,
@@ -59,8 +77,6 @@ class Note {
59
77
  this.noteNumber = noteNumber;
60
78
  this.velocity = velocity;
61
79
  this.startTime = startTime;
62
- this.voice = voice;
63
- this.voiceParams = voiceParams;
64
80
  }
65
81
  }
66
82
  const drumExclusiveClasses = new Uint8Array(128);
@@ -213,7 +229,7 @@ export class MidyGMLite {
213
229
  enumerable: true,
214
230
  configurable: true,
215
231
  writable: true,
216
- value: this.initSoundFontTable()
232
+ value: Array.from({ length: 128 }, () => [])
217
233
  });
218
234
  Object.defineProperty(this, "voiceCounter", {
219
235
  enumerable: true,
@@ -227,6 +243,12 @@ export class MidyGMLite {
227
243
  writable: true,
228
244
  value: new Map()
229
245
  });
246
+ Object.defineProperty(this, "realtimeVoiceCache", {
247
+ enumerable: true,
248
+ configurable: true,
249
+ writable: true,
250
+ value: new Map()
251
+ });
230
252
  Object.defineProperty(this, "isPlaying", {
231
253
  enumerable: true,
232
254
  configurable: true,
@@ -300,6 +322,7 @@ export class MidyGMLite {
300
322
  length: 1,
301
323
  sampleRate: audioContext.sampleRate,
302
324
  });
325
+ this.messageHandlers = this.createMessageHandlers();
303
326
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
304
327
  this.controlChangeHandlers = this.createControlChangeHandlers();
305
328
  this.channels = this.createChannels(audioContext);
@@ -307,21 +330,14 @@ export class MidyGMLite {
307
330
  this.scheduler.connect(audioContext.destination);
308
331
  this.GM1SystemOn();
309
332
  }
310
- initSoundFontTable() {
311
- const table = new Array(128);
312
- for (let i = 0; i < 128; i++) {
313
- table[i] = new Map();
314
- }
315
- return table;
316
- }
317
333
  addSoundFont(soundFont) {
318
334
  const index = this.soundFonts.length;
319
335
  this.soundFonts.push(soundFont);
320
336
  const presetHeaders = soundFont.parsed.presetHeaders;
337
+ const soundFontTable = this.soundFontTable;
321
338
  for (let i = 0; i < presetHeaders.length; i++) {
322
- const presetHeader = presetHeaders[i];
323
- const banks = this.soundFontTable[presetHeader.preset];
324
- banks.set(presetHeader.bank, index);
339
+ const { preset, bank } = presetHeaders[i];
340
+ soundFontTable[preset][bank] = index;
325
341
  }
326
342
  }
327
343
  async toUint8Array(input) {
@@ -399,13 +415,16 @@ export class MidyGMLite {
399
415
  this.GM1SystemOn();
400
416
  }
401
417
  getVoiceId(channel, noteNumber, velocity) {
402
- const bankNumber = this.calcBank(channel);
403
- const soundFontIndex = this.soundFontTable[channel.programNumber]
404
- .get(bankNumber);
418
+ const programNumber = channel.programNumber;
419
+ const bankTable = this.soundFontTable[programNumber];
420
+ if (!bankTable)
421
+ return;
422
+ const bank = channel.isDrum ? 128 : 0;
423
+ const soundFontIndex = bankTable[bank];
405
424
  if (soundFontIndex === undefined)
406
425
  return;
407
426
  const soundFont = this.soundFonts[soundFontIndex];
408
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
427
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
409
428
  const { instrument, sampleID } = voice.generators;
410
429
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
411
430
  }
@@ -456,19 +475,22 @@ export class MidyGMLite {
456
475
  }
457
476
  return bufferSource;
458
477
  }
459
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
460
- while (queueIndex < this.timeline.length) {
461
- const event = this.timeline[queueIndex];
462
- if (event.startTime > t + this.lookAhead)
478
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
479
+ const timeOffset = this.resumeTime - this.startTime;
480
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
481
+ const schedulingOffset = this.startDelay - timeOffset;
482
+ const timeline = this.timeline;
483
+ while (queueIndex < timeline.length) {
484
+ const event = timeline[queueIndex];
485
+ if (lookAheadCheckTime < event.startTime)
463
486
  break;
464
- const delay = this.startDelay - resumeTime;
465
- const startTime = event.startTime + delay;
487
+ const startTime = event.startTime + schedulingOffset;
466
488
  switch (event.type) {
467
489
  case "noteOn":
468
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
490
+ await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
469
491
  break;
470
492
  case "noteOff": {
471
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
493
+ const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
472
494
  if (notePromise)
473
495
  this.notePromises.push(notePromise);
474
496
  break;
@@ -501,6 +523,7 @@ export class MidyGMLite {
501
523
  this.exclusiveClassNotes.fill(undefined);
502
524
  this.drumExclusiveClassNotes.fill(undefined);
503
525
  this.voiceCache.clear();
526
+ this.realtimeVoiceCache.clear();
504
527
  for (let i = 0; i < this.channels.length; i++) {
505
528
  this.channels[i].scheduledNotes = [];
506
529
  this.resetChannelStates(i);
@@ -534,13 +557,10 @@ export class MidyGMLite {
534
557
  this.isPaused = false;
535
558
  this.startTime = this.audioContext.currentTime;
536
559
  let queueIndex = this.getQueueIndex(this.resumeTime);
537
- let resumeTime = this.resumeTime - this.startTime;
538
560
  let finished = false;
539
561
  this.notePromises = [];
540
562
  while (queueIndex < this.timeline.length) {
541
563
  const now = this.audioContext.currentTime;
542
- const t = now + resumeTime;
543
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
544
564
  if (this.isPausing) {
545
565
  await this.stopNotes(0, true, now);
546
566
  await this.audioContext.suspend();
@@ -559,10 +579,10 @@ export class MidyGMLite {
559
579
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
560
580
  this.updateStates(queueIndex, nextQueueIndex);
561
581
  queueIndex = nextQueueIndex;
562
- resumeTime = this.resumeTime - this.startTime;
563
582
  this.isSeeking = false;
564
583
  continue;
565
584
  }
585
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
566
586
  const waitTime = now + this.noteCheckInterval;
567
587
  await this.scheduleTask(() => { }, waitTime);
568
588
  }
@@ -578,16 +598,16 @@ export class MidyGMLite {
578
598
  secondToTicks(second, secondsPerBeat) {
579
599
  return second * this.ticksPerBeat / secondsPerBeat;
580
600
  }
601
+ getSoundFontId(channel) {
602
+ const programNumber = channel.programNumber;
603
+ const bank = channel.isDrum ? "128" : "000";
604
+ const program = programNumber.toString().padStart(3, "0");
605
+ return `${bank}:${program}`;
606
+ }
581
607
  extractMidiData(midi) {
582
608
  const instruments = new Set();
583
609
  const timeline = [];
584
- const tmpChannels = new Array(this.channels.length);
585
- for (let i = 0; i < tmpChannels.length; i++) {
586
- tmpChannels[i] = {
587
- programNumber: -1,
588
- bank: this.channels[i].bank,
589
- };
590
- }
610
+ const channels = this.channels;
591
611
  for (let i = 0; i < midi.tracks.length; i++) {
592
612
  const track = midi.tracks[i];
593
613
  let currentTicks = 0;
@@ -597,17 +617,15 @@ export class MidyGMLite {
597
617
  event.ticks = currentTicks;
598
618
  switch (event.type) {
599
619
  case "noteOn": {
600
- const channel = tmpChannels[event.channel];
601
- if (channel.programNumber < 0) {
602
- instruments.add(`${channel.bank}:0`);
603
- channel.programNumber = 0;
604
- }
620
+ const channel = channels[event.channel];
621
+ instruments.add(this.getSoundFontId(channel));
605
622
  break;
606
623
  }
607
624
  case "programChange": {
608
- const channel = tmpChannels[event.channel];
609
- channel.programNumber = event.programNumber;
610
- instruments.add(`${channel.bankNumber}:${channel.programNumber}`);
625
+ const channel = channels[event.channel];
626
+ this.setProgramChange(event.channel, event.programNumber);
627
+ instruments.add(this.getSoundFontId(channel));
628
+ break;
611
629
  }
612
630
  }
613
631
  delete event.deltaTime;
@@ -642,7 +660,7 @@ export class MidyGMLite {
642
660
  const channel = this.channels[channelNumber];
643
661
  const promises = [];
644
662
  this.processActiveNotes(channel, scheduleTime, (note) => {
645
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
663
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
646
664
  this.notePromises.push(promise);
647
665
  promises.push(promise);
648
666
  });
@@ -652,7 +670,7 @@ export class MidyGMLite {
652
670
  const channel = this.channels[channelNumber];
653
671
  const promises = [];
654
672
  this.processScheduledNotes(channel, (note) => {
655
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
673
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
656
674
  this.notePromises.push(promise);
657
675
  promises.push(promise);
658
676
  });
@@ -685,7 +703,7 @@ export class MidyGMLite {
685
703
  if (!this.isPlaying || this.isPaused)
686
704
  return;
687
705
  const now = this.audioContext.currentTime;
688
- this.resumeTime += now - this.startTime - this.startDelay;
706
+ this.resumeTime = now - this.startTime - this.startDelay;
689
707
  this.isPausing = true;
690
708
  await this.playPromise;
691
709
  this.isPausing = false;
@@ -711,11 +729,13 @@ export class MidyGMLite {
711
729
  if (totalTime < event.startTime)
712
730
  totalTime = event.startTime;
713
731
  }
714
- return totalTime;
732
+ return totalTime + this.startDelay;
715
733
  }
716
734
  currentTime() {
735
+ if (!this.isPlaying)
736
+ return this.resumeTime;
717
737
  const now = this.audioContext.currentTime;
718
- return this.resumeTime + now - this.startTime - this.startDelay;
738
+ return now + this.resumeTime - this.startTime;
719
739
  }
720
740
  processScheduledNotes(channel, callback) {
721
741
  const scheduledNotes = channel.scheduledNotes;
@@ -852,32 +872,43 @@ export class MidyGMLite {
852
872
  note.modulationLFO.connect(note.volumeDepth);
853
873
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
854
874
  }
855
- async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
875
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
856
876
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
857
- const cache = this.voiceCache.get(audioBufferId);
858
- if (cache) {
859
- cache.counter += 1;
860
- if (cache.maxCount <= cache.counter) {
861
- this.voiceCache.delete(audioBufferId);
862
- }
863
- return cache.audioBuffer;
864
- }
865
- else {
866
- const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
877
+ if (realtime) {
878
+ const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
879
+ if (cachedAudioBuffer)
880
+ return cachedAudioBuffer;
867
881
  const audioBuffer = await this.createAudioBuffer(voiceParams);
868
- const cache = { audioBuffer, maxCount, counter: 1 };
869
- this.voiceCache.set(audioBufferId, cache);
882
+ this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
870
883
  return audioBuffer;
871
884
  }
885
+ else {
886
+ const cache = this.voiceCache.get(audioBufferId);
887
+ if (cache) {
888
+ cache.counter += 1;
889
+ if (cache.maxCount <= cache.counter) {
890
+ this.voiceCache.delete(audioBufferId);
891
+ }
892
+ return cache.audioBuffer;
893
+ }
894
+ else {
895
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
896
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
897
+ const cache = { audioBuffer, maxCount, counter: 1 };
898
+ this.voiceCache.set(audioBufferId, cache);
899
+ return audioBuffer;
900
+ }
901
+ }
872
902
  }
873
- async createNote(channel, voice, noteNumber, velocity, startTime) {
903
+ async setNoteAudioNode(channel, note, realtime) {
874
904
  const now = this.audioContext.currentTime;
905
+ const { noteNumber, velocity, startTime } = note;
875
906
  const state = channel.state;
876
907
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
877
- const voiceParams = voice.getAllParams(controllerState);
878
- const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
879
- const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
880
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
908
+ const voiceParams = note.voice.getAllParams(controllerState);
909
+ note.voiceParams = voiceParams;
910
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
911
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
881
912
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
882
913
  note.filterNode = new BiquadFilterNode(this.audioContext, {
883
914
  type: "lowpass",
@@ -903,7 +934,7 @@ export class MidyGMLite {
903
934
  if (prev) {
904
935
  const [prevNote, prevChannelNumber] = prev;
905
936
  if (prevNote && !prevNote.ending) {
906
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
937
+ this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
907
938
  startTime, true);
908
939
  }
909
940
  }
@@ -913,43 +944,56 @@ export class MidyGMLite {
913
944
  const channel = this.channels[channelNumber];
914
945
  if (!channel.isDrum)
915
946
  return;
916
- const drumExclusiveClass = drumExclusiveClasses[noteNumber];
947
+ const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
917
948
  if (drumExclusiveClass === 0)
918
949
  return;
919
950
  const index = drumExclusiveClass * this.channels.length + channelNumber;
920
951
  const prevNote = this.drumExclusiveClassNotes[index];
921
952
  if (prevNote && !prevNote.ending) {
922
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
953
+ this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
923
954
  startTime, true);
924
955
  }
925
956
  this.drumExclusiveClassNotes[index] = note;
926
957
  }
927
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
958
+ setNoteRouting(channelNumber, note, startTime) {
928
959
  const channel = this.channels[channelNumber];
929
- const bankNumber = channel.bank;
930
- const soundFontIndex = this.soundFontTable[channel.programNumber]
931
- .get(bankNumber);
932
- if (soundFontIndex === undefined)
933
- return;
934
- const soundFont = this.soundFonts[soundFontIndex];
935
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
936
- if (!voice)
937
- return;
938
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
939
- note.volumeEnvelopeNode.connect(channel.gainL);
940
- note.volumeEnvelopeNode.connect(channel.gainR);
960
+ const volumeEnvelopeNode = note.volumeEnvelopeNode;
961
+ volumeEnvelopeNode.connect(channel.gainL);
962
+ volumeEnvelopeNode.connect(channel.gainR);
941
963
  if (0.5 <= channel.state.sustainPedal) {
942
964
  channel.sustainNotes.push(note);
943
965
  }
944
966
  this.handleExclusiveClass(note, channelNumber, startTime);
945
967
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
968
+ }
969
+ async noteOn(channelNumber, noteNumber, velocity, startTime) {
970
+ const channel = this.channels[channelNumber];
971
+ const realtime = startTime === undefined;
972
+ if (realtime)
973
+ startTime = this.audioContext.currentTime;
974
+ const note = new Note(noteNumber, velocity, startTime);
946
975
  const scheduledNotes = channel.scheduledNotes;
947
976
  note.index = scheduledNotes.length;
948
977
  scheduledNotes.push(note);
949
- }
950
- noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
951
- scheduleTime ??= this.audioContext.currentTime;
952
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
978
+ const programNumber = channel.programNumber;
979
+ const bankTable = this.soundFontTable[programNumber];
980
+ if (!bankTable)
981
+ return;
982
+ const bank = channel.isDrum ? 128 : 0;
983
+ const soundFontIndex = bankTable[bank];
984
+ if (soundFontIndex === undefined)
985
+ return;
986
+ const soundFont = this.soundFonts[soundFontIndex];
987
+ note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
988
+ if (!note.voice)
989
+ return;
990
+ await this.setNoteAudioNode(channel, note, realtime);
991
+ this.setNoteRouting(channelNumber, note, startTime);
992
+ note.pending = false;
993
+ const off = note.offEvent;
994
+ if (off) {
995
+ this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
996
+ }
953
997
  }
954
998
  disconnectNote(note) {
955
999
  note.bufferSource.disconnect();
@@ -962,6 +1006,7 @@ export class MidyGMLite {
962
1006
  }
963
1007
  }
964
1008
  releaseNote(channel, note, endTime) {
1009
+ endTime ??= this.audioContext.currentTime;
965
1010
  const volRelease = endTime + note.voiceParams.volRelease;
966
1011
  const modRelease = endTime + note.voiceParams.modRelease;
967
1012
  const stopTime = Math.min(volRelease, modRelease);
@@ -982,7 +1027,7 @@ export class MidyGMLite {
982
1027
  }, stopTime);
983
1028
  });
984
1029
  }
985
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1030
+ noteOff(channelNumber, noteNumber, velocity, endTime, force) {
986
1031
  const channel = this.channels[channelNumber];
987
1032
  if (!force) {
988
1033
  if (channel.isDrum)
@@ -994,6 +1039,10 @@ export class MidyGMLite {
994
1039
  if (index < 0)
995
1040
  return;
996
1041
  const note = channel.scheduledNotes[index];
1042
+ if (note.pending) {
1043
+ note.offEvent = { velocity, startTime: endTime };
1044
+ return;
1045
+ }
997
1046
  note.ending = true;
998
1047
  this.setNoteIndex(channel, index);
999
1048
  this.releaseNote(channel, note, endTime);
@@ -1024,22 +1073,37 @@ export class MidyGMLite {
1024
1073
  }
1025
1074
  return -1;
1026
1075
  }
1027
- noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1028
- scheduleTime ??= this.audioContext.currentTime;
1029
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1030
- }
1031
1076
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1032
1077
  const velocity = halfVelocity * 2;
1033
1078
  const channel = this.channels[channelNumber];
1034
1079
  const promises = [];
1035
1080
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1036
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1081
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1037
1082
  promises.push(promise);
1038
1083
  }
1039
1084
  channel.sustainNotes = [];
1040
1085
  return promises;
1041
1086
  }
1042
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1087
+ createMessageHandlers() {
1088
+ const handlers = new Array(256);
1089
+ // Channel Message
1090
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1091
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1092
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1093
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1094
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1095
+ return handlers;
1096
+ }
1097
+ handleMessage(data, scheduleTime) {
1098
+ const status = data[0];
1099
+ if (status === 0xF0) {
1100
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1101
+ }
1102
+ const handler = this.messageHandlers[status];
1103
+ if (handler)
1104
+ handler(data, scheduleTime);
1105
+ }
1106
+ handleChannelMessage(statusByte, data1, data2, scheduleTime) {
1043
1107
  const channelNumber = statusByte & 0x0F;
1044
1108
  const messageType = statusByte & 0xF0;
1045
1109
  switch (messageType) {
@@ -1422,10 +1486,8 @@ export class MidyGMLite {
1422
1486
  for (let i = 0; i < this.channels.length; i++) {
1423
1487
  this.allSoundOff(i, 0, scheduleTime);
1424
1488
  const channel = this.channels[i];
1425
- channel.bank = 0;
1426
1489
  channel.isDrum = false;
1427
1490
  }
1428
- this.channels[9].bank = 128;
1429
1491
  this.channels[9].isDrum = true;
1430
1492
  }
1431
1493
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -1446,16 +1508,11 @@ export class MidyGMLite {
1446
1508
  const volume = (data[5] * 128 + data[4]) / 16383;
1447
1509
  this.setMasterVolume(volume, scheduleTime);
1448
1510
  }
1449
- setMasterVolume(volume, scheduleTime) {
1511
+ setMasterVolume(value, scheduleTime) {
1450
1512
  scheduleTime ??= this.audioContext.currentTime;
1451
- if (volume < 0 && 1 < volume) {
1452
- console.error("Master Volume is out of range");
1453
- }
1454
- else {
1455
- this.masterVolume.gain
1456
- .cancelScheduledValues(scheduleTime)
1457
- .setValueAtTime(volume * volume, scheduleTime);
1458
- }
1513
+ this.masterVolume.gain
1514
+ .cancelScheduledValues(scheduleTime)
1515
+ .setValueAtTime(value * value, scheduleTime);
1459
1516
  }
1460
1517
  handleSysEx(data, scheduleTime) {
1461
1518
  switch (data[0]) {
@@ -1495,7 +1552,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1495
1552
  scheduleIndex: 0,
1496
1553
  detune: 0,
1497
1554
  programNumber: 0,
1498
- bank: 0,
1499
1555
  dataMSB: 0,
1500
1556
  dataLSB: 0,
1501
1557
  rpnMSB: 127,