@marmooo/midy 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/script/midy.js CHANGED
@@ -294,18 +294,6 @@ class Midy {
294
294
  delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
295
295
  }
296
296
  });
297
- Object.defineProperty(this, "mono", {
298
- enumerable: true,
299
- configurable: true,
300
- writable: true,
301
- value: false
302
- }); // CC#124, CC#125
303
- Object.defineProperty(this, "omni", {
304
- enumerable: true,
305
- configurable: true,
306
- writable: true,
307
- value: false
308
- }); // CC#126, CC#127
309
297
  Object.defineProperty(this, "noteCheckInterval", {
310
298
  enumerable: true,
311
299
  configurable: true,
@@ -507,6 +495,7 @@ class Midy {
507
495
  controlTable: this.initControlTable(),
508
496
  ...this.setChannelAudioNodes(audioContext),
509
497
  scheduledNotes: new SparseMap(128),
498
+ sustainNotes: [],
510
499
  sostenutoNotes: new SparseMap(128),
511
500
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
512
501
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
@@ -590,7 +579,7 @@ class Midy {
590
579
  const portamentoTarget = this.findPortamentoTarget(queueIndex);
591
580
  if (portamentoTarget)
592
581
  portamentoTarget.portamento = true;
593
- const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
582
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
594
583
  portamentoTarget?.noteNumber);
595
584
  if (notePromise) {
596
585
  this.notePromises.push(notePromise);
@@ -601,7 +590,7 @@ class Midy {
601
590
  this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
602
591
  break;
603
592
  case "controller":
604
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
593
+ this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
605
594
  break;
606
595
  case "programChange":
607
596
  this.handleProgramChange(event.channel, event.programNumber, startTime);
@@ -797,15 +786,10 @@ class Midy {
797
786
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
798
787
  const channel = this.channels[channelNumber];
799
788
  const promises = [];
800
- channel.scheduledNotes.forEach((noteList) => {
801
- for (let i = 0; i < noteList.length; i++) {
802
- const note = noteList[i];
803
- if (!note)
804
- continue;
805
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
806
- this.notePromises.push(promise);
807
- promises.push(promise);
808
- }
789
+ this.processScheduledNotes(channel, (note) => {
790
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
791
+ this.notePromises.push(promise);
792
+ promises.push(promise);
809
793
  });
810
794
  channel.scheduledNotes.clear();
811
795
  return Promise.all(promises);
@@ -861,14 +845,12 @@ class Midy {
861
845
  const now = this.audioContext.currentTime;
862
846
  return this.resumeTime + now - this.startTime - this.startDelay;
863
847
  }
864
- processScheduledNotes(channel, scheduleTime, callback) {
848
+ processScheduledNotes(channel, callback) {
865
849
  channel.scheduledNotes.forEach((noteList) => {
866
850
  for (let i = 0; i < noteList.length; i++) {
867
851
  const note = noteList[i];
868
852
  if (!note)
869
853
  continue;
870
- if (scheduleTime < note.startTime)
871
- continue;
872
854
  callback(note);
873
855
  }
874
856
  });
@@ -1049,7 +1031,7 @@ class Midy {
1049
1031
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1050
1032
  }
1051
1033
  updateChannelDetune(channel, scheduleTime) {
1052
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1034
+ this.processScheduledNotes(channel, (note) => {
1053
1035
  this.updateDetune(channel, note, scheduleTime);
1054
1036
  });
1055
1037
  }
@@ -1254,7 +1236,7 @@ class Midy {
1254
1236
  if (0 < state.modulationDepth) {
1255
1237
  this.startModulation(channel, note, now);
1256
1238
  }
1257
- if (this.mono && channel.currentBufferSource) {
1239
+ if (channel.mono && channel.currentBufferSource) {
1258
1240
  channel.currentBufferSource.stop(startTime);
1259
1241
  channel.currentBufferSource = note.bufferSource;
1260
1242
  }
@@ -1295,8 +1277,8 @@ class Midy {
1295
1277
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1296
1278
  note.gainL.connect(channel.gainL);
1297
1279
  note.gainR.connect(channel.gainR);
1298
- if (channel.state.sostenutoPedal) {
1299
- channel.sostenutoNotes.set(noteNumber, note);
1280
+ if (0.5 <= channel.state.sustainPedal) {
1281
+ channel.sustainNotes.push(note);
1300
1282
  }
1301
1283
  const exclusiveClass = note.voiceParams.exclusiveClass;
1302
1284
  if (exclusiveClass !== 0) {
@@ -1365,7 +1347,7 @@ class Midy {
1365
1347
  const channel = this.channels[channelNumber];
1366
1348
  const state = channel.state;
1367
1349
  if (!force) {
1368
- if (0.5 < state.sustainPedal)
1350
+ if (0.5 <= state.sustainPedal)
1369
1351
  return;
1370
1352
  if (channel.sostenutoNotes.has(noteNumber))
1371
1353
  return;
@@ -1410,28 +1392,27 @@ class Midy {
1410
1392
  const velocity = halfVelocity * 2;
1411
1393
  const channel = this.channels[channelNumber];
1412
1394
  const promises = [];
1413
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1414
- const { noteNumber } = note;
1415
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1395
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
1396
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1416
1397
  promises.push(promise);
1417
- });
1398
+ }
1399
+ channel.sustainNotes = [];
1418
1400
  return promises;
1419
1401
  }
1420
- releaseSostenutoPedal(channelNumber, halfVelocity) {
1402
+ releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
1421
1403
  const velocity = halfVelocity * 2;
1422
1404
  const channel = this.channels[channelNumber];
1423
1405
  const promises = [];
1424
1406
  channel.state.sostenutoPedal = 0;
1425
- channel.sostenutoNotes.forEach((activeNote) => {
1426
- const { noteNumber } = activeNote;
1427
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1407
+ channel.sostenutoNotes.forEach((note) => {
1408
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1428
1409
  promises.push(promise);
1429
1410
  });
1430
1411
  channel.sostenutoNotes.clear();
1431
1412
  return promises;
1432
1413
  }
1433
1414
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1434
- const channelNumber = omni ? 0 : statusByte & 0x0F;
1415
+ const channelNumber = statusByte & 0x0F;
1435
1416
  const messageType = statusByte & 0xF0;
1436
1417
  switch (messageType) {
1437
1418
  case 0x80:
@@ -1659,53 +1640,48 @@ class Midy {
1659
1640
  return state;
1660
1641
  }
1661
1642
  applyVoiceParams(channel, controllerType, scheduleTime) {
1662
- channel.scheduledNotes.forEach((noteList) => {
1663
- for (let i = 0; i < noteList.length; i++) {
1664
- const note = noteList[i];
1665
- if (!note)
1643
+ this.processScheduledNotes(channel, (note) => {
1644
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1645
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1646
+ let appliedFilterEnvelope = false;
1647
+ let appliedVolumeEnvelope = false;
1648
+ for (const [key, value] of Object.entries(voiceParams)) {
1649
+ const prevValue = note.voiceParams[key];
1650
+ if (value === prevValue)
1666
1651
  continue;
1667
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1668
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1669
- let appliedFilterEnvelope = false;
1670
- let appliedVolumeEnvelope = false;
1671
- for (const [key, value] of Object.entries(voiceParams)) {
1672
- const prevValue = note.voiceParams[key];
1673
- if (value === prevValue)
1652
+ note.voiceParams[key] = value;
1653
+ if (key in this.voiceParamsHandlers) {
1654
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1655
+ }
1656
+ else if (filterEnvelopeKeySet.has(key)) {
1657
+ if (appliedFilterEnvelope)
1674
1658
  continue;
1675
- note.voiceParams[key] = value;
1676
- if (key in this.voiceParamsHandlers) {
1677
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1659
+ appliedFilterEnvelope = true;
1660
+ const noteVoiceParams = note.voiceParams;
1661
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1662
+ const key = filterEnvelopeKeys[i];
1663
+ if (key in voiceParams)
1664
+ noteVoiceParams[key] = voiceParams[key];
1678
1665
  }
1679
- else if (filterEnvelopeKeySet.has(key)) {
1680
- if (appliedFilterEnvelope)
1681
- continue;
1682
- appliedFilterEnvelope = true;
1683
- const noteVoiceParams = note.voiceParams;
1684
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1685
- const key = filterEnvelopeKeys[i];
1686
- if (key in voiceParams)
1687
- noteVoiceParams[key] = voiceParams[key];
1688
- }
1689
- if (note.portamento) {
1690
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1691
- }
1692
- else {
1693
- this.setFilterEnvelope(channel, note, scheduleTime);
1694
- }
1695
- this.setPitchEnvelope(note, scheduleTime);
1666
+ if (note.portamento) {
1667
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1696
1668
  }
1697
- else if (volumeEnvelopeKeySet.has(key)) {
1698
- if (appliedVolumeEnvelope)
1699
- continue;
1700
- appliedVolumeEnvelope = true;
1701
- const noteVoiceParams = note.voiceParams;
1702
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1703
- const key = volumeEnvelopeKeys[i];
1704
- if (key in voiceParams)
1705
- noteVoiceParams[key] = voiceParams[key];
1706
- }
1707
- this.setVolumeEnvelope(channel, note, scheduleTime);
1669
+ else {
1670
+ this.setFilterEnvelope(channel, note, scheduleTime);
1671
+ }
1672
+ this.setPitchEnvelope(note, scheduleTime);
1673
+ }
1674
+ else if (volumeEnvelopeKeySet.has(key)) {
1675
+ if (appliedVolumeEnvelope)
1676
+ continue;
1677
+ appliedVolumeEnvelope = true;
1678
+ const noteVoiceParams = note.voiceParams;
1679
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1680
+ const key = volumeEnvelopeKeys[i];
1681
+ if (key in voiceParams)
1682
+ noteVoiceParams[key] = voiceParams[key];
1708
1683
  }
1684
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1709
1685
  }
1710
1686
  }
1711
1687
  });
@@ -1764,9 +1740,8 @@ class Midy {
1764
1740
  this.channels[channelNumber].bankMSB = msb;
1765
1741
  }
1766
1742
  updateModulation(channel, scheduleTime) {
1767
- scheduleTime ??= this.audioContext.currentTime;
1768
1743
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1769
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1744
+ this.processScheduledNotes(channel, (note) => {
1770
1745
  if (note.modulationDepth) {
1771
1746
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1772
1747
  }
@@ -1777,6 +1752,7 @@ class Midy {
1777
1752
  });
1778
1753
  }
1779
1754
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1755
+ scheduleTime ??= this.audioContext.currentTime;
1780
1756
  const channel = this.channels[channelNumber];
1781
1757
  channel.state.modulationDepth = modulation / 127;
1782
1758
  this.updateModulation(channel, scheduleTime);
@@ -1787,8 +1763,7 @@ class Midy {
1787
1763
  channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1788
1764
  }
1789
1765
  setKeyBasedVolume(channel, scheduleTime) {
1790
- scheduleTime ??= this.audioContext.currentTime;
1791
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1766
+ this.processScheduledNotes(channel, (note) => {
1792
1767
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1793
1768
  if (keyBasedValue !== 0) {
1794
1769
  note.volumeNode.gain
@@ -1798,6 +1773,7 @@ class Midy {
1798
1773
  });
1799
1774
  }
1800
1775
  setVolume(channelNumber, volume, scheduleTime) {
1776
+ scheduleTime ??= this.audioContext.currentTime;
1801
1777
  const channel = this.channels[channelNumber];
1802
1778
  channel.state.volume = volume / 127;
1803
1779
  this.updateChannelVolume(channel, scheduleTime);
@@ -1811,8 +1787,7 @@ class Midy {
1811
1787
  };
1812
1788
  }
1813
1789
  setKeyBasedPan(channel, scheduleTime) {
1814
- scheduleTime ??= this.audioContext.currentTime;
1815
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1790
+ this.processScheduledNotes(channel, (note) => {
1816
1791
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1817
1792
  if (keyBasedValue !== 0) {
1818
1793
  const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
@@ -1826,12 +1801,14 @@ class Midy {
1826
1801
  });
1827
1802
  }
1828
1803
  setPan(channelNumber, pan, scheduleTime) {
1804
+ scheduleTime ??= this.audioContext.currentTime;
1829
1805
  const channel = this.channels[channelNumber];
1830
1806
  channel.state.pan = pan / 127;
1831
1807
  this.updateChannelVolume(channel, scheduleTime);
1832
1808
  this.setKeyBasedPan(channel, scheduleTime);
1833
1809
  }
1834
1810
  setExpression(channelNumber, expression, scheduleTime) {
1811
+ scheduleTime ??= this.audioContext.currentTime;
1835
1812
  const channel = this.channels[channelNumber];
1836
1813
  channel.state.expression = expression / 127;
1837
1814
  this.updateChannelVolume(channel, scheduleTime);
@@ -1856,8 +1833,14 @@ class Midy {
1856
1833
  }
1857
1834
  setSustainPedal(channelNumber, value, scheduleTime) {
1858
1835
  scheduleTime ??= this.audioContext.currentTime;
1859
- this.channels[channelNumber].state.sustainPedal = value / 127;
1860
- if (value < 64) {
1836
+ const channel = this.channels[channelNumber];
1837
+ channel.state.sustainPedal = value / 127;
1838
+ if (64 <= value) {
1839
+ this.processScheduledNotes(channel, (note) => {
1840
+ channel.sustainNotes.push(note);
1841
+ });
1842
+ }
1843
+ else {
1861
1844
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1862
1845
  }
1863
1846
  }
@@ -1865,13 +1848,14 @@ class Midy {
1865
1848
  this.channels[channelNumber].state.portamento = value / 127;
1866
1849
  }
1867
1850
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1851
+ scheduleTime ??= this.audioContext.currentTime;
1868
1852
  const channel = this.channels[channelNumber];
1869
1853
  channel.state.sostenutoPedal = value / 127;
1870
1854
  if (64 <= value) {
1871
1855
  channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
1872
1856
  }
1873
1857
  else {
1874
- this.releaseSostenutoPedal(channelNumber, value);
1858
+ this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
1875
1859
  }
1876
1860
  }
1877
1861
  setSoftPedal(channelNumber, softPedal, _scheduleTime) {
@@ -1879,120 +1863,89 @@ class Midy {
1879
1863
  channel.state.softPedal = softPedal / 127;
1880
1864
  }
1881
1865
  setFilterResonance(channelNumber, filterResonance, scheduleTime) {
1866
+ scheduleTime ??= this.audioContext.currentTime;
1882
1867
  const channel = this.channels[channelNumber];
1883
1868
  const state = channel.state;
1884
1869
  state.filterResonance = filterResonance / 64;
1885
- channel.scheduledNotes.forEach((noteList) => {
1886
- for (let i = 0; i < noteList.length; i++) {
1887
- const note = noteList[i];
1888
- if (!note)
1889
- continue;
1890
- const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1891
- note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1892
- }
1870
+ this.processScheduledNotes(channel, (note) => {
1871
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1872
+ note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1893
1873
  });
1894
1874
  }
1895
1875
  setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
1876
+ scheduleTime ??= this.audioContext.currentTime;
1896
1877
  const channel = this.channels[channelNumber];
1897
1878
  channel.state.releaseTime = releaseTime / 64;
1898
1879
  }
1899
1880
  setAttackTime(channelNumber, attackTime, scheduleTime) {
1881
+ scheduleTime ??= this.audioContext.currentTime;
1900
1882
  const channel = this.channels[channelNumber];
1901
1883
  channel.state.attackTime = attackTime / 64;
1902
- channel.scheduledNotes.forEach((noteList) => {
1903
- for (let i = 0; i < noteList.length; i++) {
1904
- const note = noteList[i];
1905
- if (!note)
1906
- continue;
1907
- if (note.startTime < scheduleTime)
1908
- continue;
1909
- this.setVolumeEnvelope(channel, note);
1910
- }
1884
+ this.processScheduledNotes(channel, (note) => {
1885
+ if (note.startTime < scheduleTime)
1886
+ return false;
1887
+ this.setVolumeEnvelope(channel, note);
1911
1888
  });
1912
1889
  }
1913
1890
  setBrightness(channelNumber, brightness, scheduleTime) {
1891
+ scheduleTime ??= this.audioContext.currentTime;
1914
1892
  const channel = this.channels[channelNumber];
1915
1893
  channel.state.brightness = brightness / 64;
1916
- channel.scheduledNotes.forEach((noteList) => {
1917
- for (let i = 0; i < noteList.length; i++) {
1918
- const note = noteList[i];
1919
- if (!note)
1920
- continue;
1921
- if (note.portamento) {
1922
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1923
- }
1924
- else {
1925
- this.setFilterEnvelope(channel, note);
1926
- }
1894
+ this.processScheduledNotes(channel, (note) => {
1895
+ if (note.portamento) {
1896
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1897
+ }
1898
+ else {
1899
+ this.setFilterEnvelope(channel, note);
1927
1900
  }
1928
1901
  });
1929
1902
  }
1930
1903
  setDecayTime(channelNumber, dacayTime, scheduleTime) {
1904
+ scheduleTime ??= this.audioContext.currentTime;
1931
1905
  const channel = this.channels[channelNumber];
1932
1906
  channel.state.decayTime = dacayTime / 64;
1933
- channel.scheduledNotes.forEach((noteList) => {
1934
- for (let i = 0; i < noteList.length; i++) {
1935
- const note = noteList[i];
1936
- if (!note)
1937
- continue;
1938
- this.setVolumeEnvelope(channel, note, scheduleTime);
1939
- }
1907
+ this.processScheduledNotes(channel, (note) => {
1908
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1940
1909
  });
1941
1910
  }
1942
1911
  setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
1912
+ scheduleTime ??= this.audioContext.currentTime;
1943
1913
  const channel = this.channels[channelNumber];
1944
1914
  channel.state.vibratoRate = vibratoRate / 64;
1945
1915
  if (channel.vibratoDepth <= 0)
1946
1916
  return;
1947
- channel.scheduledNotes.forEach((noteList) => {
1948
- for (let i = 0; i < noteList.length; i++) {
1949
- const note = noteList[i];
1950
- if (!note)
1951
- continue;
1952
- this.setVibLfoToPitch(channel, note, scheduleTime);
1953
- }
1917
+ this.processScheduledNotes(channel, (note) => {
1918
+ this.setVibLfoToPitch(channel, note, scheduleTime);
1954
1919
  });
1955
1920
  }
1956
1921
  setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
1922
+ scheduleTime ??= this.audioContext.currentTime;
1957
1923
  const channel = this.channels[channelNumber];
1958
1924
  const prev = channel.state.vibratoDepth;
1959
1925
  channel.state.vibratoDepth = vibratoDepth / 64;
1960
1926
  if (0 < prev) {
1961
- channel.scheduledNotes.forEach((noteList) => {
1962
- for (let i = 0; i < noteList.length; i++) {
1963
- const note = noteList[i];
1964
- if (!note)
1965
- continue;
1966
- this.setFreqVibLFO(channel, note, scheduleTime);
1967
- }
1927
+ this.processScheduledNotes(channel, (note) => {
1928
+ this.setFreqVibLFO(channel, note, scheduleTime);
1968
1929
  });
1969
1930
  }
1970
1931
  else {
1971
- channel.scheduledNotes.forEach((noteList) => {
1972
- for (let i = 0; i < noteList.length; i++) {
1973
- const note = noteList[i];
1974
- if (!note)
1975
- continue;
1976
- this.startVibrato(channel, note, scheduleTime);
1977
- }
1932
+ this.processScheduledNotes(channel, (note) => {
1933
+ this.startVibrato(channel, note, scheduleTime);
1978
1934
  });
1979
1935
  }
1980
1936
  }
1981
1937
  setVibratoDelay(channelNumber, vibratoDelay) {
1938
+ scheduleTime ??= this.audioContext.currentTime;
1982
1939
  const channel = this.channels[channelNumber];
1983
1940
  channel.state.vibratoDelay = vibratoDelay / 64;
1984
1941
  if (0 < channel.state.vibratoDepth) {
1985
- channel.scheduledNotes.forEach((noteList) => {
1986
- for (let i = 0; i < noteList.length; i++) {
1987
- const note = noteList[i];
1988
- if (!note)
1989
- continue;
1990
- this.startVibrato(channel, note, scheduleTime);
1991
- }
1942
+ this.processScheduledNotes(channel, (note) => {
1943
+ this.startVibrato(channel, note, scheduleTime);
1992
1944
  });
1993
1945
  }
1994
1946
  }
1995
1947
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
1948
+ scheduleTime ??= this.audioContext.currentTime;
1996
1949
  const channel = this.channels[channelNumber];
1997
1950
  const state = channel.state;
1998
1951
  const reverbEffect = this.reverbEffect;
@@ -2004,27 +1957,17 @@ class Midy {
2004
1957
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2005
1958
  }
2006
1959
  else {
2007
- channel.scheduledNotes.forEach((noteList) => {
2008
- for (let i = 0; i < noteList.length; i++) {
2009
- const note = noteList[i];
2010
- if (!note)
2011
- continue;
2012
- if (note.voiceParams.reverbEffectsSend <= 0)
2013
- continue;
2014
- note.reverbEffectsSend.disconnect();
2015
- }
1960
+ this.processScheduledNotes(channel, (note) => {
1961
+ if (note.voiceParams.reverbEffectsSend <= 0)
1962
+ return false;
1963
+ note.reverbEffectsSend.disconnect();
2016
1964
  });
2017
1965
  }
2018
1966
  }
2019
1967
  else {
2020
1968
  if (0 < reverbSendLevel) {
2021
- channel.scheduledNotes.forEach((noteList) => {
2022
- for (let i = 0; i < noteList.length; i++) {
2023
- const note = noteList[i];
2024
- if (!note)
2025
- continue;
2026
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2027
- }
1969
+ this.processScheduledNotes(channel, (note) => {
1970
+ this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2028
1971
  });
2029
1972
  state.reverbSendLevel = reverbSendLevel / 127;
2030
1973
  reverbEffect.input.gain
@@ -2034,6 +1977,7 @@ class Midy {
2034
1977
  }
2035
1978
  }
2036
1979
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
1980
+ scheduleTime ??= this.audioContext.currentTime;
2037
1981
  const channel = this.channels[channelNumber];
2038
1982
  const state = channel.state;
2039
1983
  const chorusEffect = this.chorusEffect;
@@ -2045,27 +1989,17 @@ class Midy {
2045
1989
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2046
1990
  }
2047
1991
  else {
2048
- channel.scheduledNotes.forEach((noteList) => {
2049
- for (let i = 0; i < noteList.length; i++) {
2050
- const note = noteList[i];
2051
- if (!note)
2052
- continue;
2053
- if (note.voiceParams.chorusEffectsSend <= 0)
2054
- continue;
2055
- note.chorusEffectsSend.disconnect();
2056
- }
1992
+ this.processScheduledNotes(channel, (note) => {
1993
+ if (note.voiceParams.chorusEffectsSend <= 0)
1994
+ return false;
1995
+ note.chorusEffectsSend.disconnect();
2057
1996
  });
2058
1997
  }
2059
1998
  }
2060
1999
  else {
2061
2000
  if (0 < chorusSendLevel) {
2062
- channel.scheduledNotes.forEach((noteList) => {
2063
- for (let i = 0; i < noteList.length; i++) {
2064
- const note = noteList[i];
2065
- if (!note)
2066
- continue;
2067
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2068
- }
2001
+ this.processScheduledNotes(channel, (note) => {
2002
+ this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2069
2003
  });
2070
2004
  state.chorusSendLevel = chorusSendLevel / 127;
2071
2005
  chorusEffect.input.gain
@@ -2110,15 +2044,15 @@ class Midy {
2110
2044
  break;
2111
2045
  case 1:
2112
2046
  channel.dataLSB += value;
2113
- this.handleFineTuningRPN(channelNumber);
2047
+ this.handleFineTuningRPN(channelNumber, scheduleTime);
2114
2048
  break;
2115
2049
  case 2:
2116
2050
  channel.dataMSB += value;
2117
- this.handleCoarseTuningRPN(channelNumber);
2051
+ this.handleCoarseTuningRPN(channelNumber, scheduleTime);
2118
2052
  break;
2119
2053
  case 5:
2120
2054
  channel.dataLSB += value;
2121
- this.handleModulationDepthRangeRPN(channelNumber);
2055
+ this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
2122
2056
  break;
2123
2057
  default:
2124
2058
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -2159,44 +2093,47 @@ class Midy {
2159
2093
  this.updateChannelDetune(channel, scheduleTime);
2160
2094
  this.applyVoiceParams(channel, 16, scheduleTime);
2161
2095
  }
2162
- handleFineTuningRPN(channelNumber) {
2096
+ handleFineTuningRPN(channelNumber, scheduleTime) {
2163
2097
  const channel = this.channels[channelNumber];
2164
2098
  this.limitData(channel, 0, 127, 0, 127);
2165
2099
  const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
2166
- this.setFineTuning(channelNumber, fineTuning);
2100
+ this.setFineTuning(channelNumber, fineTuning, scheduleTime);
2167
2101
  }
2168
- setFineTuning(channelNumber, value) {
2102
+ setFineTuning(channelNumber, value, scheduleTime) {
2103
+ scheduleTime ??= this.audioContext.currentTime;
2169
2104
  const channel = this.channels[channelNumber];
2170
2105
  const prev = channel.fineTuning;
2171
2106
  const next = (value - 8192) / 8.192; // cent
2172
2107
  channel.fineTuning = next;
2173
2108
  channel.detune += next - prev;
2174
- this.updateChannelDetune(channel);
2109
+ this.updateChannelDetune(channel, scheduleTime);
2175
2110
  }
2176
- handleCoarseTuningRPN(channelNumber) {
2111
+ handleCoarseTuningRPN(channelNumber, scheduleTime) {
2177
2112
  const channel = this.channels[channelNumber];
2178
2113
  this.limitDataMSB(channel, 0, 127);
2179
2114
  const coarseTuning = channel.dataMSB;
2180
- this.setCoarseTuning(channelNumber, coarseTuning);
2115
+ this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
2181
2116
  }
2182
- setCoarseTuning(channelNumber, value) {
2117
+ setCoarseTuning(channelNumber, value, scheduleTime) {
2118
+ scheduleTime ??= this.audioContext.currentTime;
2183
2119
  const channel = this.channels[channelNumber];
2184
2120
  const prev = channel.coarseTuning;
2185
2121
  const next = (value - 64) * 100; // cent
2186
2122
  channel.coarseTuning = next;
2187
2123
  channel.detune += next - prev;
2188
- this.updateChannelDetune(channel);
2124
+ this.updateChannelDetune(channel, scheduleTime);
2189
2125
  }
2190
- handleModulationDepthRangeRPN(channelNumber) {
2126
+ handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
2191
2127
  const channel = this.channels[channelNumber];
2192
2128
  this.limitData(channel, 0, 127, 0, 127);
2193
2129
  const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
2194
- this.setModulationDepthRange(channelNumber, modulationDepthRange);
2130
+ this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
2195
2131
  }
2196
- setModulationDepthRange(channelNumber, modulationDepthRange) {
2132
+ setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2133
+ scheduleTime ??= this.audioContext.currentTime;
2197
2134
  const channel = this.channels[channelNumber];
2198
2135
  channel.modulationDepthRange = modulationDepthRange;
2199
- this.updateModulation(channel);
2136
+ this.updateModulation(channel, scheduleTime);
2200
2137
  }
2201
2138
  allSoundOff(channelNumber, _value, scheduleTime) {
2202
2139
  scheduleTime ??= this.audioContext.currentTime;
@@ -2232,17 +2169,21 @@ class Midy {
2232
2169
  scheduleTime ??= this.audioContext.currentTime;
2233
2170
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2234
2171
  }
2235
- omniOff() {
2236
- this.omni = false;
2172
+ omniOff(channelNumber, value, scheduleTime) {
2173
+ this.allNotesOff(channelNumber, value, scheduleTime);
2237
2174
  }
2238
- omniOn() {
2239
- this.omni = true;
2175
+ omniOn(channelNumber, value, scheduleTime) {
2176
+ this.allNotesOff(channelNumber, value, scheduleTime);
2240
2177
  }
2241
- monoOn() {
2242
- this.mono = true;
2178
+ monoOn(channelNumber, value, scheduleTime) {
2179
+ const channel = this.channels[channelNumber];
2180
+ this.allNotesOff(channelNumber, value, scheduleTime);
2181
+ channel.mono = true;
2243
2182
  }
2244
- polyOn() {
2245
- this.mono = false;
2183
+ polyOn(channelNumber, value, scheduleTime) {
2184
+ const channel = this.channels[channelNumber];
2185
+ this.allNotesOff(channelNumber, value, scheduleTime);
2186
+ channel.mono = false;
2246
2187
  }
2247
2188
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
2248
2189
  switch (data[2]) {
@@ -2704,13 +2645,8 @@ class Midy {
2704
2645
  const slotSize = 6;
2705
2646
  const offset = controllerType * slotSize;
2706
2647
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2707
- channel.scheduledNotes.forEach((noteList) => {
2708
- for (let i = 0; i < noteList.length; i++) {
2709
- const note = noteList[i];
2710
- if (!note)
2711
- continue;
2712
- this.setControllerParameters(channel, note, table);
2713
- }
2648
+ this.processScheduledNotes(channel, (note) => {
2649
+ this.setControllerParameters(channel, note, table);
2714
2650
  });
2715
2651
  }
2716
2652
  handleControlChangeSysEx(data) {
@@ -2778,6 +2714,7 @@ Object.defineProperty(Midy, "channelSettings", {
2778
2714
  dataLSB: 0,
2779
2715
  rpnMSB: 127,
2780
2716
  rpnLSB: 127,
2717
+ mono: false, // CC#124, CC#125
2781
2718
  fineTuning: 0, // cb
2782
2719
  coarseTuning: 0, // cb
2783
2720
  modulationDepthRange: 50, // cent