@marmooo/midy 0.3.4 → 0.3.6

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.
@@ -71,13 +71,13 @@ class Note {
71
71
  writable: true,
72
72
  value: void 0
73
73
  });
74
- Object.defineProperty(this, "reverbEffectsSend", {
74
+ Object.defineProperty(this, "reverbSend", {
75
75
  enumerable: true,
76
76
  configurable: true,
77
77
  writable: true,
78
78
  value: void 0
79
79
  });
80
- Object.defineProperty(this, "chorusEffectsSend", {
80
+ Object.defineProperty(this, "chorusSend", {
81
81
  enumerable: true,
82
82
  configurable: true,
83
83
  writable: true,
@@ -317,13 +317,13 @@ class MidyGM2 {
317
317
  writable: true,
318
318
  value: this.initSoundFontTable()
319
319
  });
320
- Object.defineProperty(this, "audioBufferCounter", {
320
+ Object.defineProperty(this, "voiceCounter", {
321
321
  enumerable: true,
322
322
  configurable: true,
323
323
  writable: true,
324
324
  value: new Map()
325
325
  });
326
- Object.defineProperty(this, "audioBufferCache", {
326
+ Object.defineProperty(this, "voiceCache", {
327
327
  enumerable: true,
328
328
  configurable: true,
329
329
  writable: true,
@@ -365,17 +365,17 @@ class MidyGM2 {
365
365
  writable: true,
366
366
  value: []
367
367
  });
368
- Object.defineProperty(this, "instruments", {
368
+ Object.defineProperty(this, "notePromises", {
369
369
  enumerable: true,
370
370
  configurable: true,
371
371
  writable: true,
372
372
  value: []
373
373
  });
374
- Object.defineProperty(this, "notePromises", {
374
+ Object.defineProperty(this, "instruments", {
375
375
  enumerable: true,
376
376
  configurable: true,
377
377
  writable: true,
378
- value: []
378
+ value: new Set()
379
379
  });
380
380
  Object.defineProperty(this, "exclusiveClassNotes", {
381
381
  enumerable: true,
@@ -420,13 +420,11 @@ class MidyGM2 {
420
420
  const presetHeaders = soundFont.parsed.presetHeaders;
421
421
  for (let i = 0; i < presetHeaders.length; i++) {
422
422
  const presetHeader = presetHeaders[i];
423
- if (!presetHeader.presetName.startsWith("\u0000")) { // TODO: Only SF3 generated by PolyPone?
424
- const banks = this.soundFontTable[presetHeader.preset];
425
- banks.set(presetHeader.bank, index);
426
- }
423
+ const banks = this.soundFontTable[presetHeader.preset];
424
+ banks.set(presetHeader.bank, index);
427
425
  }
428
426
  }
429
- async loadSoundFont(input) {
427
+ async toUint8Array(input) {
430
428
  let uint8Array;
431
429
  if (typeof input === "string") {
432
430
  const response = await fetch(input);
@@ -439,23 +437,32 @@ class MidyGM2 {
439
437
  else {
440
438
  throw new TypeError("input must be a URL string or Uint8Array");
441
439
  }
442
- const parsed = (0, soundfont_parser_1.parse)(uint8Array);
443
- const soundFont = new soundfont_parser_1.SoundFont(parsed);
444
- this.addSoundFont(soundFont);
440
+ return uint8Array;
445
441
  }
446
- async loadMIDI(input) {
447
- let uint8Array;
448
- if (typeof input === "string") {
449
- const response = await fetch(input);
450
- const arrayBuffer = await response.arrayBuffer();
451
- uint8Array = new Uint8Array(arrayBuffer);
452
- }
453
- else if (input instanceof Uint8Array) {
454
- uint8Array = input;
442
+ async loadSoundFont(input) {
443
+ this.voiceCounter.clear();
444
+ if (Array.isArray(input)) {
445
+ const promises = new Array(input.length);
446
+ for (let i = 0; i < input.length; i++) {
447
+ promises[i] = this.toUint8Array(input[i]);
448
+ }
449
+ const uint8Arrays = await Promise.all(promises);
450
+ for (let i = 0; i < uint8Arrays.length; i++) {
451
+ const parsed = (0, soundfont_parser_1.parse)(uint8Arrays[i]);
452
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
453
+ this.addSoundFont(soundFont);
454
+ }
455
455
  }
456
456
  else {
457
- throw new TypeError("input must be a URL string or Uint8Array");
457
+ const uint8Array = await this.toUint8Array(input);
458
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
459
+ const soundFont = new soundfont_parser_1.SoundFont(parsed);
460
+ this.addSoundFont(soundFont);
458
461
  }
462
+ }
463
+ async loadMIDI(input) {
464
+ this.voiceCounter.clear();
465
+ const uint8Array = await this.toUint8Array(input);
459
466
  const midi = (0, midi_file_1.parseMidi)(uint8Array);
460
467
  this.ticksPerBeat = midi.header.ticksPerBeat;
461
468
  const midiData = this.extractMidiData(midi);
@@ -463,6 +470,45 @@ class MidyGM2 {
463
470
  this.timeline = midiData.timeline;
464
471
  this.totalTime = this.calcTotalTime();
465
472
  }
473
+ cacheVoiceIds() {
474
+ const timeline = this.timeline;
475
+ for (let i = 0; i < timeline.length; i++) {
476
+ const event = timeline[i];
477
+ switch (event.type) {
478
+ case "noteOn": {
479
+ const audioBufferId = this.getVoiceId(this.channels[event.channel], event.noteNumber, event.velocity);
480
+ this.voiceCounter.set(audioBufferId, (this.voiceCounter.get(audioBufferId) ?? 0) + 1);
481
+ break;
482
+ }
483
+ case "controller":
484
+ if (event.controllerType === 0) {
485
+ this.setBankMSB(event.channel, event.value);
486
+ }
487
+ else if (event.controllerType === 32) {
488
+ this.setBankLSB(event.channel, event.value);
489
+ }
490
+ break;
491
+ case "programChange":
492
+ this.setProgramChange(event.channel, event.programNumber, event.startTime);
493
+ }
494
+ }
495
+ for (const [audioBufferId, count] of this.voiceCounter) {
496
+ if (count === 1)
497
+ this.voiceCounter.delete(audioBufferId);
498
+ }
499
+ this.GM2SystemOn();
500
+ }
501
+ getVoiceId(channel, noteNumber, velocity) {
502
+ const bankNumber = this.calcBank(channel);
503
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
504
+ .get(bankNumber);
505
+ if (soundFontIndex === undefined)
506
+ return;
507
+ const soundFont = this.soundFonts[soundFontIndex];
508
+ const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
509
+ const { instrument, sampleID } = voice.generators;
510
+ return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
511
+ }
466
512
  createChannelAudioNodes(audioContext) {
467
513
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
468
514
  const gainL = new GainNode(audioContext, { gain: gainLeft });
@@ -504,34 +550,12 @@ class MidyGM2 {
504
550
  });
505
551
  return channels;
506
552
  }
507
- async createNoteBuffer(voiceParams, isSF3) {
553
+ async createAudioBuffer(voiceParams) {
554
+ const sample = voiceParams.sample;
508
555
  const sampleStart = voiceParams.start;
509
- const sampleEnd = voiceParams.sample.length + voiceParams.end;
510
- if (isSF3) {
511
- const sample = voiceParams.sample;
512
- const start = sample.byteOffset + sampleStart;
513
- const end = sample.byteOffset + sampleEnd;
514
- const buffer = sample.buffer.slice(start, end);
515
- const audioBuffer = await this.audioContext.decodeAudioData(buffer);
516
- return audioBuffer;
517
- }
518
- else {
519
- const sample = voiceParams.sample;
520
- const start = sample.byteOffset + sampleStart;
521
- const end = sample.byteOffset + sampleEnd;
522
- const buffer = sample.buffer.slice(start, end);
523
- const audioBuffer = new AudioBuffer({
524
- numberOfChannels: 1,
525
- length: sample.length,
526
- sampleRate: voiceParams.sampleRate,
527
- });
528
- const channelData = audioBuffer.getChannelData(0);
529
- const int16Array = new Int16Array(buffer);
530
- for (let i = 0; i < int16Array.length; i++) {
531
- channelData[i] = int16Array[i] / 32768;
532
- }
533
- return audioBuffer;
534
- }
556
+ const sampleEnd = sample.data.length + voiceParams.end;
557
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
558
+ return audioBuffer;
535
559
  }
536
560
  isLoopDrum(channel, noteNumber) {
537
561
  const programNumber = channel.programNumber;
@@ -568,13 +592,13 @@ class MidyGM2 {
568
592
  break;
569
593
  }
570
594
  case "controller":
571
- this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
595
+ this.setControlChange(event.channel, event.controllerType, event.value, startTime);
572
596
  break;
573
597
  case "programChange":
574
- this.handleProgramChange(event.channel, event.programNumber, startTime);
598
+ this.setProgramChange(event.channel, event.programNumber, startTime);
575
599
  break;
576
600
  case "channelAftertouch":
577
- this.handleChannelPressure(event.channel, event.amount, startTime);
601
+ this.setChannelPressure(event.channel, event.amount, startTime);
578
602
  break;
579
603
  case "pitchBend":
580
604
  this.setPitchBend(event.channel, event.value + 8192, startTime);
@@ -608,8 +632,9 @@ class MidyGM2 {
608
632
  this.notePromises = [];
609
633
  this.exclusiveClassNotes.fill(undefined);
610
634
  this.drumExclusiveClassNotes.fill(undefined);
611
- this.audioBufferCache.clear();
635
+ this.voiceCache.clear();
612
636
  for (let i = 0; i < this.channels.length; i++) {
637
+ this.channels[i].scheduledNotes = [];
613
638
  this.resetAllStates(i);
614
639
  }
615
640
  resolve();
@@ -631,8 +656,9 @@ class MidyGM2 {
631
656
  this.notePromises = [];
632
657
  this.exclusiveClassNotes.fill(undefined);
633
658
  this.drumExclusiveClassNotes.fill(undefined);
634
- this.audioBufferCache.clear();
659
+ this.voiceCache.clear();
635
660
  for (let i = 0; i < this.channels.length; i++) {
661
+ this.channels[i].scheduledNotes = [];
636
662
  this.resetAllStates(i);
637
663
  }
638
664
  this.isStopping = false;
@@ -665,11 +691,7 @@ class MidyGM2 {
665
691
  secondToTicks(second, secondsPerBeat) {
666
692
  return second * this.ticksPerBeat / secondsPerBeat;
667
693
  }
668
- getAudioBufferId(programNumber, noteNumber, velocity) {
669
- return `${programNumber}:${noteNumber}:${velocity}`;
670
- }
671
694
  extractMidiData(midi) {
672
- this.audioBufferCounter.clear();
673
695
  const instruments = new Set();
674
696
  const timeline = [];
675
697
  const tmpChannels = new Array(this.channels.length);
@@ -690,8 +712,6 @@ class MidyGM2 {
690
712
  switch (event.type) {
691
713
  case "noteOn": {
692
714
  const channel = tmpChannels[event.channel];
693
- const audioBufferId = this.getAudioBufferId(channel.programNumber, event.noteNumber, event.velocity);
694
- this.audioBufferCounter.set(audioBufferId, (this.audioBufferCounter.get(audioBufferId) ?? 0) + 1);
695
715
  if (channel.programNumber < 0) {
696
716
  channel.programNumber = event.programNumber;
697
717
  switch (channel.bankMSB) {
@@ -741,10 +761,6 @@ class MidyGM2 {
741
761
  timeline.push(event);
742
762
  }
743
763
  }
744
- for (const [audioBufferId, count] of this.audioBufferCounter) {
745
- if (count === 1)
746
- this.audioBufferCounter.delete(audioBufferId);
747
- }
748
764
  const priority = {
749
765
  controller: 0,
750
766
  sysEx: 1,
@@ -784,12 +800,11 @@ class MidyGM2 {
784
800
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
785
801
  const channel = this.channels[channelNumber];
786
802
  const promises = [];
787
- this.processScheduledNotes(channel, scheduleTime, (note) => {
803
+ this.processScheduledNotes(channel, (note) => {
788
804
  const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
789
805
  this.notePromises.push(promise);
790
806
  promises.push(promise);
791
807
  });
792
- channel.scheduledNotes = [];
793
808
  return Promise.all(promises);
794
809
  }
795
810
  stopNotes(velocity, force, scheduleTime) {
@@ -803,6 +818,8 @@ class MidyGM2 {
803
818
  if (this.isPlaying || this.isPaused)
804
819
  return;
805
820
  this.resumeTime = 0;
821
+ if (this.voiceCounter.size === 0)
822
+ this.cacheVoiceIds();
806
823
  await this.playNotes();
807
824
  this.isPlaying = false;
808
825
  }
@@ -843,22 +860,20 @@ class MidyGM2 {
843
860
  const now = this.audioContext.currentTime;
844
861
  return this.resumeTime + now - this.startTime - this.startDelay;
845
862
  }
846
- processScheduledNotes(channel, scheduleTime, callback) {
863
+ processScheduledNotes(channel, callback) {
847
864
  const scheduledNotes = channel.scheduledNotes;
848
- for (let i = 0; i < scheduledNotes.length; i++) {
865
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
849
866
  const note = scheduledNotes[i];
850
867
  if (!note)
851
868
  continue;
852
869
  if (note.ending)
853
870
  continue;
854
- if (note.startTime < scheduleTime)
855
- continue;
856
871
  callback(note);
857
872
  }
858
873
  }
859
874
  processActiveNotes(channel, scheduleTime, callback) {
860
875
  const scheduledNotes = channel.scheduledNotes;
861
- for (let i = 0; i < scheduledNotes.length; i++) {
876
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
862
877
  const note = scheduledNotes[i];
863
878
  if (!note)
864
879
  continue;
@@ -892,13 +907,11 @@ class MidyGM2 {
892
907
  return impulse;
893
908
  }
894
909
  createConvolutionReverb(audioContext, impulse) {
895
- const input = new GainNode(audioContext);
896
910
  const convolverNode = new ConvolverNode(audioContext, {
897
911
  buffer: impulse,
898
912
  });
899
- input.connect(convolverNode);
900
913
  return {
901
- input,
914
+ input: convolverNode,
902
915
  output: convolverNode,
903
916
  convolverNode,
904
917
  };
@@ -1049,7 +1062,7 @@ class MidyGM2 {
1049
1062
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1050
1063
  }
1051
1064
  updateChannelDetune(channel, scheduleTime) {
1052
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1065
+ this.processScheduledNotes(channel, (note) => {
1053
1066
  this.updateDetune(channel, note, scheduleTime);
1054
1067
  });
1055
1068
  }
@@ -1270,31 +1283,31 @@ class MidyGM2 {
1270
1283
  note.vibratoLFO.connect(note.vibratoDepth);
1271
1284
  note.vibratoDepth.connect(note.bufferSource.detune);
1272
1285
  }
1273
- async getAudioBuffer(programNumber, noteNumber, velocity, voiceParams, isSF3) {
1274
- const audioBufferId = this.getAudioBufferId(programNumber, noteNumber, velocity);
1275
- const cache = this.audioBufferCache.get(audioBufferId);
1286
+ async getAudioBuffer(channel, noteNumber, velocity, voiceParams) {
1287
+ const audioBufferId = this.getVoiceId(channel, noteNumber, velocity);
1288
+ const cache = this.voiceCache.get(audioBufferId);
1276
1289
  if (cache) {
1277
1290
  cache.counter += 1;
1278
1291
  if (cache.maxCount <= cache.counter) {
1279
- this.audioBufferCache.delete(audioBufferId);
1292
+ this.voiceCache.delete(audioBufferId);
1280
1293
  }
1281
1294
  return cache.audioBuffer;
1282
1295
  }
1283
1296
  else {
1284
- const maxCount = this.audioBufferCounter.get(audioBufferId) ?? 0;
1285
- const audioBuffer = await this.createNoteBuffer(voiceParams, isSF3);
1297
+ const maxCount = this.voiceCounter.get(audioBufferId) ?? 0;
1298
+ const audioBuffer = await this.createAudioBuffer(voiceParams);
1286
1299
  const cache = { audioBuffer, maxCount, counter: 1 };
1287
- this.audioBufferCache.set(audioBufferId, cache);
1300
+ this.voiceCache.set(audioBufferId, cache);
1288
1301
  return audioBuffer;
1289
1302
  }
1290
1303
  }
1291
- async createNote(channel, voice, noteNumber, velocity, startTime, isSF3) {
1304
+ async createNote(channel, voice, noteNumber, velocity, startTime) {
1292
1305
  const now = this.audioContext.currentTime;
1293
1306
  const state = channel.state;
1294
1307
  const controllerState = this.getControllerState(channel, noteNumber, velocity);
1295
1308
  const voiceParams = voice.getAllParams(controllerState);
1296
1309
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1297
- const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1310
+ const audioBuffer = await this.getAudioBuffer(channel, noteNumber, velocity, voiceParams);
1298
1311
  note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1299
1312
  note.volumeEnvelopeNode = new GainNode(this.audioContext);
1300
1313
  note.filterNode = new BiquadFilterNode(this.audioContext, {
@@ -1328,12 +1341,8 @@ class MidyGM2 {
1328
1341
  }
1329
1342
  note.bufferSource.connect(note.filterNode);
1330
1343
  note.filterNode.connect(note.volumeEnvelopeNode);
1331
- if (0 < state.chorusSendLevel) {
1332
- this.setChorusEffectsSend(channel, note, 0, now);
1333
- }
1334
- if (0 < state.reverbSendLevel) {
1335
- this.setReverbEffectsSend(channel, note, 0, now);
1336
- }
1344
+ this.setChorusSend(channel, note, now);
1345
+ this.setReverbSend(channel, note, now);
1337
1346
  note.bufferSource.start(startTime);
1338
1347
  return note;
1339
1348
  }
@@ -1389,20 +1398,24 @@ class MidyGM2 {
1389
1398
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1390
1399
  const channel = this.channels[channelNumber];
1391
1400
  const bankNumber = this.calcBank(channel, channelNumber);
1392
- const soundFontIndex = this.soundFontTable[channel.programNumber].get(bankNumber);
1401
+ const soundFontIndex = this.soundFontTable[channel.programNumber]
1402
+ .get(bankNumber);
1393
1403
  if (soundFontIndex === undefined)
1394
1404
  return;
1395
1405
  const soundFont = this.soundFonts[soundFontIndex];
1396
1406
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1397
1407
  if (!voice)
1398
1408
  return;
1399
- const isSF3 = soundFont.parsed.info.version.major === 3;
1400
- const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, isSF3);
1409
+ const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1401
1410
  if (channel.isDrum) {
1402
- const audioContext = this.audioContext;
1403
- const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1404
- channel.keyBasedGainLs[noteNumber] = gainL;
1405
- channel.keyBasedGainRs[noteNumber] = gainR;
1411
+ const { keyBasedGainLs, keyBasedGainRs } = channel;
1412
+ let gainL = keyBasedGainLs[noteNumber];
1413
+ let gainR = keyBasedGainRs[noteNumber];
1414
+ if (!gainL) {
1415
+ const audioNodes = this.createChannelAudioNodes(this.audioContext);
1416
+ gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
1417
+ gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
1418
+ }
1406
1419
  note.volumeEnvelopeNode.connect(gainL);
1407
1420
  note.volumeEnvelopeNode.connect(gainR);
1408
1421
  }
@@ -1436,11 +1449,11 @@ class MidyGM2 {
1436
1449
  note.vibratoDepth.disconnect();
1437
1450
  note.vibratoLFO.stop();
1438
1451
  }
1439
- if (note.reverbEffectsSend) {
1440
- note.reverbEffectsSend.disconnect();
1452
+ if (note.reverbSend) {
1453
+ note.reverbSend.disconnect();
1441
1454
  }
1442
- if (note.chorusEffectsSend) {
1443
- note.chorusEffectsSend.disconnect();
1455
+ if (note.chorusSend) {
1456
+ note.chorusSend.disconnect();
1444
1457
  }
1445
1458
  }
1446
1459
  releaseNote(channel, note, endTime) {
@@ -1479,15 +1492,29 @@ class MidyGM2 {
1479
1492
  return;
1480
1493
  }
1481
1494
  }
1482
- const note = this.findNoteOffTarget(channel, noteNumber);
1483
- if (!note)
1495
+ const index = this.findNoteOffIndex(channel, noteNumber);
1496
+ if (index < 0)
1484
1497
  return;
1498
+ const note = channel.scheduledNotes[index];
1485
1499
  note.ending = true;
1500
+ this.setNoteIndex(channel, index);
1486
1501
  this.releaseNote(channel, note, endTime);
1487
1502
  }
1488
- findNoteOffTarget(channel, noteNumber) {
1503
+ setNoteIndex(channel, index) {
1504
+ let allEnds = true;
1505
+ for (let i = channel.scheduleIndex; i < index; i++) {
1506
+ const note = channel.scheduledNotes[i];
1507
+ if (note && !note.ending) {
1508
+ allEnds = false;
1509
+ break;
1510
+ }
1511
+ }
1512
+ if (allEnds)
1513
+ channel.scheduleIndex = index + 1;
1514
+ }
1515
+ findNoteOffIndex(channel, noteNumber) {
1489
1516
  const scheduledNotes = channel.scheduledNotes;
1490
- for (let i = 0; i < scheduledNotes.length; i++) {
1517
+ for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
1491
1518
  const note = scheduledNotes[i];
1492
1519
  if (!note)
1493
1520
  continue;
@@ -1495,8 +1522,9 @@ class MidyGM2 {
1495
1522
  continue;
1496
1523
  if (note.noteNumber !== noteNumber)
1497
1524
  continue;
1498
- return note;
1525
+ return i;
1499
1526
  }
1527
+ return -1;
1500
1528
  }
1501
1529
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1502
1530
  scheduleTime ??= this.audioContext.currentTime;
@@ -1536,18 +1564,18 @@ class MidyGM2 {
1536
1564
  case 0x90:
1537
1565
  return this.noteOn(channelNumber, data1, data2, scheduleTime);
1538
1566
  case 0xB0:
1539
- return this.handleControlChange(channelNumber, data1, data2, scheduleTime);
1567
+ return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1540
1568
  case 0xC0:
1541
- return this.handleProgramChange(channelNumber, data1, scheduleTime);
1569
+ return this.setProgramChange(channelNumber, data1, scheduleTime);
1542
1570
  case 0xD0:
1543
- return this.handleChannelPressure(channelNumber, data1, scheduleTime);
1571
+ return this.setChannelPressure(channelNumber, data1, scheduleTime);
1544
1572
  case 0xE0:
1545
1573
  return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1546
1574
  default:
1547
1575
  console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1548
1576
  }
1549
1577
  }
1550
- handleProgramChange(channelNumber, programNumber, _scheduleTime) {
1578
+ setProgramChange(channelNumber, programNumber, _scheduleTime) {
1551
1579
  const channel = this.channels[channelNumber];
1552
1580
  channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1553
1581
  channel.programNumber = programNumber;
@@ -1562,7 +1590,7 @@ class MidyGM2 {
1562
1590
  }
1563
1591
  }
1564
1592
  }
1565
- handleChannelPressure(channelNumber, value, scheduleTime) {
1593
+ setChannelPressure(channelNumber, value, scheduleTime) {
1566
1594
  const channel = this.channels[channelNumber];
1567
1595
  if (channel.isDrum)
1568
1596
  return;
@@ -1576,7 +1604,7 @@ class MidyGM2 {
1576
1604
  }
1577
1605
  const table = channel.channelPressureTable;
1578
1606
  this.processActiveNotes(channel, scheduleTime, (note) => {
1579
- this.setControllerParameters(channel, note, table);
1607
+ this.setEffects(channel, note, table);
1580
1608
  });
1581
1609
  this.applyVoiceParams(channel, 13);
1582
1610
  }
@@ -1598,13 +1626,18 @@ class MidyGM2 {
1598
1626
  this.applyVoiceParams(channel, 14, scheduleTime);
1599
1627
  }
1600
1628
  setModLfoToPitch(channel, note, scheduleTime) {
1601
- const modLfoToPitch = note.voiceParams.modLfoToPitch +
1602
- this.getLFOPitchDepth(channel);
1603
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1604
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1605
- note.modulationDepth.gain
1606
- .cancelScheduledValues(scheduleTime)
1607
- .setValueAtTime(modulationDepth, scheduleTime);
1629
+ if (note.modulationDepth) {
1630
+ const modLfoToPitch = note.voiceParams.modLfoToPitch +
1631
+ this.getLFOPitchDepth(channel, note);
1632
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1633
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1634
+ note.modulationDepth.gain
1635
+ .cancelScheduledValues(scheduleTime)
1636
+ .setValueAtTime(modulationDepth, scheduleTime);
1637
+ }
1638
+ else {
1639
+ this.startModulation(channel, note, scheduleTime);
1640
+ }
1608
1641
  }
1609
1642
  setVibLfoToPitch(channel, note, scheduleTime) {
1610
1643
  const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
@@ -1631,63 +1664,63 @@ class MidyGM2 {
1631
1664
  .cancelScheduledValues(scheduleTime)
1632
1665
  .setValueAtTime(volumeDepth, scheduleTime);
1633
1666
  }
1634
- setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1635
- let value = note.voiceParams.reverbEffectsSend;
1667
+ setReverbSend(channel, note, scheduleTime) {
1668
+ let value = note.voiceParams.reverbEffectsSend *
1669
+ channel.state.reverbSendLevel;
1636
1670
  if (channel.isDrum) {
1637
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1638
- if (0 <= keyBasedValue) {
1639
- value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1640
- }
1671
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1672
+ if (0 <= keyBasedValue)
1673
+ value = keyBasedValue / 127;
1641
1674
  }
1642
- if (0 < prevValue) {
1675
+ if (!note.reverbSend) {
1643
1676
  if (0 < value) {
1644
- note.reverbEffectsSend.gain
1645
- .cancelScheduledValues(scheduleTime)
1646
- .setValueAtTime(value, scheduleTime);
1647
- }
1648
- else {
1649
- note.reverbEffectsSend.disconnect();
1677
+ note.reverbSend = new GainNode(this.audioContext, { gain: value });
1678
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1679
+ note.reverbSend.connect(this.reverbEffect.input);
1650
1680
  }
1651
1681
  }
1652
1682
  else {
1683
+ note.reverbSend.gain
1684
+ .cancelScheduledValues(scheduleTime)
1685
+ .setValueAtTime(value, scheduleTime);
1653
1686
  if (0 < value) {
1654
- if (!note.reverbEffectsSend) {
1655
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1656
- gain: value,
1657
- });
1658
- note.volumeNode.connect(note.reverbEffectsSend);
1687
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1688
+ }
1689
+ else {
1690
+ try {
1691
+ note.volumeEnvelopeNode.disconnect(note.reverbSend);
1659
1692
  }
1660
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1693
+ catch { /* empty */ }
1661
1694
  }
1662
1695
  }
1663
1696
  }
1664
- setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1665
- let value = note.voiceParams.chorusEffectsSend;
1697
+ setChorusSend(channel, note, scheduleTime) {
1698
+ let value = note.voiceParams.chorusEffectsSend *
1699
+ channel.state.chorusSendLevel;
1666
1700
  if (channel.isDrum) {
1667
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1668
- if (0 <= keyBasedValue) {
1669
- value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1670
- }
1701
+ const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1702
+ if (0 <= keyBasedValue)
1703
+ value = keyBasedValue / 127;
1671
1704
  }
1672
- if (0 < prevValue) {
1673
- if (0 < vaule) {
1674
- note.chorusEffectsSend.gain
1675
- .cancelScheduledValues(scheduleTime)
1676
- .setValueAtTime(value, scheduleTime);
1677
- }
1678
- else {
1679
- note.chorusEffectsSend.disconnect();
1705
+ if (!note.chorusSend) {
1706
+ if (0 < value) {
1707
+ note.chorusSend = new GainNode(this.audioContext, { gain: value });
1708
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1709
+ note.chorusSend.connect(this.chorusEffect.input);
1680
1710
  }
1681
1711
  }
1682
1712
  else {
1713
+ note.chorusSend.gain
1714
+ .cancelScheduledValues(scheduleTime)
1715
+ .setValueAtTime(value, scheduleTime);
1683
1716
  if (0 < value) {
1684
- if (!note.chorusEffectsSend) {
1685
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1686
- gain: value,
1687
- });
1688
- note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1717
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1718
+ }
1719
+ else {
1720
+ try {
1721
+ note.volumeEnvelopeNode.disconnect(note.chorusSend);
1689
1722
  }
1690
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1723
+ catch { /* empty */ }
1691
1724
  }
1692
1725
  }
1693
1726
  }
@@ -1769,7 +1802,7 @@ class MidyGM2 {
1769
1802
  return state;
1770
1803
  }
1771
1804
  applyVoiceParams(channel, controllerType, scheduleTime) {
1772
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1805
+ this.processScheduledNotes(channel, (note) => {
1773
1806
  const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1774
1807
  const voiceParams = note.voice.getParams(controllerType, controllerState);
1775
1808
  let applyVolumeEnvelope = false;
@@ -1830,13 +1863,13 @@ class MidyGM2 {
1830
1863
  handlers[127] = this.polyOn;
1831
1864
  return handlers;
1832
1865
  }
1833
- handleControlChange(channelNumber, controllerType, value, scheduleTime) {
1866
+ setControlChange(channelNumber, controllerType, value, scheduleTime) {
1834
1867
  const handler = this.controlChangeHandlers[controllerType];
1835
1868
  if (handler) {
1836
1869
  handler.call(this, channelNumber, value, scheduleTime);
1837
1870
  const channel = this.channels[channelNumber];
1838
1871
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1839
- this.applyControlTable(channel, controllerType, scheduleTime);
1872
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
1840
1873
  }
1841
1874
  else {
1842
1875
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1847,12 +1880,11 @@ class MidyGM2 {
1847
1880
  }
1848
1881
  updateModulation(channel, scheduleTime) {
1849
1882
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1850
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1883
+ this.processScheduledNotes(channel, (note) => {
1851
1884
  if (note.modulationDepth) {
1852
1885
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1853
1886
  }
1854
1887
  else {
1855
- this.setPitchEnvelope(note, scheduleTime);
1856
1888
  this.startModulation(channel, note, scheduleTime);
1857
1889
  }
1858
1890
  });
@@ -1866,7 +1898,7 @@ class MidyGM2 {
1866
1898
  this.updateModulation(channel, scheduleTime);
1867
1899
  }
1868
1900
  updatePortamento(channel, scheduleTime) {
1869
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1901
+ this.processScheduledNotes(channel, (note) => {
1870
1902
  if (0.5 <= channel.state.portamento) {
1871
1903
  if (0 <= note.portamentoNoteNumber) {
1872
1904
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
@@ -1897,8 +1929,14 @@ class MidyGM2 {
1897
1929
  scheduleTime ??= this.audioContext.currentTime;
1898
1930
  const channel = this.channels[channelNumber];
1899
1931
  channel.state.volume = volume / 127;
1900
- this.updateChannelVolume(channel, scheduleTime);
1901
- this.updateKeyBasedVolume(channel, scheduleTime);
1932
+ if (channel.isDrum) {
1933
+ for (let i = 0; i < 128; i++) {
1934
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1935
+ }
1936
+ }
1937
+ else {
1938
+ this.updateChannelVolume(channel, scheduleTime);
1939
+ }
1902
1940
  }
1903
1941
  panToGain(pan) {
1904
1942
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1911,8 +1949,14 @@ class MidyGM2 {
1911
1949
  scheduleTime ??= this.audioContext.currentTime;
1912
1950
  const channel = this.channels[channelNumber];
1913
1951
  channel.state.pan = pan / 127;
1914
- this.updateChannelVolume(channel, scheduleTime);
1915
- this.updateKeyBasedVolume(channel, scheduleTime);
1952
+ if (channel.isDrum) {
1953
+ for (let i = 0; i < 128; i++) {
1954
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1955
+ }
1956
+ }
1957
+ else {
1958
+ this.updateChannelVolume(channel, scheduleTime);
1959
+ }
1916
1960
  }
1917
1961
  setExpression(channelNumber, expression, scheduleTime) {
1918
1962
  scheduleTime ??= this.audioContext.currentTime;
@@ -1938,33 +1982,27 @@ class MidyGM2 {
1938
1982
  .cancelScheduledValues(scheduleTime)
1939
1983
  .setValueAtTime(volume * gainRight, scheduleTime);
1940
1984
  }
1941
- updateKeyBasedVolume(channel, scheduleTime) {
1942
- if (!channel.isDrum)
1943
- return;
1985
+ updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
1944
1986
  const state = channel.state;
1945
1987
  const defaultVolume = state.volume * state.expression;
1946
1988
  const defaultPan = state.pan;
1947
- for (let i = 0; i < 128; i++) {
1948
- const gainL = channel.keyBasedGainLs[i];
1949
- const gainR = channel.keyBasedGainLs[i];
1950
- if (!gainL)
1951
- continue;
1952
- if (!gainR)
1953
- continue;
1954
- const keyBasedVolume = this.getKeyBasedInstrumentControlValue(channel, i, 7);
1955
- const volume = (0 <= keyBasedVolume)
1956
- ? defaultVolume * keyBasedVolume / 64
1957
- : defaultVolume;
1958
- const keyBasedPan = this.getKeyBasedInstrumentControlValue(channel, i, 10);
1959
- const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1960
- const { gainLeft, gainRight } = this.panToGain(pan);
1961
- gainL.gain
1962
- .cancelScheduledValues(scheduleTime)
1963
- .setValueAtTime(volume * gainLeft, scheduleTime);
1964
- gainR.gain
1965
- .cancelScheduledValues(scheduleTime)
1966
- .setValueAtTime(volume * gainRight, scheduleTime);
1967
- }
1989
+ const gainL = channel.keyBasedGainLs[keyNumber];
1990
+ const gainR = channel.keyBasedGainRs[keyNumber];
1991
+ if (!gainL)
1992
+ return;
1993
+ const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
1994
+ const volume = (0 <= keyBasedVolume)
1995
+ ? defaultVolume * keyBasedVolume / 64
1996
+ : defaultVolume;
1997
+ const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
1998
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1999
+ const { gainLeft, gainRight } = this.panToGain(pan);
2000
+ gainL.gain
2001
+ .cancelScheduledValues(scheduleTime)
2002
+ .setValueAtTime(volume * gainLeft, scheduleTime);
2003
+ gainR.gain
2004
+ .cancelScheduledValues(scheduleTime)
2005
+ .setValueAtTime(volume * gainRight, scheduleTime);
1968
2006
  }
1969
2007
  setSustainPedal(channelNumber, value, scheduleTime) {
1970
2008
  const channel = this.channels[channelNumber];
@@ -1973,7 +2011,7 @@ class MidyGM2 {
1973
2011
  scheduleTime ??= this.audioContext.currentTime;
1974
2012
  channel.state.sustainPedal = value / 127;
1975
2013
  if (64 <= value) {
1976
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2014
+ this.processScheduledNotes(channel, (note) => {
1977
2015
  channel.sustainNotes.push(note);
1978
2016
  });
1979
2017
  }
@@ -2016,7 +2054,7 @@ class MidyGM2 {
2016
2054
  const state = channel.state;
2017
2055
  scheduleTime ??= this.audioContext.currentTime;
2018
2056
  state.softPedal = softPedal / 127;
2019
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2057
+ this.processScheduledNotes(channel, (note) => {
2020
2058
  if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2021
2059
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2022
2060
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2031,67 +2069,19 @@ class MidyGM2 {
2031
2069
  scheduleTime ??= this.audioContext.currentTime;
2032
2070
  const channel = this.channels[channelNumber];
2033
2071
  const state = channel.state;
2034
- const reverbEffect = this.reverbEffect;
2035
- if (0 < state.reverbSendLevel) {
2036
- if (0 < reverbSendLevel) {
2037
- state.reverbSendLevel = reverbSendLevel / 127;
2038
- reverbEffect.input.gain
2039
- .cancelScheduledValues(scheduleTime)
2040
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2041
- }
2042
- else {
2043
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2044
- if (note.voiceParams.reverbEffectsSend <= 0)
2045
- return false;
2046
- if (note.reverbEffectsSend)
2047
- note.reverbEffectsSend.disconnect();
2048
- });
2049
- }
2050
- }
2051
- else {
2052
- if (0 < reverbSendLevel) {
2053
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2054
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2055
- });
2056
- state.reverbSendLevel = reverbSendLevel / 127;
2057
- reverbEffect.input.gain
2058
- .cancelScheduledValues(scheduleTime)
2059
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2060
- }
2061
- }
2072
+ state.reverbSendLevel = reverbSendLevel / 127;
2073
+ this.processScheduledNotes(channel, (note) => {
2074
+ this.setReverbSend(channel, note, scheduleTime);
2075
+ });
2062
2076
  }
2063
2077
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2064
2078
  scheduleTime ??= this.audioContext.currentTime;
2065
2079
  const channel = this.channels[channelNumber];
2066
2080
  const state = channel.state;
2067
- const chorusEffect = this.chorusEffect;
2068
- if (0 < state.chorusSendLevel) {
2069
- if (0 < chorusSendLevel) {
2070
- state.chorusSendLevel = chorusSendLevel / 127;
2071
- chorusEffect.input.gain
2072
- .cancelScheduledValues(scheduleTime)
2073
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2074
- }
2075
- else {
2076
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2077
- if (note.voiceParams.chorusEffectsSend <= 0)
2078
- return false;
2079
- if (note.chorusEffectsSend)
2080
- note.chorusEffectsSend.disconnect();
2081
- });
2082
- }
2083
- }
2084
- else {
2085
- if (0 < chorusSendLevel) {
2086
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2087
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2088
- });
2089
- state.chorusSendLevel = chorusSendLevel / 127;
2090
- chorusEffect.input.gain
2091
- .cancelScheduledValues(scheduleTime)
2092
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2093
- }
2094
- }
2081
+ state.chorusSendLevel = chorusSendLevel / 127;
2082
+ this.processScheduledNotes(channel, (note) => {
2083
+ this.setChorusSend(channel, note, scheduleTime);
2084
+ });
2095
2085
  }
2096
2086
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
2097
2087
  if (maxLSB < channel.dataLSB) {
@@ -2227,7 +2217,7 @@ class MidyGM2 {
2227
2217
  const entries = Object.entries(defaultControllerState);
2228
2218
  for (const [key, { type, defaultValue }] of entries) {
2229
2219
  if (128 <= type) {
2230
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2220
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2231
2221
  }
2232
2222
  else {
2233
2223
  state[key] = defaultValue;
@@ -2259,7 +2249,7 @@ class MidyGM2 {
2259
2249
  const key = keys[i];
2260
2250
  const { type, defaultValue } = defaultControllerState[key];
2261
2251
  if (128 <= type) {
2262
- this.handleControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2252
+ this.setControlChange(channelNumber, type - 128, Math.ceil(defaultValue * 127), scheduleTime);
2263
2253
  }
2264
2254
  else {
2265
2255
  state[key] = defaultValue;
@@ -2373,9 +2363,9 @@ class MidyGM2 {
2373
2363
  case 9:
2374
2364
  switch (data[3]) {
2375
2365
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2376
- return this.handlePressureSysEx(data, "channelPressureTable");
2366
+ return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
2377
2367
  case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2378
- return this.handleControlChangeSysEx(data);
2368
+ return this.handleControlChangeSysEx(data, scheduleTime);
2379
2369
  default:
2380
2370
  console.warn(`Unsupported Exclusive Message: ${data}`);
2381
2371
  }
@@ -2694,29 +2684,31 @@ class MidyGM2 {
2694
2684
  : 0;
2695
2685
  return channelPressure / 127;
2696
2686
  }
2697
- setControllerParameters(channel, note, table) {
2687
+ setEffects(channel, note, table, scheduleTime) {
2698
2688
  if (0 <= table[0])
2699
- this.updateDetune(channel, note);
2689
+ this.updateDetune(channel, note, scheduleTime);
2700
2690
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2701
- if (0 <= table[1])
2702
- this.setPortamentoFilterEnvelope(channel, note);
2703
- if (0 <= table[2])
2704
- this.setPortamentoVolumeEnvelope(channel, note);
2691
+ if (0 <= table[1]) {
2692
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2693
+ }
2694
+ if (0 <= table[2]) {
2695
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2696
+ }
2705
2697
  }
2706
2698
  else {
2707
2699
  if (0 <= table[1])
2708
- this.setFilterEnvelope(channel, note);
2700
+ this.setFilterEnvelope(channel, note, scheduleTime);
2709
2701
  if (0 <= table[2])
2710
- this.setVolumeEnvelope(channel, note);
2702
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2711
2703
  }
2712
2704
  if (0 <= table[3])
2713
- this.setModLfoToPitch(channel, note);
2705
+ this.setModLfoToPitch(channel, note, scheduleTime);
2714
2706
  if (0 <= table[4])
2715
- this.setModLfoToFilterFc(channel, note);
2707
+ this.setModLfoToFilterFc(channel, note, scheduleTime);
2716
2708
  if (0 <= table[5])
2717
- this.setModLfoToVolume(channel, note);
2709
+ this.setModLfoToVolume(channel, note, scheduleTime);
2718
2710
  }
2719
- handlePressureSysEx(data, tableName) {
2711
+ handlePressureSysEx(data, tableName, scheduleTime) {
2720
2712
  const channelNumber = data[4];
2721
2713
  const channel = this.channels[channelNumber];
2722
2714
  if (channel.isDrum)
@@ -2727,34 +2719,40 @@ class MidyGM2 {
2727
2719
  const rr = data[i + 1];
2728
2720
  table[pp] = rr;
2729
2721
  }
2722
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2723
+ this.setEffects(channel, note, table, scheduleTime);
2724
+ });
2730
2725
  }
2731
2726
  initControlTable() {
2732
2727
  const ccCount = 128;
2733
2728
  const slotSize = 6;
2734
2729
  return new Int8Array(ccCount * slotSize).fill(-1);
2735
2730
  }
2736
- applyControlTable(channel, controllerType, scheduleTime) {
2731
+ setControlChangeEffects(channel, controllerType, scheduleTime) {
2737
2732
  const slotSize = 6;
2738
2733
  const offset = controllerType * slotSize;
2739
2734
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2740
- this.processScheduledNotes(channel, scheduleTime, (note) => {
2741
- this.setControllerParameters(channel, note, table);
2735
+ this.processScheduledNotes(channel, (note) => {
2736
+ this.setEffects(channel, note, table, scheduleTime);
2742
2737
  });
2743
2738
  }
2744
- handleControlChangeSysEx(data) {
2739
+ handleControlChangeSysEx(data, scheduleTime) {
2745
2740
  const channelNumber = data[4];
2746
2741
  const channel = this.channels[channelNumber];
2747
2742
  if (channel.isDrum)
2748
2743
  return;
2744
+ const slotSize = 6;
2749
2745
  const controllerType = data[5];
2750
- const table = channel.controlTable[controllerType];
2751
- for (let i = 6; i < data.length - 1; i += 2) {
2746
+ const offset = controllerType * slotSize;
2747
+ const table = channel.controlTable;
2748
+ for (let i = 6; i < data.length; i += 2) {
2752
2749
  const pp = data[i];
2753
2750
  const rr = data[i + 1];
2754
- table[pp] = rr;
2751
+ table[offset + pp] = rr;
2755
2752
  }
2753
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
2756
2754
  }
2757
- getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
2755
+ getKeyBasedValue(channel, keyNumber, controllerType) {
2758
2756
  const index = keyNumber * 128 + controllerType;
2759
2757
  const controlValue = channel.keyBasedInstrumentControlTable[index];
2760
2758
  return controlValue;
@@ -2766,13 +2764,20 @@ class MidyGM2 {
2766
2764
  return;
2767
2765
  const keyNumber = data[5];
2768
2766
  const table = channel.keyBasedInstrumentControlTable;
2769
- for (let i = 6; i < data.length - 1; i += 2) {
2767
+ for (let i = 6; i < data.length; i += 2) {
2770
2768
  const controllerType = data[i];
2771
2769
  const value = data[i + 1];
2772
2770
  const index = keyNumber * 128 + controllerType;
2773
2771
  table[index] = value;
2772
+ switch (controllerType) {
2773
+ case 7:
2774
+ case 10:
2775
+ this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
2776
+ break;
2777
+ default: // TODO
2778
+ this.setControlChange(channelNumber, controllerType, value, scheduleTime);
2779
+ }
2774
2780
  }
2775
- this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2776
2781
  }
2777
2782
  handleSysEx(data, scheduleTime) {
2778
2783
  switch (data[0]) {
@@ -2810,6 +2815,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2810
2815
  configurable: true,
2811
2816
  writable: true,
2812
2817
  value: {
2818
+ scheduleIndex: 0,
2813
2819
  detune: 0,
2814
2820
  programNumber: 0,
2815
2821
  bank: 121 * 128,