@marmooo/midy 0.3.3 → 0.3.5

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.js CHANGED
@@ -70,13 +70,11 @@ const defaultControllerState = {
70
70
  pitchWheel: { type: 14, defaultValue: 8192 / 16383 },
71
71
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
72
72
  link: { type: 127, defaultValue: 0 },
73
- // bankMSB: { type: 128 + 0, defaultValue: 121, },
74
73
  modulationDepth: { type: 128 + 1, defaultValue: 0 },
75
74
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
76
75
  volume: { type: 128 + 7, defaultValue: 100 / 127 },
77
76
  pan: { type: 128 + 10, defaultValue: 64 / 127 },
78
77
  expression: { type: 128 + 11, defaultValue: 1 },
79
- // bankLSB: { type: 128 + 32, defaultValue: 0, },
80
78
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
81
79
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
82
80
  // rpnLSB: { type: 128 + 100, defaultValue: 127 },
@@ -105,6 +103,16 @@ class ControllerState {
105
103
  }
106
104
  }
107
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);
108
116
  const filterEnvelopeKeys = [
109
117
  "modEnvToPitch",
110
118
  "initialFilterFc",
@@ -114,20 +122,18 @@ const filterEnvelopeKeys = [
114
122
  "modHold",
115
123
  "modDecay",
116
124
  "modSustain",
117
- "modRelease",
118
- "playbackRate",
119
125
  ];
120
126
  const filterEnvelopeKeySet = new Set(filterEnvelopeKeys);
121
- const volumeEnvelopeKeys = [
122
- "volDelay",
123
- "volAttack",
124
- "volHold",
125
- "volDecay",
126
- "volSustain",
127
- "volRelease",
128
- "initialAttenuation",
127
+ const pitchEnvelopeKeys = [
128
+ "modEnvToPitch",
129
+ "modDelay",
130
+ "modAttack",
131
+ "modHold",
132
+ "modDecay",
133
+ "modSustain",
134
+ "playbackRate",
129
135
  ];
130
- const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
136
+ const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
131
137
  export class MidyGM1 {
132
138
  constructor(audioContext) {
133
139
  Object.defineProperty(this, "mode", {
@@ -196,13 +202,13 @@ export class MidyGM1 {
196
202
  writable: true,
197
203
  value: this.initSoundFontTable()
198
204
  });
199
- Object.defineProperty(this, "audioBufferCounter", {
205
+ Object.defineProperty(this, "voiceCounter", {
200
206
  enumerable: true,
201
207
  configurable: true,
202
208
  writable: true,
203
209
  value: new Map()
204
210
  });
205
- Object.defineProperty(this, "audioBufferCache", {
211
+ Object.defineProperty(this, "voiceCache", {
206
212
  enumerable: true,
207
213
  configurable: true,
208
214
  writable: true,
@@ -289,13 +295,11 @@ export class MidyGM1 {
289
295
  const presetHeaders = soundFont.parsed.presetHeaders;
290
296
  for (let i = 0; i < presetHeaders.length; i++) {
291
297
  const presetHeader = presetHeaders[i];
292
- if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
293
- const banks = this.soundFontTable[presetHeader.preset];
294
- banks.set(presetHeader.bank, index);
295
- }
298
+ const banks = this.soundFontTable[presetHeader.preset];
299
+ banks.set(presetHeader.bank, index);
296
300
  }
297
301
  }
298
- async loadSoundFont(input) {
302
+ async toUint8Array(input) {
299
303
  let uint8Array;
300
304
  if (typeof input === "string") {
301
305
  const response = await fetch(input);
@@ -308,23 +312,32 @@ export class MidyGM1 {
308
312
  else {
309
313
  throw new TypeError("input must be a URL string or Uint8Array");
310
314
  }
311
- const parsed = parse(uint8Array);
312
- const soundFont = new SoundFont(parsed);
313
- this.addSoundFont(soundFont);
315
+ return uint8Array;
314
316
  }
315
- async loadMIDI(input) {
316
- let uint8Array;
317
- if (typeof input === "string") {
318
- const response = await fetch(input);
319
- const arrayBuffer = await response.arrayBuffer();
320
- uint8Array = new Uint8Array(arrayBuffer);
321
- }
322
- else if (input instanceof Uint8Array) {
323
- uint8Array = input;
317
+ async loadSoundFont(input) {
318
+ this.voiceCounter.clear();
319
+ if (Array.isArray(input)) {
320
+ const promises = new Array(input.length);
321
+ for (let i = 0; i < input.length; i++) {
322
+ promises[i] = this.toUint8Array(input[i]);
323
+ }
324
+ const uint8Arrays = await Promise.all(promises);
325
+ for (let i = 0; i < uint8Arrays.length; i++) {
326
+ const parsed = parse(uint8Arrays[i]);
327
+ const soundFont = new SoundFont(parsed);
328
+ this.addSoundFont(soundFont);
329
+ }
324
330
  }
325
331
  else {
326
- throw new TypeError("input must be a URL string or Uint8Array");
332
+ const uint8Array = await this.toUint8Array(input);
333
+ const parsed = parse(uint8Array);
334
+ const soundFont = new SoundFont(parsed);
335
+ this.addSoundFont(soundFont);
327
336
  }
337
+ }
338
+ async loadMIDI(input) {
339
+ this.voiceCounter.clear();
340
+ const uint8Array = await this.toUint8Array(input);
328
341
  const midi = parseMidi(uint8Array);
329
342
  this.ticksPerBeat = midi.header.ticksPerBeat;
330
343
  const midiData = this.extractMidiData(midi);
@@ -332,7 +345,46 @@ export class MidyGM1 {
332
345
  this.timeline = midiData.timeline;
333
346
  this.totalTime = this.calcTotalTime();
334
347
  }
335
- setChannelAudioNodes(audioContext) {
348
+ cacheVoiceIds() {
349
+ const timeline = this.timeline;
350
+ for (let i = 0; i < timeline.length; i++) {
351
+ const event = timeline[i];
352
+ switch (event.type) {
353
+ case "noteOn": {
354
+ const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
355
+ this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
356
+ break;
357
+ }
358
+ case "controller":
359
+ if (event.controllerType === 0) {
360
+ this.setBankMSB(event.channel, event.value);
361
+ }
362
+ else if (event.controllerType === 32) {
363
+ this.setBankLSB(event.channel, event.value);
364
+ }
365
+ break;
366
+ case "programChange":
367
+ this.setProgramChange(event.channel, event.programNumber, event.startTime);
368
+ }
369
+ }
370
+ for (const [audioBufferId, count] of this.voiceCounter) {
371
+ if (count === 1)
372
+ this.voiceCounter.delete(audioBufferId);
373
+ }
374
+ this.GM1SystemOn();
375
+ }
376
+ getVoiceId(channel, noteNumber, velocity) {
377
+ const bankNumber = this.calcBank(channel);
378
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
379
+ .get(bankNumber);
380
+ if (soundFontIndex === undefined)
381
+ return;
382
+ const soundFont = this.soundFonts[soundFontIndex];
383
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
384
+ const { instrument, sampleID } = voice.generators;
385
+ return `${soundFontIndex}:${instrument}:${sampleID}`;
386
+ }
387
+ createChannelAudioNodes(audioContext) {
336
388
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
337
389
  const gainL = new GainNode(audioContext, { gain: gainLeft });
338
390
  const gainR = new GainNode(audioContext, { gain: gainRight });
@@ -353,41 +405,19 @@ export class MidyGM1 {
353
405
  isDrum: false,
354
406
  state: new ControllerState(),
355
407
  ...this.constructor.channelSettings,
356
- ...this.setChannelAudioNodes(audioContext),
408
+ ...this.createChannelAudioNodes(audioContext),
357
409
  scheduledNotes: [],
358
410
  sustainNotes: [],
359
411
  };
360
412
  });
361
413
  return channels;
362
414
  }
363
- async createNoteBuffer(voiceParams, isSF3) {
415
+ async createAudioBuffer(voiceParams) {
416
+ const sample = voiceParams.sample;
364
417
  const sampleStart = voiceParams.start;
365
- const sampleEnd = voiceParams.sample.length + voiceParams.end;
366
- if (isSF3) {
367
- const sample = voiceParams.sample;
368
- const start = sample.byteOffset + sampleStart;
369
- const end = sample.byteOffset + sampleEnd;
370
- const buffer = sample.buffer.slice(start, end);
371
- const audioBuffer = await this.audioContext.decodeAudioData(buffer);
372
- return audioBuffer;
373
- }
374
- else {
375
- const sample = voiceParams.sample;
376
- const start = sample.byteOffset + sampleStart;
377
- const end = sample.byteOffset + sampleEnd;
378
- const buffer = sample.buffer.slice(start, end);
379
- const audioBuffer = new AudioBuffer({
380
- numberOfChannels: 1,
381
- length: sample.length,
382
- sampleRate: voiceParams.sampleRate,
383
- });
384
- const channelData = audioBuffer.getChannelData(0);
385
- const int16Array = new Int16Array(buffer);
386
- for (let i = 0; i < int16Array.length; i++) {
387
- channelData[i] = int16Array[i] / 32768;
388
- }
389
- return audioBuffer;
390
- }
418
+ const sampleEnd = sample.data.length + voiceParams.end;
419
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
420
+ return audioBuffer;
391
421
  }
392
422
  createBufferSource(voiceParams, audioBuffer) {
393
423
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
@@ -417,10 +447,10 @@ export class MidyGM1 {
417
447
  break;
418
448
  }
419
449
  case "controller":
420
- this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
450
+ this.setControlChange(event.channel, event.controllerType, event.value, startTime);
421
451
  break;
422
452
  case "programChange":
423
- this.handleProgramChange(event.channel, event.programNumber, startTime);
453
+ this.setProgramChange(event.channel, event.programNumber, startTime);
424
454
  break;
425
455
  case "pitchBend":
426
456
  this.setPitchBend(event.channel, event.value + 8192, startTime);
@@ -453,8 +483,9 @@ export class MidyGM1 {
453
483
  await Promise.all(this.notePromises);
454
484
  this.notePromises = [];
455
485
  this.exclusiveClassNotes.fill(undefined);
456
- this.audioBufferCache.clear();
486
+ this.voiceCache.clear();
457
487
  for (let i = 0; i < this.channels.length; i++) {
488
+ this.channels[i].scheduledNotes = [];
458
489
  this.resetAllStates(i);
459
490
  }
460
491
  resolve();
@@ -475,8 +506,9 @@ export class MidyGM1 {
475
506
  await this.stopNotes(0, true, now);
476
507
  this.notePromises = [];
477
508
  this.exclusiveClassNotes.fill(undefined);
478
- this.audioBufferCache.clear();
509
+ this.voiceCache.clear();
479
510
  for (let i = 0; i < this.channels.length; i++) {
511
+ this.channels[i].scheduledNotes = [];
480
512
  this.resetAllStates(i);
481
513
  }
482
514
  this.isStopping = false;
@@ -508,11 +540,7 @@ export class MidyGM1 {
508
540
  secondToTicks(second, secondsPerBeat) {
509
541
  return second * this.ticksPerBeat / secondsPerBeat;
510
542
  }
511
- getAudioBufferId(programNumber, noteNumber, velocity) {
512
- return `${programNumber}:${noteNumber}:${velocity}`;
513
- }
514
543
  extractMidiData(midi) {
515
- this.audioBufferCounter.clear();
516
544
  const instruments = new Set();
517
545
  const timeline = [];
518
546
  const tmpChannels = new Array(this.channels.length);
@@ -532,8 +560,6 @@ export class MidyGM1 {
532
560
  switch (event.type) {
533
561
  case "noteOn": {
534
562
  const channel = tmpChannels[event.channel];
535
- const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
536
- this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
537
563
  if (channel.programNumber < 0) {
538
564
  instruments.add(`${channel.bank}:0`);
539
565
  channel.programNumber = 0;
@@ -550,10 +576,6 @@ export class MidyGM1 {
550
576
  timeline.push(event);
551
577
  }
552
578
  }
553
- for (const [audioBufferId, count] of this.audioBufferCounter) {
554
- if (count === 1)
555
- this.audioBufferCounter.delete(audioBufferId);
556
- }
557
579
  const priority = {
558
580
  controller: 0,
559
581
  sysEx: 1,
@@ -596,7 +618,6 @@ export class MidyGM1 {
596
618
  this.notePromises.push(promise);
597
619
  promises.push(promise);
598
620
  });
599
- channel.scheduledNotes = [];
600
621
  return Promise.all(promises);
601
622
  }
602
623
  stopNotes(velocity, force, scheduleTime) {
@@ -610,6 +631,8 @@ export class MidyGM1 {
610
631
  if (this.isPlaying || this.isPaused)
611
632
  return;
612
633
  this.resumeTime = 0;
634
+ if (this.voiceCounter.size === 0)
635
+ this.cacheVoiceIds();
613
636
  await this.playNotes();
614
637
  this.isPlaying = false;
615
638
  }
@@ -652,7 +675,7 @@ export class MidyGM1 {
652
675
  }
653
676
  processScheduledNotes(channel, callback) {
654
677
  const scheduledNotes = channel.scheduledNotes;
655
- for (let i = 0; i < scheduledNotes.length; i++) {
678
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
656
679
  const note = scheduledNotes[i];
657
680
  if (!note)
658
681
  continue;
@@ -663,14 +686,14 @@ export class MidyGM1 {
663
686
  }
664
687
  processActiveNotes(channel, scheduleTime, callback) {
665
688
  const scheduledNotes = channel.scheduledNotes;
666
- for (let i = 0; i < scheduledNotes.length; i++) {
689
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
667
690
  const note = scheduledNotes[i];
668
691
  if (!note)
669
692
  continue;
670
693
  if (note.ending)
671
694
  continue;
672
695
  if (scheduleTime < note.startTime)
673
- continue;
696
+ break;
674
697
  callback(note);
675
698
  }
676
699
  }
@@ -787,31 +810,31 @@ export class MidyGM1 {
787
810
  note.modulationLFO.connect(note.volumeDepth);
788
811
  note.volumeDepth.connect(note.volumeEnvelopeNode.gain);
789
812
  }
790
- async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
791
- const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
792
- const cache = this.audioBufferCache.get(audioBufferId);
813
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
814
+ const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
815
+ const cache = this.voiceCache.get(audioBufferId);
793
816
  if (cache) {
794
817
  cache.counter += 1;
795
818
  if (cache.maxCount <= cache.counter) {
796
- this.audioBufferCache.delete(audioBufferId);
819
+ this.voiceCache.delete(audioBufferId);
797
820
  }
798
821
  return cache.audioBuffer;
799
822
  }
800
823
  else {
801
- const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
802
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
824
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
825
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
803
826
  const cache = { audioBuffer, maxCount, counter: 1 };
804
- this.audioBufferCache.set(audioBufferId, cache);
827
+ this.voiceCache.set(audioBufferId, cache);
805
828
  return audioBuffer;
806
829
  }
807
830
  }
808
- async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
831
+ async createNote(channel, voice, noteNumber, velocity, startTime) {
809
832
  const now = this.audioContext.currentTime;
810
833
  const state = channel.state;
811
834
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
812
835
  const voiceParams = voice.getAllParams(controllerState);
813
836
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
814
- const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
837
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
815
838
  note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
816
839
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
817
840
  note.filterNode = new BiquadFilterNode(this.audioContext, {
@@ -844,19 +867,18 @@ export class MidyGM1 {
844
867
  }
845
868
  this.exclusiveClassNotes[exclusiveClass] = [note, channelNumber];
846
869
  }
847
- async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
870
+ async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
848
871
  const channel = this.channels[channelNumber];
849
872
  const bankNumber = channel.bank;
850
- const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
873
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
874
+ .get(bankNumber);
851
875
  if (soundFontIndex === undefined)
852
876
  return;
853
877
  const soundFont = this.soundFonts[soundFontIndex];
854
878
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
855
879
  if (!voice)
856
880
  return;
857
- const isSF3 = soundFont.parsed.info.version.major === 3;
858
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
859
- note.noteOffEvent = noteOffEvent;
881
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
860
882
  note.volumeEnvelopeNode.connect(channel.gainL);
861
883
  note.volumeEnvelopeNode.connect(channel.gainR);
862
884
  if (0.5 <= channel.state.sustainPedal) {
@@ -906,15 +928,29 @@ export class MidyGM1 {
906
928
  const channel = this.channels[channelNumber];
907
929
  if (!force && 0.5 <= channel.state.sustainPedal)
908
930
  return;
909
- const note = this.findNoteOffTarget(channel, noteNumber);
910
- if (!note)
931
+ const index = this.findNoteOffIndex(channel, noteNumber);
932
+ if (index < 0)
911
933
  return;
934
+ const note = channel.scheduledNotes[index];
912
935
  note.ending = true;
936
+ this.setNoteIndex(channel, index);
913
937
  this.releaseNote(channel, note, endTime);
914
938
  }
915
- findNoteOffTarget(channel, noteNumber) {
939
+ setNoteIndex(channel, index) {
940
+ let allEnds = true;
941
+ for (let i = channel.scheduleIndex; i < index; i++) {
942
+ const note = channel.scheduledNotes[i];
943
+ if (note && !note.ending) {
944
+ allEnds = false;
945
+ break;
946
+ }
947
+ }
948
+ if (allEnds)
949
+ channel.scheduleIndex = index + 1;
950
+ }
951
+ findNoteOffIndex(channel, noteNumber) {
916
952
  const scheduledNotes = channel.scheduledNotes;
917
- for (let i = 0; i < scheduledNotes.length; i++) {
953
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
918
954
  const note = scheduledNotes[i];
919
955
  if (!note)
920
956
  continue;
@@ -922,8 +958,9 @@ export class MidyGM1 {
922
958
  continue;
923
959
  if (note.noteNumber !== noteNumber)
924
960
  continue;
925
- return note;
961
+ return i;
926
962
  }
963
+ return -1;
927
964
  }
928
965
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
929
966
  scheduleTime ??= this.audioContext.currentTime;
@@ -949,16 +986,16 @@ export class MidyGM1 {
949
986
  case 0x90:
950
987
  return this.noteOn(channelNumber, data1, data2, scheduleTime);
951
988
  case 0xB0:
952
- return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
989
+ return this.setControlChange(channelNumber, data1, data2, scheduleTime);
953
990
  case 0xC0:
954
- return this.handleProgramChange(channelNumber, data1, scheduleTime);
991
+ return this.setProgramChange(channelNumber, data1, scheduleTime);
955
992
  case 0xE0:
956
993
  return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
957
994
  default:
958
995
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
959
996
  }
960
997
  }
961
- handleProgramChange(channelNumber, programNumber, _scheduleTime) {
998
+ setProgramChange(channelNumber, programNumber, _scheduleTime) {
962
999
  const channel = this.channels[channelNumber];
963
1000
  channel.programNumber = programNumber;
964
1001
  }
@@ -1051,8 +1088,9 @@ export class MidyGM1 {
1051
1088
  this.processScheduledNotes(channel, (note) => {
1052
1089
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1053
1090
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1054
- let appliedFilterEnvelope = false;
1055
- let appliedVolumeEnvelope = false;
1091
+ let applyVolumeEnvelope = false;
1092
+ let applyFilterEnvelope = false;
1093
+ let applyPitchEnvelope = false;
1056
1094
  for (const [key, value] of Object.entries(voiceParams)) {
1057
1095
  const prevValue = note.voiceParams[key];
1058
1096
  if (value === prevValue)
@@ -1061,32 +1099,21 @@ export class MidyGM1 {
1061
1099
  if (key in this.voiceParamsHandlers) {
1062
1100
  this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1063
1101
  }
1064
- else if (filterEnvelopeKeySet.has(key)) {
1065
- if (appliedFilterEnvelope)
1066
- continue;
1067
- appliedFilterEnvelope = true;
1068
- const noteVoiceParams = note.voiceParams;
1069
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1070
- const key = filterEnvelopeKeys[i];
1071
- if (key in voiceParams)
1072
- noteVoiceParams[key] = voiceParams[key];
1073
- }
1074
- this.setFilterEnvelope(note, scheduleTime);
1075
- this.setPitchEnvelope(note, scheduleTime);
1076
- }
1077
- else if (volumeEnvelopeKeySet.has(key)) {
1078
- if (appliedVolumeEnvelope)
1079
- continue;
1080
- appliedVolumeEnvelope = true;
1081
- const noteVoiceParams = note.voiceParams;
1082
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1083
- const key = volumeEnvelopeKeys[i];
1084
- if (key in voiceParams)
1085
- noteVoiceParams[key] = voiceParams[key];
1086
- }
1087
- this.setVolumeEnvelope(note, scheduleTime);
1102
+ else {
1103
+ if (volumeEnvelopeKeySet.has(key))
1104
+ applyVolumeEnvelope = true;
1105
+ if (filterEnvelopeKeySet.has(key))
1106
+ applyFilterEnvelope = true;
1107
+ if (pitchEnvelopeKeySet.has(key))
1108
+ applyPitchEnvelope = true;
1088
1109
  }
1089
1110
  }
1111
+ if (applyVolumeEnvelope)
1112
+ this.setVolumeEnvelope(note, scheduleTime);
1113
+ if (applyFilterEnvelope)
1114
+ this.setFilterEnvelope(note, scheduleTime);
1115
+ if (applyPitchEnvelope)
1116
+ this.setPitchEnvelope(note, scheduleTime);
1090
1117
  });
1091
1118
  }
1092
1119
  createControlChangeHandlers() {
@@ -1102,9 +1129,10 @@ export class MidyGM1 {
1102
1129
  handlers[101] = this.setRPNMSB;
1103
1130
  handlers[120] = this.allSoundOff;
1104
1131
  handlers[121] = this.resetAllControllers;
1132
+ handlers[123] = this.allNotesOff;
1105
1133
  return handlers;
1106
1134
  }
1107
- handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1135
+ setControlChange(channelNumber, controllerType, value, scheduleTime) {
1108
1136
  const handler = this.controlChangeHandlers[controllerType];
1109
1137
  if (handler) {
1110
1138
  handler.call(this, channelNumber, value, scheduleTime);
@@ -1297,7 +1325,7 @@ export class MidyGM1 {
1297
1325
  const entries = Object.entries(defaultControllerState);
1298
1326
  for (const [key, { type, defaultValue }] of entries) {
1299
1327
  if (128 <= type) {
1300
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1328
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1301
1329
  }
1302
1330
  else {
1303
1331
  state[key] = defaultValue;
@@ -1322,7 +1350,7 @@ export class MidyGM1 {
1322
1350
  const key = keys[i];
1323
1351
  const { type, defaultValue } = defaultControllerState[key];
1324
1352
  if (128 <= type) {
1325
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1353
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
1326
1354
  }
1327
1355
  else {
1328
1356
  state[key] = defaultValue;