@marmooo/midy 0.3.6 → 0.3.8

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.
@@ -228,13 +228,13 @@ class MidyGM2 {
228
228
  configurable: true,
229
229
  writable: true,
230
230
  value: 0
231
- }); // cb
231
+ }); // cent
232
232
  Object.defineProperty(this, "masterCoarseTuning", {
233
233
  enumerable: true,
234
234
  configurable: true,
235
235
  writable: true,
236
236
  value: 0
237
- }); // cb
237
+ }); // cent
238
238
  Object.defineProperty(this, "reverb", {
239
239
  enumerable: true,
240
240
  configurable: true,
@@ -275,6 +275,18 @@ class MidyGM2 {
275
275
  writable: true,
276
276
  value: 0
277
277
  });
278
+ Object.defineProperty(this, "lastActiveSensing", {
279
+ enumerable: true,
280
+ configurable: true,
281
+ writable: true,
282
+ value: 0
283
+ });
284
+ Object.defineProperty(this, "activeSensingThreshold", {
285
+ enumerable: true,
286
+ configurable: true,
287
+ writable: true,
288
+ value: 0.3
289
+ });
278
290
  Object.defineProperty(this, "noteCheckInterval", {
279
291
  enumerable: true,
280
292
  configurable: true,
@@ -315,7 +327,7 @@ class MidyGM2 {
315
327
  enumerable: true,
316
328
  configurable: true,
317
329
  writable: true,
318
- value: this.initSoundFontTable()
330
+ value: Array.from({ length: 128 }, () => [])
319
331
  });
320
332
  Object.defineProperty(this, "voiceCounter", {
321
333
  enumerable: true,
@@ -359,6 +371,12 @@ class MidyGM2 {
359
371
  writable: true,
360
372
  value: false
361
373
  });
374
+ Object.defineProperty(this, "playPromise", {
375
+ enumerable: true,
376
+ configurable: true,
377
+ writable: true,
378
+ value: void 0
379
+ });
362
380
  Object.defineProperty(this, "timeline", {
363
381
  enumerable: true,
364
382
  configurable: true,
@@ -396,8 +414,10 @@ class MidyGM2 {
396
414
  length: 1,
397
415
  sampleRate: audioContext.sampleRate,
398
416
  });
417
+ this.messageHandlers = this.createMessageHandlers();
399
418
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
400
419
  this.controlChangeHandlers = this.createControlChangeHandlers();
420
+ this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
401
421
  this.channels = this.createChannels(audioContext);
402
422
  this.reverbEffect = this.createReverbEffect(audioContext);
403
423
  this.chorusEffect = this.createChorusEffect(audioContext);
@@ -407,21 +427,14 @@ class MidyGM2 {
407
427
  this.scheduler.connect(audioContext.destination);
408
428
  this.GM2SystemOn();
409
429
  }
410
- initSoundFontTable() {
411
- const table = new Array(128);
412
- for (let i = 0; i < 128; i++) {
413
- table[i] = new Map();
414
- }
415
- return table;
416
- }
417
430
  addSoundFont(soundFont) {
418
431
  const index = this.soundFonts.length;
419
432
  this.soundFonts.push(soundFont);
420
433
  const presetHeaders = soundFont.parsed.presetHeaders;
434
+ const soundFontTable = this.soundFontTable;
421
435
  for (let i = 0; i < presetHeaders.length; i++) {
422
- const presetHeader = presetHeaders[i];
423
- const banks = this.soundFontTable[presetHeader.preset];
424
- banks.set(presetHeader.bank, index);
436
+ const { preset, bank } = presetHeaders[i];
437
+ soundFontTable[preset][bank] = index;
425
438
  }
426
439
  }
427
440
  async toUint8Array(input) {
@@ -499,13 +512,17 @@ class MidyGM2 {
499
512
  this.GM2SystemOn();
500
513
  }
501
514
  getVoiceId(channel, noteNumber, velocity) {
502
- const bankNumber = this.calcBank(channel);
503
- const soundFontIndex = this.soundFontTable[channel.programNumber]
504
- .get(bankNumber);
515
+ const programNumber = channel.programNumber;
516
+ const bankTable = this.soundFontTable[programNumber];
517
+ if (!bankTable)
518
+ return;
519
+ const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
520
+ const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
521
+ const soundFontIndex = bankTable[bank];
505
522
  if (soundFontIndex === undefined)
506
523
  return;
507
524
  const soundFont = this.soundFonts[soundFontIndex];
508
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
525
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
509
526
  const { instrument, sampleID } = voice.generators;
510
527
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
511
528
  }
@@ -527,7 +544,7 @@ class MidyGM2 {
527
544
  channel.controlTable.fill(-1);
528
545
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
529
546
  channel.channelPressureTable.fill(-1);
530
- channel.keyBasedInstrumentControlTable.fill(-1);
547
+ channel.keyBasedTable.fill(-1);
531
548
  }
532
549
  createChannels(audioContext) {
533
550
  const channels = Array.from({ length: this.numChannels }, () => {
@@ -543,7 +560,7 @@ class MidyGM2 {
543
560
  controlTable: this.initControlTable(),
544
561
  scaleOctaveTuningTable: new Int8Array(12), // [-64, 63] cent
545
562
  channelPressureTable: new Int8Array(6).fill(-1),
546
- keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
563
+ keyBasedTable: new Int8Array(128 * 128).fill(-1),
547
564
  keyBasedGainLs: new Array(128),
548
565
  keyBasedGainRs: new Array(128),
549
566
  };
@@ -574,13 +591,16 @@ class MidyGM2 {
574
591
  }
575
592
  return bufferSource;
576
593
  }
577
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
578
- while (queueIndex < this.timeline.length) {
579
- const event = this.timeline[queueIndex];
580
- if (event.startTime > t + this.lookAhead)
594
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
595
+ const timeOffset = this.resumeTime - this.startTime;
596
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
597
+ const schedulingOffset = this.startDelay - timeOffset;
598
+ const timeline = this.timeline;
599
+ while (queueIndex < timeline.length) {
600
+ const event = timeline[queueIndex];
601
+ if (lookAheadCheckTime < event.startTime)
581
602
  break;
582
- const delay = this.startDelay - resumeTime;
583
- const startTime = event.startTime + delay;
603
+ const startTime = event.startTime + schedulingOffset;
584
604
  switch (event.type) {
585
605
  case "noteOn":
586
606
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
@@ -618,72 +638,85 @@ class MidyGM2 {
618
638
  }
619
639
  return 0;
620
640
  }
621
- playNotes() {
622
- return new Promise((resolve) => {
623
- this.isPlaying = true;
624
- this.isPaused = false;
625
- this.startTime = this.audioContext.currentTime;
626
- let queueIndex = this.getQueueIndex(this.resumeTime);
627
- let resumeTime = this.resumeTime - this.startTime;
641
+ resetAllStates() {
642
+ this.exclusiveClassNotes.fill(undefined);
643
+ this.drumExclusiveClassNotes.fill(undefined);
644
+ this.voiceCache.clear();
645
+ for (let i = 0; i < this.channels.length; i++) {
646
+ this.channels[i].scheduledNotes = [];
647
+ this.resetChannelStates(i);
648
+ }
649
+ }
650
+ updateStates(queueIndex, nextQueueIndex) {
651
+ if (nextQueueIndex < queueIndex)
652
+ queueIndex = 0;
653
+ for (let i = queueIndex; i < nextQueueIndex; i++) {
654
+ const event = this.timeline[i];
655
+ switch (event.type) {
656
+ case "controller":
657
+ this.setControlChange(event.channel, event.controllerType, event.value, 0);
658
+ break;
659
+ case "programChange":
660
+ this.setProgramChange(event.channel, event.programNumber, 0);
661
+ break;
662
+ case "pitchBend":
663
+ this.setPitchBend(event.channel, event.value + 8192, 0);
664
+ break;
665
+ case "sysEx":
666
+ this.handleSysEx(event.data, 0);
667
+ }
668
+ }
669
+ }
670
+ async playNotes() {
671
+ if (this.audioContext.state === "suspended") {
672
+ await this.audioContext.resume();
673
+ }
674
+ this.isPlaying = true;
675
+ this.isPaused = false;
676
+ this.startTime = this.audioContext.currentTime;
677
+ let queueIndex = this.getQueueIndex(this.resumeTime);
678
+ let finished = false;
679
+ this.notePromises = [];
680
+ while (queueIndex < this.timeline.length) {
681
+ const now = this.audioContext.currentTime;
682
+ if (0 < this.lastActiveSensing &&
683
+ this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
684
+ await this.stopNotes(0, true, now);
685
+ await this.audioContext.suspend();
686
+ finished = true;
687
+ break;
688
+ }
689
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
690
+ if (this.isPausing) {
691
+ await this.stopNotes(0, true, now);
692
+ await this.audioContext.suspend();
693
+ this.notePromises = [];
694
+ break;
695
+ }
696
+ else if (this.isStopping) {
697
+ await this.stopNotes(0, true, now);
698
+ await this.audioContext.suspend();
699
+ finished = true;
700
+ break;
701
+ }
702
+ else if (this.isSeeking) {
703
+ await this.stopNotes(0, true, now);
704
+ this.startTime = this.audioContext.currentTime;
705
+ const nextQueueIndex = this.getQueueIndex(this.resumeTime);
706
+ this.updateStates(queueIndex, nextQueueIndex);
707
+ queueIndex = nextQueueIndex;
708
+ this.isSeeking = false;
709
+ continue;
710
+ }
711
+ const waitTime = now + this.noteCheckInterval;
712
+ await this.scheduleTask(() => { }, waitTime);
713
+ }
714
+ if (finished) {
628
715
  this.notePromises = [];
629
- const schedulePlayback = async () => {
630
- if (queueIndex >= this.timeline.length) {
631
- await Promise.all(this.notePromises);
632
- this.notePromises = [];
633
- this.exclusiveClassNotes.fill(undefined);
634
- this.drumExclusiveClassNotes.fill(undefined);
635
- this.voiceCache.clear();
636
- for (let i = 0; i < this.channels.length; i++) {
637
- this.channels[i].scheduledNotes = [];
638
- this.resetAllStates(i);
639
- }
640
- resolve();
641
- return;
642
- }
643
- const now = this.audioContext.currentTime;
644
- const t = now + resumeTime;
645
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
646
- if (this.isPausing) {
647
- await this.stopNotes(0, true, now);
648
- this.notePromises = [];
649
- this.isPausing = false;
650
- this.isPaused = true;
651
- resolve();
652
- return;
653
- }
654
- else if (this.isStopping) {
655
- await this.stopNotes(0, true, now);
656
- this.notePromises = [];
657
- this.exclusiveClassNotes.fill(undefined);
658
- this.drumExclusiveClassNotes.fill(undefined);
659
- this.voiceCache.clear();
660
- for (let i = 0; i < this.channels.length; i++) {
661
- this.channels[i].scheduledNotes = [];
662
- this.resetAllStates(i);
663
- }
664
- this.isStopping = false;
665
- this.isPaused = false;
666
- resolve();
667
- return;
668
- }
669
- else if (this.isSeeking) {
670
- this.stopNotes(0, true, now);
671
- this.exclusiveClassNotes.fill(undefined);
672
- this.drumExclusiveClassNotes.fill(undefined);
673
- this.startTime = this.audioContext.currentTime;
674
- queueIndex = this.getQueueIndex(this.resumeTime);
675
- resumeTime = this.resumeTime - this.startTime;
676
- this.isSeeking = false;
677
- await schedulePlayback();
678
- }
679
- else {
680
- const waitTime = now + this.noteCheckInterval;
681
- await this.scheduleTask(() => { }, waitTime);
682
- await schedulePlayback();
683
- }
684
- };
685
- schedulePlayback();
686
- });
716
+ this.resetAllStates();
717
+ this.lastActiveSensing = 0;
718
+ }
719
+ this.isPlaying = false;
687
720
  }
688
721
  ticksToSecond(ticks, secondsPerBeat) {
689
722
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -691,17 +724,17 @@ class MidyGM2 {
691
724
  secondToTicks(second, secondsPerBeat) {
692
725
  return second * this.ticksPerBeat / secondsPerBeat;
693
726
  }
727
+ getSoundFontId(channel) {
728
+ const programNumber = channel.programNumber;
729
+ const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
730
+ const bank = bankNumber.toString().padStart(3, "0");
731
+ const program = programNumber.toString().padStart(3, "0");
732
+ return `${bank}:${program}`;
733
+ }
694
734
  extractMidiData(midi) {
695
735
  const instruments = new Set();
696
736
  const timeline = [];
697
- const tmpChannels = new Array(this.channels.length);
698
- for (let i = 0; i < tmpChannels.length; i++) {
699
- tmpChannels[i] = {
700
- programNumber: -1,
701
- bankMSB: this.channels[i].bankMSB,
702
- bankLSB: this.channels[i].bankLSB,
703
- };
704
- }
737
+ const channels = this.channels;
705
738
  for (let i = 0; i < midi.tracks.length; i++) {
706
739
  const track = midi.tracks[i];
707
740
  let currentTicks = 0;
@@ -711,48 +744,40 @@ class MidyGM2 {
711
744
  event.ticks = currentTicks;
712
745
  switch (event.type) {
713
746
  case "noteOn": {
714
- const channel = tmpChannels[event.channel];
715
- if (channel.programNumber < 0) {
716
- channel.programNumber = event.programNumber;
717
- switch (channel.bankMSB) {
718
- case 120:
719
- instruments.add(`128:0`);
720
- break;
721
- case 121:
722
- instruments.add(`${channel.bankLSB}:0`);
723
- break;
724
- default: {
725
- const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
726
- instruments.add(`${bankNumber}:0`);
727
- }
728
- }
729
- channel.programNumber = 0;
730
- }
747
+ const channel = channels[event.channel];
748
+ instruments.add(this.getSoundFontId(channel));
731
749
  break;
732
750
  }
733
751
  case "controller":
734
752
  switch (event.controllerType) {
735
753
  case 0:
736
- tmpChannels[event.channel].bankMSB = event.value;
754
+ this.setBankMSB(event.channel, event.value);
737
755
  break;
738
756
  case 32:
739
- tmpChannels[event.channel].bankLSB = event.value;
757
+ this.setBankLSB(event.channel, event.value);
740
758
  break;
741
759
  }
742
760
  break;
743
761
  case "programChange": {
744
- const channel = tmpChannels[event.channel];
745
- channel.programNumber = event.programNumber;
746
- switch (channel.bankMSB) {
747
- case 120:
748
- instruments.add(`128:${channel.programNumber}`);
749
- break;
750
- case 121:
751
- instruments.add(`${channel.bankLSB}:${channel.programNumber}`);
752
- break;
753
- default: {
754
- const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
755
- instruments.add(`${bankNumber}:${channel.programNumber}`);
762
+ const channel = channels[event.channel];
763
+ this.setProgramChange(event.channel, event.programNumber);
764
+ instruments.add(this.getSoundFontId(channel));
765
+ break;
766
+ }
767
+ case "sysEx": {
768
+ const data = event.data;
769
+ if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
770
+ switch (data[3]) {
771
+ case 1:
772
+ this.GM1SystemOn(scheduleTime);
773
+ break;
774
+ case 2: // GM System Off
775
+ break;
776
+ case 3:
777
+ this.GM2SystemOn(scheduleTime);
778
+ break;
779
+ default:
780
+ console.warn(`Unsupported Exclusive Message: ${data}`);
756
781
  }
757
782
  }
758
783
  }
@@ -820,26 +845,32 @@ class MidyGM2 {
820
845
  this.resumeTime = 0;
821
846
  if (this.voiceCounter.size === 0)
822
847
  this.cacheVoiceIds();
823
- await this.playNotes();
824
- this.isPlaying = false;
848
+ this.playPromise = this.playNotes();
849
+ await this.playPromise;
825
850
  }
826
- stop() {
851
+ async stop() {
827
852
  if (!this.isPlaying)
828
853
  return;
829
854
  this.isStopping = true;
855
+ await this.playPromise;
856
+ this.isStopping = false;
830
857
  }
831
- pause() {
858
+ async pause() {
832
859
  if (!this.isPlaying || this.isPaused)
833
860
  return;
834
861
  const now = this.audioContext.currentTime;
835
862
  this.resumeTime += now - this.startTime - this.startDelay;
836
863
  this.isPausing = true;
864
+ await this.playPromise;
865
+ this.isPausing = false;
866
+ this.isPaused = true;
837
867
  }
838
868
  async resume() {
839
869
  if (!this.isPaused)
840
870
  return;
841
- await this.playNotes();
842
- this.isPlaying = false;
871
+ this.playPromise = this.playNotes();
872
+ await this.playPromise;
873
+ this.isPaused = false;
843
874
  }
844
875
  seekTo(second) {
845
876
  this.resumeTime = second;
@@ -1069,7 +1100,7 @@ class MidyGM2 {
1069
1100
  updateDetune(channel, note, scheduleTime) {
1070
1101
  const noteDetune = this.calcNoteDetune(channel, note);
1071
1102
  const detune = channel.detune + noteDetune;
1072
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1103
+ if (this.isPortamento(channel, note)) {
1073
1104
  const startTime = note.startTime;
1074
1105
  const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1075
1106
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
@@ -1274,10 +1305,12 @@ class MidyGM2 {
1274
1305
  startVibrato(channel, note, scheduleTime) {
1275
1306
  const { voiceParams } = note;
1276
1307
  const state = channel.state;
1308
+ const vibratoRate = state.vibratoRate * 2;
1309
+ const vibratoDelay = state.vibratoDelay * 2;
1277
1310
  note.vibratoLFO = new OscillatorNode(this.audioContext, {
1278
- frequency: this.centToHz(voiceParams.freqVibLFO) * state.vibratoRate * 2,
1311
+ frequency: this.centToHz(voiceParams.freqVibLFO) * vibratoRate,
1279
1312
  });
1280
- note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * state.vibratoDelay * 2);
1313
+ note.vibratoLFO.start(note.startTime + voiceParams.delayVibLFO * vibratoDelay);
1281
1314
  note.vibratoDepth = new GainNode(this.audioContext);
1282
1315
  this.setVibLfoToPitch(channel, note, scheduleTime);
1283
1316
  note.vibratoLFO.connect(note.vibratoDepth);
@@ -1318,7 +1351,7 @@ class MidyGM2 {
1318
1351
  if (prevNote && prevNote.noteNumber !== noteNumber) {
1319
1352
  note.portamentoNoteNumber = prevNote.noteNumber;
1320
1353
  }
1321
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1354
+ if (!channel.isDrum && this.isPortamento(channel, note)) {
1322
1355
  this.setPortamentoVolumeEnvelope(channel, note, now);
1323
1356
  this.setPortamentoFilterEnvelope(channel, note, now);
1324
1357
  this.setPortamentoPitchEnvelope(note, now);
@@ -1346,22 +1379,6 @@ class MidyGM2 {
1346
1379
  note.bufferSource.start(startTime);
1347
1380
  return note;
1348
1381
  }
1349
- calcBank(channel) {
1350
- switch (this.mode) {
1351
- case "GM1":
1352
- if (channel.isDrum)
1353
- return 128;
1354
- return 0;
1355
- case "GM2":
1356
- if (channel.bankMSB === 121)
1357
- return 0;
1358
- if (channel.isDrum)
1359
- return 128;
1360
- return channel.bank;
1361
- default:
1362
- return channel.bank;
1363
- }
1364
- }
1365
1382
  handleExclusiveClass(note, channelNumber, startTime) {
1366
1383
  const exclusiveClass = note.voiceParams.exclusiveClass;
1367
1384
  if (exclusiveClass === 0)
@@ -1397,13 +1414,17 @@ class MidyGM2 {
1397
1414
  }
1398
1415
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1399
1416
  const channel = this.channels[channelNumber];
1400
- const bankNumber = this.calcBank(channel, channelNumber);
1401
- const soundFontIndex = this.soundFontTable[channel.programNumber]
1402
- .get(bankNumber);
1417
+ const programNumber = channel.programNumber;
1418
+ const bankTable = this.soundFontTable[programNumber];
1419
+ if (!bankTable)
1420
+ return;
1421
+ const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
1422
+ const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
1423
+ const soundFontIndex = bankTable[bank];
1403
1424
  if (soundFontIndex === undefined)
1404
1425
  return;
1405
1426
  const soundFont = this.soundFonts[soundFontIndex];
1406
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1427
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1407
1428
  if (!voice)
1408
1429
  return;
1409
1430
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
@@ -1555,34 +1576,39 @@ class MidyGM2 {
1555
1576
  channel.sostenutoNotes = [];
1556
1577
  return promises;
1557
1578
  }
1558
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1559
- const channelNumber = statusByte & 0x0F;
1560
- const messageType = statusByte & 0xF0;
1561
- switch (messageType) {
1562
- case 0x80:
1563
- return this.noteOff(channelNumber, data1, data2, scheduleTime);
1564
- case 0x90:
1565
- return this.noteOn(channelNumber, data1, data2, scheduleTime);
1566
- case 0xB0:
1567
- return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1568
- case 0xC0:
1569
- return this.setProgramChange(channelNumber, data1, scheduleTime);
1570
- case 0xD0:
1571
- return this.setChannelPressure(channelNumber, data1, scheduleTime);
1572
- case 0xE0:
1573
- return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1574
- default:
1575
- console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1579
+ createMessageHandlers() {
1580
+ const handlers = new Array(256);
1581
+ // Channel Message
1582
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1583
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1584
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1585
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1586
+ handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
1587
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1588
+ // System Real Time Message
1589
+ handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
1590
+ return handlers;
1591
+ }
1592
+ handleMessage(data, scheduleTime) {
1593
+ const status = data[0];
1594
+ if (status === 0xF0) {
1595
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1576
1596
  }
1597
+ const handler = this.messageHandlers[status];
1598
+ if (handler)
1599
+ handler(data, scheduleTime);
1600
+ }
1601
+ activeSensing() {
1602
+ this.lastActiveSensing = performance.now();
1577
1603
  }
1578
1604
  setProgramChange(channelNumber, programNumber, _scheduleTime) {
1579
1605
  const channel = this.channels[channelNumber];
1580
- channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1581
1606
  channel.programNumber = programNumber;
1582
1607
  if (this.mode === "GM2") {
1583
1608
  switch (channel.bankMSB) {
1584
1609
  case 120:
1585
1610
  channel.isDrum = true;
1611
+ channel.keyBasedTable.fill(-1);
1586
1612
  break;
1587
1613
  case 121:
1588
1614
  channel.isDrum = false;
@@ -1604,7 +1630,7 @@ class MidyGM2 {
1604
1630
  }
1605
1631
  const table = channel.channelPressureTable;
1606
1632
  this.processActiveNotes(channel, scheduleTime, (note) => {
1607
- this.setEffects(channel, note, table);
1633
+ this.setEffects(channel, note, table, scheduleTime);
1608
1634
  });
1609
1635
  this.applyVoiceParams(channel, 13);
1610
1636
  }
@@ -1630,23 +1656,28 @@ class MidyGM2 {
1630
1656
  const modLfoToPitch = note.voiceParams.modLfoToPitch +
1631
1657
  this.getLFOPitchDepth(channel, note);
1632
1658
  const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1633
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1659
+ const depth = baseDepth * Math.sign(modLfoToPitch);
1634
1660
  note.modulationDepth.gain
1635
1661
  .cancelScheduledValues(scheduleTime)
1636
- .setValueAtTime(modulationDepth, scheduleTime);
1662
+ .setValueAtTime(depth, scheduleTime);
1637
1663
  }
1638
1664
  else {
1639
1665
  this.startModulation(channel, note, scheduleTime);
1640
1666
  }
1641
1667
  }
1642
1668
  setVibLfoToPitch(channel, note, scheduleTime) {
1643
- const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1644
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1645
- 2;
1646
- const vibratoDepthSign = 0 < vibLfoToPitch;
1647
- note.vibratoDepth.gain
1648
- .cancelScheduledValues(scheduleTime)
1649
- .setValueAtTime(vibratoDepth * vibratoDepthSign, scheduleTime);
1669
+ if (note.vibratoDepth) {
1670
+ const vibratoDepth = channel.state.vibratoDepth * 2;
1671
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1672
+ const baseDepth = Math.abs(vibLfoToPitch) * vibratoDepth;
1673
+ const depth = baseDepth * Math.sign(vibLfoToPitch);
1674
+ note.vibratoDepth.gain
1675
+ .cancelScheduledValues(scheduleTime)
1676
+ .setValueAtTime(depth, scheduleTime);
1677
+ }
1678
+ else {
1679
+ this.startVibrato(channel, note, scheduleTime);
1680
+ }
1650
1681
  }
1651
1682
  setModLfoToFilterFc(channel, note, scheduleTime) {
1652
1683
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc +
@@ -1724,13 +1755,12 @@ class MidyGM2 {
1724
1755
  }
1725
1756
  }
1726
1757
  }
1727
- setDelayModLFO(note, scheduleTime) {
1728
- const startTime = note.startTime;
1729
- if (startTime < scheduleTime)
1730
- return;
1731
- note.modulationLFO.stop(scheduleTime);
1732
- note.modulationLFO.start(startTime + note.voiceParams.delayModLFO);
1733
- note.modulationLFO.connect(note.filterDepth);
1758
+ setDelayModLFO(note) {
1759
+ const startTime = note.startTime + note.voiceParams.delayModLFO;
1760
+ try {
1761
+ note.modulationLFO.start(startTime);
1762
+ }
1763
+ catch { /* empty */ }
1734
1764
  }
1735
1765
  setFreqModLFO(note, scheduleTime) {
1736
1766
  const freqModLFO = note.voiceParams.freqModLFO;
@@ -1739,54 +1769,65 @@ class MidyGM2 {
1739
1769
  .setValueAtTime(freqModLFO, scheduleTime);
1740
1770
  }
1741
1771
  setFreqVibLFO(channel, note, scheduleTime) {
1772
+ const vibratoRate = channel.state.vibratoRate * 2;
1742
1773
  const freqVibLFO = note.voiceParams.freqVibLFO;
1743
1774
  note.vibratoLFO.frequency
1744
1775
  .cancelScheduledValues(scheduleTime)
1745
- .setValueAtTime(freqVibLFO * channel.state.vibratoRate * 2, scheduleTime);
1776
+ .setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
1777
+ }
1778
+ setDelayVibLFO(channel, note) {
1779
+ const vibratoDelay = channel.state.vibratoDelay * 2;
1780
+ const value = note.voiceParams.delayVibLFO;
1781
+ const startTime = note.startTime + value * vibratoDelay;
1782
+ try {
1783
+ note.vibratoLFO.start(startTime);
1784
+ }
1785
+ catch { /* empty */ }
1746
1786
  }
1747
1787
  createVoiceParamsHandlers() {
1748
1788
  return {
1749
- modLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1789
+ modLfoToPitch: (channel, note, scheduleTime) => {
1750
1790
  if (0 < channel.state.modulationDepth) {
1751
1791
  this.setModLfoToPitch(channel, note, scheduleTime);
1752
1792
  }
1753
1793
  },
1754
- vibLfoToPitch: (channel, note, _prevValue, scheduleTime) => {
1794
+ vibLfoToPitch: (channel, note, scheduleTime) => {
1755
1795
  if (0 < channel.state.vibratoDepth) {
1756
1796
  this.setVibLfoToPitch(channel, note, scheduleTime);
1757
1797
  }
1758
1798
  },
1759
- modLfoToFilterFc: (channel, note, _prevValue, scheduleTime) => {
1799
+ modLfoToFilterFc: (channel, note, scheduleTime) => {
1760
1800
  if (0 < channel.state.modulationDepth) {
1761
1801
  this.setModLfoToFilterFc(channel, note, scheduleTime);
1762
1802
  }
1763
1803
  },
1764
- modLfoToVolume: (channel, note, _prevValue, scheduleTime) => {
1804
+ modLfoToVolume: (channel, note, scheduleTime) => {
1765
1805
  if (0 < channel.state.modulationDepth) {
1766
1806
  this.setModLfoToVolume(channel, note, scheduleTime);
1767
1807
  }
1768
1808
  },
1769
- chorusEffectsSend: (channel, note, prevValue, scheduleTime) => {
1770
- this.setChorusEffectsSend(channel, note, prevValue, scheduleTime);
1809
+ chorusEffectsSend: (channel, note, scheduleTime) => {
1810
+ this.setChorusSend(channel, note, scheduleTime);
1771
1811
  },
1772
- reverbEffectsSend: (channel, note, prevValue, scheduleTime) => {
1773
- this.setReverbEffectsSend(channel, note, prevValue, scheduleTime);
1812
+ reverbEffectsSend: (channel, note, scheduleTime) => {
1813
+ this.setReverbSend(channel, note, scheduleTime);
1774
1814
  },
1775
- delayModLFO: (_channel, note, _prevValue, scheduleTime) => this.setDelayModLFO(note, scheduleTime),
1776
- freqModLFO: (_channel, note, _prevValue, scheduleTime) => this.setFreqModLFO(note, scheduleTime),
1777
- delayVibLFO: (channel, note, prevValue, scheduleTime) => {
1815
+ delayModLFO: (_channel, note, _scheduleTime) => {
1816
+ if (0 < channel.state.modulationDepth) {
1817
+ this.setDelayModLFO(note);
1818
+ }
1819
+ },
1820
+ freqModLFO: (_channel, note, scheduleTime) => {
1821
+ if (0 < channel.state.modulationDepth) {
1822
+ this.setFreqModLFO(note, scheduleTime);
1823
+ }
1824
+ },
1825
+ delayVibLFO: (channel, note, _scheduleTime) => {
1778
1826
  if (0 < channel.state.vibratoDepth) {
1779
- const vibratoDelay = channel.state.vibratoDelay * 2;
1780
- const prevStartTime = note.startTime + prevValue * vibratoDelay;
1781
- if (scheduleTime < prevStartTime)
1782
- return;
1783
- const value = note.voiceParams.delayVibLFO;
1784
- const startTime = note.startTime + value * vibratoDelay;
1785
- note.vibratoLFO.stop(scheduleTime);
1786
- note.vibratoLFO.start(startTime);
1827
+ setDelayVibLFO(channel, note);
1787
1828
  }
1788
1829
  },
1789
- freqVibLFO: (channel, note, _prevValue, scheduleTime) => {
1830
+ freqVibLFO: (channel, note, scheduleTime) => {
1790
1831
  if (0 < channel.state.vibratoDepth) {
1791
1832
  this.setFreqVibLFO(channel, note, scheduleTime);
1792
1833
  }
@@ -1814,7 +1855,7 @@ class MidyGM2 {
1814
1855
  continue;
1815
1856
  note.voiceParams[key] = value;
1816
1857
  if (key in this.voiceParamsHandlers) {
1817
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1858
+ this.voiceParamsHandlers[key](channel, note, scheduleTime);
1818
1859
  }
1819
1860
  else {
1820
1861
  if (volumeEnvelopeKeySet.has(key))
@@ -1898,22 +1939,20 @@ class MidyGM2 {
1898
1939
  this.updateModulation(channel, scheduleTime);
1899
1940
  }
1900
1941
  updatePortamento(channel, scheduleTime) {
1942
+ if (channel.isDrum)
1943
+ return;
1901
1944
  this.processScheduledNotes(channel, (note) => {
1902
- if (0.5 <= channel.state.portamento) {
1903
- if (0 <= note.portamentoNoteNumber) {
1904
- this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
1905
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1906
- this.setPortamentoPitchEnvelope(note, scheduleTime);
1907
- this.updateDetune(channel, note, scheduleTime);
1908
- }
1945
+ if (this.isPortamento(channel, note)) {
1946
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
1947
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1948
+ this.setPortamentoPitchEnvelope(note, scheduleTime);
1949
+ this.updateDetune(channel, note, scheduleTime);
1909
1950
  }
1910
1951
  else {
1911
- if (0 <= note.portamentoNoteNumber) {
1912
- this.setVolumeEnvelope(channel, note, scheduleTime);
1913
- this.setFilterEnvelope(channel, note, scheduleTime);
1914
- this.setPitchEnvelope(note, scheduleTime);
1915
- this.updateDetune(channel, note, scheduleTime);
1916
- }
1952
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1953
+ this.setFilterEnvelope(channel, note, scheduleTime);
1954
+ this.setPitchEnvelope(note, scheduleTime);
1955
+ this.updateDetune(channel, note, scheduleTime);
1917
1956
  }
1918
1957
  });
1919
1958
  }
@@ -1983,13 +2022,13 @@ class MidyGM2 {
1983
2022
  .setValueAtTime(volume * gainRight, scheduleTime);
1984
2023
  }
1985
2024
  updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
1986
- const state = channel.state;
1987
- const defaultVolume = state.volume * state.expression;
1988
- const defaultPan = state.pan;
1989
2025
  const gainL = channel.keyBasedGainLs[keyNumber];
1990
- const gainR = channel.keyBasedGainRs[keyNumber];
1991
2026
  if (!gainL)
1992
2027
  return;
2028
+ const gainR = channel.keyBasedGainRs[keyNumber];
2029
+ const state = channel.state;
2030
+ const defaultVolume = state.volume * state.expression;
2031
+ const defaultPan = state.pan;
1993
2032
  const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
1994
2033
  const volume = (0 <= keyBasedVolume)
1995
2034
  ? defaultVolume * keyBasedVolume / 64
@@ -2019,6 +2058,9 @@ class MidyGM2 {
2019
2058
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
2020
2059
  }
2021
2060
  }
2061
+ isPortamento(channel, note) {
2062
+ return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
2063
+ }
2022
2064
  setPortamento(channelNumber, value, scheduleTime) {
2023
2065
  const channel = this.channels[channelNumber];
2024
2066
  if (channel.isDrum)
@@ -2055,7 +2097,7 @@ class MidyGM2 {
2055
2097
  scheduleTime ??= this.audioContext.currentTime;
2056
2098
  state.softPedal = softPedal / 127;
2057
2099
  this.processScheduledNotes(channel, (note) => {
2058
- if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2100
+ if (this.isPortamento(channel, note)) {
2059
2101
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2060
2102
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2061
2103
  }
@@ -2141,8 +2183,8 @@ class MidyGM2 {
2141
2183
  }
2142
2184
  handlePitchBendRangeRPN(channelNumber, scheduleTime) {
2143
2185
  const channel = this.channels[channelNumber];
2144
- this.limitData(channel, 0, 127, 0, 99);
2145
- const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
2186
+ this.limitData(channel, 0, 127, 0, 127);
2187
+ const pitchBendRange = (channel.dataMSB + channel.dataLSB / 128) * 100;
2146
2188
  this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
2147
2189
  }
2148
2190
  setPitchBendRange(channelNumber, value, scheduleTime) {
@@ -2152,7 +2194,7 @@ class MidyGM2 {
2152
2194
  scheduleTime ??= this.audioContext.currentTime;
2153
2195
  const state = channel.state;
2154
2196
  const prev = state.pitchWheelSensitivity;
2155
- const next = value / 128;
2197
+ const next = value / 12800;
2156
2198
  state.pitchWheelSensitivity = next;
2157
2199
  channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
2158
2200
  this.updateChannelDetune(channel, scheduleTime);
@@ -2161,7 +2203,8 @@ class MidyGM2 {
2161
2203
  handleFineTuningRPN(channelNumber, scheduleTime) {
2162
2204
  const channel = this.channels[channelNumber];
2163
2205
  this.limitData(channel, 0, 127, 0, 127);
2164
- const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
2206
+ const value = channel.dataMSB * 128 + channel.dataLSB;
2207
+ const fineTuning = (value - 8192) / 8192 * 100;
2165
2208
  this.setFineTuning(channelNumber, fineTuning, scheduleTime);
2166
2209
  }
2167
2210
  setFineTuning(channelNumber, value, scheduleTime) {
@@ -2170,7 +2213,7 @@ class MidyGM2 {
2170
2213
  return;
2171
2214
  scheduleTime ??= this.audioContext.currentTime;
2172
2215
  const prev = channel.fineTuning;
2173
- const next = (value - 8192) / 8.192; // cent
2216
+ const next = value;
2174
2217
  channel.fineTuning = next;
2175
2218
  channel.detune += next - prev;
2176
2219
  this.updateChannelDetune(channel, scheduleTime);
@@ -2178,7 +2221,7 @@ class MidyGM2 {
2178
2221
  handleCoarseTuningRPN(channelNumber, scheduleTime) {
2179
2222
  const channel = this.channels[channelNumber];
2180
2223
  this.limitDataMSB(channel, 0, 127);
2181
- const coarseTuning = channel.dataMSB;
2224
+ const coarseTuning = (channel.dataMSB - 64) * 100;
2182
2225
  this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
2183
2226
  }
2184
2227
  setCoarseTuning(channelNumber, value, scheduleTime) {
@@ -2187,7 +2230,7 @@ class MidyGM2 {
2187
2230
  return;
2188
2231
  scheduleTime ??= this.audioContext.currentTime;
2189
2232
  const prev = channel.coarseTuning;
2190
- const next = (value - 64) * 100; // cent
2233
+ const next = value;
2191
2234
  channel.coarseTuning = next;
2192
2235
  channel.detune += next - prev;
2193
2236
  this.updateChannelDetune(channel, scheduleTime);
@@ -2195,22 +2238,22 @@ class MidyGM2 {
2195
2238
  handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
2196
2239
  const channel = this.channels[channelNumber];
2197
2240
  this.limitData(channel, 0, 127, 0, 127);
2198
- const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
2199
- this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
2241
+ const value = (channel.dataMSB + channel.dataLSB / 128) * 100;
2242
+ this.setModulationDepthRange(channelNumber, value, scheduleTime);
2200
2243
  }
2201
- setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2244
+ setModulationDepthRange(channelNumber, value, scheduleTime) {
2202
2245
  const channel = this.channels[channelNumber];
2203
2246
  if (channel.isDrum)
2204
2247
  return;
2205
2248
  scheduleTime ??= this.audioContext.currentTime;
2206
- channel.modulationDepthRange = modulationDepthRange;
2249
+ channel.modulationDepthRange = value;
2207
2250
  this.updateModulation(channel, scheduleTime);
2208
2251
  }
2209
2252
  allSoundOff(channelNumber, _value, scheduleTime) {
2210
2253
  scheduleTime ??= this.audioContext.currentTime;
2211
2254
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2212
2255
  }
2213
- resetAllStates(channelNumber) {
2256
+ resetChannelStates(channelNumber) {
2214
2257
  const scheduleTime = this.audioContext.currentTime;
2215
2258
  const channel = this.channels[channelNumber];
2216
2259
  const state = channel.state;
@@ -2228,8 +2271,8 @@ class MidyGM2 {
2228
2271
  }
2229
2272
  this.resetChannelTable(channel);
2230
2273
  this.mode = "GM2";
2231
- this.masterFineTuning = 0; // cb
2232
- this.masterCoarseTuning = 0; // cb
2274
+ this.masterFineTuning = 0; // cent
2275
+ this.masterCoarseTuning = 0; // cent
2233
2276
  }
2234
2277
  // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/rp15.pdf
2235
2278
  resetAllControllers(channelNumber, _value, scheduleTime) {
@@ -2322,11 +2365,9 @@ class MidyGM2 {
2322
2365
  const channel = this.channels[i];
2323
2366
  channel.bankMSB = 0;
2324
2367
  channel.bankLSB = 0;
2325
- channel.bank = 0;
2326
2368
  channel.isDrum = false;
2327
2369
  }
2328
2370
  this.channels[9].bankMSB = 1;
2329
- this.channels[9].bank = 128;
2330
2371
  this.channels[9].isDrum = true;
2331
2372
  }
2332
2373
  GM2SystemOn(scheduleTime) {
@@ -2337,11 +2378,9 @@ class MidyGM2 {
2337
2378
  const channel = this.channels[i];
2338
2379
  channel.bankMSB = 121;
2339
2380
  channel.bankLSB = 0;
2340
- channel.bank = 121 * 128;
2341
2381
  channel.isDrum = false;
2342
2382
  }
2343
2383
  this.channels[9].bankMSB = 120;
2344
- this.channels[9].bank = 120 * 128;
2345
2384
  this.channels[9].isDrum = true;
2346
2385
  }
2347
2386
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -2386,24 +2425,20 @@ class MidyGM2 {
2386
2425
  const volume = (data[5] * 128 + data[4]) / 16383;
2387
2426
  this.setMasterVolume(volume, scheduleTime);
2388
2427
  }
2389
- setMasterVolume(volume, scheduleTime) {
2428
+ setMasterVolume(value, scheduleTime) {
2390
2429
  scheduleTime ??= this.audioContext.currentTime;
2391
- if (volume < 0 && 1 < volume) {
2392
- console.error("Master Volume is out of range");
2393
- }
2394
- else {
2395
- this.masterVolume.gain
2396
- .cancelScheduledValues(scheduleTime)
2397
- .setValueAtTime(volume * volume, scheduleTime);
2398
- }
2430
+ this.masterVolume.gain
2431
+ .cancelScheduledValues(scheduleTime)
2432
+ .setValueAtTime(value * value, scheduleTime);
2399
2433
  }
2400
2434
  handleMasterFineTuningSysEx(data, scheduleTime) {
2401
- const fineTuning = data[5] * 128 + data[4];
2435
+ const value = (data[5] * 128 + data[4]) / 16383;
2436
+ const fineTuning = (value - 8192) / 8192 * 100;
2402
2437
  this.setMasterFineTuning(fineTuning, scheduleTime);
2403
2438
  }
2404
2439
  setMasterFineTuning(value, scheduleTime) {
2405
2440
  const prev = this.masterFineTuning;
2406
- const next = (value - 8192) / 8.192; // cent
2441
+ const next = value;
2407
2442
  this.masterFineTuning = next;
2408
2443
  const detuneChange = next - prev;
2409
2444
  for (let i = 0; i < this.channels.length; i++) {
@@ -2415,12 +2450,12 @@ class MidyGM2 {
2415
2450
  }
2416
2451
  }
2417
2452
  handleMasterCoarseTuningSysEx(data, scheduleTime) {
2418
- const coarseTuning = data[4];
2453
+ const coarseTuning = (data[4] - 64) * 100;
2419
2454
  this.setMasterCoarseTuning(coarseTuning, scheduleTime);
2420
2455
  }
2421
2456
  setMasterCoarseTuning(value, scheduleTime) {
2422
2457
  const prev = this.masterCoarseTuning;
2423
- const next = (value - 64) * 100; // cent
2458
+ const next = value;
2424
2459
  this.masterCoarseTuning = next;
2425
2460
  const detuneChange = next - prev;
2426
2461
  for (let i = 0; i < this.channels.length; i++) {
@@ -2754,29 +2789,40 @@ class MidyGM2 {
2754
2789
  }
2755
2790
  getKeyBasedValue(channel, keyNumber, controllerType) {
2756
2791
  const index = keyNumber * 128 + controllerType;
2757
- const controlValue = channel.keyBasedInstrumentControlTable[index];
2792
+ const controlValue = channel.keyBasedTable[index];
2758
2793
  return controlValue;
2759
2794
  }
2795
+ createKeyBasedControllerHandlers() {
2796
+ const handlers = new Array(128);
2797
+ handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
2798
+ handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
2799
+ handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
2800
+ if (note.noteNumber === keyNumber) {
2801
+ this.setReverbSend(channel, note, scheduleTime);
2802
+ }
2803
+ });
2804
+ handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
2805
+ if (note.noteNumber === keyNumber) {
2806
+ this.setChorusSend(channel, note, scheduleTime);
2807
+ }
2808
+ });
2809
+ return handlers;
2810
+ }
2760
2811
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
2761
2812
  const channelNumber = data[4];
2762
2813
  const channel = this.channels[channelNumber];
2763
2814
  if (!channel.isDrum)
2764
2815
  return;
2765
2816
  const keyNumber = data[5];
2766
- const table = channel.keyBasedInstrumentControlTable;
2817
+ const table = channel.keyBasedTable;
2767
2818
  for (let i = 6; i < data.length; i += 2) {
2768
2819
  const controllerType = data[i];
2769
2820
  const value = data[i + 1];
2770
2821
  const index = keyNumber * 128 + controllerType;
2771
2822
  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
- }
2823
+ const handler = this.keyBasedControllerHandlers[controllerType];
2824
+ if (handler)
2825
+ handler(channel, keyNumber, scheduleTime);
2780
2826
  }
2781
2827
  }
2782
2828
  handleSysEx(data, scheduleTime) {
@@ -2818,7 +2864,6 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2818
2864
  scheduleIndex: 0,
2819
2865
  detune: 0,
2820
2866
  programNumber: 0,
2821
- bank: 121 * 128,
2822
2867
  bankMSB: 121,
2823
2868
  bankLSB: 0,
2824
2869
  dataMSB: 0,
@@ -2827,7 +2872,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2827
2872
  rpnLSB: 127,
2828
2873
  mono: false, // CC#124, CC#125
2829
2874
  modulationDepthRange: 50, // cent
2830
- fineTuning: 0, // cb
2831
- coarseTuning: 0, // cb
2875
+ fineTuning: 0, // cent
2876
+ coarseTuning: 0, // cent
2832
2877
  }
2833
2878
  });