@marmooo/midy 0.4.0 → 0.4.2

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.
@@ -29,12 +29,6 @@ class Note {
29
29
  writable: true,
30
30
  value: false
31
31
  });
32
- Object.defineProperty(this, "pending", {
33
- enumerable: true,
34
- configurable: true,
35
- writable: true,
36
- value: true
37
- });
38
32
  Object.defineProperty(this, "bufferSource", {
39
33
  enumerable: true,
40
34
  configurable: true,
@@ -110,6 +104,9 @@ class Note {
110
104
  this.noteNumber = noteNumber;
111
105
  this.velocity = velocity;
112
106
  this.startTime = startTime;
107
+ this.ready = new Promise((resolve) => {
108
+ this.resolveReady = resolve;
109
+ });
113
110
  }
114
111
  }
115
112
  const drumExclusiveClassesByKit = new Array(57);
@@ -154,12 +151,12 @@ const defaultControllerState = {
154
151
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
155
152
  link: { type: 127, defaultValue: 0 },
156
153
  // bankMSB: { type: 128 + 0, defaultValue: 121, },
157
- modulationDepth: { type: 128 + 1, defaultValue: 0 },
158
- portamentoTime: { type: 128 + 5, defaultValue: 0 },
154
+ modulationDepthMSB: { type: 128 + 1, defaultValue: 0 },
155
+ portamentoTimeMSB: { type: 128 + 5, defaultValue: 0 },
159
156
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
160
- volume: { type: 128 + 7, defaultValue: 100 / 127 },
161
- pan: { type: 128 + 10, defaultValue: 64 / 127 },
162
- expression: { type: 128 + 11, defaultValue: 1 },
157
+ volumeMSB: { type: 128 + 7, defaultValue: 100 / 127 },
158
+ panMSB: { type: 128 + 10, defaultValue: 64 / 127 },
159
+ expressionMSB: { type: 128 + 11, defaultValue: 1 },
163
160
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
164
161
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
165
162
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
@@ -231,8 +228,9 @@ const pitchEnvelopeKeys = [
231
228
  "playbackRate",
232
229
  ];
233
230
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
234
- class MidyGM2 {
231
+ class MidyGM2 extends EventTarget {
235
232
  constructor(audioContext) {
233
+ super();
236
234
  Object.defineProperty(this, "mode", {
237
235
  enumerable: true,
238
236
  configurable: true,
@@ -393,6 +391,12 @@ class MidyGM2 {
393
391
  writable: true,
394
392
  value: false
395
393
  });
394
+ Object.defineProperty(this, "loop", {
395
+ enumerable: true,
396
+ configurable: true,
397
+ writable: true,
398
+ value: false
399
+ });
396
400
  Object.defineProperty(this, "playPromise", {
397
401
  enumerable: true,
398
402
  configurable: true,
@@ -549,7 +553,7 @@ class MidyGM2 {
549
553
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
550
554
  }
551
555
  createChannelAudioNodes(audioContext) {
552
- const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
556
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.panMSB.defaultValue);
553
557
  const gainL = new GainNode(audioContext, { gain: gainLeft });
554
558
  const gainR = new GainNode(audioContext, { gain: gainRight });
555
559
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -590,10 +594,9 @@ class MidyGM2 {
590
594
  return channels;
591
595
  }
592
596
  async createAudioBuffer(voiceParams) {
593
- const sample = voiceParams.sample;
594
- const sampleStart = voiceParams.start;
595
- const sampleEnd = sample.data.length + voiceParams.end;
596
- const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
597
+ const { sample, start, end } = voiceParams;
598
+ const sampleEnd = sample.data.length + end;
599
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
597
600
  return audioBuffer;
598
601
  }
599
602
  isLoopDrum(channel, noteNumber) {
@@ -613,7 +616,7 @@ class MidyGM2 {
613
616
  }
614
617
  return bufferSource;
615
618
  }
616
- async scheduleTimelineEvents(scheduleTime, queueIndex) {
619
+ scheduleTimelineEvents(scheduleTime, queueIndex) {
617
620
  const timeOffset = this.resumeTime - this.startTime;
618
621
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
619
622
  const schedulingOffset = this.startDelay - timeOffset;
@@ -625,12 +628,10 @@ class MidyGM2 {
625
628
  const startTime = event.startTime + schedulingOffset;
626
629
  switch (event.type) {
627
630
  case "noteOn":
628
- await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
631
+ this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
629
632
  break;
630
633
  case "noteOff": {
631
- const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
632
- if (notePromise)
633
- this.notePromises.push(notePromise);
634
+ this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
634
635
  break;
635
636
  }
636
637
  case "controller":
@@ -671,22 +672,23 @@ class MidyGM2 {
671
672
  }
672
673
  }
673
674
  updateStates(queueIndex, nextQueueIndex) {
675
+ const now = this.audioContext.currentTime;
674
676
  if (nextQueueIndex < queueIndex)
675
677
  queueIndex = 0;
676
678
  for (let i = queueIndex; i < nextQueueIndex; i++) {
677
679
  const event = this.timeline[i];
678
680
  switch (event.type) {
679
681
  case "controller":
680
- this.setControlChange(event.channel, event.controllerType, event.value, 0);
682
+ this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
681
683
  break;
682
684
  case "programChange":
683
- this.setProgramChange(event.channel, event.programNumber, 0);
685
+ this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
684
686
  break;
685
687
  case "pitchBend":
686
- this.setPitchBend(event.channel, event.value + 8192, 0);
688
+ this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
687
689
  break;
688
690
  case "sysEx":
689
- this.handleSysEx(event.data, 0);
691
+ this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
690
692
  }
691
693
  }
692
694
  }
@@ -694,52 +696,88 @@ class MidyGM2 {
694
696
  if (this.audioContext.state === "suspended") {
695
697
  await this.audioContext.resume();
696
698
  }
699
+ const paused = this.isPaused;
697
700
  this.isPlaying = true;
698
701
  this.isPaused = false;
699
702
  this.startTime = this.audioContext.currentTime;
703
+ if (paused) {
704
+ this.dispatchEvent(new Event("resumed"));
705
+ }
706
+ else {
707
+ this.dispatchEvent(new Event("started"));
708
+ }
700
709
  let queueIndex = this.getQueueIndex(this.resumeTime);
701
- let finished = false;
710
+ let exitReason;
702
711
  this.notePromises = [];
703
- while (queueIndex < this.timeline.length) {
712
+ while (true) {
704
713
  const now = this.audioContext.currentTime;
705
714
  if (0 < this.lastActiveSensing &&
706
715
  this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
707
716
  await this.stopNotes(0, true, now);
708
717
  await this.audioContext.suspend();
709
- finished = true;
718
+ exitReason = "aborted";
710
719
  break;
711
720
  }
721
+ if (this.timeline.length <= queueIndex) {
722
+ await this.stopNotes(0, true, now);
723
+ if (this.loop) {
724
+ this.notePromises = [];
725
+ this.resetAllStates();
726
+ this.startTime = this.audioContext.currentTime;
727
+ this.resumeTime = 0;
728
+ queueIndex = 0;
729
+ this.dispatchEvent(new Event("looped"));
730
+ continue;
731
+ }
732
+ else {
733
+ await this.audioContext.suspend();
734
+ exitReason = "ended";
735
+ break;
736
+ }
737
+ }
712
738
  if (this.isPausing) {
713
739
  await this.stopNotes(0, true, now);
714
740
  await this.audioContext.suspend();
715
741
  this.notePromises = [];
742
+ this.isPausing = false;
743
+ exitReason = "paused";
716
744
  break;
717
745
  }
718
746
  else if (this.isStopping) {
719
747
  await this.stopNotes(0, true, now);
720
748
  await this.audioContext.suspend();
721
- finished = true;
749
+ this.isStopping = false;
750
+ exitReason = "stopped";
722
751
  break;
723
752
  }
724
753
  else if (this.isSeeking) {
725
- await this.stopNotes(0, true, now);
754
+ this.stopNotes(0, true, now);
726
755
  this.startTime = this.audioContext.currentTime;
727
756
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
728
757
  this.updateStates(queueIndex, nextQueueIndex);
729
758
  queueIndex = nextQueueIndex;
730
759
  this.isSeeking = false;
760
+ this.dispatchEvent(new Event("seeked"));
731
761
  continue;
732
762
  }
733
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
763
+ queueIndex = this.scheduleTimelineEvents(now, queueIndex);
734
764
  const waitTime = now + this.noteCheckInterval;
735
765
  await this.scheduleTask(() => { }, waitTime);
736
766
  }
737
- if (finished) {
767
+ if (exitReason !== "paused") {
738
768
  this.notePromises = [];
739
769
  this.resetAllStates();
740
770
  this.lastActiveSensing = 0;
741
771
  }
742
772
  this.isPlaying = false;
773
+ if (exitReason === "paused") {
774
+ this.isPaused = true;
775
+ this.dispatchEvent(new Event("paused"));
776
+ }
777
+ else {
778
+ this.isPaused = false;
779
+ this.dispatchEvent(new Event(exitReason));
780
+ }
743
781
  }
744
782
  ticksToSecond(ticks, secondsPerBeat) {
745
783
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -876,24 +914,20 @@ class MidyGM2 {
876
914
  return;
877
915
  this.isStopping = true;
878
916
  await this.playPromise;
879
- this.isStopping = false;
880
917
  }
881
918
  async pause() {
882
919
  if (!this.isPlaying || this.isPaused)
883
920
  return;
884
921
  const now = this.audioContext.currentTime;
885
- this.resumeTime = now - this.startTime - this.startDelay;
922
+ this.resumeTime = now + this.resumeTime - this.startTime;
886
923
  this.isPausing = true;
887
924
  await this.playPromise;
888
- this.isPausing = false;
889
- this.isPaused = true;
890
925
  }
891
926
  async resume() {
892
927
  if (!this.isPaused)
893
928
  return;
894
929
  this.playPromise = this.playNotes();
895
930
  await this.playPromise;
896
- this.isPaused = false;
897
931
  }
898
932
  seekTo(second) {
899
933
  this.resumeTime = second;
@@ -916,19 +950,23 @@ class MidyGM2 {
916
950
  const now = this.audioContext.currentTime;
917
951
  return now + this.resumeTime - this.startTime;
918
952
  }
919
- processScheduledNotes(channel, callback) {
953
+ async processScheduledNotes(channel, callback) {
920
954
  const scheduledNotes = channel.scheduledNotes;
955
+ const tasks = [];
921
956
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
922
957
  const note = scheduledNotes[i];
923
958
  if (!note)
924
959
  continue;
925
960
  if (note.ending)
926
961
  continue;
927
- callback(note);
962
+ const task = note.ready.then(() => callback(note));
963
+ tasks.push(task);
928
964
  }
965
+ await Promise.all(tasks);
929
966
  }
930
- processActiveNotes(channel, scheduleTime, callback) {
967
+ async processActiveNotes(channel, scheduleTime, callback) {
931
968
  const scheduledNotes = channel.scheduledNotes;
969
+ const tasks = [];
932
970
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
933
971
  const note = scheduledNotes[i];
934
972
  if (!note)
@@ -937,8 +975,10 @@ class MidyGM2 {
937
975
  continue;
938
976
  if (scheduleTime < note.startTime)
939
977
  break;
940
- callback(note);
978
+ const task = note.ready.then(() => callback(note));
979
+ tasks.push(task);
941
980
  }
981
+ await Promise.all(tasks);
942
982
  }
943
983
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
944
984
  const sampleRate = audioContext.sampleRate;
@@ -1142,7 +1182,7 @@ class MidyGM2 {
1142
1182
  }
1143
1183
  getPortamentoTime(channel, note) {
1144
1184
  const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
1145
- const value = Math.ceil(channel.state.portamentoTime * 127);
1185
+ const value = Math.ceil(channel.state.portamentoTimeMSB * 127);
1146
1186
  return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
1147
1187
  }
1148
1188
  getPitchIncrementSpeed(value) {
@@ -1401,7 +1441,7 @@ class MidyGM2 {
1401
1441
  if (0 < state.vibratoDepth) {
1402
1442
  this.startVibrato(channel, note, now);
1403
1443
  }
1404
- if (0 < state.modulationDepth) {
1444
+ if (0 < state.modulationDepthMSB) {
1405
1445
  this.startModulation(channel, note, now);
1406
1446
  }
1407
1447
  if (channel.mono && channel.currentBufferSource) {
@@ -1412,7 +1452,13 @@ class MidyGM2 {
1412
1452
  note.filterNode.connect(note.volumeEnvelopeNode);
1413
1453
  this.setChorusSend(channel, note, now);
1414
1454
  this.setReverbSend(channel, note, now);
1415
- note.bufferSource.start(startTime);
1455
+ if (voiceParams.sample.type === "compressed") {
1456
+ const offset = voiceParams.start / audioBuffer.sampleRate;
1457
+ note.bufferSource.start(startTime, offset);
1458
+ }
1459
+ else {
1460
+ note.bufferSource.start(startTime);
1461
+ }
1416
1462
  return note;
1417
1463
  }
1418
1464
  handleExclusiveClass(note, channelNumber, startTime) {
@@ -1497,11 +1543,7 @@ class MidyGM2 {
1497
1543
  return;
1498
1544
  await this.setNoteAudioNode(channel, note, realtime);
1499
1545
  this.setNoteRouting(channelNumber, note, startTime);
1500
- note.pending = false;
1501
- const off = note.offEvent;
1502
- if (off) {
1503
- this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
1504
- }
1546
+ note.resolveReady();
1505
1547
  }
1506
1548
  disconnectNote(note) {
1507
1549
  note.bufferSource.disconnect();
@@ -1545,7 +1587,7 @@ class MidyGM2 {
1545
1587
  }, stopTime);
1546
1588
  });
1547
1589
  }
1548
- noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1590
+ noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1549
1591
  const channel = this.channels[channelNumber];
1550
1592
  const state = channel.state;
1551
1593
  if (!force) {
@@ -1564,13 +1606,13 @@ class MidyGM2 {
1564
1606
  if (index < 0)
1565
1607
  return;
1566
1608
  const note = channel.scheduledNotes[index];
1567
- if (note.pending) {
1568
- note.offEvent = { velocity, startTime: endTime };
1569
- return;
1570
- }
1571
1609
  note.ending = true;
1572
1610
  this.setNoteIndex(channel, index);
1573
- this.releaseNote(channel, note, endTime);
1611
+ const promise = note.ready.then(() => {
1612
+ return this.releaseNote(channel, note, endTime);
1613
+ });
1614
+ this.notePromises.push(promise);
1615
+ return promise;
1574
1616
  }
1575
1617
  setNoteIndex(channel, index) {
1576
1618
  let allEnds = true;
@@ -1679,7 +1721,7 @@ class MidyGM2 {
1679
1721
  this.processActiveNotes(channel, scheduleTime, (note) => {
1680
1722
  this.setEffects(channel, note, table, scheduleTime);
1681
1723
  });
1682
- this.applyVoiceParams(channel, 13);
1724
+ this.applyVoiceParams(channel, 13, scheduleTime);
1683
1725
  }
1684
1726
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1685
1727
  const pitchBend = msb * 128 + lsb;
@@ -1689,7 +1731,8 @@ class MidyGM2 {
1689
1731
  const channel = this.channels[channelNumber];
1690
1732
  if (channel.isDrum)
1691
1733
  return;
1692
- scheduleTime ??= this.audioContext.currentTime;
1734
+ if (!(0 <= scheduleTime))
1735
+ scheduleTime = this.audioContext.currentTime;
1693
1736
  const state = channel.state;
1694
1737
  const prev = state.pitchWheel * 2 - 1;
1695
1738
  const next = (value - 8192) / 8192;
@@ -1702,7 +1745,8 @@ class MidyGM2 {
1702
1745
  if (note.modulationDepth) {
1703
1746
  const modLfoToPitch = note.voiceParams.modLfoToPitch +
1704
1747
  this.getLFOPitchDepth(channel, note);
1705
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1748
+ const baseDepth = Math.abs(modLfoToPitch) +
1749
+ channel.state.modulationDepthMSB;
1706
1750
  const depth = baseDepth * Math.sign(modLfoToPitch);
1707
1751
  note.modulationDepth.gain
1708
1752
  .cancelScheduledValues(scheduleTime)
@@ -1834,7 +1878,7 @@ class MidyGM2 {
1834
1878
  createVoiceParamsHandlers() {
1835
1879
  return {
1836
1880
  modLfoToPitch: (channel, note, scheduleTime) => {
1837
- if (0 < channel.state.modulationDepth) {
1881
+ if (0 < channel.state.modulationDepthMSB) {
1838
1882
  this.setModLfoToPitch(channel, note, scheduleTime);
1839
1883
  }
1840
1884
  },
@@ -1844,12 +1888,12 @@ class MidyGM2 {
1844
1888
  }
1845
1889
  },
1846
1890
  modLfoToFilterFc: (channel, note, scheduleTime) => {
1847
- if (0 < channel.state.modulationDepth) {
1891
+ if (0 < channel.state.modulationDepthMSB) {
1848
1892
  this.setModLfoToFilterFc(channel, note, scheduleTime);
1849
1893
  }
1850
1894
  },
1851
1895
  modLfoToVolume: (channel, note, scheduleTime) => {
1852
- if (0 < channel.state.modulationDepth) {
1896
+ if (0 < channel.state.modulationDepthMSB) {
1853
1897
  this.setModLfoToVolume(channel, note, scheduleTime);
1854
1898
  }
1855
1899
  },
@@ -1860,12 +1904,12 @@ class MidyGM2 {
1860
1904
  this.setReverbSend(channel, note, scheduleTime);
1861
1905
  },
1862
1906
  delayModLFO: (_channel, note, _scheduleTime) => {
1863
- if (0 < channel.state.modulationDepth) {
1907
+ if (0 < channel.state.modulationDepthMSB) {
1864
1908
  this.setDelayModLFO(note);
1865
1909
  }
1866
1910
  },
1867
1911
  freqModLFO: (_channel, note, scheduleTime) => {
1868
- if (0 < channel.state.modulationDepth) {
1912
+ if (0 < channel.state.modulationDepthMSB) {
1869
1913
  this.setFreqModLFO(note, scheduleTime);
1870
1914
  }
1871
1915
  },
@@ -1967,7 +2011,8 @@ class MidyGM2 {
1967
2011
  this.channels[channelNumber].bankMSB = msb;
1968
2012
  }
1969
2013
  updateModulation(channel, scheduleTime) {
1970
- const depth = channel.state.modulationDepth * channel.modulationDepthRange;
2014
+ const depth = channel.state.modulationDepthMSB *
2015
+ channel.modulationDepthRange;
1971
2016
  this.processScheduledNotes(channel, (note) => {
1972
2017
  if (note.modulationDepth) {
1973
2018
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
@@ -1981,8 +2026,9 @@ class MidyGM2 {
1981
2026
  const channel = this.channels[channelNumber];
1982
2027
  if (channel.isDrum)
1983
2028
  return;
1984
- scheduleTime ??= this.audioContext.currentTime;
1985
- channel.state.modulationDepth = modulation / 127;
2029
+ if (!(0 <= scheduleTime))
2030
+ scheduleTime = this.audioContext.currentTime;
2031
+ channel.state.modulationDepthMSB = modulation / 127;
1986
2032
  this.updateModulation(channel, scheduleTime);
1987
2033
  }
1988
2034
  updatePortamento(channel, scheduleTime) {
@@ -2004,17 +2050,19 @@ class MidyGM2 {
2004
2050
  });
2005
2051
  }
2006
2052
  setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
2053
+ if (!(0 <= scheduleTime))
2054
+ scheduleTime = this.audioContext.currentTime;
2007
2055
  const channel = this.channels[channelNumber];
2008
- scheduleTime ??= this.audioContext.currentTime;
2009
- channel.state.portamentoTime = portamentoTime / 127;
2056
+ channel.state.portamentoTimeMSB = portamentoTime / 127;
2010
2057
  if (channel.isDrum)
2011
2058
  return;
2012
2059
  this.updatePortamento(channel, scheduleTime);
2013
2060
  }
2014
2061
  setVolume(channelNumber, volume, scheduleTime) {
2015
- scheduleTime ??= this.audioContext.currentTime;
2062
+ if (!(0 <= scheduleTime))
2063
+ scheduleTime = this.audioContext.currentTime;
2016
2064
  const channel = this.channels[channelNumber];
2017
- channel.state.volume = volume / 127;
2065
+ channel.state.volumeMSB = volume / 127;
2018
2066
  if (channel.isDrum) {
2019
2067
  for (let i = 0; i < 128; i++) {
2020
2068
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2032,9 +2080,10 @@ class MidyGM2 {
2032
2080
  };
2033
2081
  }
2034
2082
  setPan(channelNumber, pan, scheduleTime) {
2035
- scheduleTime ??= this.audioContext.currentTime;
2083
+ if (!(0 <= scheduleTime))
2084
+ scheduleTime = this.audioContext.currentTime;
2036
2085
  const channel = this.channels[channelNumber];
2037
- channel.state.pan = pan / 127;
2086
+ channel.state.panMSB = pan / 127;
2038
2087
  if (channel.isDrum) {
2039
2088
  for (let i = 0; i < 128; i++) {
2040
2089
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2045,9 +2094,10 @@ class MidyGM2 {
2045
2094
  }
2046
2095
  }
2047
2096
  setExpression(channelNumber, expression, scheduleTime) {
2048
- scheduleTime ??= this.audioContext.currentTime;
2097
+ if (!(0 <= scheduleTime))
2098
+ scheduleTime = this.audioContext.currentTime;
2049
2099
  const channel = this.channels[channelNumber];
2050
- channel.state.expression = expression / 127;
2100
+ channel.state.expressionMSB = expression / 127;
2051
2101
  this.updateChannelVolume(channel, scheduleTime);
2052
2102
  }
2053
2103
  setBankLSB(channelNumber, lsb) {
@@ -2059,8 +2109,8 @@ class MidyGM2 {
2059
2109
  }
2060
2110
  updateChannelVolume(channel, scheduleTime) {
2061
2111
  const state = channel.state;
2062
- const volume = state.volume * state.expression;
2063
- const { gainLeft, gainRight } = this.panToGain(state.pan);
2112
+ const volume = state.volumeMSB * state.expressionMSB;
2113
+ const { gainLeft, gainRight } = this.panToGain(state.panMSB);
2064
2114
  channel.gainL.gain
2065
2115
  .cancelScheduledValues(scheduleTime)
2066
2116
  .setValueAtTime(volume * gainLeft, scheduleTime);
@@ -2074,8 +2124,8 @@ class MidyGM2 {
2074
2124
  return;
2075
2125
  const gainR = channel.keyBasedGainRs[keyNumber];
2076
2126
  const state = channel.state;
2077
- const defaultVolume = state.volume * state.expression;
2078
- const defaultPan = state.pan;
2127
+ const defaultVolume = state.volumeMSB * state.expressionMSB;
2128
+ const defaultPan = state.panMSB;
2079
2129
  const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
2080
2130
  const volume = (0 <= keyBasedVolume)
2081
2131
  ? defaultVolume * keyBasedVolume / 64
@@ -2094,7 +2144,8 @@ class MidyGM2 {
2094
2144
  const channel = this.channels[channelNumber];
2095
2145
  if (channel.isDrum)
2096
2146
  return;
2097
- scheduleTime ??= this.audioContext.currentTime;
2147
+ if (!(0 <= scheduleTime))
2148
+ scheduleTime = this.audioContext.currentTime;
2098
2149
  channel.state.sustainPedal = value / 127;
2099
2150
  if (64 <= value) {
2100
2151
  this.processScheduledNotes(channel, (note) => {
@@ -2112,7 +2163,8 @@ class MidyGM2 {
2112
2163
  const channel = this.channels[channelNumber];
2113
2164
  if (channel.isDrum)
2114
2165
  return;
2115
- scheduleTime ??= this.audioContext.currentTime;
2166
+ if (!(0 <= scheduleTime))
2167
+ scheduleTime = this.audioContext.currentTime;
2116
2168
  channel.state.portamento = value / 127;
2117
2169
  this.updatePortamento(channel, scheduleTime);
2118
2170
  }
@@ -2120,7 +2172,8 @@ class MidyGM2 {
2120
2172
  const channel = this.channels[channelNumber];
2121
2173
  if (channel.isDrum)
2122
2174
  return;
2123
- scheduleTime ??= this.audioContext.currentTime;
2175
+ if (!(0 <= scheduleTime))
2176
+ scheduleTime = this.audioContext.currentTime;
2124
2177
  channel.state.sostenutoPedal = value / 127;
2125
2178
  if (64 <= value) {
2126
2179
  const sostenutoNotes = [];
@@ -2141,7 +2194,8 @@ class MidyGM2 {
2141
2194
  if (channel.isDrum)
2142
2195
  return;
2143
2196
  const state = channel.state;
2144
- scheduleTime ??= this.audioContext.currentTime;
2197
+ if (!(0 <= scheduleTime))
2198
+ scheduleTime = this.audioContext.currentTime;
2145
2199
  state.softPedal = softPedal / 127;
2146
2200
  this.processScheduledNotes(channel, (note) => {
2147
2201
  if (this.isPortamento(channel, note)) {
@@ -2155,7 +2209,8 @@ class MidyGM2 {
2155
2209
  });
2156
2210
  }
2157
2211
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
2158
- scheduleTime ??= this.audioContext.currentTime;
2212
+ if (!(0 <= scheduleTime))
2213
+ scheduleTime = this.audioContext.currentTime;
2159
2214
  const channel = this.channels[channelNumber];
2160
2215
  const state = channel.state;
2161
2216
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2164,7 +2219,8 @@ class MidyGM2 {
2164
2219
  });
2165
2220
  }
2166
2221
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2167
- scheduleTime ??= this.audioContext.currentTime;
2222
+ if (!(0 <= scheduleTime))
2223
+ scheduleTime = this.audioContext.currentTime;
2168
2224
  const channel = this.channels[channelNumber];
2169
2225
  const state = channel.state;
2170
2226
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2238,7 +2294,8 @@ class MidyGM2 {
2238
2294
  const channel = this.channels[channelNumber];
2239
2295
  if (channel.isDrum)
2240
2296
  return;
2241
- scheduleTime ??= this.audioContext.currentTime;
2297
+ if (!(0 <= scheduleTime))
2298
+ scheduleTime = this.audioContext.currentTime;
2242
2299
  const state = channel.state;
2243
2300
  const prev = state.pitchWheelSensitivity;
2244
2301
  const next = value / 12800;
@@ -2258,7 +2315,8 @@ class MidyGM2 {
2258
2315
  const channel = this.channels[channelNumber];
2259
2316
  if (channel.isDrum)
2260
2317
  return;
2261
- scheduleTime ??= this.audioContext.currentTime;
2318
+ if (!(0 <= scheduleTime))
2319
+ scheduleTime = this.audioContext.currentTime;
2262
2320
  const prev = channel.fineTuning;
2263
2321
  const next = value;
2264
2322
  channel.fineTuning = next;
@@ -2275,7 +2333,8 @@ class MidyGM2 {
2275
2333
  const channel = this.channels[channelNumber];
2276
2334
  if (channel.isDrum)
2277
2335
  return;
2278
- scheduleTime ??= this.audioContext.currentTime;
2336
+ if (!(0 <= scheduleTime))
2337
+ scheduleTime = this.audioContext.currentTime;
2279
2338
  const prev = channel.coarseTuning;
2280
2339
  const next = value;
2281
2340
  channel.coarseTuning = next;
@@ -2292,12 +2351,14 @@ class MidyGM2 {
2292
2351
  const channel = this.channels[channelNumber];
2293
2352
  if (channel.isDrum)
2294
2353
  return;
2295
- scheduleTime ??= this.audioContext.currentTime;
2354
+ if (!(0 <= scheduleTime))
2355
+ scheduleTime = this.audioContext.currentTime;
2296
2356
  channel.modulationDepthRange = value;
2297
2357
  this.updateModulation(channel, scheduleTime);
2298
2358
  }
2299
2359
  allSoundOff(channelNumber, _value, scheduleTime) {
2300
- scheduleTime ??= this.audioContext.currentTime;
2360
+ if (!(0 <= scheduleTime))
2361
+ scheduleTime = this.audioContext.currentTime;
2301
2362
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2302
2363
  }
2303
2364
  resetChannelStates(channelNumber) {
@@ -2326,8 +2387,8 @@ class MidyGM2 {
2326
2387
  const keys = [
2327
2388
  "channelPressure",
2328
2389
  "pitchWheel",
2329
- "expression",
2330
- "modulationDepth",
2390
+ "expressionMSB",
2391
+ "modulationDepthMSB",
2331
2392
  "sustainPedal",
2332
2393
  "portamento",
2333
2394
  "sostenutoPedal",
@@ -2356,7 +2417,8 @@ class MidyGM2 {
2356
2417
  }
2357
2418
  }
2358
2419
  allNotesOff(channelNumber, _value, scheduleTime) {
2359
- scheduleTime ??= this.audioContext.currentTime;
2420
+ if (!(0 <= scheduleTime))
2421
+ scheduleTime = this.audioContext.currentTime;
2360
2422
  return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2361
2423
  }
2362
2424
  omniOff(channelNumber, value, scheduleTime) {
@@ -2405,7 +2467,8 @@ class MidyGM2 {
2405
2467
  }
2406
2468
  }
2407
2469
  GM1SystemOn(scheduleTime) {
2408
- scheduleTime ??= this.audioContext.currentTime;
2470
+ if (!(0 <= scheduleTime))
2471
+ scheduleTime = this.audioContext.currentTime;
2409
2472
  this.mode = "GM1";
2410
2473
  for (let i = 0; i < this.channels.length; i++) {
2411
2474
  this.allSoundOff(i, 0, scheduleTime);
@@ -2418,7 +2481,8 @@ class MidyGM2 {
2418
2481
  this.channels[9].isDrum = true;
2419
2482
  }
2420
2483
  GM2SystemOn(scheduleTime) {
2421
- scheduleTime ??= this.audioContext.currentTime;
2484
+ if (!(0 <= scheduleTime))
2485
+ scheduleTime = this.audioContext.currentTime;
2422
2486
  this.mode = "GM2";
2423
2487
  for (let i = 0; i < this.channels.length; i++) {
2424
2488
  this.allSoundOff(i, 0, scheduleTime);
@@ -2473,7 +2537,8 @@ class MidyGM2 {
2473
2537
  this.setMasterVolume(volume, scheduleTime);
2474
2538
  }
2475
2539
  setMasterVolume(value, scheduleTime) {
2476
- scheduleTime ??= this.audioContext.currentTime;
2540
+ if (!(0 <= scheduleTime))
2541
+ scheduleTime = this.audioContext.currentTime;
2477
2542
  this.masterVolume.gain
2478
2543
  .cancelScheduledValues(scheduleTime)
2479
2544
  .setValueAtTime(value * value, scheduleTime);