@marmooo/midy 0.3.2 → 0.3.4

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.
@@ -53,16 +53,16 @@ export class MidyGMLite {
53
53
  channels: any[];
54
54
  initSoundFontTable(): any[];
55
55
  addSoundFont(soundFont: any): void;
56
- loadSoundFont(soundFontUrl: any): Promise<void>;
57
- loadMIDI(midiUrl: any): Promise<void>;
58
- setChannelAudioNodes(audioContext: any): {
56
+ loadSoundFont(input: any): Promise<void>;
57
+ loadMIDI(input: any): Promise<void>;
58
+ createChannelAudioNodes(audioContext: any): {
59
59
  gainL: any;
60
60
  gainR: any;
61
61
  merger: any;
62
62
  };
63
63
  createChannels(audioContext: any): any[];
64
64
  createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
65
- createBufferSource(voiceParams: any, audioBuffer: any): any;
65
+ createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
66
66
  scheduleTimelineEvents(t: any, resumeTime: any, queueIndex: any): Promise<any>;
67
67
  getQueueIndex(second: any): number;
68
68
  playNotes(): Promise<any>;
@@ -83,7 +83,7 @@ export class MidyGMLite {
83
83
  seekTo(second: any): void;
84
84
  calcTotalTime(): number;
85
85
  currentTime(): number;
86
- processScheduledNotes(channel: any, callback: any): void;
86
+ processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
87
87
  processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
88
88
  cbToRatio(cb: any): number;
89
89
  rateToCent(rate: any): number;
@@ -101,15 +101,15 @@ export class MidyGMLite {
101
101
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
102
102
  handleExclusiveClass(note: any, channelNumber: any, startTime: any): void;
103
103
  handleDrumExclusiveClass(note: any, channelNumber: any, startTime: any): void;
104
- scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any, noteOffEvent: any): Promise<void>;
104
+ scheduleNoteOn(channelNumber: any, noteNumber: any, velocity: any, startTime: any): Promise<void>;
105
105
  noteOn(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<void>;
106
106
  disconnectNote(note: any): void;
107
- stopNote(channel: any, note: any, endTime: any, stopTime: any): Promise<any>;
108
- scheduleNoteOff(channelNumber: any, note: any, _velocity: any, endTime: any, force: any): Promise<any> | undefined;
107
+ releaseNote(channel: any, note: any, endTime: any): Promise<any>;
108
+ scheduleNoteOff(channelNumber: any, noteNumber: any, _velocity: any, endTime: any, force: any): void;
109
109
  findNoteOffTarget(channel: any, noteNumber: any): any;
110
- noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): Promise<any> | undefined;
111
- releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): (Promise<any> | undefined)[];
112
- handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<any>;
110
+ noteOff(channelNumber: any, noteNumber: any, velocity: any, scheduleTime: any): void;
111
+ releaseSustainPedal(channelNumber: any, halfVelocity: any, scheduleTime: any): void[];
112
+ handleMIDIMessage(statusByte: any, data1: any, data2: any, scheduleTime: any): void | Promise<void>;
113
113
  handleProgramChange(channelNumber: any, programNumber: any, _scheduleTime: any): void;
114
114
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any, scheduleTime: any): void;
115
115
  setPitchBend(channelNumber: any, value: any, scheduleTime: any): void;
@@ -168,6 +168,7 @@ export class MidyGMLite {
168
168
  declare class Note {
169
169
  constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
170
170
  index: number;
171
+ ending: boolean;
171
172
  bufferSource: any;
172
173
  filterNode: any;
173
174
  filterDepth: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAmGA;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,4DASC;IAED,+EA6CC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MAoGC;IAED,kGAgBC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEAWC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAyCC;IAED,0EAiBC;IAED,8EAiBC;IAED,qHAgEC;IAED,6FASC;IAED,gCASC;IAED,6EAgBC;IAED,mHAiBC;IAED,sDASC;IAED,yGAWC;IAED,4GAeC;IAED,mGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED,qCAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA/+CD;IAUE,0FAMC;IAfD,cAAW;IACX,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;IA2BE;;;;;;;;;MASE;IAEF,+BAcC;IAnDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAcA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,yCAcC;IAED,oCAiBC;IAED;;;;MAeC;IAED,yCAaC;IAED,6DA2BC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA6EC;IAED,kGAeC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,4EASC;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,+GA0BC;IAED,gHA6CC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAuCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAgBC;IAED,sDASC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,sFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;IAED,qCAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAp9CD;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"}
@@ -11,6 +11,12 @@ class Note {
11
11
  writable: true,
12
12
  value: -1
13
13
  });
14
+ Object.defineProperty(this, "ending", {
15
+ enumerable: true,
16
+ configurable: true,
17
+ writable: true,
18
+ value: false
19
+ });
14
20
  Object.defineProperty(this, "bufferSource", {
15
21
  enumerable: true,
16
22
  configurable: true,
@@ -80,13 +86,11 @@ const defaultControllerState = {
80
86
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
81
87
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
82
88
  link: { type: 127, defaultValue: 0 },
83
- // bankMSB: { type: 128 + 0, defaultValue: 121, },
84
89
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
85
90
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
86
91
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
87
92
  pan: { type: 128 + 10, defaultValue: 64 / 127 },
88
93
  expression: { type: 128 + 11, defaultValue: 1 },
89
- // bankLSB: { type: 128 + 32, defaultValue: 0, },
90
94
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
91
95
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
92
96
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
@@ -115,6 +119,16 @@ class ControllerState {
115
119
  }
116
120
  }
117
121
  }
122
+ const volumeEnvelopeKeys = [
123
+ "volDelay",
124
+ "volAttack",
125
+ "volHold",
126
+ "volDecay",
127
+ "volSustain",
128
+ "volRelease",
129
+ "initialAttenuation",
130
+ ];
131
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
118
132
  const filterEnvelopeKeys = [
119
133
  "modEnvToPitch",
120
134
  "initialFilterFc",
@@ -124,20 +138,18 @@ const filterEnvelopeKeys = [
124
138
  "modHold",
125
139
  "modDecay",
126
140
  "modSustain",
127
- "modRelease",
128
- "playbackRate",
129
141
  ];
130
142
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
131
- const volumeEnvelopeKeys = [
132
- "volDelay",
133
- "volAttack",
134
- "volHold",
135
- "volDecay",
136
- "volSustain",
137
- "volRelease",
138
- "initialAttenuation",
143
+ const pitchEnvelopeKeys = [
144
+ "modEnvToPitch",
145
+ "modDelay",
146
+ "modAttack",
147
+ "modHold",
148
+ "modDecay",
149
+ "modSustain",
150
+ "playbackRate",
139
151
  ];
140
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
152
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
141
153
  class MidyGMLite {
142
154
  constructor(audioContext) {
143
155
  Object.defineProperty(this, "mode", {
@@ -311,24 +323,44 @@ class MidyGMLite {
311
323
  }
312
324
  }
313
325
  }
314
- async loadSoundFont(soundFontUrl) {
315
- const response = await fetch(soundFontUrl);
316
- const arrayBuffer = await response.arrayBuffer();
317
- const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
326
+ async loadSoundFont(input) {
327
+ let uint8Array;
328
+ if (typeof input === "string") {
329
+ const response = await fetch(input);
330
+ const arrayBuffer = await response.arrayBuffer();
331
+ uint8Array = new Uint8Array(arrayBuffer);
332
+ }
333
+ else if (input instanceof Uint8Array) {
334
+ uint8Array = input;
335
+ }
336
+ else {
337
+ throw new TypeError("input must be a URL string or Uint8Array");
338
+ }
339
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
318
340
  const soundFont = new soundfont_parser_1.SoundFont(parsed);
319
341
  this.addSoundFont(soundFont);
320
342
  }
321
- async loadMIDI(midiUrl) {
322
- const response = await fetch(midiUrl);
323
- const arrayBuffer = await response.arrayBuffer();
324
- const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
343
+ async loadMIDI(input) {
344
+ let uint8Array;
345
+ if (typeof input === "string") {
346
+ const response = await fetch(input);
347
+ const arrayBuffer = await response.arrayBuffer();
348
+ uint8Array = new Uint8Array(arrayBuffer);
349
+ }
350
+ else if (input instanceof Uint8Array) {
351
+ uint8Array = input;
352
+ }
353
+ else {
354
+ throw new TypeError("input must be a URL string or Uint8Array");
355
+ }
356
+ const midi = (0, midi_file_1.parseMidi)(uint8Array);
325
357
  this.ticksPerBeat = midi.header.ticksPerBeat;
326
358
  const midiData = this.extractMidiData(midi);
327
359
  this.instruments = midiData.instruments;
328
360
  this.timeline = midiData.timeline;
329
361
  this.totalTime = this.calcTotalTime();
330
362
  }
331
- setChannelAudioNodes(audioContext) {
363
+ createChannelAudioNodes(audioContext) {
332
364
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
333
365
  const gainL = new GainNode(audioContext, { gain: gainLeft });
334
366
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -349,7 +381,7 @@ class MidyGMLite {
349
381
  isDrum: false,
350
382
  state: new ControllerState(),
351
383
  ...this.constructor.channelSettings,
352
- ...this.setChannelAudioNodes(audioContext),
384
+ ...this.createChannelAudioNodes(audioContext),
353
385
  scheduledNotes: [],
354
386
  sustainNotes: [],
355
387
  };
@@ -385,10 +417,12 @@ class MidyGMLite {
385
417
  return audioBuffer;
386
418
  }
387
419
  }
388
- createBufferSource(voiceParams, audioBuffer) {
420
+ createBufferSource(channel, voiceParams, audioBuffer) {
389
421
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
390
422
  bufferSource.buffer = audioBuffer;
391
423
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
424
+ if (channel.isDrum)
425
+ bufferSource.loop = false;
392
426
  if (bufferSource.loop) {
393
427
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
394
428
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -403,12 +437,13 @@ class MidyGMLite {
403
437
  const delay = this.startDelay - resumeTime;
404
438
  const startTime = event.startTime + delay;
405
439
  switch (event.type) {
406
- case "noteOn": {
407
- const noteOffEvent = {
408
- ...event.noteOffEvent,
409
- startTime: event.noteOffEvent.startTime + delay,
410
- };
411
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
440
+ case "noteOn":
441
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
442
+ break;
443
+ case "noteOff": {
444
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
445
+ if (notePromise)
446
+ this.notePromises.push(notePromise);
412
447
  break;
413
448
  }
414
449
  case "controller":
@@ -510,6 +545,7 @@ class MidyGMLite {
510
545
  return `${programNumber}:${noteNumber}:${velocity}`;
511
546
  }
512
547
  extractMidiData(midi) {
548
+ this.audioBufferCounter.clear();
513
549
  const instruments = new Set();
514
550
  const timeline = [];
515
551
  const tmpChannels = new Array(this.channels.length);
@@ -573,38 +609,13 @@ class MidyGMLite {
573
609
  prevTempoTicks = event.ticks;
574
610
  }
575
611
  }
576
- const activeNotes = new Array(this.channels.length * 128);
577
- for (let i = 0; i < activeNotes.length; i++) {
578
- activeNotes[i] = [];
579
- }
580
- for (let i = 0; i < timeline.length; i++) {
581
- const event = timeline[i];
582
- switch (event.type) {
583
- case "noteOn": {
584
- const index = event.channel * 128 + event.noteNumber;
585
- activeNotes[index].push(event);
586
- break;
587
- }
588
- case "noteOff": {
589
- const index = event.channel * 128 + event.noteNumber;
590
- const noteOn = activeNotes[index].pop();
591
- if (noteOn) {
592
- noteOn.noteOffEvent = event;
593
- }
594
- else {
595
- const eventString = JSON.stringify(event, null, 2);
596
- console.warn(`noteOff without matching noteOn: ${eventString}`);
597
- }
598
- }
599
- }
600
- }
601
612
  return { instruments, timeline };
602
613
  }
603
614
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
604
615
  const channel = this.channels[channelNumber];
605
616
  const promises = [];
606
617
  this.processActiveNotes(channel, scheduleTime, (note) => {
607
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
618
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
608
619
  this.notePromises.push(promise);
609
620
  promises.push(promise);
610
621
  });
@@ -613,8 +624,8 @@ class MidyGMLite {
613
624
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
614
625
  const channel = this.channels[channelNumber];
615
626
  const promises = [];
616
- this.processScheduledNotes(channel, (note) => {
617
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
627
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
628
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
618
629
  this.notePromises.push(promise);
619
630
  promises.push(promise);
620
631
  });
@@ -672,7 +683,7 @@ class MidyGMLite {
672
683
  const now = this.audioContext.currentTime;
673
684
  return this.resumeTime + now - this.startTime - this.startDelay;
674
685
  }
675
- processScheduledNotes(channel, callback) {
686
+ processScheduledNotes(channel, scheduleTime, callback) {
676
687
  const scheduledNotes = channel.scheduledNotes;
677
688
  for (let i = 0; i < scheduledNotes.length; i++) {
678
689
  const note = scheduledNotes[i];
@@ -680,6 +691,8 @@ class MidyGMLite {
680
691
  continue;
681
692
  if (note.ending)
682
693
  continue;
694
+ if (note.startTime < scheduleTime)
695
+ continue;
683
696
  callback(note);
684
697
  }
685
698
  }
@@ -691,11 +704,8 @@ class MidyGMLite {
691
704
  continue;
692
705
  if (note.ending)
693
706
  continue;
694
- const noteOffEvent = note.noteOffEvent;
695
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
696
- continue;
697
707
  if (scheduleTime < note.startTime)
698
- continue;
708
+ break;
699
709
  callback(note);
700
710
  }
701
711
  }
@@ -717,7 +727,7 @@ class MidyGMLite {
717
727
  return pitchWheel * pitchWheelSensitivity;
718
728
  }
719
729
  updateChannelDetune(channel, scheduleTime) {
720
- this.processScheduledNotes(channel, (note) => {
730
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
721
731
  this.updateDetune(channel, note, scheduleTime);
722
732
  });
723
733
  }
@@ -835,7 +845,7 @@ class MidyGMLite {
835
845
  const voiceParams = voice.getAllParams(controllerState);
836
846
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
837
847
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
838
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
848
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
839
849
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
840
850
  note.filterNode = new BiquadFilterNode(this.audioContext, {
841
851
  type: "lowpass",
@@ -861,7 +871,7 @@ class MidyGMLite {
861
871
  if (prev) {
862
872
  const [prevNote, prevChannelNumber] = prev;
863
873
  if (prevNote && !prevNote.ending) {
864
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
874
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
865
875
  startTime, true);
866
876
  }
867
877
  }
@@ -877,12 +887,12 @@ class MidyGMLite {
877
887
  const index = drumExclusiveClass * this.channels.length + channelNumber;
878
888
  const prevNote = this.drumExclusiveClassNotes[index];
879
889
  if (prevNote && !prevNote.ending) {
880
- this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
890
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
881
891
  startTime, true);
882
892
  }
883
893
  this.drumExclusiveClassNotes[index] = note;
884
894
  }
885
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
895
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
886
896
  const channel = this.channels[channelNumber];
887
897
  const bankNumber = channel.bank;
888
898
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -894,7 +904,6 @@ class MidyGMLite {
894
904
  return;
895
905
  const isSF3 = soundFont.parsed.info.version.major === 3;
896
906
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
897
- note.noteOffEvent = noteOffEvent;
898
907
  note.volumeEnvelopeNode.connect(channel.gainL);
899
908
  note.volumeEnvelopeNode.connect(channel.gainR);
900
909
  if (0.5 <= channel.state.sustainPedal) {
@@ -905,24 +914,6 @@ class MidyGMLite {
905
914
  const scheduledNotes = channel.scheduledNotes;
906
915
  note.index = scheduledNotes.length;
907
916
  scheduledNotes.push(note);
908
- if (channel.isDrum) {
909
- const stopTime = startTime + note.bufferSource.buffer.duration;
910
- const promise = new Promise((resolve) => {
911
- note.bufferSource.onended = () => {
912
- scheduledNotes[note.index] = undefined;
913
- this.disconnectNote(note);
914
- resolve();
915
- };
916
- note.bufferSource.stop(stopTime);
917
- });
918
- this.notePromises.push(promise);
919
- }
920
- else if (noteOffEvent) {
921
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
922
- if (notePromise) {
923
- this.notePromises.push(notePromise);
924
- }
925
- }
926
917
  }
927
918
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
928
919
  scheduleTime ??= this.audioContext.currentTime;
@@ -938,36 +929,40 @@ class MidyGMLite {
938
929
  note.modulationLFO.stop();
939
930
  }
940
931
  }
941
- stopNote(channel, note, endTime, stopTime) {
932
+ releaseNote(channel, note, endTime) {
933
+ const volRelease = endTime + note.voiceParams.volRelease;
934
+ const modRelease = endTime + note.voiceParams.modRelease;
935
+ const stopTime = Math.min(volRelease, modRelease);
936
+ note.filterNode.frequency
937
+ .cancelScheduledValues(endTime)
938
+ .linearRampToValueAtTime(0, modRelease);
942
939
  note.volumeEnvelopeNode.gain
943
940
  .cancelScheduledValues(endTime)
944
- .linearRampToValueAtTime(0, stopTime);
945
- note.ending = true;
946
- this.scheduleTask(() => {
947
- note.bufferSource.loop = false;
948
- }, stopTime);
941
+ .linearRampToValueAtTime(0, volRelease);
949
942
  return new Promise((resolve) => {
950
- note.bufferSource.onended = () => {
951
- channel.scheduledNotes[note.index] = undefined;
943
+ this.scheduleTask(() => {
944
+ const bufferSource = note.bufferSource;
945
+ bufferSource.loop = false;
946
+ bufferSource.stop(stopTime);
952
947
  this.disconnectNote(note);
948
+ channel.scheduledNotes[note.index] = undefined;
953
949
  resolve();
954
- };
955
- note.bufferSource.stop(stopTime);
950
+ }, stopTime);
956
951
  });
957
952
  }
958
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
953
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
959
954
  const channel = this.channels[channelNumber];
960
- if (channel.isDrum)
961
- return;
962
- if (!force && 0.5 <= channel.state.sustainPedal)
955
+ if (!force) {
956
+ if (channel.isDrum)
957
+ return;
958
+ if (0.5 <= channel.state.sustainPedal)
959
+ return;
960
+ }
961
+ const note = this.findNoteOffTarget(channel, noteNumber);
962
+ if (!note)
963
963
  return;
964
- const volRelease = endTime + note.voiceParams.volRelease;
965
- const modRelease = endTime + note.voiceParams.modRelease;
966
- note.filterNode.frequency
967
- .cancelScheduledValues(endTime)
968
- .linearRampToValueAtTime(0, modRelease);
969
- const stopTime = Math.min(volRelease, modRelease);
970
- return this.stopNote(channel, note, endTime, stopTime);
964
+ note.ending = true;
965
+ this.releaseNote(channel, note, endTime);
971
966
  }
972
967
  findNoteOffTarget(channel, noteNumber) {
973
968
  const scheduledNotes = channel.scheduledNotes;
@@ -984,16 +979,14 @@ class MidyGMLite {
984
979
  }
985
980
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
986
981
  scheduleTime ??= this.audioContext.currentTime;
987
- const channel = this.channels[channelNumber];
988
- const note = this.findNoteOffTarget(channel, noteNumber);
989
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
982
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
990
983
  }
991
984
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
992
985
  const velocity = halfVelocity * 2;
993
986
  const channel = this.channels[channelNumber];
994
987
  const promises = [];
995
988
  for (let i = 0; i < channel.sustainNotes.length; i++) {
996
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
989
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
997
990
  promises.push(promise);
998
991
  }
999
992
  channel.sustainNotes = [];
@@ -1107,11 +1100,12 @@ class MidyGMLite {
1107
1100
  return state;
1108
1101
  }
1109
1102
  applyVoiceParams(channel, controllerType, scheduleTime) {
1110
- this.processScheduledNotes(channel, (note) => {
1103
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1111
1104
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1112
1105
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1113
- let appliedFilterEnvelope = false;
1114
- let appliedVolumeEnvelope = false;
1106
+ let applyVolumeEnvelope = false;
1107
+ let applyFilterEnvelope = false;
1108
+ let applyPitchEnvelope = false;
1115
1109
  for (const [key, value] of Object.entries(voiceParams)) {
1116
1110
  const prevValue = note.voiceParams[key];
1117
1111
  if (value === prevValue)
@@ -1120,32 +1114,21 @@ class MidyGMLite {
1120
1114
  if (key in this.voiceParamsHandlers) {
1121
1115
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1122
1116
  }
1123
- else if (filterEnvelopeKeySet.has(key)) {
1124
- if (appliedFilterEnvelope)
1125
- continue;
1126
- appliedFilterEnvelope = true;
1127
- const noteVoiceParams = note.voiceParams;
1128
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1129
- const key = filterEnvelopeKeys[i];
1130
- if (key in voiceParams)
1131
- noteVoiceParams[key] = voiceParams[key];
1132
- }
1133
- this.setFilterEnvelope(note, scheduleTime);
1134
- this.setPitchEnvelope(note, scheduleTime);
1135
- }
1136
- else if (volumeEnvelopeKeySet.has(key)) {
1137
- if (appliedVolumeEnvelope)
1138
- continue;
1139
- appliedVolumeEnvelope = true;
1140
- const noteVoiceParams = note.voiceParams;
1141
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1142
- const key = volumeEnvelopeKeys[i];
1143
- if (key in voiceParams)
1144
- noteVoiceParams[key] = voiceParams[key];
1145
- }
1146
- this.setVolumeEnvelope(note, scheduleTime);
1117
+ else {
1118
+ if (volumeEnvelopeKeySet.has(key))
1119
+ applyVolumeEnvelope = true;
1120
+ if (filterEnvelopeKeySet.has(key))
1121
+ applyFilterEnvelope = true;
1122
+ if (pitchEnvelopeKeySet.has(key))
1123
+ applyPitchEnvelope = true;
1147
1124
  }
1148
1125
  }
1126
+ if (applyVolumeEnvelope)
1127
+ this.setVolumeEnvelope(note, scheduleTime);
1128
+ if (applyFilterEnvelope)
1129
+ this.setFilterEnvelope(note, scheduleTime);
1130
+ if (applyPitchEnvelope)
1131
+ this.setPitchEnvelope(note, scheduleTime);
1149
1132
  });
1150
1133
  }
1151
1134
  createControlChangeHandlers() {
@@ -1177,7 +1160,7 @@ class MidyGMLite {
1177
1160
  }
1178
1161
  updateModulation(channel, scheduleTime) {
1179
1162
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1180
- this.processScheduledNotes(channel, (note) => {
1163
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1181
1164
  if (note.modulationDepth) {
1182
1165
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1183
1166
  }
@@ -1238,7 +1221,7 @@ class MidyGMLite {
1238
1221
  scheduleTime ??= this.audioContext.currentTime;
1239
1222
  channel.state.sustainPedal = value / 127;
1240
1223
  if (64 <= value) {
1241
- this.processScheduledNotes(channel, (note) => {
1224
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1242
1225
  channel.sustainNotes.push(note);
1243
1226
  });
1244
1227
  }