@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"}
@@ -8,6 +8,12 @@ class Note {
8
8
  writable: true,
9
9
  value: -1
10
10
  });
11
+ Object.defineProperty(this, "ending", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: false
16
+ });
11
17
  Object.defineProperty(this, "bufferSource", {
12
18
  enumerable: true,
13
19
  configurable: true,
@@ -77,13 +83,11 @@ const defaultControllerState = {
77
83
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
78
84
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
79
85
  link: { type: 127, defaultValue: 0 },
80
- // bankMSB: { type: 128 + 0, defaultValue: 121, },
81
86
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
82
87
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
83
88
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
84
89
  pan: { type: 128 + 10, defaultValue: 64 / 127 },
85
90
  expression: { type: 128 + 11, defaultValue: 1 },
86
- // bankLSB: { type: 128 + 32, defaultValue: 0, },
87
91
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
88
92
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
89
93
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
@@ -112,6 +116,16 @@ class ControllerState {
112
116
  }
113
117
  }
114
118
  }
119
+ const volumeEnvelopeKeys = [
120
+ "volDelay",
121
+ "volAttack",
122
+ "volHold",
123
+ "volDecay",
124
+ "volSustain",
125
+ "volRelease",
126
+ "initialAttenuation",
127
+ ];
128
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
115
129
  const filterEnvelopeKeys = [
116
130
  "modEnvToPitch",
117
131
  "initialFilterFc",
@@ -121,20 +135,18 @@ const filterEnvelopeKeys = [
121
135
  "modHold",
122
136
  "modDecay",
123
137
  "modSustain",
124
- "modRelease",
125
- "playbackRate",
126
138
  ];
127
139
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
128
- const volumeEnvelopeKeys = [
129
- "volDelay",
130
- "volAttack",
131
- "volHold",
132
- "volDecay",
133
- "volSustain",
134
- "volRelease",
135
- "initialAttenuation",
140
+ const pitchEnvelopeKeys = [
141
+ "modEnvToPitch",
142
+ "modDelay",
143
+ "modAttack",
144
+ "modHold",
145
+ "modDecay",
146
+ "modSustain",
147
+ "playbackRate",
136
148
  ];
137
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
149
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
138
150
  export class MidyGMLite {
139
151
  constructor(audioContext) {
140
152
  Object.defineProperty(this, "mode", {
@@ -308,24 +320,44 @@ export class MidyGMLite {
308
320
  }
309
321
  }
310
322
  }
311
- async loadSoundFont(soundFontUrl) {
312
- const response = await fetch(soundFontUrl);
313
- const arrayBuffer = await response.arrayBuffer();
314
- const parsed = parse(new Uint8Array(arrayBuffer));
323
+ async loadSoundFont(input) {
324
+ let uint8Array;
325
+ if (typeof input === "string") {
326
+ const response = await fetch(input);
327
+ const arrayBuffer = await response.arrayBuffer();
328
+ uint8Array = new Uint8Array(arrayBuffer);
329
+ }
330
+ else if (input instanceof Uint8Array) {
331
+ uint8Array = input;
332
+ }
333
+ else {
334
+ throw new TypeError("input must be a URL string or Uint8Array");
335
+ }
336
+ const parsed = parse(uint8Array);
315
337
  const soundFont = new SoundFont(parsed);
316
338
  this.addSoundFont(soundFont);
317
339
  }
318
- async loadMIDI(midiUrl) {
319
- const response = await fetch(midiUrl);
320
- const arrayBuffer = await response.arrayBuffer();
321
- const midi = parseMidi(new Uint8Array(arrayBuffer));
340
+ async loadMIDI(input) {
341
+ let uint8Array;
342
+ if (typeof input === "string") {
343
+ const response = await fetch(input);
344
+ const arrayBuffer = await response.arrayBuffer();
345
+ uint8Array = new Uint8Array(arrayBuffer);
346
+ }
347
+ else if (input instanceof Uint8Array) {
348
+ uint8Array = input;
349
+ }
350
+ else {
351
+ throw new TypeError("input must be a URL string or Uint8Array");
352
+ }
353
+ const midi = parseMidi(uint8Array);
322
354
  this.ticksPerBeat = midi.header.ticksPerBeat;
323
355
  const midiData = this.extractMidiData(midi);
324
356
  this.instruments = midiData.instruments;
325
357
  this.timeline = midiData.timeline;
326
358
  this.totalTime = this.calcTotalTime();
327
359
  }
328
- setChannelAudioNodes(audioContext) {
360
+ createChannelAudioNodes(audioContext) {
329
361
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
330
362
  const gainL = new GainNode(audioContext, { gain: gainLeft });
331
363
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -346,7 +378,7 @@ export class MidyGMLite {
346
378
  isDrum: false,
347
379
  state: new ControllerState(),
348
380
  ...this.constructor.channelSettings,
349
- ...this.setChannelAudioNodes(audioContext),
381
+ ...this.createChannelAudioNodes(audioContext),
350
382
  scheduledNotes: [],
351
383
  sustainNotes: [],
352
384
  };
@@ -382,10 +414,12 @@ export class MidyGMLite {
382
414
  return audioBuffer;
383
415
  }
384
416
  }
385
- createBufferSource(voiceParams, audioBuffer) {
417
+ createBufferSource(channel, voiceParams, audioBuffer) {
386
418
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
387
419
  bufferSource.buffer = audioBuffer;
388
420
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
421
+ if (channel.isDrum)
422
+ bufferSource.loop = false;
389
423
  if (bufferSource.loop) {
390
424
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
391
425
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -400,12 +434,13 @@ export class MidyGMLite {
400
434
  const delay = this.startDelay - resumeTime;
401
435
  const startTime = event.startTime + delay;
402
436
  switch (event.type) {
403
- case "noteOn": {
404
- const noteOffEvent = {
405
- ...event.noteOffEvent,
406
- startTime: event.noteOffEvent.startTime + delay,
407
- };
408
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
437
+ case "noteOn":
438
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
439
+ break;
440
+ case "noteOff": {
441
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
442
+ if (notePromise)
443
+ this.notePromises.push(notePromise);
409
444
  break;
410
445
  }
411
446
  case "controller":
@@ -507,6 +542,7 @@ export class MidyGMLite {
507
542
  return `${programNumber}:${noteNumber}:${velocity}`;
508
543
  }
509
544
  extractMidiData(midi) {
545
+ this.audioBufferCounter.clear();
510
546
  const instruments = new Set();
511
547
  const timeline = [];
512
548
  const tmpChannels = new Array(this.channels.length);
@@ -570,38 +606,13 @@ export class MidyGMLite {
570
606
  prevTempoTicks = event.ticks;
571
607
  }
572
608
  }
573
- const activeNotes = new Array(this.channels.length * 128);
574
- for (let i = 0; i < activeNotes.length; i++) {
575
- activeNotes[i] = [];
576
- }
577
- for (let i = 0; i < timeline.length; i++) {
578
- const event = timeline[i];
579
- switch (event.type) {
580
- case "noteOn": {
581
- const index = event.channel * 128 + event.noteNumber;
582
- activeNotes[index].push(event);
583
- break;
584
- }
585
- case "noteOff": {
586
- const index = event.channel * 128 + event.noteNumber;
587
- const noteOn = activeNotes[index].pop();
588
- if (noteOn) {
589
- noteOn.noteOffEvent = event;
590
- }
591
- else {
592
- const eventString = JSON.stringify(event, null, 2);
593
- console.warn(`noteOff without matching noteOn: ${eventString}`);
594
- }
595
- }
596
- }
597
- }
598
609
  return { instruments, timeline };
599
610
  }
600
611
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
601
612
  const channel = this.channels[channelNumber];
602
613
  const promises = [];
603
614
  this.processActiveNotes(channel, scheduleTime, (note) => {
604
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
615
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
605
616
  this.notePromises.push(promise);
606
617
  promises.push(promise);
607
618
  });
@@ -610,8 +621,8 @@ export class MidyGMLite {
610
621
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
611
622
  const channel = this.channels[channelNumber];
612
623
  const promises = [];
613
- this.processScheduledNotes(channel, (note) => {
614
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
624
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
625
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
615
626
  this.notePromises.push(promise);
616
627
  promises.push(promise);
617
628
  });
@@ -669,7 +680,7 @@ export class MidyGMLite {
669
680
  const now = this.audioContext.currentTime;
670
681
  return this.resumeTime + now - this.startTime - this.startDelay;
671
682
  }
672
- processScheduledNotes(channel, callback) {
683
+ processScheduledNotes(channel, scheduleTime, callback) {
673
684
  const scheduledNotes = channel.scheduledNotes;
674
685
  for (let i = 0; i < scheduledNotes.length; i++) {
675
686
  const note = scheduledNotes[i];
@@ -677,6 +688,8 @@ export class MidyGMLite {
677
688
  continue;
678
689
  if (note.ending)
679
690
  continue;
691
+ if (note.startTime < scheduleTime)
692
+ continue;
680
693
  callback(note);
681
694
  }
682
695
  }
@@ -688,11 +701,8 @@ export class MidyGMLite {
688
701
  continue;
689
702
  if (note.ending)
690
703
  continue;
691
- const noteOffEvent = note.noteOffEvent;
692
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
693
- continue;
694
704
  if (scheduleTime < note.startTime)
695
- continue;
705
+ break;
696
706
  callback(note);
697
707
  }
698
708
  }
@@ -714,7 +724,7 @@ export class MidyGMLite {
714
724
  return pitchWheel * pitchWheelSensitivity;
715
725
  }
716
726
  updateChannelDetune(channel, scheduleTime) {
717
- this.processScheduledNotes(channel, (note) => {
727
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
718
728
  this.updateDetune(channel, note, scheduleTime);
719
729
  });
720
730
  }
@@ -832,7 +842,7 @@ export class MidyGMLite {
832
842
  const voiceParams = voice.getAllParams(controllerState);
833
843
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
834
844
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
835
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
845
+ note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
836
846
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
837
847
  note.filterNode = new BiquadFilterNode(this.audioContext, {
838
848
  type: "lowpass",
@@ -858,7 +868,7 @@ export class MidyGMLite {
858
868
  if (prev) {
859
869
  const [prevNote, prevChannelNumber] = prev;
860
870
  if (prevNote && !prevNote.ending) {
861
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
871
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
862
872
  startTime, true);
863
873
  }
864
874
  }
@@ -874,12 +884,12 @@ export class MidyGMLite {
874
884
  const index = drumExclusiveClass * this.channels.length + channelNumber;
875
885
  const prevNote = this.drumExclusiveClassNotes[index];
876
886
  if (prevNote && !prevNote.ending) {
877
- this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
887
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
878
888
  startTime, true);
879
889
  }
880
890
  this.drumExclusiveClassNotes[index] = note;
881
891
  }
882
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
892
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
883
893
  const channel = this.channels[channelNumber];
884
894
  const bankNumber = channel.bank;
885
895
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -891,7 +901,6 @@ export class MidyGMLite {
891
901
  return;
892
902
  const isSF3 = soundFont.parsed.info.version.major === 3;
893
903
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
894
- note.noteOffEvent = noteOffEvent;
895
904
  note.volumeEnvelopeNode.connect(channel.gainL);
896
905
  note.volumeEnvelopeNode.connect(channel.gainR);
897
906
  if (0.5 <= channel.state.sustainPedal) {
@@ -902,24 +911,6 @@ export class MidyGMLite {
902
911
  const scheduledNotes = channel.scheduledNotes;
903
912
  note.index = scheduledNotes.length;
904
913
  scheduledNotes.push(note);
905
- if (channel.isDrum) {
906
- const stopTime = startTime + note.bufferSource.buffer.duration;
907
- const promise = new Promise((resolve) => {
908
- note.bufferSource.onended = () => {
909
- scheduledNotes[note.index] = undefined;
910
- this.disconnectNote(note);
911
- resolve();
912
- };
913
- note.bufferSource.stop(stopTime);
914
- });
915
- this.notePromises.push(promise);
916
- }
917
- else if (noteOffEvent) {
918
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
919
- if (notePromise) {
920
- this.notePromises.push(notePromise);
921
- }
922
- }
923
914
  }
924
915
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
925
916
  scheduleTime ??= this.audioContext.currentTime;
@@ -935,36 +926,40 @@ export class MidyGMLite {
935
926
  note.modulationLFO.stop();
936
927
  }
937
928
  }
938
- stopNote(channel, note, endTime, stopTime) {
929
+ releaseNote(channel, note, endTime) {
930
+ const volRelease = endTime + note.voiceParams.volRelease;
931
+ const modRelease = endTime + note.voiceParams.modRelease;
932
+ const stopTime = Math.min(volRelease, modRelease);
933
+ note.filterNode.frequency
934
+ .cancelScheduledValues(endTime)
935
+ .linearRampToValueAtTime(0, modRelease);
939
936
  note.volumeEnvelopeNode.gain
940
937
  .cancelScheduledValues(endTime)
941
- .linearRampToValueAtTime(0, stopTime);
942
- note.ending = true;
943
- this.scheduleTask(() => {
944
- note.bufferSource.loop = false;
945
- }, stopTime);
938
+ .linearRampToValueAtTime(0, volRelease);
946
939
  return new Promise((resolve) => {
947
- note.bufferSource.onended = () => {
948
- channel.scheduledNotes[note.index] = undefined;
940
+ this.scheduleTask(() => {
941
+ const bufferSource = note.bufferSource;
942
+ bufferSource.loop = false;
943
+ bufferSource.stop(stopTime);
949
944
  this.disconnectNote(note);
945
+ channel.scheduledNotes[note.index] = undefined;
950
946
  resolve();
951
- };
952
- note.bufferSource.stop(stopTime);
947
+ }, stopTime);
953
948
  });
954
949
  }
955
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
950
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
956
951
  const channel = this.channels[channelNumber];
957
- if (channel.isDrum)
958
- return;
959
- if (!force && 0.5 <= channel.state.sustainPedal)
952
+ if (!force) {
953
+ if (channel.isDrum)
954
+ return;
955
+ if (0.5 <= channel.state.sustainPedal)
956
+ return;
957
+ }
958
+ const note = this.findNoteOffTarget(channel, noteNumber);
959
+ if (!note)
960
960
  return;
961
- const volRelease = endTime + note.voiceParams.volRelease;
962
- const modRelease = endTime + note.voiceParams.modRelease;
963
- note.filterNode.frequency
964
- .cancelScheduledValues(endTime)
965
- .linearRampToValueAtTime(0, modRelease);
966
- const stopTime = Math.min(volRelease, modRelease);
967
- return this.stopNote(channel, note, endTime, stopTime);
961
+ note.ending = true;
962
+ this.releaseNote(channel, note, endTime);
968
963
  }
969
964
  findNoteOffTarget(channel, noteNumber) {
970
965
  const scheduledNotes = channel.scheduledNotes;
@@ -981,16 +976,14 @@ export class MidyGMLite {
981
976
  }
982
977
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
983
978
  scheduleTime ??= this.audioContext.currentTime;
984
- const channel = this.channels[channelNumber];
985
- const note = this.findNoteOffTarget(channel, noteNumber);
986
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
979
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
987
980
  }
988
981
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
989
982
  const velocity = halfVelocity * 2;
990
983
  const channel = this.channels[channelNumber];
991
984
  const promises = [];
992
985
  for (let i = 0; i < channel.sustainNotes.length; i++) {
993
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
986
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
994
987
  promises.push(promise);
995
988
  }
996
989
  channel.sustainNotes = [];
@@ -1104,11 +1097,12 @@ export class MidyGMLite {
1104
1097
  return state;
1105
1098
  }
1106
1099
  applyVoiceParams(channel, controllerType, scheduleTime) {
1107
- this.processScheduledNotes(channel, (note) => {
1100
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1108
1101
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1109
1102
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1110
- let appliedFilterEnvelope = false;
1111
- let appliedVolumeEnvelope = false;
1103
+ let applyVolumeEnvelope = false;
1104
+ let applyFilterEnvelope = false;
1105
+ let applyPitchEnvelope = false;
1112
1106
  for (const [key, value] of Object.entries(voiceParams)) {
1113
1107
  const prevValue = note.voiceParams[key];
1114
1108
  if (value === prevValue)
@@ -1117,32 +1111,21 @@ export class MidyGMLite {
1117
1111
  if (key in this.voiceParamsHandlers) {
1118
1112
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1119
1113
  }
1120
- else if (filterEnvelopeKeySet.has(key)) {
1121
- if (appliedFilterEnvelope)
1122
- continue;
1123
- appliedFilterEnvelope = true;
1124
- const noteVoiceParams = note.voiceParams;
1125
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1126
- const key = filterEnvelopeKeys[i];
1127
- if (key in voiceParams)
1128
- noteVoiceParams[key] = voiceParams[key];
1129
- }
1130
- this.setFilterEnvelope(note, scheduleTime);
1131
- this.setPitchEnvelope(note, scheduleTime);
1132
- }
1133
- else if (volumeEnvelopeKeySet.has(key)) {
1134
- if (appliedVolumeEnvelope)
1135
- continue;
1136
- appliedVolumeEnvelope = true;
1137
- const noteVoiceParams = note.voiceParams;
1138
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1139
- const key = volumeEnvelopeKeys[i];
1140
- if (key in voiceParams)
1141
- noteVoiceParams[key] = voiceParams[key];
1142
- }
1143
- this.setVolumeEnvelope(note, scheduleTime);
1114
+ else {
1115
+ if (volumeEnvelopeKeySet.has(key))
1116
+ applyVolumeEnvelope = true;
1117
+ if (filterEnvelopeKeySet.has(key))
1118
+ applyFilterEnvelope = true;
1119
+ if (pitchEnvelopeKeySet.has(key))
1120
+ applyPitchEnvelope = true;
1144
1121
  }
1145
1122
  }
1123
+ if (applyVolumeEnvelope)
1124
+ this.setVolumeEnvelope(note, scheduleTime);
1125
+ if (applyFilterEnvelope)
1126
+ this.setFilterEnvelope(note, scheduleTime);
1127
+ if (applyPitchEnvelope)
1128
+ this.setPitchEnvelope(note, scheduleTime);
1146
1129
  });
1147
1130
  }
1148
1131
  createControlChangeHandlers() {
@@ -1174,7 +1157,7 @@ export class MidyGMLite {
1174
1157
  }
1175
1158
  updateModulation(channel, scheduleTime) {
1176
1159
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1177
- this.processScheduledNotes(channel, (note) => {
1160
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1178
1161
  if (note.modulationDepth) {
1179
1162
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1180
1163
  }
@@ -1235,7 +1218,7 @@ export class MidyGMLite {
1235
1218
  scheduleTime ??= this.audioContext.currentTime;
1236
1219
  channel.state.sustainPedal = value / 127;
1237
1220
  if (64 <= value) {
1238
- this.processScheduledNotes(channel, (note) => {
1221
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1239
1222
  channel.sustainNotes.push(note);
1240
1223
  });
1241
1224
  }