@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.
package/esm/midy-GM1.d.ts CHANGED
@@ -54,9 +54,9 @@ export class MidyGM1 {
54
54
  channels: any[];
55
55
  initSoundFontTable(): any[];
56
56
  addSoundFont(soundFont: any): void;
57
- loadSoundFont(soundFontUrl: any): Promise<void>;
58
- loadMIDI(midiUrl: any): Promise<void>;
59
- setChannelAudioNodes(audioContext: any): {
57
+ loadSoundFont(input: any): Promise<void>;
58
+ loadMIDI(input: any): Promise<void>;
59
+ createChannelAudioNodes(audioContext: any): {
60
60
  gainL: any;
61
61
  gainR: any;
62
62
  merger: any;
@@ -84,7 +84,7 @@ export class MidyGM1 {
84
84
  seekTo(second: any): void;
85
85
  calcTotalTime(): number;
86
86
  currentTime(): number;
87
- processScheduledNotes(channel: any, callback: any): void;
87
+ processScheduledNotes(channel: any, scheduleTime: any, callback: any): void;
88
88
  processActiveNotes(channel: any, scheduleTime: any, callback: any): void;
89
89
  cbToRatio(cb: any): number;
90
90
  rateToCent(rate: any): number;
@@ -101,15 +101,15 @@ export class MidyGM1 {
101
101
  getAudioBuffer(programNumber: any, noteNumber: any, velocity: any, voiceParams: any, isSF3: any): Promise<any>;
102
102
  createNote(channel: any, voice: any, noteNumber: any, velocity: any, startTime: any, isSF3: any): Promise<Note>;
103
103
  handleExclusiveClass(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;
@@ -173,6 +173,7 @@ export class MidyGM1 {
173
173
  declare class Note {
174
174
  constructor(noteNumber: any, velocity: any, startTime: any, voice: any, voiceParams: any);
175
175
  index: number;
176
+ ending: boolean;
176
177
  bufferSource: any;
177
178
  filterNode: any;
178
179
  filterDepth: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqFA;IAwBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAlDD,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;IAgBnC,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,0BA8DC;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,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAyCC;IAED,0EAiBC;IAED,qHAoDC;IAED,6FASC;IAED,gCASC;IAED,6EAgBC;IAED,mHAgBC;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,qCAcC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAKC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AA9+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-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AA4FA;IAwBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAlDD,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;IAgBnC,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,4DASC;IAED,+EAkDC;IAED,mCAOC;IAED,0BA8DC;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,qCAMC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,+GA0BC;IAED,gHAyCC;IAED,0EAiBC;IAED,kGAsCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAaC;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,qCAcC;IAED,kGAWC;IAED,wDAUC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,2DAMC;IAED,uDAkBC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,iEAKC;IAED,uEAQC;IAED,mEAKC;IAED,yEAQC;IAED,gFAGC;IAED,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAv9CD;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"}
package/esm/midy-GM1.js CHANGED
@@ -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,
@@ -64,13 +70,11 @@ const defaultControllerState = {
64
70
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
65
71
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
66
72
  link: { type: 127, defaultValue: 0 },
67
- // bankMSB: { type: 128 + 0, defaultValue: 121, },
68
73
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
69
74
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
70
75
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
71
76
  pan: { type: 128 + 10, defaultValue: 64 / 127 },
72
77
  expression: { type: 128 + 11, defaultValue: 1 },
73
- // bankLSB: { type: 128 + 32, defaultValue: 0, },
74
78
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
75
79
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
76
80
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
@@ -99,6 +103,16 @@ class ControllerState {
99
103
  }
100
104
  }
101
105
  }
106
+ const volumeEnvelopeKeys = [
107
+ "volDelay",
108
+ "volAttack",
109
+ "volHold",
110
+ "volDecay",
111
+ "volSustain",
112
+ "volRelease",
113
+ "initialAttenuation",
114
+ ];
115
+ const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
102
116
  const filterEnvelopeKeys = [
103
117
  "modEnvToPitch",
104
118
  "initialFilterFc",
@@ -108,20 +122,18 @@ const filterEnvelopeKeys = [
108
122
  "modHold",
109
123
  "modDecay",
110
124
  "modSustain",
111
- "modRelease",
112
- "playbackRate",
113
125
  ];
114
126
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
115
- const volumeEnvelopeKeys = [
116
- "volDelay",
117
- "volAttack",
118
- "volHold",
119
- "volDecay",
120
- "volSustain",
121
- "volRelease",
122
- "initialAttenuation",
127
+ const pitchEnvelopeKeys = [
128
+ "modEnvToPitch",
129
+ "modDelay",
130
+ "modAttack",
131
+ "modHold",
132
+ "modDecay",
133
+ "modSustain",
134
+ "playbackRate",
123
135
  ];
124
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
136
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
125
137
  export class MidyGM1 {
126
138
  constructor(audioContext) {
127
139
  Object.defineProperty(this, "mode", {
@@ -289,24 +301,44 @@ export class MidyGM1 {
289
301
  }
290
302
  }
291
303
  }
292
- async loadSoundFont(soundFontUrl) {
293
- const response = await fetch(soundFontUrl);
294
- const arrayBuffer = await response.arrayBuffer();
295
- const parsed = parse(new Uint8Array(arrayBuffer));
304
+ async loadSoundFont(input) {
305
+ let uint8Array;
306
+ if (typeof input === "string") {
307
+ const response = await fetch(input);
308
+ const arrayBuffer = await response.arrayBuffer();
309
+ uint8Array = new Uint8Array(arrayBuffer);
310
+ }
311
+ else if (input instanceof Uint8Array) {
312
+ uint8Array = input;
313
+ }
314
+ else {
315
+ throw new TypeError("input must be a URL string or Uint8Array");
316
+ }
317
+ const parsed = parse(uint8Array);
296
318
  const soundFont = new SoundFont(parsed);
297
319
  this.addSoundFont(soundFont);
298
320
  }
299
- async loadMIDI(midiUrl) {
300
- const response = await fetch(midiUrl);
301
- const arrayBuffer = await response.arrayBuffer();
302
- const midi = parseMidi(new Uint8Array(arrayBuffer));
321
+ async loadMIDI(input) {
322
+ let uint8Array;
323
+ if (typeof input === "string") {
324
+ const response = await fetch(input);
325
+ const arrayBuffer = await response.arrayBuffer();
326
+ uint8Array = new Uint8Array(arrayBuffer);
327
+ }
328
+ else if (input instanceof Uint8Array) {
329
+ uint8Array = input;
330
+ }
331
+ else {
332
+ throw new TypeError("input must be a URL string or Uint8Array");
333
+ }
334
+ const midi = parseMidi(uint8Array);
303
335
  this.ticksPerBeat = midi.header.ticksPerBeat;
304
336
  const midiData = this.extractMidiData(midi);
305
337
  this.instruments = midiData.instruments;
306
338
  this.timeline = midiData.timeline;
307
339
  this.totalTime = this.calcTotalTime();
308
340
  }
309
- setChannelAudioNodes(audioContext) {
341
+ createChannelAudioNodes(audioContext) {
310
342
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
311
343
  const gainL = new GainNode(audioContext, { gain: gainLeft });
312
344
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -327,7 +359,7 @@ export class MidyGM1 {
327
359
  isDrum: false,
328
360
  state: new ControllerState(),
329
361
  ...this.constructor.channelSettings,
330
- ...this.setChannelAudioNodes(audioContext),
362
+ ...this.createChannelAudioNodes(audioContext),
331
363
  scheduledNotes: [],
332
364
  sustainNotes: [],
333
365
  };
@@ -381,12 +413,13 @@ export class MidyGM1 {
381
413
  const delay = this.startDelay - resumeTime;
382
414
  const startTime = event.startTime + delay;
383
415
  switch (event.type) {
384
- case "noteOn": {
385
- const noteOffEvent = {
386
- ...event.noteOffEvent,
387
- startTime: event.noteOffEvent.startTime + delay,
388
- };
389
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
416
+ case "noteOn":
417
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
418
+ break;
419
+ case "noteOff": {
420
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
421
+ if (notePromise)
422
+ this.notePromises.push(notePromise);
390
423
  break;
391
424
  }
392
425
  case "controller":
@@ -485,6 +518,7 @@ export class MidyGM1 {
485
518
  return `${programNumber}:${noteNumber}:${velocity}`;
486
519
  }
487
520
  extractMidiData(midi) {
521
+ this.audioBufferCounter.clear();
488
522
  const instruments = new Set();
489
523
  const timeline = [];
490
524
  const tmpChannels = new Array(this.channels.length);
@@ -548,38 +582,13 @@ export class MidyGM1 {
548
582
  prevTempoTicks = event.ticks;
549
583
  }
550
584
  }
551
- const activeNotes = new Array(this.channels.length * 128);
552
- for (let i = 0; i < activeNotes.length; i++) {
553
- activeNotes[i] = [];
554
- }
555
- for (let i = 0; i < timeline.length; i++) {
556
- const event = timeline[i];
557
- switch (event.type) {
558
- case "noteOn": {
559
- const index = event.channel * 128 + event.noteNumber;
560
- activeNotes[index].push(event);
561
- break;
562
- }
563
- case "noteOff": {
564
- const index = event.channel * 128 + event.noteNumber;
565
- const noteOn = activeNotes[index].pop();
566
- if (noteOn) {
567
- noteOn.noteOffEvent = event;
568
- }
569
- else {
570
- const eventString = JSON.stringify(event, null, 2);
571
- console.warn(`noteOff without matching noteOn: ${eventString}`);
572
- }
573
- }
574
- }
575
- }
576
585
  return { instruments, timeline };
577
586
  }
578
587
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
579
588
  const channel = this.channels[channelNumber];
580
589
  const promises = [];
581
590
  this.processActiveNotes(channel, scheduleTime, (note) => {
582
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
591
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
583
592
  this.notePromises.push(promise);
584
593
  promises.push(promise);
585
594
  });
@@ -588,8 +597,8 @@ export class MidyGM1 {
588
597
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
589
598
  const channel = this.channels[channelNumber];
590
599
  const promises = [];
591
- this.processScheduledNotes(channel, (note) => {
592
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
600
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
601
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
593
602
  this.notePromises.push(promise);
594
603
  promises.push(promise);
595
604
  });
@@ -647,7 +656,7 @@ export class MidyGM1 {
647
656
  const now = this.audioContext.currentTime;
648
657
  return this.resumeTime + now - this.startTime - this.startDelay;
649
658
  }
650
- processScheduledNotes(channel, callback) {
659
+ processScheduledNotes(channel, scheduleTime, callback) {
651
660
  const scheduledNotes = channel.scheduledNotes;
652
661
  for (let i = 0; i < scheduledNotes.length; i++) {
653
662
  const note = scheduledNotes[i];
@@ -655,6 +664,8 @@ export class MidyGM1 {
655
664
  continue;
656
665
  if (note.ending)
657
666
  continue;
667
+ if (note.startTime < scheduleTime)
668
+ continue;
658
669
  callback(note);
659
670
  }
660
671
  }
@@ -666,11 +677,8 @@ export class MidyGM1 {
666
677
  continue;
667
678
  if (note.ending)
668
679
  continue;
669
- const noteOffEvent = note.noteOffEvent;
670
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
671
- continue;
672
680
  if (scheduleTime < note.startTime)
673
- continue;
681
+ break;
674
682
  callback(note);
675
683
  }
676
684
  }
@@ -694,7 +702,7 @@ export class MidyGM1 {
694
702
  return tuning + pitch;
695
703
  }
696
704
  updateChannelDetune(channel, scheduleTime) {
697
- this.processScheduledNotes(channel, (note) => {
705
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
698
706
  this.updateDetune(channel, note, scheduleTime);
699
707
  });
700
708
  }
@@ -838,13 +846,13 @@ export class MidyGM1 {
838
846
  if (prev) {
839
847
  const [prevNote, prevChannelNumber] = prev;
840
848
  if (prevNote && !prevNote.ending) {
841
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
849
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
842
850
  startTime, true);
843
851
  }
844
852
  }
845
853
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
846
854
  }
847
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
855
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
848
856
  const channel = this.channels[channelNumber];
849
857
  const bankNumber = channel.bank;
850
858
  const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
@@ -856,7 +864,6 @@ export class MidyGM1 {
856
864
  return;
857
865
  const isSF3 = soundFont.parsed.info.version.major === 3;
858
866
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
859
- note.noteOffEvent = noteOffEvent;
860
867
  note.volumeEnvelopeNode.connect(channel.gainL);
861
868
  note.volumeEnvelopeNode.connect(channel.gainR);
862
869
  if (0.5 <= channel.state.sustainPedal) {
@@ -866,12 +873,6 @@ export class MidyGM1 {
866
873
  const scheduledNotes = channel.scheduledNotes;
867
874
  note.index = scheduledNotes.length;
868
875
  scheduledNotes.push(note);
869
- if (noteOffEvent) {
870
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
871
- if (notePromise) {
872
- this.notePromises.push(notePromise);
873
- }
874
- }
875
876
  }
876
877
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
877
878
  scheduleTime ??= this.audioContext.currentTime;
@@ -887,34 +888,36 @@ export class MidyGM1 {
887
888
  note.modulationLFO.stop();
888
889
  }
889
890
  }
890
- stopNote(channel, note, endTime, stopTime) {
891
+ releaseNote(channel, note, endTime) {
892
+ const volRelease = endTime + note.voiceParams.volRelease;
893
+ const modRelease = endTime + note.voiceParams.modRelease;
894
+ const stopTime = Math.min(volRelease, modRelease);
895
+ note.filterNode.frequency
896
+ .cancelScheduledValues(endTime)
897
+ .linearRampToValueAtTime(0, modRelease);
891
898
  note.volumeEnvelopeNode.gain
892
899
  .cancelScheduledValues(endTime)
893
- .linearRampToValueAtTime(0, stopTime);
894
- note.ending = true;
895
- this.scheduleTask(() => {
896
- note.bufferSource.loop = false;
897
- }, stopTime);
900
+ .linearRampToValueAtTime(0, volRelease);
898
901
  return new Promise((resolve) => {
899
- note.bufferSource.onended = () => {
900
- channel.scheduledNotes[note.index] = undefined;
902
+ this.scheduleTask(() => {
903
+ const bufferSource = note.bufferSource;
904
+ bufferSource.loop = false;
905
+ bufferSource.stop(stopTime);
901
906
  this.disconnectNote(note);
907
+ channel.scheduledNotes[note.index] = undefined;
902
908
  resolve();
903
- };
904
- note.bufferSource.stop(stopTime);
909
+ }, stopTime);
905
910
  });
906
911
  }
907
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
912
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
908
913
  const channel = this.channels[channelNumber];
909
914
  if (!force && 0.5 <= channel.state.sustainPedal)
910
915
  return;
911
- const volRelease = endTime + note.voiceParams.volRelease;
912
- const modRelease = endTime + note.voiceParams.modRelease;
913
- note.filterNode.frequency
914
- .cancelScheduledValues(endTime)
915
- .linearRampToValueAtTime(0, modRelease);
916
- const stopTime = Math.min(volRelease, modRelease);
917
- return this.stopNote(channel, note, endTime, stopTime);
916
+ const note = this.findNoteOffTarget(channel, noteNumber);
917
+ if (!note)
918
+ return;
919
+ note.ending = true;
920
+ this.releaseNote(channel, note, endTime);
918
921
  }
919
922
  findNoteOffTarget(channel, noteNumber) {
920
923
  const scheduledNotes = channel.scheduledNotes;
@@ -931,16 +934,14 @@ export class MidyGM1 {
931
934
  }
932
935
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
933
936
  scheduleTime ??= this.audioContext.currentTime;
934
- const channel = this.channels[channelNumber];
935
- const note = this.findNoteOffTarget(channel, noteNumber);
936
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
937
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
937
938
  }
938
939
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
939
940
  const velocity = halfVelocity * 2;
940
941
  const channel = this.channels[channelNumber];
941
942
  const promises = [];
942
943
  for (let i = 0; i < channel.sustainNotes.length; i++) {
943
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
944
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
944
945
  promises.push(promise);
945
946
  }
946
947
  channel.sustainNotes = [];
@@ -1054,11 +1055,12 @@ export class MidyGM1 {
1054
1055
  return state;
1055
1056
  }
1056
1057
  applyVoiceParams(channel, controllerType, scheduleTime) {
1057
- this.processScheduledNotes(channel, (note) => {
1058
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1058
1059
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1059
1060
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1060
- let appliedFilterEnvelope = false;
1061
- let appliedVolumeEnvelope = false;
1061
+ let applyVolumeEnvelope = false;
1062
+ let applyFilterEnvelope = false;
1063
+ let applyPitchEnvelope = false;
1062
1064
  for (const [key, value] of Object.entries(voiceParams)) {
1063
1065
  const prevValue = note.voiceParams[key];
1064
1066
  if (value === prevValue)
@@ -1067,32 +1069,21 @@ export class MidyGM1 {
1067
1069
  if (key in this.voiceParamsHandlers) {
1068
1070
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1069
1071
  }
1070
- else if (filterEnvelopeKeySet.has(key)) {
1071
- if (appliedFilterEnvelope)
1072
- continue;
1073
- appliedFilterEnvelope = true;
1074
- const noteVoiceParams = note.voiceParams;
1075
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1076
- const key = filterEnvelopeKeys[i];
1077
- if (key in voiceParams)
1078
- noteVoiceParams[key] = voiceParams[key];
1079
- }
1080
- this.setFilterEnvelope(note, scheduleTime);
1081
- this.setPitchEnvelope(note, scheduleTime);
1082
- }
1083
- else if (volumeEnvelopeKeySet.has(key)) {
1084
- if (appliedVolumeEnvelope)
1085
- continue;
1086
- appliedVolumeEnvelope = true;
1087
- const noteVoiceParams = note.voiceParams;
1088
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1089
- const key = volumeEnvelopeKeys[i];
1090
- if (key in voiceParams)
1091
- noteVoiceParams[key] = voiceParams[key];
1092
- }
1093
- this.setVolumeEnvelope(note, scheduleTime);
1072
+ else {
1073
+ if (volumeEnvelopeKeySet.has(key))
1074
+ applyVolumeEnvelope = true;
1075
+ if (filterEnvelopeKeySet.has(key))
1076
+ applyFilterEnvelope = true;
1077
+ if (pitchEnvelopeKeySet.has(key))
1078
+ applyPitchEnvelope = true;
1094
1079
  }
1095
1080
  }
1081
+ if (applyVolumeEnvelope)
1082
+ this.setVolumeEnvelope(note, scheduleTime);
1083
+ if (applyFilterEnvelope)
1084
+ this.setFilterEnvelope(note, scheduleTime);
1085
+ if (applyPitchEnvelope)
1086
+ this.setPitchEnvelope(note, scheduleTime);
1096
1087
  });
1097
1088
  }
1098
1089
  createControlChangeHandlers() {
@@ -1123,7 +1114,7 @@ export class MidyGM1 {
1123
1114
  }
1124
1115
  updateModulation(channel, scheduleTime) {
1125
1116
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1126
- this.processScheduledNotes(channel, (note) => {
1117
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1127
1118
  if (note.modulationDepth) {
1128
1119
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1129
1120
  }
@@ -1184,7 +1175,7 @@ export class MidyGM1 {
1184
1175
  scheduleTime ??= this.audioContext.currentTime;
1185
1176
  channel.state.sustainPedal = value / 127;
1186
1177
  if (64 <= value) {
1187
- this.processScheduledNotes(channel, (note) => {
1178
+ this.processScheduledNotes(channel, scheduleTime, (note) => {
1188
1179
  channel.sustainNotes.push(note);
1189
1180
  });
1190
1181
  }