@marmooo/midy 0.3.2 → 0.3.3

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
@@ -11,11 +11,11 @@ class Note {
11
11
  writable: true,
12
12
  value: -1
13
13
  });
14
- Object.defineProperty(this, "noteOffEvent", {
14
+ Object.defineProperty(this, "ending", {
15
15
  enumerable: true,
16
16
  configurable: true,
17
17
  writable: true,
18
- value: void 0
18
+ value: false
19
19
  });
20
20
  Object.defineProperty(this, "bufferSource", {
21
21
  enumerable: true,
@@ -473,17 +473,37 @@ class Midy {
473
473
  }
474
474
  }
475
475
  }
476
- async loadSoundFont(soundFontUrl) {
477
- const response = await fetch(soundFontUrl);
478
- const arrayBuffer = await response.arrayBuffer();
479
- const parsed = (0, soundfont_parser_1.parse)(new Uint8Array(arrayBuffer));
476
+ async loadSoundFont(input) {
477
+ let uint8Array;
478
+ if (typeof input === "string") {
479
+ const response = await fetch(input);
480
+ const arrayBuffer = await response.arrayBuffer();
481
+ uint8Array = new Uint8Array(arrayBuffer);
482
+ }
483
+ else if (input instanceof Uint8Array) {
484
+ uint8Array = input;
485
+ }
486
+ else {
487
+ throw new TypeError("input must be a URL string or Uint8Array");
488
+ }
489
+ const parsed = (0, soundfont_parser_1.parse)(uint8Array);
480
490
  const soundFont = new soundfont_parser_1.SoundFont(parsed);
481
491
  this.addSoundFont(soundFont);
482
492
  }
483
- async loadMIDI(midiUrl) {
484
- const response = await fetch(midiUrl);
485
- const arrayBuffer = await response.arrayBuffer();
486
- const midi = (0, midi_file_1.parseMidi)(new Uint8Array(arrayBuffer));
493
+ async loadMIDI(input) {
494
+ let uint8Array;
495
+ if (typeof input === "string") {
496
+ const response = await fetch(input);
497
+ const arrayBuffer = await response.arrayBuffer();
498
+ uint8Array = new Uint8Array(arrayBuffer);
499
+ }
500
+ else if (input instanceof Uint8Array) {
501
+ uint8Array = input;
502
+ }
503
+ else {
504
+ throw new TypeError("input must be a URL string or Uint8Array");
505
+ }
506
+ const midi = (0, midi_file_1.parseMidi)(uint8Array);
487
507
  this.ticksPerBeat = midi.header.ticksPerBeat;
488
508
  const midiData = this.extractMidiData(midi);
489
509
  this.instruments = midiData.instruments;
@@ -509,7 +529,7 @@ class Midy {
509
529
  channel.scaleOctaveTuningTable.fill(0); // [-100, 100] cent
510
530
  channel.channelPressureTable.set([64, 64, 64, 0, 0, 0]);
511
531
  channel.polyphonicKeyPressureTable.set([64, 64, 64, 0, 0, 0]);
512
- channel.keyBasedInstrumentControlTable.fill(0); // [-64, 63]
532
+ channel.keyBasedInstrumentControlTable.fill(-1);
513
533
  }
514
534
  createChannels(audioContext) {
515
535
  const channels = Array.from({ length: this.numChannels }, () => {
@@ -526,7 +546,7 @@ class Midy {
526
546
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
527
547
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
528
548
  polyphonicKeyPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
529
- keyBasedInstrumentControlTable: new Int8Array(128 * 128), // [-64, 63]
549
+ keyBasedInstrumentControlTable: new Int8Array(128 * 128).fill(-1),
530
550
  };
531
551
  });
532
552
  return channels;
@@ -560,10 +580,18 @@ class Midy {
560
580
  return audioBuffer;
561
581
  }
562
582
  }
563
- createBufferSource(voiceParams, audioBuffer) {
583
+ isLoopDrum(channel, noteNumber) {
584
+ const programNumber = channel.programNumber;
585
+ return ((programNumber === 48 && noteNumber === 88) ||
586
+ (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
587
+ }
588
+ createBufferSource(channel, noteNumber, voiceParams, audioBuffer) {
564
589
  const bufferSource = new AudioBufferSourceNode(this.audioContext);
565
590
  bufferSource.buffer = audioBuffer;
566
591
  bufferSource.loop = voiceParams.sampleModes % 2 !== 0;
592
+ if (channel.isDrum) {
593
+ bufferSource.loop = this.isLoopDrum(channel, noteNumber);
594
+ }
567
595
  if (bufferSource.loop) {
568
596
  bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
569
597
  bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
@@ -578,12 +606,13 @@ class Midy {
578
606
  const delay = this.startDelay - resumeTime;
579
607
  const startTime = event.startTime + delay;
580
608
  switch (event.type) {
581
- case "noteOn": {
582
- const noteOffEvent = {
583
- ...event.noteOffEvent,
584
- startTime: event.noteOffEvent.startTime + delay,
585
- };
586
- await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime, noteOffEvent);
609
+ case "noteOn":
610
+ await this.scheduleNoteOn(event.channel, event.noteNumber, event.velocity, startTime);
611
+ break;
612
+ case "noteOff": {
613
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
614
+ if (notePromise)
615
+ this.notePromises.push(notePromise);
587
616
  break;
588
617
  }
589
618
  case "noteAftertouch":
@@ -691,6 +720,7 @@ class Midy {
691
720
  return `${programNumber}:${noteNumber}:${velocity}`;
692
721
  }
693
722
  extractMidiData(midi) {
723
+ this.audioBufferCounter.clear();
694
724
  const instruments = new Set();
695
725
  const timeline = [];
696
726
  const tmpChannels = new Array(this.channels.length);
@@ -790,38 +820,13 @@ class Midy {
790
820
  prevTempoTicks = event.ticks;
791
821
  }
792
822
  }
793
- const activeNotes = new Array(this.channels.length * 128);
794
- for (let i = 0; i < activeNotes.length; i++) {
795
- activeNotes[i] = [];
796
- }
797
- for (let i = 0; i < timeline.length; i++) {
798
- const event = timeline[i];
799
- switch (event.type) {
800
- case "noteOn": {
801
- const index = event.channel * 128 + event.noteNumber;
802
- activeNotes[index].push(event);
803
- break;
804
- }
805
- case "noteOff": {
806
- const index = event.channel * 128 + event.noteNumber;
807
- const noteOn = activeNotes[index].pop();
808
- if (noteOn) {
809
- noteOn.noteOffEvent = event;
810
- }
811
- else {
812
- const eventString = JSON.stringify(event, null, 2);
813
- console.warn(`noteOff without matching noteOn: ${eventString}`);
814
- }
815
- }
816
- }
817
- }
818
823
  return { instruments, timeline };
819
824
  }
820
825
  stopActiveNotes(channelNumber, velocity, force, scheduleTime) {
821
826
  const channel = this.channels[channelNumber];
822
827
  const promises = [];
823
828
  this.processActiveNotes(channel, scheduleTime, (note) => {
824
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
829
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
825
830
  this.notePromises.push(promise);
826
831
  promises.push(promise);
827
832
  });
@@ -831,7 +836,7 @@ class Midy {
831
836
  const channel = this.channels[channelNumber];
832
837
  const promises = [];
833
838
  this.processScheduledNotes(channel, (note) => {
834
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, force);
839
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force);
835
840
  this.notePromises.push(promise);
836
841
  promises.push(promise);
837
842
  });
@@ -908,9 +913,6 @@ class Midy {
908
913
  continue;
909
914
  if (note.ending)
910
915
  continue;
911
- const noteOffEvent = note.noteOffEvent;
912
- if (noteOffEvent && noteOffEvent.startTime < scheduleTime)
913
- continue;
914
916
  if (scheduleTime < note.startTime)
915
917
  continue;
916
918
  callback(note);
@@ -1223,9 +1225,8 @@ class Midy {
1223
1225
  }
1224
1226
  setPortamentoFilterEnvelope(channel, note, scheduleTime) {
1225
1227
  const state = channel.state;
1226
- const { voiceParams, noteNumber, startTime } = note;
1227
- const softPedalFactor = 1 -
1228
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1228
+ const { voiceParams, startTime } = note;
1229
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1229
1230
  const baseCent = voiceParams.initialFilterFc +
1230
1231
  this.getFilterCutoffControl(channel, note);
1231
1232
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1245,9 +1246,8 @@ class Midy {
1245
1246
  }
1246
1247
  setFilterEnvelope(channel, note, scheduleTime) {
1247
1248
  const state = channel.state;
1248
- const { voiceParams, noteNumber, startTime } = note;
1249
- const softPedalFactor = 1 -
1250
- (0.1 + (noteNumber / 127) * 0.2) * state.softPedal;
1249
+ const { voiceParams, startTime } = note;
1250
+ const softPedalFactor = this.getSoftPedalFactor(channel, note);
1251
1251
  const baseCent = voiceParams.initialFilterFc +
1252
1252
  this.getFilterCutoffControl(channel, note);
1253
1253
  const baseFreq = this.centToHz(baseCent) * softPedalFactor *
@@ -1328,7 +1328,7 @@ class Midy {
1328
1328
  const voiceParams = voice.getAllParams(controllerState);
1329
1329
  const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
1330
1330
  const audioBuffer = await this.getAudioBuffer(channel.programNumber, noteNumber, velocity, voiceParams, isSF3);
1331
- note.bufferSource = this.createBufferSource(voiceParams, audioBuffer);
1331
+ note.bufferSource = this.createBufferSource(channel, noteNumber, voiceParams, audioBuffer);
1332
1332
  note.volumeNode = new GainNode(this.audioContext);
1333
1333
  note.gainL = new GainNode(this.audioContext);
1334
1334
  note.gainR = new GainNode(this.audioContext);
@@ -1367,10 +1367,10 @@ class Midy {
1367
1367
  note.volumeEnvelopeNode.connect(note.volumeNode);
1368
1368
  note.volumeNode.connect(note.gainL);
1369
1369
  note.volumeNode.connect(note.gainR);
1370
- if (0 < channel.chorusSendLevel) {
1370
+ if (0 < state.chorusSendLevel) {
1371
1371
  this.setChorusEffectsSend(channel, note, 0, now);
1372
1372
  }
1373
- if (0 < channel.reverbSendLevel) {
1373
+ if (0 < state.reverbSendLevel) {
1374
1374
  this.setReverbEffectsSend(channel, note, 0, now);
1375
1375
  }
1376
1376
  note.bufferSource.start(startTime);
@@ -1400,7 +1400,7 @@ class Midy {
1400
1400
  if (prev) {
1401
1401
  const [prevNote, prevChannelNumber] = prev;
1402
1402
  if (prevNote && !prevNote.ending) {
1403
- this.scheduleNoteOff(prevChannelNumber, prevNote, 0, // velocity,
1403
+ this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
1404
1404
  startTime, true);
1405
1405
  }
1406
1406
  }
@@ -1420,18 +1420,11 @@ class Midy {
1420
1420
  channelNumber;
1421
1421
  const prevNote = this.drumExclusiveClassNotes[index];
1422
1422
  if (prevNote && !prevNote.ending) {
1423
- this.scheduleNoteOff(channelNumber, prevNote, 0, // velocity,
1423
+ this.scheduleNoteOff(channelNumber, prevNote.noteNumber, 0, // velocity,
1424
1424
  startTime, true);
1425
1425
  }
1426
1426
  this.drumExclusiveClassNotes[index] = note;
1427
1427
  }
1428
- isDrumNoteOffException(channel, noteNumber) {
1429
- if (!channel.isDrum)
1430
- return false;
1431
- const programNumber = channel.programNumber;
1432
- return !((programNumber === 48 && noteNumber === 88) ||
1433
- (programNumber === 56 && 47 <= noteNumber && noteNumber <= 84));
1434
- }
1435
1428
  async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, noteOffEvent) {
1436
1429
  const channel = this.channels[channelNumber];
1437
1430
  const bankNumber = this.calcBank(channel, channelNumber);
@@ -1455,31 +1448,6 @@ class Midy {
1455
1448
  const scheduledNotes = channel.scheduledNotes;
1456
1449
  note.index = scheduledNotes.length;
1457
1450
  scheduledNotes.push(note);
1458
- if (this.isDrumNoteOffException(channel, noteNumber)) {
1459
- const stopTime = startTime + note.bufferSource.buffer.duration;
1460
- const promise = new Promise((resolve) => {
1461
- note.bufferSource.onended = () => {
1462
- scheduledNotes[note.index] = undefined;
1463
- this.disconnectNote(note);
1464
- resolve();
1465
- };
1466
- note.bufferSource.stop(stopTime);
1467
- });
1468
- this.notePromises.push(promise);
1469
- }
1470
- else if (noteOffEvent) {
1471
- if (0.5 <= channel.state.portamento && 0 <= note.portamentoNoteNumber) {
1472
- const portamentoTime = this.getPortamentoTime(channel, note);
1473
- const portamentoEndTime = startTime + portamentoTime;
1474
- const notePromise = this.scheduleNoteOff(channelNumber, note, 0, // velocity
1475
- Math.max(noteOffEvent.startTime, portamentoEndTime), false);
1476
- this.notePromises.push(notePromise);
1477
- }
1478
- else {
1479
- const notePromise = this.scheduleNoteOff(channelNumber, note, noteOffEvent.velocity, noteOffEvent.startTime, false);
1480
- this.notePromises.push(notePromise);
1481
- }
1482
- }
1483
1451
  }
1484
1452
  noteOn(channelNumber, noteNumber, velocity, scheduleTime) {
1485
1453
  scheduleTime ??= this.audioContext.currentTime;
@@ -1508,42 +1476,48 @@ class Midy {
1508
1476
  note.chorusEffectsSend.disconnect();
1509
1477
  }
1510
1478
  }
1511
- stopNote(channel, note, endTime, stopTime) {
1479
+ releaseNote(channel, note, endTime) {
1480
+ const volRelease = endTime +
1481
+ note.voiceParams.volRelease * channel.state.releaseTime * 2;
1482
+ const modRelease = endTime + note.voiceParams.modRelease;
1483
+ const stopTime = Math.min(volRelease, modRelease);
1484
+ note.filterNode.frequency
1485
+ .cancelScheduledValues(endTime)
1486
+ .linearRampToValueAtTime(0, modRelease);
1512
1487
  note.volumeEnvelopeNode.gain
1513
1488
  .cancelScheduledValues(endTime)
1514
- .linearRampToValueAtTime(0, stopTime);
1515
- note.ending = true;
1516
- this.scheduleTask(() => {
1517
- note.bufferSource.loop = false;
1518
- }, stopTime);
1489
+ .linearRampToValueAtTime(0, volRelease);
1519
1490
  return new Promise((resolve) => {
1520
- note.bufferSource.onended = () => {
1521
- channel.scheduledNotes[note.index] = undefined;
1491
+ this.scheduleTask(() => {
1492
+ const bufferSource = note.bufferSource;
1493
+ bufferSource.loop = false;
1494
+ bufferSource.stop(stopTime);
1522
1495
  this.disconnectNote(note);
1496
+ channel.scheduledNotes[note.index] = undefined;
1523
1497
  resolve();
1524
- };
1525
- note.bufferSource.stop(stopTime);
1498
+ }, stopTime);
1526
1499
  });
1527
1500
  }
1528
- scheduleNoteOff(channelNumber, note, _velocity, endTime, force) {
1501
+ scheduleNoteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1529
1502
  const channel = this.channels[channelNumber];
1530
- if (this.isDrumNoteOffException(channel, note.noteNumber))
1531
- return;
1532
1503
  const state = channel.state;
1533
1504
  if (!force) {
1534
- if (0.5 <= state.sustainPedal)
1535
- return;
1536
- if (0.5 <= channel.state.sostenutoPedal)
1537
- return;
1505
+ if (channel.isDrum) {
1506
+ if (!this.isLoopDrum(channel, noteNumber))
1507
+ return;
1508
+ }
1509
+ else {
1510
+ if (0.5 <= state.sustainPedal)
1511
+ return;
1512
+ if (0.5 <= state.sostenutoPedal)
1513
+ return;
1514
+ }
1538
1515
  }
1539
- const volRelease = endTime +
1540
- note.voiceParams.volRelease * channel.state.releaseTime * 2;
1541
- const modRelease = endTime + note.voiceParams.modRelease;
1542
- note.filterNode.frequency
1543
- .cancelScheduledValues(endTime)
1544
- .linearRampToValueAtTime(0, modRelease);
1545
- const stopTime = Math.min(volRelease, modRelease);
1546
- return this.stopNote(channel, note, endTime, stopTime);
1516
+ const note = this.findNoteOffTarget(channel, noteNumber);
1517
+ if (!note)
1518
+ return;
1519
+ note.ending = true;
1520
+ this.releaseNote(channel, note, endTime);
1547
1521
  }
1548
1522
  findNoteOffTarget(channel, noteNumber) {
1549
1523
  const scheduledNotes = channel.scheduledNotes;
@@ -1560,16 +1534,14 @@ class Midy {
1560
1534
  }
1561
1535
  noteOff(channelNumber, noteNumber, velocity, scheduleTime) {
1562
1536
  scheduleTime ??= this.audioContext.currentTime;
1563
- const channel = this.channels[channelNumber];
1564
- const note = this.findNoteOffTarget(channel, noteNumber);
1565
- return this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime, false);
1537
+ return this.scheduleNoteOff(channelNumber, noteNumber, velocity, scheduleTime, false);
1566
1538
  }
1567
1539
  releaseSustainPedal(channelNumber, halfVelocity, scheduleTime) {
1568
1540
  const velocity = halfVelocity * 2;
1569
1541
  const channel = this.channels[channelNumber];
1570
1542
  const promises = [];
1571
1543
  for (let i = 0; i < channel.sustainNotes.length; i++) {
1572
- const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i], velocity, scheduleTime);
1544
+ const promise = this.scheduleNoteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1573
1545
  promises.push(promise);
1574
1546
  }
1575
1547
  channel.sustainNotes = [];
@@ -1583,7 +1555,7 @@ class Midy {
1583
1555
  channel.state.sostenutoPedal = 0;
1584
1556
  for (let i = 0; i < sostenutoNotes.length; i++) {
1585
1557
  const note = sostenutoNotes[i];
1586
- const promise = this.scheduleNoteOff(channelNumber, note, velocity, scheduleTime);
1558
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1587
1559
  promises.push(promise);
1588
1560
  }
1589
1561
  channel.sostenutoNotes = [];
@@ -1706,10 +1678,13 @@ class Midy {
1706
1678
  .setValueAtTime(volumeDepth, scheduleTime);
1707
1679
  }
1708
1680
  setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1681
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1682
+ let value = note.voiceParams.reverbEffectsSend;
1683
+ if (0 <= keyBasedValue) {
1684
+ value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1685
+ }
1709
1686
  if (0 < prevValue) {
1710
- if (0 < note.voiceParams.reverbEffectsSend) {
1711
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 91);
1712
- const value = note.voiceParams.reverbEffectsSend + keyBasedValue;
1687
+ if (0 < value) {
1713
1688
  note.reverbEffectsSend.gain
1714
1689
  .cancelScheduledValues(scheduleTime)
1715
1690
  .setValueAtTime(value, scheduleTime);
@@ -1719,10 +1694,10 @@ class Midy {
1719
1694
  }
1720
1695
  }
1721
1696
  else {
1722
- if (0 < note.voiceParams.reverbEffectsSend) {
1697
+ if (0 < value) {
1723
1698
  if (!note.reverbEffectsSend) {
1724
1699
  note.reverbEffectsSend = new GainNode(this.audioContext, {
1725
- gain: note.voiceParams.reverbEffectsSend,
1700
+ gain: value,
1726
1701
  });
1727
1702
  note.volumeNode.connect(note.reverbEffectsSend);
1728
1703
  }
@@ -1731,10 +1706,13 @@ class Midy {
1731
1706
  }
1732
1707
  }
1733
1708
  setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1709
+ const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1710
+ let value = note.voiceParams.chorusEffectsSend;
1711
+ if (0 <= keyBasedValue) {
1712
+ value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1713
+ }
1734
1714
  if (0 < prevValue) {
1735
- if (0 < note.voiceParams.chorusEffectsSend) {
1736
- const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 93);
1737
- const value = note.voiceParams.chorusEffectsSend + keyBasedValue;
1715
+ if (0 < vaule) {
1738
1716
  note.chorusEffectsSend.gain
1739
1717
  .cancelScheduledValues(scheduleTime)
1740
1718
  .setValueAtTime(value, scheduleTime);
@@ -1744,10 +1722,10 @@ class Midy {
1744
1722
  }
1745
1723
  }
1746
1724
  else {
1747
- if (0 < note.voiceParams.chorusEffectsSend) {
1725
+ if (0 < value) {
1748
1726
  if (!note.chorusEffectsSend) {
1749
1727
  note.chorusEffectsSend = new GainNode(this.audioContext, {
1750
- gain: note.voiceParams.chorusEffectsSend,
1728
+ gain: value,
1751
1729
  });
1752
1730
  note.volumeNode.connect(note.chorusEffectsSend);
1753
1731
  }
@@ -1984,10 +1962,10 @@ class Midy {
1984
1962
  setKeyBasedVolume(channel, scheduleTime) {
1985
1963
  this.processScheduledNotes(channel, (note) => {
1986
1964
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1987
- if (keyBasedValue !== 0) {
1965
+ if (0 <= keyBasedValue) {
1988
1966
  note.volumeNode.gain
1989
1967
  .cancelScheduledValues(scheduleTime)
1990
- .setValueAtTime(1 + keyBasedValue, scheduleTime);
1968
+ .setValueAtTime(keyBasedValue / 127, scheduleTime);
1991
1969
  }
1992
1970
  });
1993
1971
  }
@@ -2008,8 +1986,8 @@ class Midy {
2008
1986
  setKeyBasedPan(channel, scheduleTime) {
2009
1987
  this.processScheduledNotes(channel, (note) => {
2010
1988
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
2011
- if (keyBasedValue !== 0) {
2012
- const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
1989
+ if (0 <= keyBasedValue) {
1990
+ const { gainLeft, gainRight } = this.panToGain(keyBasedValue / 127);
2013
1991
  note.gainL.gain
2014
1992
  .cancelScheduledValues(scheduleTime)
2015
1993
  .setValueAtTime(gainLeft, scheduleTime);
@@ -2090,6 +2068,9 @@ class Midy {
2090
2068
  this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
2091
2069
  }
2092
2070
  }
2071
+ getSoftPedalFactor(channel, note) {
2072
+ return 1 - (0.1 + (note.noteNumber / 127) * 0.2) * channel.state.softPedal;
2073
+ }
2093
2074
  setSoftPedal(channelNumber, softPedal, scheduleTime) {
2094
2075
  const channel = this.channels[channelNumber];
2095
2076
  if (channel.isDrum)
@@ -2223,7 +2204,8 @@ class Midy {
2223
2204
  this.processScheduledNotes(channel, (note) => {
2224
2205
  if (note.voiceParams.reverbEffectsSend <= 0)
2225
2206
  return false;
2226
- note.reverbEffectsSend.disconnect();
2207
+ if (note.reverbEffectsSend)
2208
+ note.reverbEffectsSend.disconnect();
2227
2209
  });
2228
2210
  }
2229
2211
  }
@@ -2255,7 +2237,8 @@ class Midy {
2255
2237
  this.processScheduledNotes(channel, (note) => {
2256
2238
  if (note.voiceParams.chorusEffectsSend <= 0)
2257
2239
  return false;
2258
- note.chorusEffectsSend.disconnect();
2240
+ if (note.chorusEffectsSend)
2241
+ note.chorusEffectsSend.disconnect();
2259
2242
  });
2260
2243
  }
2261
2244
  }
@@ -3008,7 +2991,7 @@ class Midy {
3008
2991
  getKeyBasedInstrumentControlValue(channel, keyNumber, controllerType) {
3009
2992
  const index = keyNumber * 128 + controllerType;
3010
2993
  const controlValue = channel.keyBasedInstrumentControlTable[index];
3011
- return (controlValue + 64) / 64;
2994
+ return controlValue;
3012
2995
  }
3013
2996
  handleKeyBasedInstrumentControlSysEx(data, scheduleTime) {
3014
2997
  const channelNumber = data[4];
@@ -3021,7 +3004,7 @@ class Midy {
3021
3004
  const controllerType = data[i];
3022
3005
  const value = data[i + 1];
3023
3006
  const index = keyNumber * 128 + controllerType;
3024
- table[index] = value - 64;
3007
+ table[index] = value;
3025
3008
  }
3026
3009
  this.handleChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
3027
3010
  }