@marmooo/midy 0.3.7 → 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/script/midy.js CHANGED
@@ -290,6 +290,18 @@ class Midy {
290
290
  writable: true,
291
291
  value: 0
292
292
  });
293
+ Object.defineProperty(this, "lastActiveSensing", {
294
+ enumerable: true,
295
+ configurable: true,
296
+ writable: true,
297
+ value: 0
298
+ });
299
+ Object.defineProperty(this, "activeSensingThreshold", {
300
+ enumerable: true,
301
+ configurable: true,
302
+ writable: true,
303
+ value: 0.3
304
+ });
293
305
  Object.defineProperty(this, "noteCheckInterval", {
294
306
  enumerable: true,
295
307
  configurable: true,
@@ -330,7 +342,7 @@ class Midy {
330
342
  enumerable: true,
331
343
  configurable: true,
332
344
  writable: true,
333
- value: this.initSoundFontTable()
345
+ value: Array.from({ length: 128 }, () => [])
334
346
  });
335
347
  Object.defineProperty(this, "voiceCounter", {
336
348
  enumerable: true,
@@ -417,8 +429,10 @@ class Midy {
417
429
  length: 1,
418
430
  sampleRate: audioContext.sampleRate,
419
431
  });
432
+ this.messageHandlers = this.createMessageHandlers();
420
433
  this.voiceParamsHandlers = this.createVoiceParamsHandlers();
421
434
  this.controlChangeHandlers = this.createControlChangeHandlers();
435
+ this.keyBasedControllerHandlers = this.createKeyBasedControllerHandlers();
422
436
  this.channels = this.createChannels(audioContext);
423
437
  this.reverbEffect = this.createReverbEffect(audioContext);
424
438
  this.chorusEffect = this.createChorusEffect(audioContext);
@@ -428,21 +442,14 @@ class Midy {
428
442
  this.scheduler.connect(audioContext.destination);
429
443
  this.GM2SystemOn();
430
444
  }
431
- initSoundFontTable() {
432
- const table = new Array(128);
433
- for (let i = 0; i < 128; i++) {
434
- table[i] = new Map();
435
- }
436
- return table;
437
- }
438
445
  addSoundFont(soundFont) {
439
446
  const index = this.soundFonts.length;
440
447
  this.soundFonts.push(soundFont);
441
448
  const presetHeaders = soundFont.parsed.presetHeaders;
449
+ const soundFontTable = this.soundFontTable;
442
450
  for (let i = 0; i < presetHeaders.length; i++) {
443
- const presetHeader = presetHeaders[i];
444
- const banks = this.soundFontTable[presetHeader.preset];
445
- banks.set(presetHeader.bank, index);
451
+ const { preset, bank } = presetHeaders[i];
452
+ soundFontTable[preset][bank] = index;
446
453
  }
447
454
  }
448
455
  async toUint8Array(input) {
@@ -520,13 +527,17 @@ class Midy {
520
527
  this.GM2SystemOn();
521
528
  }
522
529
  getVoiceId(channel, noteNumber, velocity) {
523
- const bankNumber = this.calcBank(channel);
524
- const soundFontIndex = this.soundFontTable[channel.programNumber]
525
- .get(bankNumber);
530
+ const programNumber = channel.programNumber;
531
+ const bankTable = this.soundFontTable[programNumber];
532
+ if (!bankTable)
533
+ return;
534
+ const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
535
+ const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
536
+ const soundFontIndex = bankTable[bank];
526
537
  if (soundFontIndex === undefined)
527
538
  return;
528
539
  const soundFont = this.soundFonts[soundFontIndex];
529
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
540
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
530
541
  const { instrument, sampleID } = voice.generators;
531
542
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
532
543
  }
@@ -597,13 +608,16 @@ class Midy {
597
608
  }
598
609
  return bufferSource;
599
610
  }
600
- async scheduleTimelineEvents(t, resumeTime, queueIndex) {
601
- while (queueIndex < this.timeline.length) {
602
- const event = this.timeline[queueIndex];
603
- if (event.startTime > t + this.lookAhead)
611
+ async scheduleTimelineEvents(scheduleTime, queueIndex) {
612
+ const timeOffset = this.resumeTime - this.startTime;
613
+ const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
614
+ const schedulingOffset = this.startDelay - timeOffset;
615
+ const timeline = this.timeline;
616
+ while (queueIndex < timeline.length) {
617
+ const event = timeline[queueIndex];
618
+ if (lookAheadCheckTime < event.startTime)
604
619
  break;
605
- const delay = this.startDelay - resumeTime;
606
- const startTime = event.startTime + delay;
620
+ const startTime = event.startTime + schedulingOffset;
607
621
  switch (event.type) {
608
622
  case "noteOn":
609
623
  await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
@@ -681,13 +695,18 @@ class Midy {
681
695
  this.isPaused = false;
682
696
  this.startTime = this.audioContext.currentTime;
683
697
  let queueIndex = this.getQueueIndex(this.resumeTime);
684
- let resumeTime = this.resumeTime - this.startTime;
685
698
  let finished = false;
686
699
  this.notePromises = [];
687
700
  while (queueIndex < this.timeline.length) {
688
701
  const now = this.audioContext.currentTime;
689
- const t = now + resumeTime;
690
- queueIndex = await this.scheduleTimelineEvents(t, resumeTime, queueIndex);
702
+ if (0 < this.lastActiveSensing &&
703
+ this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
704
+ await this.stopNotes(0, true, now);
705
+ await this.audioContext.suspend();
706
+ finished = true;
707
+ break;
708
+ }
709
+ queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
691
710
  if (this.isPausing) {
692
711
  await this.stopNotes(0, true, now);
693
712
  await this.audioContext.suspend();
@@ -706,7 +725,6 @@ class Midy {
706
725
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
707
726
  this.updateStates(queueIndex, nextQueueIndex);
708
727
  queueIndex = nextQueueIndex;
709
- resumeTime = this.resumeTime - this.startTime;
710
728
  this.isSeeking = false;
711
729
  continue;
712
730
  }
@@ -716,6 +734,7 @@ class Midy {
716
734
  if (finished) {
717
735
  this.notePromises = [];
718
736
  this.resetAllStates();
737
+ this.lastActiveSensing = 0;
719
738
  }
720
739
  this.isPlaying = false;
721
740
  }
@@ -725,17 +744,17 @@ class Midy {
725
744
  secondToTicks(second, secondsPerBeat) {
726
745
  return second * this.ticksPerBeat / secondsPerBeat;
727
746
  }
747
+ getSoundFontId(channel) {
748
+ const programNumber = channel.programNumber;
749
+ const bankNumber = channel.isDrum ? 128 : channel.bankLSB;
750
+ const bank = bankNumber.toString().padStart(3, "0");
751
+ const program = programNumber.toString().padStart(3, "0");
752
+ return `${bank}:${program}`;
753
+ }
728
754
  extractMidiData(midi) {
729
755
  const instruments = new Set();
730
756
  const timeline = [];
731
- const tmpChannels = new Array(this.channels.length);
732
- for (let i = 0; i < tmpChannels.length; i++) {
733
- tmpChannels[i] = {
734
- programNumber: -1,
735
- bankMSB: this.channels[i].bankMSB,
736
- bankLSB: this.channels[i].bankLSB,
737
- };
738
- }
757
+ const channels = this.channels;
739
758
  for (let i = 0; i < midi.tracks.length; i++) {
740
759
  const track = midi.tracks[i];
741
760
  let currentTicks = 0;
@@ -745,48 +764,40 @@ class Midy {
745
764
  event.ticks = currentTicks;
746
765
  switch (event.type) {
747
766
  case "noteOn": {
748
- const channel = tmpChannels[event.channel];
749
- if (channel.programNumber < 0) {
750
- channel.programNumber = event.programNumber;
751
- switch (channel.bankMSB) {
752
- case 120:
753
- instruments.add(`128:0`);
754
- break;
755
- case 121:
756
- instruments.add(`${channel.bankLSB}:0`);
757
- break;
758
- default: {
759
- const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
760
- instruments.add(`${bankNumber}:0`);
761
- }
762
- }
763
- channel.programNumber = 0;
764
- }
767
+ const channel = channels[event.channel];
768
+ instruments.add(this.getSoundFontId(channel));
765
769
  break;
766
770
  }
767
771
  case "controller":
768
772
  switch (event.controllerType) {
769
773
  case 0:
770
- tmpChannels[event.channel].bankMSB = event.value;
774
+ this.setBankMSB(event.channel, event.value);
771
775
  break;
772
776
  case 32:
773
- tmpChannels[event.channel].bankLSB = event.value;
777
+ this.setBankLSB(event.channel, event.value);
774
778
  break;
775
779
  }
776
780
  break;
777
781
  case "programChange": {
778
- const channel = tmpChannels[event.channel];
779
- channel.programNumber = event.programNumber;
780
- switch (channel.bankMSB) {
781
- case 120:
782
- instruments.add(`128:${channel.programNumber}`);
783
- break;
784
- case 121:
785
- instruments.add(`${channel.bankLSB}:${channel.programNumber}`);
786
- break;
787
- default: {
788
- const bankNumber = channel.bankMSB * 128 + channel.bankLSB;
789
- instruments.add(`${bankNumber}:${channel.programNumber}`);
782
+ const channel = channels[event.channel];
783
+ this.setProgramChange(event.channel, event.programNumber);
784
+ instruments.add(this.getSoundFontId(channel));
785
+ break;
786
+ }
787
+ case "sysEx": {
788
+ const data = event.data;
789
+ if (data[0] === 126 && data[1] === 9 && data[2] === 3) {
790
+ switch (data[3]) {
791
+ case 1:
792
+ this.GM1SystemOn(scheduleTime);
793
+ break;
794
+ case 2: // GM System Off
795
+ break;
796
+ case 3:
797
+ this.GM2SystemOn(scheduleTime);
798
+ break;
799
+ default:
800
+ console.warn(`Unsupported Exclusive Message: ${data}`);
790
801
  }
791
802
  }
792
803
  }
@@ -1110,7 +1121,7 @@ class Midy {
1110
1121
  const noteDetune = this.calcNoteDetune(channel, note);
1111
1122
  const pitchControl = this.getPitchControl(channel, note);
1112
1123
  const detune = channel.detune + noteDetune + pitchControl;
1113
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1124
+ if (this.isPortamento(channel, note)) {
1114
1125
  const startTime = note.startTime;
1115
1126
  const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1116
1127
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
@@ -1366,7 +1377,7 @@ class Midy {
1366
1377
  if (prevNote && prevNote.noteNumber !== noteNumber) {
1367
1378
  note.portamentoNoteNumber = prevNote.noteNumber;
1368
1379
  }
1369
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1380
+ if (!channel.isDrum && this.isPortamento(channel, note)) {
1370
1381
  this.setPortamentoVolumeEnvelope(channel, note, now);
1371
1382
  this.setPortamentoFilterEnvelope(channel, note, now);
1372
1383
  this.setPortamentoPitchEnvelope(note, now);
@@ -1394,22 +1405,6 @@ class Midy {
1394
1405
  note.bufferSource.start(startTime);
1395
1406
  return note;
1396
1407
  }
1397
- calcBank(channel) {
1398
- switch (this.mode) {
1399
- case "GM1":
1400
- if (channel.isDrum)
1401
- return 128;
1402
- return 0;
1403
- case "GM2":
1404
- if (channel.bankMSB === 121)
1405
- return 0;
1406
- if (channel.isDrum)
1407
- return 128;
1408
- return channel.bank;
1409
- default:
1410
- return channel.bank;
1411
- }
1412
- }
1413
1408
  handleExclusiveClass(note, channelNumber, startTime) {
1414
1409
  const exclusiveClass = note.voiceParams.exclusiveClass;
1415
1410
  if (exclusiveClass === 0)
@@ -1445,13 +1440,17 @@ class Midy {
1445
1440
  }
1446
1441
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime) {
1447
1442
  const channel = this.channels[channelNumber];
1448
- const bankNumber = this.calcBank(channel, channelNumber);
1449
- const soundFontIndex = this.soundFontTable[channel.programNumber]
1450
- .get(bankNumber);
1443
+ const programNumber = channel.programNumber;
1444
+ const bankTable = this.soundFontTable[programNumber];
1445
+ if (!bankTable)
1446
+ return;
1447
+ const bankLSB = channel.isDrum ? 128 : channel.bankLSB;
1448
+ const bank = bankTable[bankLSB] !== undefined ? bankLSB : 0;
1449
+ const soundFontIndex = bankTable[bank];
1451
1450
  if (soundFontIndex === undefined)
1452
1451
  return;
1453
1452
  const soundFont = this.soundFonts[soundFontIndex];
1454
- const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
1453
+ const voice = soundFont.getVoice(bank, programNumber, noteNumber, velocity);
1455
1454
  if (!voice)
1456
1455
  return;
1457
1456
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
@@ -1604,27 +1603,42 @@ class Midy {
1604
1603
  channel.sostenutoNotes = [];
1605
1604
  return promises;
1606
1605
  }
1607
- handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1608
- const channelNumber = statusByte & 0x0F;
1609
- const messageType = statusByte & 0xF0;
1610
- switch (messageType) {
1611
- case 0x80:
1612
- return this.noteOff(channelNumber, data1, data2, scheduleTime);
1613
- case 0x90:
1614
- return this.noteOn(channelNumber, data1, data2, scheduleTime);
1615
- case 0xA0:
1616
- return this.setPolyphonicKeyPressure(channelNumber, data1, data2, scheduleTime);
1617
- case 0xB0:
1618
- return this.setControlChange(channelNumber, data1, data2, scheduleTime);
1619
- case 0xC0:
1620
- return this.setProgramChange(channelNumber, data1, scheduleTime);
1621
- case 0xD0:
1622
- return this.setChannelPressure(channelNumber, data1, scheduleTime);
1623
- case 0xE0:
1624
- return this.handlePitchBendMessage(channelNumber, data1, data2, scheduleTime);
1625
- default:
1626
- console.warn(`Unsupported MIDI message: ${messageType.toString(16)}`);
1606
+ createMessageHandlers() {
1607
+ const handlers = new Array(256);
1608
+ // Channel Message
1609
+ handlers[0x80] = (data, scheduleTime) => this.noteOff(data[0] & 0x0F, data[1], data[2], scheduleTime);
1610
+ handlers[0x90] = (data, scheduleTime) => this.noteOn(data[0] & 0x0F, data[1], data[2], scheduleTime);
1611
+ handlers[0xA0] = (data, scheduleTime) => this.setPolyphonicKeyPressure(data[0] & 0x0F, data[1], data[2], scheduleTime);
1612
+ handlers[0xB0] = (data, scheduleTime) => this.setControlChange(data[0] & 0x0F, data[1], data[2], scheduleTime);
1613
+ handlers[0xC0] = (data, scheduleTime) => this.setProgramChange(data[0] & 0x0F, data[1], scheduleTime);
1614
+ handlers[0xD0] = (data, scheduleTime) => this.setChannelPressure(data[0] & 0x0F, data[1], scheduleTime);
1615
+ handlers[0xE0] = (data, scheduleTime) => this.handlePitchBendMessage(data[0] & 0x0F, data[1], data[2], scheduleTime);
1616
+ // System Common Message
1617
+ // handlers[0xF1] = (_data, _scheduleTime) => {}; // MTC Quarter Frame
1618
+ // handlers[0xF2] = (_data, _scheduleTime) => {}; // Song Position Pointer
1619
+ // handlers[0xF3] = (_data, _scheduleTime) => {}; // Song Select
1620
+ // handlers[0xF6] = (_data, _scheduleTime) => {}; // Tune Request
1621
+ // handlers[0xF7] = (_data, _scheduleTime) => {}; // End of Exclusive (EOX)
1622
+ // System Real Time Message
1623
+ // handlers[0xF8] = (_data, _scheduleTime) => {}; // Timing Clock
1624
+ // handlers[0xFA] = (_data, _scheduleTime) => {}; // Start
1625
+ // handlers[0xFB] = (_data, _scheduleTime) => {}; // Continue
1626
+ // handlers[0xFC] = (_data, _scheduleTime) => {}; // Stop
1627
+ handlers[0xFE] = (_data, _scheduleTime) => this.activeSensing();
1628
+ // handlers[0xFF] = (_data, _scheduleTime) => {}; // Reset
1629
+ return handlers;
1630
+ }
1631
+ handleMessage(data, scheduleTime) {
1632
+ const status = data[0];
1633
+ if (status === 0xF0) {
1634
+ return this.handleSysEx(data.subarray(1), scheduleTime);
1627
1635
  }
1636
+ const handler = this.messageHandlers[status];
1637
+ if (handler)
1638
+ handler(data, scheduleTime);
1639
+ }
1640
+ activeSensing() {
1641
+ this.lastActiveSensing = performance.now();
1628
1642
  }
1629
1643
  setPolyphonicKeyPressure(channelNumber, noteNumber, pressure, scheduleTime) {
1630
1644
  const channel = this.channels[channelNumber];
@@ -1639,19 +1653,18 @@ class Midy {
1639
1653
  }
1640
1654
  setProgramChange(channelNumber, programNumber, _scheduleTime) {
1641
1655
  const channel = this.channels[channelNumber];
1642
- channel.bank = channel.bankMSB * 128 + channel.bankLSB;
1643
1656
  channel.programNumber = programNumber;
1644
1657
  if (this.mode === "GM2") {
1645
1658
  switch (channel.bankMSB) {
1646
1659
  case 120:
1647
1660
  channel.isDrum = true;
1661
+ channel.keyBasedTable.fill(-1);
1648
1662
  break;
1649
1663
  case 121:
1650
1664
  channel.isDrum = false;
1651
1665
  break;
1652
1666
  }
1653
1667
  }
1654
- channel.keyBasedTable.fill(-1);
1655
1668
  }
1656
1669
  setChannelPressure(channelNumber, value, scheduleTime) {
1657
1670
  const channel = this.channels[channelNumber];
@@ -1988,22 +2001,20 @@ class Midy {
1988
2001
  this.updateModulation(channel, scheduleTime);
1989
2002
  }
1990
2003
  updatePortamento(channel, scheduleTime) {
2004
+ if (channel.isDrum)
2005
+ return;
1991
2006
  this.processScheduledNotes(channel, (note) => {
1992
- if (0.5 <= channel.state.portamento) {
1993
- if (0 <= note.portamentoNoteNumber) {
1994
- this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
1995
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
1996
- this.setPortamentoPitchEnvelope(note, scheduleTime);
1997
- this.updateDetune(channel, note, scheduleTime);
1998
- }
2007
+ if (this.isPortamento(channel, note)) {
2008
+ this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2009
+ this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2010
+ this.setPortamentoPitchEnvelope(note, scheduleTime);
2011
+ this.updateDetune(channel, note, scheduleTime);
1999
2012
  }
2000
2013
  else {
2001
- if (0 <= note.portamentoNoteNumber) {
2002
- this.setVolumeEnvelope(channel, note, scheduleTime);
2003
- this.setFilterEnvelope(channel, note, scheduleTime);
2004
- this.setPitchEnvelope(note, scheduleTime);
2005
- this.updateDetune(channel, note, scheduleTime);
2006
- }
2014
+ this.setVolumeEnvelope(channel, note, scheduleTime);
2015
+ this.setFilterEnvelope(channel, note, scheduleTime);
2016
+ this.setPitchEnvelope(note, scheduleTime);
2017
+ this.updateDetune(channel, note, scheduleTime);
2007
2018
  }
2008
2019
  });
2009
2020
  }
@@ -2109,6 +2120,9 @@ class Midy {
2109
2120
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
2110
2121
  }
2111
2122
  }
2123
+ isPortamento(channel, note) {
2124
+ return 0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber;
2125
+ }
2112
2126
  setPortamento(channelNumber, value, scheduleTime) {
2113
2127
  const channel = this.channels[channelNumber];
2114
2128
  if (channel.isDrum)
@@ -2145,7 +2159,7 @@ class Midy {
2145
2159
  scheduleTime ??= this.audioContext.currentTime;
2146
2160
  state.softPedal = softPedal / 127;
2147
2161
  this.processScheduledNotes(channel, (note) => {
2148
- if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2162
+ if (this.isPortamento(channel, note)) {
2149
2163
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2150
2164
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2151
2165
  }
@@ -2203,7 +2217,7 @@ class Midy {
2203
2217
  scheduleTime ??= this.audioContext.currentTime;
2204
2218
  state.brightness = brightness / 127;
2205
2219
  this.processScheduledNotes(channel, (note) => {
2206
- if (0.5 <= state.portamento && 0 <= note.portamentoNoteNumber) {
2220
+ if (this.isPortamento(channel, note)) {
2207
2221
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2208
2222
  }
2209
2223
  else {
@@ -2539,11 +2553,9 @@ class Midy {
2539
2553
  const channel = this.channels[i];
2540
2554
  channel.bankMSB = 0;
2541
2555
  channel.bankLSB = 0;
2542
- channel.bank = 0;
2543
2556
  channel.isDrum = false;
2544
2557
  }
2545
2558
  this.channels[9].bankMSB = 1;
2546
- this.channels[9].bank = 128;
2547
2559
  this.channels[9].isDrum = true;
2548
2560
  }
2549
2561
  GM2SystemOn(scheduleTime) {
@@ -2554,11 +2566,9 @@ class Midy {
2554
2566
  const channel = this.channels[i];
2555
2567
  channel.bankMSB = 121;
2556
2568
  channel.bankLSB = 0;
2557
- channel.bank = 121 * 128;
2558
2569
  channel.isDrum = false;
2559
2570
  }
2560
2571
  this.channels[9].bankMSB = 120;
2561
- this.channels[9].bank = 120 * 128;
2562
2572
  this.channels[9].isDrum = true;
2563
2573
  }
2564
2574
  handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
@@ -2616,16 +2626,11 @@ class Midy {
2616
2626
  const volume = (data[5] * 128 + data[4]) / 16383;
2617
2627
  this.setMasterVolume(volume, scheduleTime);
2618
2628
  }
2619
- setMasterVolume(volume, scheduleTime) {
2629
+ setMasterVolume(value, scheduleTime) {
2620
2630
  scheduleTime ??= this.audioContext.currentTime;
2621
- if (volume < 0 && 1 < volume) {
2622
- console.error("Master Volume is out of range");
2623
- }
2624
- else {
2625
- this.masterVolume.gain
2626
- .cancelScheduledValues(scheduleTime)
2627
- .setValueAtTime(volume * volume, scheduleTime);
2628
- }
2631
+ this.masterVolume.gain
2632
+ .cancelScheduledValues(scheduleTime)
2633
+ .setValueAtTime(value * value, scheduleTime);
2629
2634
  }
2630
2635
  handleMasterFineTuningSysEx(data, scheduleTime) {
2631
2636
  const value = (data[5] * 128 + data[4]) / 16383;
@@ -3040,6 +3045,70 @@ class Midy {
3040
3045
  const controlValue = channel.keyBasedTable[index];
3041
3046
  return controlValue;
3042
3047
  }
3048
+ createKeyBasedControllerHandlers() {
3049
+ const handlers = new Array(128);
3050
+ handlers[7] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
3051
+ handlers[10] = (channel, keyNumber, scheduleTime) => this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
3052
+ handlers[71] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3053
+ if (note.noteNumber === keyNumber) {
3054
+ const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
3055
+ const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
3056
+ note.filterNode.Q.setValueAtTime(Q, scheduleTime);
3057
+ }
3058
+ });
3059
+ handlers[73] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3060
+ if (note.noteNumber === keyNumber) {
3061
+ this.setVolumeEnvelope(channel, note, scheduleTime);
3062
+ }
3063
+ });
3064
+ handlers[74] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3065
+ if (note.noteNumber === keyNumber) {
3066
+ this.setFilterEnvelope(channel, note, scheduleTime);
3067
+ }
3068
+ });
3069
+ handlers[75] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3070
+ if (note.noteNumber === keyNumber) {
3071
+ this.setVolumeEnvelope(channel, note, scheduleTime);
3072
+ }
3073
+ });
3074
+ handlers[76] = (channel, keyNumber, scheduleTime) => {
3075
+ if (channel.state.vibratoDepth <= 0)
3076
+ return;
3077
+ this.processScheduledNotes(channel, (note) => {
3078
+ if (note.noteNumber === keyNumber) {
3079
+ this.setFreqVibLFO(channel, note, scheduleTime);
3080
+ }
3081
+ });
3082
+ };
3083
+ handlers[77] = (channel, keyNumber, scheduleTime) => {
3084
+ if (channel.state.vibratoDepth <= 0)
3085
+ return;
3086
+ this.processScheduledNotes(channel, (note) => {
3087
+ if (note.noteNumber === keyNumber) {
3088
+ this.setVibLfoToPitch(channel, note, scheduleTime);
3089
+ }
3090
+ });
3091
+ };
3092
+ handlers[78] = (channel, keyNumber) => {
3093
+ if (channel.state.vibratoDepth <= 0)
3094
+ return;
3095
+ this.processScheduledNotes(channel, (note) => {
3096
+ if (note.noteNumber === keyNumber)
3097
+ this.setDelayVibLFO(channel, note);
3098
+ });
3099
+ };
3100
+ handlers[91] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3101
+ if (note.noteNumber === keyNumber) {
3102
+ this.setReverbSend(channel, note, scheduleTime);
3103
+ }
3104
+ });
3105
+ handlers[93] = (channel, keyNumber, scheduleTime) => this.processScheduledNotes(channel, (note) => {
3106
+ if (note.noteNumber === keyNumber) {
3107
+ this.setChorusSend(channel, note, scheduleTime);
3108
+ }
3109
+ });
3110
+ return handlers;
3111
+ }
3043
3112
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
3044
3113
  const channelNumber = data[4];
3045
3114
  const channel = this.channels[channelNumber];
@@ -3052,95 +3121,9 @@ class Midy {
3052
3121
  const value = data[i + 1];
3053
3122
  const index = keyNumber * 128 + controllerType;
3054
3123
  table[index] = value;
3055
- switch (controllerType) {
3056
- case 7:
3057
- case 10:
3058
- this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
3059
- break;
3060
- case 71:
3061
- this.processScheduledNotes(channel, (note) => {
3062
- const filterResonance = this.getRelativeKeyBasedValue(channel, note, 71);
3063
- if (note.noteNumber === keyNumber) {
3064
- const Q = note.voiceParams.initialFilterQ / 5 * filterResonance;
3065
- note.filterNode.Q.setValueAtTime(Q, scheduleTime);
3066
- }
3067
- });
3068
- break;
3069
- case 73:
3070
- this.processScheduledNotes(channel, (note) => {
3071
- if (note.noteNumber === keyNumber) {
3072
- if (0.5 <= channel.state.portamento &&
3073
- 0 <= note.portamentoNoteNumber) {
3074
- this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
3075
- }
3076
- else {
3077
- this.setVolumeEnvelope(channel, note, scheduleTime);
3078
- }
3079
- }
3080
- });
3081
- break;
3082
- case 74:
3083
- this.processScheduledNotes(channel, (note) => {
3084
- if (note.noteNumber === keyNumber) {
3085
- if (0.5 <= channel.state.portamento &&
3086
- 0 <= note.portamentoNoteNumber) {
3087
- this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
3088
- }
3089
- else {
3090
- this.setFilterEnvelope(channel, note, scheduleTime);
3091
- }
3092
- }
3093
- });
3094
- break;
3095
- case 75:
3096
- this.processScheduledNotes(channel, (note) => {
3097
- if (note.noteNumber === keyNumber) {
3098
- this.setVolumeEnvelope(channel, note, scheduleTime);
3099
- }
3100
- });
3101
- break;
3102
- case 76:
3103
- if (channel.state.vibratoDepth <= 0)
3104
- break;
3105
- this.processScheduledNotes(channel, (note) => {
3106
- if (note.noteNumber === keyNumber) {
3107
- this.setFreqVibLFO(channel, note, scheduleTime);
3108
- }
3109
- });
3110
- break;
3111
- case 77:
3112
- if (channel.state.vibratoDepth <= 0)
3113
- break;
3114
- this.processScheduledNotes(channel, (note) => {
3115
- if (note.noteNumber === keyNumber) {
3116
- this.setVibLfoToPitch(channel, note, scheduleTime);
3117
- }
3118
- });
3119
- break;
3120
- case 78:
3121
- if (channel.state.vibratoDepth <= 0)
3122
- break;
3123
- this.processScheduledNotes(channel, (note) => {
3124
- if (note.noteNumber === keyNumber) {
3125
- this.setDelayVibLFO(channel, note);
3126
- }
3127
- });
3128
- break;
3129
- case 91:
3130
- this.processScheduledNotes(channel, (note) => {
3131
- if (note.noteNumber === keyNumber) {
3132
- this.setReverbSend(channel, note, scheduleTime);
3133
- }
3134
- });
3135
- break;
3136
- case 93:
3137
- this.processScheduledNotes(channel, (note) => {
3138
- if (note.noteNumber === keyNumber) {
3139
- this.setChorusSend(channel, note, scheduleTime);
3140
- }
3141
- });
3142
- break;
3143
- }
3124
+ const handler = this.keyBasedControllerHandlers[controllerType];
3125
+ if (handler)
3126
+ handler(channel, keyNumber, scheduleTime);
3144
3127
  }
3145
3128
  }
3146
3129
  handleSysEx(data, scheduleTime) {
@@ -3182,7 +3165,6 @@ Object.defineProperty(Midy, "channelSettings", {
3182
3165
  scheduleIndex: 0,
3183
3166
  detune: 0,
3184
3167
  programNumber: 0,
3185
- bank: 121 * 128,
3186
3168
  bankMSB: 121,
3187
3169
  bankLSB: 0,
3188
3170
  dataMSB: 0,