@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.
package/esm/midy-GM2.js CHANGED
@@ -26,12 +26,6 @@ class Note {
26
26
  writable: true,
27
27
  value: false
28
28
  });
29
- Object.defineProperty(this, "pending", {
30
- enumerable: true,
31
- configurable: true,
32
- writable: true,
33
- value: true
34
- });
35
29
  Object.defineProperty(this, "bufferSource", {
36
30
  enumerable: true,
37
31
  configurable: true,
@@ -107,6 +101,9 @@ class Note {
107
101
  this.noteNumber = noteNumber;
108
102
  this.velocity = velocity;
109
103
  this.startTime = startTime;
104
+ this.ready = new Promise((resolve) => {
105
+ this.resolveReady = resolve;
106
+ });
110
107
  }
111
108
  }
112
109
  const drumExclusiveClassesByKit = new Array(57);
@@ -151,12 +148,12 @@ const defaultControllerState = {
151
148
  pitchWheelSensitivity: { type: 16, defaultValue: 2 / 128 },
152
149
  link: { type: 127, defaultValue: 0 },
153
150
  // bankMSB: { type: 128 + 0, defaultValue: 121, },
154
- modulationDepth: { type: 128 + 1, defaultValue: 0 },
155
- portamentoTime: { type: 128 + 5, defaultValue: 0 },
151
+ modulationDepthMSB: { type: 128 + 1, defaultValue: 0 },
152
+ portamentoTimeMSB: { type: 128 + 5, defaultValue: 0 },
156
153
  // dataMSB: { type: 128 + 6, defaultValue: 0, },
157
- volume: { type: 128 + 7, defaultValue: 100 / 127 },
158
- pan: { type: 128 + 10, defaultValue: 64 / 127 },
159
- expression: { type: 128 + 11, defaultValue: 1 },
154
+ volumeMSB: { type: 128 + 7, defaultValue: 100 / 127 },
155
+ panMSB: { type: 128 + 10, defaultValue: 64 / 127 },
156
+ expressionMSB: { type: 128 + 11, defaultValue: 1 },
160
157
  // bankLSB: { type: 128 + 32, defaultValue: 0, },
161
158
  // dataLSB: { type: 128 + 38, defaultValue: 0, },
162
159
  sustainPedal: { type: 128 + 64, defaultValue: 0 },
@@ -228,8 +225,9 @@ const pitchEnvelopeKeys = [
228
225
  "playbackRate",
229
226
  ];
230
227
  const pitchEnvelopeKeySet = new Set(pitchEnvelopeKeys);
231
- export class MidyGM2 {
228
+ export class MidyGM2 extends EventTarget {
232
229
  constructor(audioContext) {
230
+ super();
233
231
  Object.defineProperty(this, "mode", {
234
232
  enumerable: true,
235
233
  configurable: true,
@@ -390,6 +388,12 @@ export class MidyGM2 {
390
388
  writable: true,
391
389
  value: false
392
390
  });
391
+ Object.defineProperty(this, "loop", {
392
+ enumerable: true,
393
+ configurable: true,
394
+ writable: true,
395
+ value: false
396
+ });
393
397
  Object.defineProperty(this, "playPromise", {
394
398
  enumerable: true,
395
399
  configurable: true,
@@ -546,7 +550,7 @@ export class MidyGM2 {
546
550
  return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
547
551
  }
548
552
  createChannelAudioNodes(audioContext) {
549
- const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
553
+ const { gainLeft, gainRight } = this.panToGain(defaultControllerState.panMSB.defaultValue);
550
554
  const gainL = new GainNode(audioContext, { gain: gainLeft });
551
555
  const gainR = new GainNode(audioContext, { gain: gainRight });
552
556
  const merger = new ChannelMergerNode(audioContext, { numberOfInputs: 2 });
@@ -587,10 +591,9 @@ export class MidyGM2 {
587
591
  return channels;
588
592
  }
589
593
  async createAudioBuffer(voiceParams) {
590
- const sample = voiceParams.sample;
591
- const sampleStart = voiceParams.start;
592
- const sampleEnd = sample.data.length + voiceParams.end;
593
- const audioBuffer = await sample.toAudioBuffer(this.audioContext, sampleStart, sampleEnd);
594
+ const { sample, start, end } = voiceParams;
595
+ const sampleEnd = sample.data.length + end;
596
+ const audioBuffer = await sample.toAudioBuffer(this.audioContext, start, sampleEnd);
594
597
  return audioBuffer;
595
598
  }
596
599
  isLoopDrum(channel, noteNumber) {
@@ -610,7 +613,7 @@ export class MidyGM2 {
610
613
  }
611
614
  return bufferSource;
612
615
  }
613
- async scheduleTimelineEvents(scheduleTime, queueIndex) {
616
+ scheduleTimelineEvents(scheduleTime, queueIndex) {
614
617
  const timeOffset = this.resumeTime - this.startTime;
615
618
  const lookAheadCheckTime = scheduleTime + timeOffset + this.lookAhead;
616
619
  const schedulingOffset = this.startDelay - timeOffset;
@@ -622,12 +625,10 @@ export class MidyGM2 {
622
625
  const startTime = event.startTime + schedulingOffset;
623
626
  switch (event.type) {
624
627
  case "noteOn":
625
- await this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
628
+ this.noteOn(event.channel, event.noteNumber, event.velocity, startTime);
626
629
  break;
627
630
  case "noteOff": {
628
- const notePromise = this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
629
- if (notePromise)
630
- this.notePromises.push(notePromise);
631
+ this.noteOff(event.channel, event.noteNumber, event.velocity, startTime, false);
631
632
  break;
632
633
  }
633
634
  case "controller":
@@ -668,22 +669,23 @@ export class MidyGM2 {
668
669
  }
669
670
  }
670
671
  updateStates(queueIndex, nextQueueIndex) {
672
+ const now = this.audioContext.currentTime;
671
673
  if (nextQueueIndex < queueIndex)
672
674
  queueIndex = 0;
673
675
  for (let i = queueIndex; i < nextQueueIndex; i++) {
674
676
  const event = this.timeline[i];
675
677
  switch (event.type) {
676
678
  case "controller":
677
- this.setControlChange(event.channel, event.controllerType, event.value, 0);
679
+ this.setControlChange(event.channel, event.controllerType, event.value, now - this.resumeTime + event.startTime);
678
680
  break;
679
681
  case "programChange":
680
- this.setProgramChange(event.channel, event.programNumber, 0);
682
+ this.setProgramChange(event.channel, event.programNumber, now - this.resumeTime + event.startTime);
681
683
  break;
682
684
  case "pitchBend":
683
- this.setPitchBend(event.channel, event.value + 8192, 0);
685
+ this.setPitchBend(event.channel, event.value + 8192, now - this.resumeTime + event.startTime);
684
686
  break;
685
687
  case "sysEx":
686
- this.handleSysEx(event.data, 0);
688
+ this.handleSysEx(event.data, now - this.resumeTime + event.startTime);
687
689
  }
688
690
  }
689
691
  }
@@ -691,52 +693,88 @@ export class MidyGM2 {
691
693
  if (this.audioContext.state === "suspended") {
692
694
  await this.audioContext.resume();
693
695
  }
696
+ const paused = this.isPaused;
694
697
  this.isPlaying = true;
695
698
  this.isPaused = false;
696
699
  this.startTime = this.audioContext.currentTime;
700
+ if (paused) {
701
+ this.dispatchEvent(new Event("resumed"));
702
+ }
703
+ else {
704
+ this.dispatchEvent(new Event("started"));
705
+ }
697
706
  let queueIndex = this.getQueueIndex(this.resumeTime);
698
- let finished = false;
707
+ let exitReason;
699
708
  this.notePromises = [];
700
- while (queueIndex < this.timeline.length) {
709
+ while (true) {
701
710
  const now = this.audioContext.currentTime;
702
711
  if (0 < this.lastActiveSensing &&
703
712
  this.activeSensingThreshold < performance.now() - this.lastActiveSensing) {
704
713
  await this.stopNotes(0, true, now);
705
714
  await this.audioContext.suspend();
706
- finished = true;
715
+ exitReason = "aborted";
707
716
  break;
708
717
  }
718
+ if (this.timeline.length <= queueIndex) {
719
+ await this.stopNotes(0, true, now);
720
+ if (this.loop) {
721
+ this.notePromises = [];
722
+ this.resetAllStates();
723
+ this.startTime = this.audioContext.currentTime;
724
+ this.resumeTime = 0;
725
+ queueIndex = 0;
726
+ this.dispatchEvent(new Event("looped"));
727
+ continue;
728
+ }
729
+ else {
730
+ await this.audioContext.suspend();
731
+ exitReason = "ended";
732
+ break;
733
+ }
734
+ }
709
735
  if (this.isPausing) {
710
736
  await this.stopNotes(0, true, now);
711
737
  await this.audioContext.suspend();
712
738
  this.notePromises = [];
739
+ this.isPausing = false;
740
+ exitReason = "paused";
713
741
  break;
714
742
  }
715
743
  else if (this.isStopping) {
716
744
  await this.stopNotes(0, true, now);
717
745
  await this.audioContext.suspend();
718
- finished = true;
746
+ this.isStopping = false;
747
+ exitReason = "stopped";
719
748
  break;
720
749
  }
721
750
  else if (this.isSeeking) {
722
- await this.stopNotes(0, true, now);
751
+ this.stopNotes(0, true, now);
723
752
  this.startTime = this.audioContext.currentTime;
724
753
  const nextQueueIndex = this.getQueueIndex(this.resumeTime);
725
754
  this.updateStates(queueIndex, nextQueueIndex);
726
755
  queueIndex = nextQueueIndex;
727
756
  this.isSeeking = false;
757
+ this.dispatchEvent(new Event("seeked"));
728
758
  continue;
729
759
  }
730
- queueIndex = await this.scheduleTimelineEvents(now, queueIndex);
760
+ queueIndex = this.scheduleTimelineEvents(now, queueIndex);
731
761
  const waitTime = now + this.noteCheckInterval;
732
762
  await this.scheduleTask(() => { }, waitTime);
733
763
  }
734
- if (finished) {
764
+ if (exitReason !== "paused") {
735
765
  this.notePromises = [];
736
766
  this.resetAllStates();
737
767
  this.lastActiveSensing = 0;
738
768
  }
739
769
  this.isPlaying = false;
770
+ if (exitReason === "paused") {
771
+ this.isPaused = true;
772
+ this.dispatchEvent(new Event("paused"));
773
+ }
774
+ else {
775
+ this.isPaused = false;
776
+ this.dispatchEvent(new Event(exitReason));
777
+ }
740
778
  }
741
779
  ticksToSecond(ticks, secondsPerBeat) {
742
780
  return ticks * secondsPerBeat / this.ticksPerBeat;
@@ -873,24 +911,20 @@ export class MidyGM2 {
873
911
  return;
874
912
  this.isStopping = true;
875
913
  await this.playPromise;
876
- this.isStopping = false;
877
914
  }
878
915
  async pause() {
879
916
  if (!this.isPlaying || this.isPaused)
880
917
  return;
881
918
  const now = this.audioContext.currentTime;
882
- this.resumeTime = now - this.startTime - this.startDelay;
919
+ this.resumeTime = now + this.resumeTime - this.startTime;
883
920
  this.isPausing = true;
884
921
  await this.playPromise;
885
- this.isPausing = false;
886
- this.isPaused = true;
887
922
  }
888
923
  async resume() {
889
924
  if (!this.isPaused)
890
925
  return;
891
926
  this.playPromise = this.playNotes();
892
927
  await this.playPromise;
893
- this.isPaused = false;
894
928
  }
895
929
  seekTo(second) {
896
930
  this.resumeTime = second;
@@ -913,19 +947,23 @@ export class MidyGM2 {
913
947
  const now = this.audioContext.currentTime;
914
948
  return now + this.resumeTime - this.startTime;
915
949
  }
916
- processScheduledNotes(channel, callback) {
950
+ async processScheduledNotes(channel, callback) {
917
951
  const scheduledNotes = channel.scheduledNotes;
952
+ const tasks = [];
918
953
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
919
954
  const note = scheduledNotes[i];
920
955
  if (!note)
921
956
  continue;
922
957
  if (note.ending)
923
958
  continue;
924
- callback(note);
959
+ const task = note.ready.then(() => callback(note));
960
+ tasks.push(task);
925
961
  }
962
+ await Promise.all(tasks);
926
963
  }
927
- processActiveNotes(channel, scheduleTime, callback) {
964
+ async processActiveNotes(channel, scheduleTime, callback) {
928
965
  const scheduledNotes = channel.scheduledNotes;
966
+ const tasks = [];
929
967
  for (let i = channel.scheduleIndex; i < scheduledNotes.length; i++) {
930
968
  const note = scheduledNotes[i];
931
969
  if (!note)
@@ -934,8 +972,10 @@ export class MidyGM2 {
934
972
  continue;
935
973
  if (scheduleTime < note.startTime)
936
974
  break;
937
- callback(note);
975
+ const task = note.ready.then(() => callback(note));
976
+ tasks.push(task);
938
977
  }
978
+ await Promise.all(tasks);
939
979
  }
940
980
  createConvolutionReverbImpulse(audioContext, decay, preDecay) {
941
981
  const sampleRate = audioContext.sampleRate;
@@ -1139,7 +1179,7 @@ export class MidyGM2 {
1139
1179
  }
1140
1180
  getPortamentoTime(channel, note) {
1141
1181
  const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
1142
- const value = Math.ceil(channel.state.portamentoTime * 127);
1182
+ const value = Math.ceil(channel.state.portamentoTimeMSB * 127);
1143
1183
  return deltaSemitone / this.getPitchIncrementSpeed(value) / 10;
1144
1184
  }
1145
1185
  getPitchIncrementSpeed(value) {
@@ -1398,7 +1438,7 @@ export class MidyGM2 {
1398
1438
  if (0 < state.vibratoDepth) {
1399
1439
  this.startVibrato(channel, note, now);
1400
1440
  }
1401
- if (0 < state.modulationDepth) {
1441
+ if (0 < state.modulationDepthMSB) {
1402
1442
  this.startModulation(channel, note, now);
1403
1443
  }
1404
1444
  if (channel.mono && channel.currentBufferSource) {
@@ -1409,7 +1449,13 @@ export class MidyGM2 {
1409
1449
  note.filterNode.connect(note.volumeEnvelopeNode);
1410
1450
  this.setChorusSend(channel, note, now);
1411
1451
  this.setReverbSend(channel, note, now);
1412
- note.bufferSource.start(startTime);
1452
+ if (voiceParams.sample.type === "compressed") {
1453
+ const offset = voiceParams.start / audioBuffer.sampleRate;
1454
+ note.bufferSource.start(startTime, offset);
1455
+ }
1456
+ else {
1457
+ note.bufferSource.start(startTime);
1458
+ }
1413
1459
  return note;
1414
1460
  }
1415
1461
  handleExclusiveClass(note, channelNumber, startTime) {
@@ -1494,11 +1540,7 @@ export class MidyGM2 {
1494
1540
  return;
1495
1541
  await this.setNoteAudioNode(channel, note, realtime);
1496
1542
  this.setNoteRouting(channelNumber, note, startTime);
1497
- note.pending = false;
1498
- const off = note.offEvent;
1499
- if (off) {
1500
- this.noteOff(channelNumber, noteNumber, off.velocity, off.startTime);
1501
- }
1543
+ note.resolveReady();
1502
1544
  }
1503
1545
  disconnectNote(note) {
1504
1546
  note.bufferSource.disconnect();
@@ -1542,7 +1584,7 @@ export class MidyGM2 {
1542
1584
  }, stopTime);
1543
1585
  });
1544
1586
  }
1545
- noteOff(channelNumber, noteNumber, velocity, endTime, force) {
1587
+ noteOff(channelNumber, noteNumber, _velocity, endTime, force) {
1546
1588
  const channel = this.channels[channelNumber];
1547
1589
  const state = channel.state;
1548
1590
  if (!force) {
@@ -1561,13 +1603,13 @@ export class MidyGM2 {
1561
1603
  if (index < 0)
1562
1604
  return;
1563
1605
  const note = channel.scheduledNotes[index];
1564
- if (note.pending) {
1565
- note.offEvent = { velocity, startTime: endTime };
1566
- return;
1567
- }
1568
1606
  note.ending = true;
1569
1607
  this.setNoteIndex(channel, index);
1570
- this.releaseNote(channel, note, endTime);
1608
+ const promise = note.ready.then(() => {
1609
+ return this.releaseNote(channel, note, endTime);
1610
+ });
1611
+ this.notePromises.push(promise);
1612
+ return promise;
1571
1613
  }
1572
1614
  setNoteIndex(channel, index) {
1573
1615
  let allEnds = true;
@@ -1676,7 +1718,7 @@ export class MidyGM2 {
1676
1718
  this.processActiveNotes(channel, scheduleTime, (note) => {
1677
1719
  this.setEffects(channel, note, table, scheduleTime);
1678
1720
  });
1679
- this.applyVoiceParams(channel, 13);
1721
+ this.applyVoiceParams(channel, 13, scheduleTime);
1680
1722
  }
1681
1723
  handlePitchBendMessage(channelNumber, lsb, msb, scheduleTime) {
1682
1724
  const pitchBend = msb * 128 + lsb;
@@ -1686,7 +1728,8 @@ export class MidyGM2 {
1686
1728
  const channel = this.channels[channelNumber];
1687
1729
  if (channel.isDrum)
1688
1730
  return;
1689
- scheduleTime ??= this.audioContext.currentTime;
1731
+ if (!(0 <= scheduleTime))
1732
+ scheduleTime = this.audioContext.currentTime;
1690
1733
  const state = channel.state;
1691
1734
  const prev = state.pitchWheel * 2 - 1;
1692
1735
  const next = (value - 8192) / 8192;
@@ -1699,7 +1742,8 @@ export class MidyGM2 {
1699
1742
  if (note.modulationDepth) {
1700
1743
  const modLfoToPitch = note.voiceParams.modLfoToPitch +
1701
1744
  this.getLFOPitchDepth(channel, note);
1702
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1745
+ const baseDepth = Math.abs(modLfoToPitch) +
1746
+ channel.state.modulationDepthMSB;
1703
1747
  const depth = baseDepth * Math.sign(modLfoToPitch);
1704
1748
  note.modulationDepth.gain
1705
1749
  .cancelScheduledValues(scheduleTime)
@@ -1831,7 +1875,7 @@ export class MidyGM2 {
1831
1875
  createVoiceParamsHandlers() {
1832
1876
  return {
1833
1877
  modLfoToPitch: (channel, note, scheduleTime) => {
1834
- if (0 < channel.state.modulationDepth) {
1878
+ if (0 < channel.state.modulationDepthMSB) {
1835
1879
  this.setModLfoToPitch(channel, note, scheduleTime);
1836
1880
  }
1837
1881
  },
@@ -1841,12 +1885,12 @@ export class MidyGM2 {
1841
1885
  }
1842
1886
  },
1843
1887
  modLfoToFilterFc: (channel, note, scheduleTime) => {
1844
- if (0 < channel.state.modulationDepth) {
1888
+ if (0 < channel.state.modulationDepthMSB) {
1845
1889
  this.setModLfoToFilterFc(channel, note, scheduleTime);
1846
1890
  }
1847
1891
  },
1848
1892
  modLfoToVolume: (channel, note, scheduleTime) => {
1849
- if (0 < channel.state.modulationDepth) {
1893
+ if (0 < channel.state.modulationDepthMSB) {
1850
1894
  this.setModLfoToVolume(channel, note, scheduleTime);
1851
1895
  }
1852
1896
  },
@@ -1857,12 +1901,12 @@ export class MidyGM2 {
1857
1901
  this.setReverbSend(channel, note, scheduleTime);
1858
1902
  },
1859
1903
  delayModLFO: (_channel, note, _scheduleTime) => {
1860
- if (0 < channel.state.modulationDepth) {
1904
+ if (0 < channel.state.modulationDepthMSB) {
1861
1905
  this.setDelayModLFO(note);
1862
1906
  }
1863
1907
  },
1864
1908
  freqModLFO: (_channel, note, scheduleTime) => {
1865
- if (0 < channel.state.modulationDepth) {
1909
+ if (0 < channel.state.modulationDepthMSB) {
1866
1910
  this.setFreqModLFO(note, scheduleTime);
1867
1911
  }
1868
1912
  },
@@ -1964,7 +2008,8 @@ export class MidyGM2 {
1964
2008
  this.channels[channelNumber].bankMSB = msb;
1965
2009
  }
1966
2010
  updateModulation(channel, scheduleTime) {
1967
- const depth = channel.state.modulationDepth * channel.modulationDepthRange;
2011
+ const depth = channel.state.modulationDepthMSB *
2012
+ channel.modulationDepthRange;
1968
2013
  this.processScheduledNotes(channel, (note) => {
1969
2014
  if (note.modulationDepth) {
1970
2015
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
@@ -1978,8 +2023,9 @@ export class MidyGM2 {
1978
2023
  const channel = this.channels[channelNumber];
1979
2024
  if (channel.isDrum)
1980
2025
  return;
1981
- scheduleTime ??= this.audioContext.currentTime;
1982
- channel.state.modulationDepth = modulation / 127;
2026
+ if (!(0 <= scheduleTime))
2027
+ scheduleTime = this.audioContext.currentTime;
2028
+ channel.state.modulationDepthMSB = modulation / 127;
1983
2029
  this.updateModulation(channel, scheduleTime);
1984
2030
  }
1985
2031
  updatePortamento(channel, scheduleTime) {
@@ -2001,17 +2047,19 @@ export class MidyGM2 {
2001
2047
  });
2002
2048
  }
2003
2049
  setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
2050
+ if (!(0 <= scheduleTime))
2051
+ scheduleTime = this.audioContext.currentTime;
2004
2052
  const channel = this.channels[channelNumber];
2005
- scheduleTime ??= this.audioContext.currentTime;
2006
- channel.state.portamentoTime = portamentoTime / 127;
2053
+ channel.state.portamentoTimeMSB = portamentoTime / 127;
2007
2054
  if (channel.isDrum)
2008
2055
  return;
2009
2056
  this.updatePortamento(channel, scheduleTime);
2010
2057
  }
2011
2058
  setVolume(channelNumber, volume, scheduleTime) {
2012
- scheduleTime ??= this.audioContext.currentTime;
2059
+ if (!(0 <= scheduleTime))
2060
+ scheduleTime = this.audioContext.currentTime;
2013
2061
  const channel = this.channels[channelNumber];
2014
- channel.state.volume = volume / 127;
2062
+ channel.state.volumeMSB = volume / 127;
2015
2063
  if (channel.isDrum) {
2016
2064
  for (let i = 0; i < 128; i++) {
2017
2065
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2029,9 +2077,10 @@ export class MidyGM2 {
2029
2077
  };
2030
2078
  }
2031
2079
  setPan(channelNumber, pan, scheduleTime) {
2032
- scheduleTime ??= this.audioContext.currentTime;
2080
+ if (!(0 <= scheduleTime))
2081
+ scheduleTime = this.audioContext.currentTime;
2033
2082
  const channel = this.channels[channelNumber];
2034
- channel.state.pan = pan / 127;
2083
+ channel.state.panMSB = pan / 127;
2035
2084
  if (channel.isDrum) {
2036
2085
  for (let i = 0; i < 128; i++) {
2037
2086
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2042,9 +2091,10 @@ export class MidyGM2 {
2042
2091
  }
2043
2092
  }
2044
2093
  setExpression(channelNumber, expression, scheduleTime) {
2045
- scheduleTime ??= this.audioContext.currentTime;
2094
+ if (!(0 <= scheduleTime))
2095
+ scheduleTime = this.audioContext.currentTime;
2046
2096
  const channel = this.channels[channelNumber];
2047
- channel.state.expression = expression / 127;
2097
+ channel.state.expressionMSB = expression / 127;
2048
2098
  this.updateChannelVolume(channel, scheduleTime);
2049
2099
  }
2050
2100
  setBankLSB(channelNumber, lsb) {
@@ -2056,8 +2106,8 @@ export class MidyGM2 {
2056
2106
  }
2057
2107
  updateChannelVolume(channel, scheduleTime) {
2058
2108
  const state = channel.state;
2059
- const volume = state.volume * state.expression;
2060
- const { gainLeft, gainRight } = this.panToGain(state.pan);
2109
+ const volume = state.volumeMSB * state.expressionMSB;
2110
+ const { gainLeft, gainRight } = this.panToGain(state.panMSB);
2061
2111
  channel.gainL.gain
2062
2112
  .cancelScheduledValues(scheduleTime)
2063
2113
  .setValueAtTime(volume * gainLeft, scheduleTime);
@@ -2071,8 +2121,8 @@ export class MidyGM2 {
2071
2121
  return;
2072
2122
  const gainR = channel.keyBasedGainRs[keyNumber];
2073
2123
  const state = channel.state;
2074
- const defaultVolume = state.volume * state.expression;
2075
- const defaultPan = state.pan;
2124
+ const defaultVolume = state.volumeMSB * state.expressionMSB;
2125
+ const defaultPan = state.panMSB;
2076
2126
  const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
2077
2127
  const volume = (0 <= keyBasedVolume)
2078
2128
  ? defaultVolume * keyBasedVolume / 64
@@ -2091,7 +2141,8 @@ export class MidyGM2 {
2091
2141
  const channel = this.channels[channelNumber];
2092
2142
  if (channel.isDrum)
2093
2143
  return;
2094
- scheduleTime ??= this.audioContext.currentTime;
2144
+ if (!(0 <= scheduleTime))
2145
+ scheduleTime = this.audioContext.currentTime;
2095
2146
  channel.state.sustainPedal = value / 127;
2096
2147
  if (64 <= value) {
2097
2148
  this.processScheduledNotes(channel, (note) => {
@@ -2109,7 +2160,8 @@ export class MidyGM2 {
2109
2160
  const channel = this.channels[channelNumber];
2110
2161
  if (channel.isDrum)
2111
2162
  return;
2112
- scheduleTime ??= this.audioContext.currentTime;
2163
+ if (!(0 <= scheduleTime))
2164
+ scheduleTime = this.audioContext.currentTime;
2113
2165
  channel.state.portamento = value / 127;
2114
2166
  this.updatePortamento(channel, scheduleTime);
2115
2167
  }
@@ -2117,7 +2169,8 @@ export class MidyGM2 {
2117
2169
  const channel = this.channels[channelNumber];
2118
2170
  if (channel.isDrum)
2119
2171
  return;
2120
- scheduleTime ??= this.audioContext.currentTime;
2172
+ if (!(0 <= scheduleTime))
2173
+ scheduleTime = this.audioContext.currentTime;
2121
2174
  channel.state.sostenutoPedal = value / 127;
2122
2175
  if (64 <= value) {
2123
2176
  const sostenutoNotes = [];
@@ -2138,7 +2191,8 @@ export class MidyGM2 {
2138
2191
  if (channel.isDrum)
2139
2192
  return;
2140
2193
  const state = channel.state;
2141
- scheduleTime ??= this.audioContext.currentTime;
2194
+ if (!(0 <= scheduleTime))
2195
+ scheduleTime = this.audioContext.currentTime;
2142
2196
  state.softPedal = softPedal / 127;
2143
2197
  this.processScheduledNotes(channel, (note) => {
2144
2198
  if (this.isPortamento(channel, note)) {
@@ -2152,7 +2206,8 @@ export class MidyGM2 {
2152
2206
  });
2153
2207
  }
2154
2208
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
2155
- scheduleTime ??= this.audioContext.currentTime;
2209
+ if (!(0 <= scheduleTime))
2210
+ scheduleTime = this.audioContext.currentTime;
2156
2211
  const channel = this.channels[channelNumber];
2157
2212
  const state = channel.state;
2158
2213
  state.reverbSendLevel = reverbSendLevel / 127;
@@ -2161,7 +2216,8 @@ export class MidyGM2 {
2161
2216
  });
2162
2217
  }
2163
2218
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2164
- scheduleTime ??= this.audioContext.currentTime;
2219
+ if (!(0 <= scheduleTime))
2220
+ scheduleTime = this.audioContext.currentTime;
2165
2221
  const channel = this.channels[channelNumber];
2166
2222
  const state = channel.state;
2167
2223
  state.chorusSendLevel = chorusSendLevel / 127;
@@ -2235,7 +2291,8 @@ export class MidyGM2 {
2235
2291
  const channel = this.channels[channelNumber];
2236
2292
  if (channel.isDrum)
2237
2293
  return;
2238
- scheduleTime ??= this.audioContext.currentTime;
2294
+ if (!(0 <= scheduleTime))
2295
+ scheduleTime = this.audioContext.currentTime;
2239
2296
  const state = channel.state;
2240
2297
  const prev = state.pitchWheelSensitivity;
2241
2298
  const next = value / 12800;
@@ -2255,7 +2312,8 @@ export class MidyGM2 {
2255
2312
  const channel = this.channels[channelNumber];
2256
2313
  if (channel.isDrum)
2257
2314
  return;
2258
- scheduleTime ??= this.audioContext.currentTime;
2315
+ if (!(0 <= scheduleTime))
2316
+ scheduleTime = this.audioContext.currentTime;
2259
2317
  const prev = channel.fineTuning;
2260
2318
  const next = value;
2261
2319
  channel.fineTuning = next;
@@ -2272,7 +2330,8 @@ export class MidyGM2 {
2272
2330
  const channel = this.channels[channelNumber];
2273
2331
  if (channel.isDrum)
2274
2332
  return;
2275
- scheduleTime ??= this.audioContext.currentTime;
2333
+ if (!(0 <= scheduleTime))
2334
+ scheduleTime = this.audioContext.currentTime;
2276
2335
  const prev = channel.coarseTuning;
2277
2336
  const next = value;
2278
2337
  channel.coarseTuning = next;
@@ -2289,12 +2348,14 @@ export class MidyGM2 {
2289
2348
  const channel = this.channels[channelNumber];
2290
2349
  if (channel.isDrum)
2291
2350
  return;
2292
- scheduleTime ??= this.audioContext.currentTime;
2351
+ if (!(0 <= scheduleTime))
2352
+ scheduleTime = this.audioContext.currentTime;
2293
2353
  channel.modulationDepthRange = value;
2294
2354
  this.updateModulation(channel, scheduleTime);
2295
2355
  }
2296
2356
  allSoundOff(channelNumber, _value, scheduleTime) {
2297
- scheduleTime ??= this.audioContext.currentTime;
2357
+ if (!(0 <= scheduleTime))
2358
+ scheduleTime = this.audioContext.currentTime;
2298
2359
  return this.stopActiveNotes(channelNumber, 0, true, scheduleTime);
2299
2360
  }
2300
2361
  resetChannelStates(channelNumber) {
@@ -2323,8 +2384,8 @@ export class MidyGM2 {
2323
2384
  const keys = [
2324
2385
  "channelPressure",
2325
2386
  "pitchWheel",
2326
- "expression",
2327
- "modulationDepth",
2387
+ "expressionMSB",
2388
+ "modulationDepthMSB",
2328
2389
  "sustainPedal",
2329
2390
  "portamento",
2330
2391
  "sostenutoPedal",
@@ -2353,7 +2414,8 @@ export class MidyGM2 {
2353
2414
  }
2354
2415
  }
2355
2416
  allNotesOff(channelNumber, _value, scheduleTime) {
2356
- scheduleTime ??= this.audioContext.currentTime;
2417
+ if (!(0 <= scheduleTime))
2418
+ scheduleTime = this.audioContext.currentTime;
2357
2419
  return this.stopActiveNotes(channelNumber, 0, false, scheduleTime);
2358
2420
  }
2359
2421
  omniOff(channelNumber, value, scheduleTime) {
@@ -2402,7 +2464,8 @@ export class MidyGM2 {
2402
2464
  }
2403
2465
  }
2404
2466
  GM1SystemOn(scheduleTime) {
2405
- scheduleTime ??= this.audioContext.currentTime;
2467
+ if (!(0 <= scheduleTime))
2468
+ scheduleTime = this.audioContext.currentTime;
2406
2469
  this.mode = "GM1";
2407
2470
  for (let i = 0; i < this.channels.length; i++) {
2408
2471
  this.allSoundOff(i, 0, scheduleTime);
@@ -2415,7 +2478,8 @@ export class MidyGM2 {
2415
2478
  this.channels[9].isDrum = true;
2416
2479
  }
2417
2480
  GM2SystemOn(scheduleTime) {
2418
- scheduleTime ??= this.audioContext.currentTime;
2481
+ if (!(0 <= scheduleTime))
2482
+ scheduleTime = this.audioContext.currentTime;
2419
2483
  this.mode = "GM2";
2420
2484
  for (let i = 0; i < this.channels.length; i++) {
2421
2485
  this.allSoundOff(i, 0, scheduleTime);
@@ -2470,7 +2534,8 @@ export class MidyGM2 {
2470
2534
  this.setMasterVolume(volume, scheduleTime);
2471
2535
  }
2472
2536
  setMasterVolume(value, scheduleTime) {
2473
- scheduleTime ??= this.audioContext.currentTime;
2537
+ if (!(0 <= scheduleTime))
2538
+ scheduleTime = this.audioContext.currentTime;
2474
2539
  this.masterVolume.gain
2475
2540
  .cancelScheduledValues(scheduleTime)
2476
2541
  .setValueAtTime(value * value, scheduleTime);