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