@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"}
@@ -4,7 +4,19 @@ exports.MidyGMLite = void 0;
4
4
  const midi_file_1 = require("midi-file");
5
5
  const soundfont_parser_1 = require("@marmooo/soundfont-parser");
6
6
  class Note {
7
- constructor(noteNumber, velocity, startTime, voice, voiceParams) {
7
+ constructor(noteNumber, velocity, startTime) {
8
+ Object.defineProperty(this, "voice", {
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true,
12
+ value: void 0
13
+ });
14
+ Object.defineProperty(this, "voiceParams", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: void 0
19
+ });
8
20
  Object.defineProperty(this, "index", {
9
21
  enumerable: true,
10
22
  configurable: true,
@@ -17,6 +29,12 @@ class Note {
17
29
  writable: true,
18
30
  value: false
19
31
  });
32
+ Object.defineProperty(this, "pending", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: true
37
+ });
20
38
  Object.defineProperty(this, "bufferSource", {
21
39
  enumerable: true,
22
40
  configurable: true,
@@ -62,8 +80,6 @@ class Note {
62
80
  this.noteNumber = noteNumber;
63
81
  this.velocity = velocity;
64
82
  this.startTime = startTime;
65
- this.voice = voice;
66
- this.voiceParams = voiceParams;
67
83
  }
68
84
  }
69
85
  const drumExclusiveClasses = new Uint8Array(128);
@@ -216,7 +232,7 @@ class MidyGMLite {
216
232
  enumerable: true,
217
233
  configurable: true,
218
234
  writable: true,
219
- value: this.initSoundFontTable()
235
+ value: Array.from({ length: 128 }, () => [])
220
236
  });
221
237
  Object.defineProperty(this, "voiceCounter", {
222
238
  enumerable: true,
@@ -230,6 +246,12 @@ class MidyGMLite {
230
246
  writable: true,
231
247
  value: new Map()
232
248
  });
249
+ Object.defineProperty(this, "realtimeVoiceCache", {
250
+ enumerable: true,
251
+ configurable: true,
252
+ writable: true,
253
+ value: new Map()
254
+ });
233
255
  Object.defineProperty(this, "isPlaying", {
234
256
  enumerable: true,
235
257
  configurable: true,
@@ -303,6 +325,7 @@ class MidyGMLite {
303
325
  length: 1,
304
326
  sampleRate: audioContext.sampleRate,
305
327
  });
328
+ this.messageHandlers = this.createMessageHandlers();
306
329
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
307
330
  this.controlChangeHandlers = this.createControlChangeHandlers();
308
331
  this.channels = this.createChannels(audioContext);
@@ -310,21 +333,14 @@ class MidyGMLite {
310
333
  this.scheduler.connect(audioContext.destination);
311
334
  this.GM1SystemOn();
312
335
  }
313
- initSoundFontTable() {
314
- const table = new Array(128);
315
- for (let i = 0; i < 128; i++) {
316
- table[i] = new Map();
317
- }
318
- return table;
319
- }
320
336
  addSoundFont(soundFont) {
321
337
  const index = this.soundFonts.length;
322
338
  this.soundFonts.push(soundFont);
323
339
  const presetHeaders = soundFont.parsed.presetHeaders;
340
+ const soundFontTable = this.soundFontTable;
324
341
  for (let i = 0; i < presetHeaders.length; i++) {
325
- const presetHeader = presetHeaders[i];
326
- const banks = this.soundFontTable[presetHeader.preset];
327
- banks.set(presetHeader.bank, index);
342
+ const { preset, bank } = presetHeaders[i];
343
+ soundFontTable[preset][bank] = index;
328
344
  }
329
345
  }
330
346
  async toUint8Array(input) {
@@ -402,13 +418,16 @@ class MidyGMLite {
402
418
  this.GM1SystemOn();
403
419
  }
404
420
  getVoiceId(channel, noteNumber, velocity) {
405
- const bankNumber = this.calcBank(channel);
406
- const soundFontIndex = this.soundFontTable[channel.programNumber]
407
- .get(bankNumber);
421
+ const programNumber = channel.programNumber;
422
+ const bankTable = this.soundFontTable[programNumber];
423
+ if (!bankTable)
424
+ return;
425
+ const bank = channel.isDrum ? 128 : 0;
426
+ const soundFontIndex = bankTable[bank];
408
427
  if (soundFontIndex === undefined)
409
428
  return;
410
429
  const soundFont = this.soundFonts[soundFontIndex];
411
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
430
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
412
431
  const { instrument, sampleID } = voice.generators;
413
432
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
414
433
  }
@@ -459,19 +478,22 @@ class MidyGMLite {
459
478
  }
460
479
  return bufferSource;
461
480
  }
462
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
463
- while (queueIndex < this.timeline.length) {
464
- const event = this.timeline[queueIndex];
465
- if (event.startTime > t + this.lookAhead)
481
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
482
+ const timeOffset = this.resumeTime - this.startTime;
483
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
484
+ const schedulingOffset = this.startDelay - timeOffset;
485
+ const timeline = this.timeline;
486
+ while (queueIndex < timeline.length) {
487
+ const event = timeline[queueIndex];
488
+ if (lookAheadCheckTime < event.startTime)
466
489
  break;
467
- const delay = this.startDelay - resumeTime;
468
- const startTime = event.startTime + delay;
490
+ const startTime = event.startTime + schedulingOffset;
469
491
  switch (event.type) {
470
492
  case "noteOn":
471
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
493
+ await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
472
494
  break;
473
495
  case "noteOff": {
474
- const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
496
+ const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
475
497
  if (notePromise)
476
498
  this.notePromises.push(notePromise);
477
499
  break;
@@ -504,6 +526,7 @@ class MidyGMLite {
504
526
  this.exclusiveClassNotes.fill(undefined);
505
527
  this.drumExclusiveClassNotes.fill(undefined);
506
528
  this.voiceCache.clear();
529
+ this.realtimeVoiceCache.clear();
507
530
  for (let i = 0; i < this.channels.length; i++) {
508
531
  this.channels[i].scheduledNotes = [];
509
532
  this.resetChannelStates(i);
@@ -537,13 +560,10 @@ class MidyGMLite {
537
560
  this.isPaused = false;
538
561
  this.startTime = this.audioContext.currentTime;
539
562
  let queueIndex = this.getQueueIndex(this.resumeTime);
540
- let resumeTime = this.resumeTime - this.startTime;
541
563
  let finished = false;
542
564
  this.notePromises = [];
543
565
  while (queueIndex < this.timeline.length) {
544
566
  const now = this.audioContext.currentTime;
545
- const t = now + resumeTime;
546
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
547
567
  if (this.isPausing) {
548
568
  await this.stopNotes(0, true, now);
549
569
  await this.audioContext.suspend();
@@ -562,10 +582,10 @@ class MidyGMLite {
562
582
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
563
583
  this.updateStates(queueIndex, nextQueueIndex);
564
584
  queueIndex = nextQueueIndex;
565
- resumeTime = this.resumeTime - this.startTime;
566
585
  this.isSeeking = false;
567
586
  continue;
568
587
  }
588
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
569
589
  const waitTime = now + this.noteCheckInterval;
570
590
  await this.scheduleTask(() => { }, waitTime);
571
591
  }
@@ -581,16 +601,16 @@ class MidyGMLite {
581
601
  secondToTicks(second, secondsPerBeat) {
582
602
  return second * this.ticksPerBeat / secondsPerBeat;
583
603
  }
604
+ getSoundFontId(channel) {
605
+ const programNumber = channel.programNumber;
606
+ const bank = channel.isDrum ? "128" : "000";
607
+ const program = programNumber.toString().padStart(3, "0");
608
+ return `${bank}:${program}`;
609
+ }
584
610
  extractMidiData(midi) {
585
611
  const instruments = new Set();
586
612
  const timeline = [];
587
- const tmpChannels = new Array(this.channels.length);
588
- for (let i = 0; i < tmpChannels.length; i++) {
589
- tmpChannels[i] = {
590
- programNumber: -1,
591
- bank: this.channels[i].bank,
592
- };
593
- }
613
+ const channels = this.channels;
594
614
  for (let i = 0; i < midi.tracks.length; i++) {
595
615
  const track = midi.tracks[i];
596
616
  let currentTicks = 0;
@@ -600,17 +620,15 @@ class MidyGMLite {
600
620
  event.ticks = currentTicks;
601
621
  switch (event.type) {
602
622
  case "noteOn": {
603
- const channel = tmpChannels[event.channel];
604
- if (channel.programNumber < 0) {
605
- instruments.add(`${channel.bank}:0`);
606
- channel.programNumber = 0;
607
- }
623
+ const channel = channels[event.channel];
624
+ instruments.add(this.getSoundFontId(channel));
608
625
  break;
609
626
  }
610
627
  case "programChange": {
611
- const channel = tmpChannels[event.channel];
612
- channel.programNumber = event.programNumber;
613
- instruments.add(`${channel.bankNumber}:${channel.programNumber}`);
628
+ const channel = channels[event.channel];
629
+ this.setProgramChange(event.channel, event.programNumber);
630
+ instruments.add(this.getSoundFontId(channel));
631
+ break;
614
632
  }
615
633
  }
616
634
  delete event.deltaTime;
@@ -645,7 +663,7 @@ class MidyGMLite {
645
663
  const channel = this.channels[channelNumber];
646
664
  const promises = [];
647
665
  this.processActiveNotes(channel, scheduleTime, (note) => {
648
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
666
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
649
667
  this.notePromises.push(promise);
650
668
  promises.push(promise);
651
669
  });
@@ -655,7 +673,7 @@ class MidyGMLite {
655
673
  const channel = this.channels[channelNumber];
656
674
  const promises = [];
657
675
  this.processScheduledNotes(channel, (note) => {
658
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
676
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
659
677
  this.notePromises.push(promise);
660
678
  promises.push(promise);
661
679
  });
@@ -688,7 +706,7 @@ class MidyGMLite {
688
706
  if (!this.isPlaying || this.isPaused)
689
707
  return;
690
708
  const now = this.audioContext.currentTime;
691
- this.resumeTime += now - this.startTime - this.startDelay;
709
+ this.resumeTime = now - this.startTime - this.startDelay;
692
710
  this.isPausing = true;
693
711
  await this.playPromise;
694
712
  this.isPausing = false;
@@ -714,11 +732,13 @@ class MidyGMLite {
714
732
  if (totalTime < event.startTime)
715
733
  totalTime = event.startTime;
716
734
  }
717
- return totalTime;
735
+ return totalTime + this.startDelay;
718
736
  }
719
737
  currentTime() {
738
+ if (!this.isPlaying)
739
+ return this.resumeTime;
720
740
  const now = this.audioContext.currentTime;
721
- return this.resumeTime + now - this.startTime - this.startDelay;
741
+ return now + this.resumeTime - this.startTime;
722
742
  }
723
743
  processScheduledNotes(channel, callback) {
724
744
  const scheduledNotes = channel.scheduledNotes;
@@ -855,32 +875,43 @@ class MidyGMLite {
855
875
  note.modulationLFO.connect(note.volumeDepth);
856
876
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
857
877
  }
858
- async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
878
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime) {
859
879
  const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
860
- const cache = this.voiceCache.get(audioBufferId);
861
- if (cache) {
862
- cache.counter += 1;
863
- if (cache.maxCount <= cache.counter) {
864
- this.voiceCache.delete(audioBufferId);
865
- }
866
- return cache.audioBuffer;
867
- }
868
- else {
869
- const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
880
+ if (realtime) {
881
+ const cachedAudioBuffer = this.realtimeVoiceCache.get(audioBufferId);
882
+ if (cachedAudioBuffer)
883
+ return cachedAudioBuffer;
870
884
  const audioBuffer = await this.createAudioBuffer(voiceParams);
871
- const cache = { audioBuffer, maxCount, counter: 1 };
872
- this.voiceCache.set(audioBufferId, cache);
885
+ this.realtimeVoiceCache.set(audioBufferId, audioBuffer);
873
886
  return audioBuffer;
874
887
  }
888
+ else {
889
+ const cache = this.voiceCache.get(audioBufferId);
890
+ if (cache) {
891
+ cache.counter += 1;
892
+ if (cache.maxCount <= cache.counter) {
893
+ this.voiceCache.delete(audioBufferId);
894
+ }
895
+ return cache.audioBuffer;
896
+ }
897
+ else {
898
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
899
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
900
+ const cache = { audioBuffer, maxCount, counter: 1 };
901
+ this.voiceCache.set(audioBufferId, cache);
902
+ return audioBuffer;
903
+ }
904
+ }
875
905
  }
876
- async createNote(channel, voice, noteNumber, velocity, startTime) {
906
+ async setNoteAudioNode(channel, note, realtime) {
877
907
  const now = this.audioContext.currentTime;
908
+ const { noteNumber, velocity, startTime } = note;
878
909
  const state = channel.state;
879
910
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
880
- const voiceParams = voice.getAllParams(controllerState);
881
- const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
882
- const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
883
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
911
+ const voiceParams = note.voice.getAllParams(controllerState);
912
+ note.voiceParams = voiceParams;
913
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams, realtime);
914
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
884
915
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
885
916
  note.filterNode = new BiquadFilterNode(this.audioContext, {
886
917
  type: "lowpass",
@@ -906,7 +937,7 @@ class MidyGMLite {
906
937
  if (prev) {
907
938
  const [prevNote, prevChannelNumber] = prev;
908
939
  if (prevNote && !prevNote.ending) {
909
- this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
940
+ this.noteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
910
941
  startTime, true);
911
942
  }
912
943
  }
@@ -916,43 +947,56 @@ class MidyGMLite {
916
947
  const channel = this.channels[channelNumber];
917
948
  if (!channel.isDrum)
918
949
  return;
919
- const drumExclusiveClass = drumExclusiveClasses[noteNumber];
950
+ const drumExclusiveClass = drumExclusiveClasses[note.noteNumber];
920
951
  if (drumExclusiveClass === 0)
921
952
  return;
922
953
  const index = drumExclusiveClass * this.channels.length + channelNumber;
923
954
  const prevNote = this.drumExclusiveClassNotes[index];
924
955
  if (prevNote && !prevNote.ending) {
925
- this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
956
+ this.noteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
926
957
  startTime, true);
927
958
  }
928
959
  this.drumExclusiveClassNotes[index] = note;
929
960
  }
930
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
961
+ setNoteRouting(channelNumber, note, startTime) {
931
962
  const channel = this.channels[channelNumber];
932
- const bankNumber = channel.bank;
933
- const soundFontIndex = this.soundFontTable[channel.programNumber]
934
- .get(bankNumber);
935
- if (soundFontIndex === undefined)
936
- return;
937
- const soundFont = this.soundFonts[soundFontIndex];
938
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
939
- if (!voice)
940
- return;
941
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
942
- note.volumeEnvelopeNode.connect(channel.gainL);
943
- note.volumeEnvelopeNode.connect(channel.gainR);
963
+ const volumeEnvelopeNode = note.volumeEnvelopeNode;
964
+ volumeEnvelopeNode.connect(channel.gainL);
965
+ volumeEnvelopeNode.connect(channel.gainR);
944
966
  if (0.5 <= channel.state.sustainPedal) {
945
967
  channel.sustainNotes.push(note);
946
968
  }
947
969
  this.handleExclusiveClass(note, channelNumber, startTime);
948
970
  this.handleDrumExclusiveClass(note, channelNumber, startTime);
971
+ }
972
+ async noteOn(channelNumber, noteNumber, velocity, startTime) {
973
+ const channel = this.channels[channelNumber];
974
+ const realtime = startTime === undefined;
975
+ if (realtime)
976
+ startTime = this.audioContext.currentTime;
977
+ const note = new Note(noteNumber, velocity, startTime);
949
978
  const scheduledNotes = channel.scheduledNotes;
950
979
  note.index = scheduledNotes.length;
951
980
  scheduledNotes.push(note);
952
- }
953
- noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
954
- scheduleTime ??= this.audioContext.currentTime;
955
- return this.scheduleNoteOn(channelNumber, noteNumber, velocity, scheduleTime, undefined);
981
+ const programNumber = channel.programNumber;
982
+ const bankTable = this.soundFontTable[programNumber];
983
+ if (!bankTable)
984
+ return;
985
+ const bank = channel.isDrum ? 128 : 0;
986
+ const soundFontIndex = bankTable[bank];
987
+ if (soundFontIndex === undefined)
988
+ return;
989
+ const soundFont = this.soundFonts[soundFontIndex];
990
+ note.voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
991
+ if (!note.voice)
992
+ return;
993
+ await this.setNoteAudioNode(channel, note, realtime);
994
+ this.setNoteRouting(channelNumber, note, startTime);
995
+ note.pending = false;
996
+ const off = note.offEvent;
997
+ if (off) {
998
+ this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
999
+ }
956
1000
  }
957
1001
  disconnectNote(note) {
958
1002
  note.bufferSource.disconnect();
@@ -965,6 +1009,7 @@ class MidyGMLite {
965
1009
  }
966
1010
  }
967
1011
  releaseNote(channel, note, endTime) {
1012
+ endTime ??= this.audioContext.currentTime;
968
1013
  const volRelease = endTime + note.voiceParams.volRelease;
969
1014
  const modRelease = endTime + note.voiceParams.modRelease;
970
1015
  const stopTime = Math.min(volRelease, modRelease);
@@ -985,7 +1030,7 @@ class MidyGMLite {
985
1030
  }, stopTime);
986
1031
  });
987
1032
  }
988
- scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1033
+ noteOff(channelNumber, noteNumber, velocity, endTime, force) {
989
1034
  const channel = this.channels[channelNumber];
990
1035
  if (!force) {
991
1036
  if (channel.isDrum)
@@ -997,6 +1042,10 @@ class MidyGMLite {
997
1042
  if (index < 0)
998
1043
  return;
999
1044
  const note = channel.scheduledNotes[index];
1045
+ if (note.pending) {
1046
+ note.offEvent = { velocity, startTime: endTime };
1047
+ return;
1048
+ }
1000
1049
  note.ending = true;
1001
1050
  this.setNoteIndex(channel, index);
1002
1051
  this.releaseNote(channel, note, endTime);
@@ -1027,22 +1076,37 @@ class MidyGMLite {
1027
1076
  }
1028
1077
  return -1;
1029
1078
  }
1030
- noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1031
- scheduleTime ??= this.audioContext.currentTime;
1032
- return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1033
- }
1034
1079
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1035
1080
  const velocity = halfVelocity * 2;
1036
1081
  const channel = this.channels[channelNumber];
1037
1082
  const promises = [];
1038
1083
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1039
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1084
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1040
1085
  promises.push(promise);
1041
1086
  }
1042
1087
  channel.sustainNotes = [];
1043
1088
  return promises;
1044
1089
  }
1045
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1090
+ createMessageHandlers() {
1091
+ const handlers = new Array(256);
1092
+ // Channel Message
1093
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1094
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1095
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1096
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1097
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1098
+ return handlers;
1099
+ }
1100
+ handleMessage(data, scheduleTime) {
1101
+ const status = data[0];
1102
+ if (status === 0xF0) {
1103
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1104
+ }
1105
+ const handler = this.messageHandlers[status];
1106
+ if (handler)
1107
+ handler(data, scheduleTime);
1108
+ }
1109
+ handleChannelMessage(statusByte, data1, data2, scheduleTime) {
1046
1110
  const channelNumber = statusByte & 0x0F;
1047
1111
  const messageType = statusByte & 0xF0;
1048
1112
  switch (messageType) {
@@ -1425,10 +1489,8 @@ class MidyGMLite {
1425
1489
  for (let i = 0; i < this.channels.length; i++) {
1426
1490
  this.allSoundOff(i, 0, scheduleTime);
1427
1491
  const channel = this.channels[i];
1428
- channel.bank = 0;
1429
1492
  channel.isDrum = false;
1430
1493
  }
1431
- this.channels[9].bank = 128;
1432
1494
  this.channels[9].isDrum = true;
1433
1495
  }
1434
1496
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -1449,16 +1511,11 @@ class MidyGMLite {
1449
1511
  const volume = (data[5] * 128 + data[4]) / 16383;
1450
1512
  this.setMasterVolume(volume, scheduleTime);
1451
1513
  }
1452
- setMasterVolume(volume, scheduleTime) {
1514
+ setMasterVolume(value, scheduleTime) {
1453
1515
  scheduleTime ??= this.audioContext.currentTime;
1454
- if (volume < 0 && 1 < volume) {
1455
- console.error("Master Volume is out of range");
1456
- }
1457
- else {
1458
- this.masterVolume.gain
1459
- .cancelScheduledValues(scheduleTime)
1460
- .setValueAtTime(volume * volume, scheduleTime);
1461
- }
1516
+ this.masterVolume.gain
1517
+ .cancelScheduledValues(scheduleTime)
1518
+ .setValueAtTime(value * value, scheduleTime);
1462
1519
  }
1463
1520
  handleSysEx(data, scheduleTime) {
1464
1521
  switch (data[0]) {
@@ -1499,7 +1556,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1499
1556
  scheduleIndex: 0,
1500
1557
  detune: 0,
1501
1558
  programNumber: 0,
1502
- bank: 0,
1503
1559
  dataMSB: 0,
1504
1560
  dataLSB: 0,
1505
1561
  rpnMSB: 127,