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