@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/esm/midy.js CHANGED
@@ -291,18 +291,6 @@ export class Midy {
291
291
  delayTimes: this.generateDistributedArray(0.02, 2, 0.5),
292
292
  }
293
293
  });
294
- Object.defineProperty(this, "mono", {
295
- enumerable: true,
296
- configurable: true,
297
- writable: true,
298
- value: false
299
- }); // CC#124, CC#125
300
- Object.defineProperty(this, "omni", {
301
- enumerable: true,
302
- configurable: true,
303
- writable: true,
304
- value: false
305
- }); // CC#126, CC#127
306
294
  Object.defineProperty(this, "noteCheckInterval", {
307
295
  enumerable: true,
308
296
  configurable: true,
@@ -504,6 +492,7 @@ export class Midy {
504
492
  controlTable: this.initControlTable(),
505
493
  ...this.setChannelAudioNodes(audioContext),
506
494
  scheduledNotes: new SparseMap(128),
495
+ sustainNotes: [],
507
496
  sostenutoNotes: new SparseMap(128),
508
497
  scaleOctaveTuningTable: new Float32Array(12), // [-100, 100] cent
509
498
  channelPressureTable: new Uint8Array([64, 64, 64, 0, 0, 0]),
@@ -587,7 +576,7 @@ export class Midy {
587
576
  const portamentoTarget = this.findPortamentoTarget(queueIndex);
588
577
  if (portamentoTarget)
589
578
  portamentoTarget.portamento = true;
590
- const notePromise = this.scheduleNoteOff(this.omni ? 0 : event.channel, event.noteNumber, event.velocity, startTime, false, // force
579
+ const notePromise = this.scheduleNoteOff(event.channel, event.noteNumber, event.velocity, startTime, false, // force
591
580
  portamentoTarget?.noteNumber);
592
581
  if (notePromise) {
593
582
  this.notePromises.push(notePromise);
@@ -598,7 +587,7 @@ export class Midy {
598
587
  this.handlePolyphonicKeyPressure(event.channel, event.noteNumber, event.amount, startTime);
599
588
  break;
600
589
  case "controller":
601
- this.handleControlChange(this.omni ? 0 : event.channel, event.controllerType, event.value, startTime);
590
+ this.handleControlChange(event.channel, event.controllerType, event.value, startTime);
602
591
  break;
603
592
  case "programChange":
604
593
  this.handleProgramChange(event.channel, event.programNumber, startTime);
@@ -794,15 +783,10 @@ export class Midy {
794
783
  stopChannelNotes(channelNumber, velocity, force, scheduleTime) {
795
784
  const channel = this.channels[channelNumber];
796
785
  const promises = [];
797
- channel.scheduledNotes.forEach((noteList) => {
798
- for (let i = 0; i < noteList.length; i++) {
799
- const note = noteList[i];
800
- if (!note)
801
- continue;
802
- const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
803
- this.notePromises.push(promise);
804
- promises.push(promise);
805
- }
786
+ this.processScheduledNotes(channel, (note) => {
787
+ const promise = this.scheduleNoteOff(channelNumber, note.noteNumber, velocity, scheduleTime, force, undefined);
788
+ this.notePromises.push(promise);
789
+ promises.push(promise);
806
790
  });
807
791
  channel.scheduledNotes.clear();
808
792
  return Promise.all(promises);
@@ -858,14 +842,12 @@ export class Midy {
858
842
  const now = this.audioContext.currentTime;
859
843
  return this.resumeTime + now - this.startTime - this.startDelay;
860
844
  }
861
- processScheduledNotes(channel, scheduleTime, callback) {
845
+ processScheduledNotes(channel, callback) {
862
846
  channel.scheduledNotes.forEach((noteList) => {
863
847
  for (let i = 0; i < noteList.length; i++) {
864
848
  const note = noteList[i];
865
849
  if (!note)
866
850
  continue;
867
- if (scheduleTime < note.startTime)
868
- continue;
869
851
  callback(note);
870
852
  }
871
853
  });
@@ -1046,7 +1028,7 @@ export class Midy {
1046
1028
  return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1047
1029
  }
1048
1030
  updateChannelDetune(channel, scheduleTime) {
1049
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1031
+ this.processScheduledNotes(channel, (note) => {
1050
1032
  this.updateDetune(channel, note, scheduleTime);
1051
1033
  });
1052
1034
  }
@@ -1251,7 +1233,7 @@ export class Midy {
1251
1233
  if (0 < state.modulationDepth) {
1252
1234
  this.startModulation(channel, note, now);
1253
1235
  }
1254
- if (this.mono && channel.currentBufferSource) {
1236
+ if (channel.mono && channel.currentBufferSource) {
1255
1237
  channel.currentBufferSource.stop(startTime);
1256
1238
  channel.currentBufferSource = note.bufferSource;
1257
1239
  }
@@ -1292,8 +1274,8 @@ export class Midy {
1292
1274
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime, portamento, isSF3);
1293
1275
  note.gainL.connect(channel.gainL);
1294
1276
  note.gainR.connect(channel.gainR);
1295
- if (channel.state.sostenutoPedal) {
1296
- channel.sostenutoNotes.set(noteNumber, note);
1277
+ if (0.5 <= channel.state.sustainPedal) {
1278
+ channel.sustainNotes.push(note);
1297
1279
  }
1298
1280
  const exclusiveClass = note.voiceParams.exclusiveClass;
1299
1281
  if (exclusiveClass !== 0) {
@@ -1362,7 +1344,7 @@ export class Midy {
1362
1344
  const channel = this.channels[channelNumber];
1363
1345
  const state = channel.state;
1364
1346
  if (!force) {
1365
- if (0.5 < state.sustainPedal)
1347
+ if (0.5 <= state.sustainPedal)
1366
1348
  return;
1367
1349
  if (channel.sostenutoNotes.has(noteNumber))
1368
1350
  return;
@@ -1407,28 +1389,27 @@ export class Midy {
1407
1389
  const velocity = halfVelocity * 2;
1408
1390
  const channel = this.channels[channelNumber];
1409
1391
  const promises = [];
1410
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1411
- const { noteNumber } = note;
1412
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1392
+ for (let i = 0; i < channel.sustainNotes.length; i++) {
1393
+ const promise = this.noteOff(channelNumber, channel.sustainNotes[i].noteNumber, velocity, scheduleTime);
1413
1394
  promises.push(promise);
1414
- });
1395
+ }
1396
+ channel.sustainNotes = [];
1415
1397
  return promises;
1416
1398
  }
1417
- releaseSostenutoPedal(channelNumber, halfVelocity) {
1399
+ releaseSostenutoPedal(channelNumber, halfVelocity, scheduleTime) {
1418
1400
  const velocity = halfVelocity * 2;
1419
1401
  const channel = this.channels[channelNumber];
1420
1402
  const promises = [];
1421
1403
  channel.state.sostenutoPedal = 0;
1422
- channel.sostenutoNotes.forEach((activeNote) => {
1423
- const { noteNumber } = activeNote;
1424
- const promise = this.noteOff(channelNumber, noteNumber, velocity);
1404
+ channel.sostenutoNotes.forEach((note) => {
1405
+ const promise = this.noteOff(channelNumber, note.noteNumber, velocity, scheduleTime);
1425
1406
  promises.push(promise);
1426
1407
  });
1427
1408
  channel.sostenutoNotes.clear();
1428
1409
  return promises;
1429
1410
  }
1430
1411
  handleMIDIMessage(statusByte, data1, data2, scheduleTime) {
1431
- const channelNumber = omni ? 0 : statusByte & 0x0F;
1412
+ const channelNumber = statusByte & 0x0F;
1432
1413
  const messageType = statusByte & 0xF0;
1433
1414
  switch (messageType) {
1434
1415
  case 0x80:
@@ -1656,53 +1637,48 @@ export class Midy {
1656
1637
  return state;
1657
1638
  }
1658
1639
  applyVoiceParams(channel, controllerType, scheduleTime) {
1659
- channel.scheduledNotes.forEach((noteList) => {
1660
- for (let i = 0; i < noteList.length; i++) {
1661
- const note = noteList[i];
1662
- if (!note)
1640
+ this.processScheduledNotes(channel, (note) => {
1641
+ const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1642
+ const voiceParams = note.voice.getParams(controllerType, controllerState);
1643
+ let appliedFilterEnvelope = false;
1644
+ let appliedVolumeEnvelope = false;
1645
+ for (const [key, value] of Object.entries(voiceParams)) {
1646
+ const prevValue = note.voiceParams[key];
1647
+ if (value === prevValue)
1663
1648
  continue;
1664
- const controllerState = this.getControllerState(channel, note.noteNumber, note.velocity);
1665
- const voiceParams = note.voice.getParams(controllerType, controllerState);
1666
- let appliedFilterEnvelope = false;
1667
- let appliedVolumeEnvelope = false;
1668
- for (const [key, value] of Object.entries(voiceParams)) {
1669
- const prevValue = note.voiceParams[key];
1670
- if (value === prevValue)
1649
+ note.voiceParams[key] = value;
1650
+ if (key in this.voiceParamsHandlers) {
1651
+ this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1652
+ }
1653
+ else if (filterEnvelopeKeySet.has(key)) {
1654
+ if (appliedFilterEnvelope)
1671
1655
  continue;
1672
- note.voiceParams[key] = value;
1673
- if (key in this.voiceParamsHandlers) {
1674
- this.voiceParamsHandlers[key](channel, note, prevValue, scheduleTime);
1656
+ appliedFilterEnvelope = true;
1657
+ const noteVoiceParams = note.voiceParams;
1658
+ for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1659
+ const key = filterEnvelopeKeys[i];
1660
+ if (key in voiceParams)
1661
+ noteVoiceParams[key] = voiceParams[key];
1675
1662
  }
1676
- else if (filterEnvelopeKeySet.has(key)) {
1677
- if (appliedFilterEnvelope)
1678
- continue;
1679
- appliedFilterEnvelope = true;
1680
- const noteVoiceParams = note.voiceParams;
1681
- for (let i = 0; i < filterEnvelopeKeys.length; i++) {
1682
- const key = filterEnvelopeKeys[i];
1683
- if (key in voiceParams)
1684
- noteVoiceParams[key] = voiceParams[key];
1685
- }
1686
- if (note.portamento) {
1687
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1688
- }
1689
- else {
1690
- this.setFilterEnvelope(channel, note, scheduleTime);
1691
- }
1692
- this.setPitchEnvelope(note, scheduleTime);
1663
+ if (note.portamento) {
1664
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1693
1665
  }
1694
- else if (volumeEnvelopeKeySet.has(key)) {
1695
- if (appliedVolumeEnvelope)
1696
- continue;
1697
- appliedVolumeEnvelope = true;
1698
- const noteVoiceParams = note.voiceParams;
1699
- for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1700
- const key = volumeEnvelopeKeys[i];
1701
- if (key in voiceParams)
1702
- noteVoiceParams[key] = voiceParams[key];
1703
- }
1704
- this.setVolumeEnvelope(channel, note, scheduleTime);
1666
+ else {
1667
+ this.setFilterEnvelope(channel, note, scheduleTime);
1668
+ }
1669
+ this.setPitchEnvelope(note, scheduleTime);
1670
+ }
1671
+ else if (volumeEnvelopeKeySet.has(key)) {
1672
+ if (appliedVolumeEnvelope)
1673
+ continue;
1674
+ appliedVolumeEnvelope = true;
1675
+ const noteVoiceParams = note.voiceParams;
1676
+ for (let i = 0; i < volumeEnvelopeKeys.length; i++) {
1677
+ const key = volumeEnvelopeKeys[i];
1678
+ if (key in voiceParams)
1679
+ noteVoiceParams[key] = voiceParams[key];
1705
1680
  }
1681
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1706
1682
  }
1707
1683
  }
1708
1684
  });
@@ -1761,9 +1737,8 @@ export class Midy {
1761
1737
  this.channels[channelNumber].bankMSB = msb;
1762
1738
  }
1763
1739
  updateModulation(channel, scheduleTime) {
1764
- scheduleTime ??= this.audioContext.currentTime;
1765
1740
  const depth = channel.state.modulationDepth * channel.modulationDepthRange;
1766
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1741
+ this.processScheduledNotes(channel, (note) => {
1767
1742
  if (note.modulationDepth) {
1768
1743
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1769
1744
  }
@@ -1774,6 +1749,7 @@ export class Midy {
1774
1749
  });
1775
1750
  }
1776
1751
  setModulationDepth(channelNumber, modulation, scheduleTime) {
1752
+ scheduleTime ??= this.audioContext.currentTime;
1777
1753
  const channel = this.channels[channelNumber];
1778
1754
  channel.state.modulationDepth = modulation / 127;
1779
1755
  this.updateModulation(channel, scheduleTime);
@@ -1784,8 +1760,7 @@ export class Midy {
1784
1760
  channel.state.portamentoTime = Math.exp(factor * portamentoTime);
1785
1761
  }
1786
1762
  setKeyBasedVolume(channel, scheduleTime) {
1787
- scheduleTime ??= this.audioContext.currentTime;
1788
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1763
+ this.processScheduledNotes(channel, (note) => {
1789
1764
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 7);
1790
1765
  if (keyBasedValue !== 0) {
1791
1766
  note.volumeNode.gain
@@ -1795,6 +1770,7 @@ export class Midy {
1795
1770
  });
1796
1771
  }
1797
1772
  setVolume(channelNumber, volume, scheduleTime) {
1773
+ scheduleTime ??= this.audioContext.currentTime;
1798
1774
  const channel = this.channels[channelNumber];
1799
1775
  channel.state.volume = volume / 127;
1800
1776
  this.updateChannelVolume(channel, scheduleTime);
@@ -1808,8 +1784,7 @@ export class Midy {
1808
1784
  };
1809
1785
  }
1810
1786
  setKeyBasedPan(channel, scheduleTime) {
1811
- scheduleTime ??= this.audioContext.currentTime;
1812
- this.processScheduledNotes(channel, scheduleTime, (note) => {
1787
+ this.processScheduledNotes(channel, (note) => {
1813
1788
  const keyBasedValue = this.getKeyBasedInstrumentControlValue(channel, note.noteNumber, 10);
1814
1789
  if (keyBasedValue !== 0) {
1815
1790
  const { gainLeft, gainRight } = this.panToGain((keyBasedValue + 1) / 2);
@@ -1823,12 +1798,14 @@ export class Midy {
1823
1798
  });
1824
1799
  }
1825
1800
  setPan(channelNumber, pan, scheduleTime) {
1801
+ scheduleTime ??= this.audioContext.currentTime;
1826
1802
  const channel = this.channels[channelNumber];
1827
1803
  channel.state.pan = pan / 127;
1828
1804
  this.updateChannelVolume(channel, scheduleTime);
1829
1805
  this.setKeyBasedPan(channel, scheduleTime);
1830
1806
  }
1831
1807
  setExpression(channelNumber, expression, scheduleTime) {
1808
+ scheduleTime ??= this.audioContext.currentTime;
1832
1809
  const channel = this.channels[channelNumber];
1833
1810
  channel.state.expression = expression / 127;
1834
1811
  this.updateChannelVolume(channel, scheduleTime);
@@ -1853,8 +1830,14 @@ export class Midy {
1853
1830
  }
1854
1831
  setSustainPedal(channelNumber, value, scheduleTime) {
1855
1832
  scheduleTime ??= this.audioContext.currentTime;
1856
- this.channels[channelNumber].state.sustainPedal = value / 127;
1857
- if (value < 64) {
1833
+ const channel = this.channels[channelNumber];
1834
+ channel.state.sustainPedal = value / 127;
1835
+ if (64 <= value) {
1836
+ this.processScheduledNotes(channel, (note) => {
1837
+ channel.sustainNotes.push(note);
1838
+ });
1839
+ }
1840
+ else {
1858
1841
  this.releaseSustainPedal(channelNumber, value, scheduleTime);
1859
1842
  }
1860
1843
  }
@@ -1862,13 +1845,14 @@ export class Midy {
1862
1845
  this.channels[channelNumber].state.portamento = value / 127;
1863
1846
  }
1864
1847
  setSostenutoPedal(channelNumber, value, scheduleTime) {
1848
+ scheduleTime ??= this.audioContext.currentTime;
1865
1849
  const channel = this.channels[channelNumber];
1866
1850
  channel.state.sostenutoPedal = value / 127;
1867
1851
  if (64 <= value) {
1868
1852
  channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
1869
1853
  }
1870
1854
  else {
1871
- this.releaseSostenutoPedal(channelNumber, value);
1855
+ this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
1872
1856
  }
1873
1857
  }
1874
1858
  setSoftPedal(channelNumber, softPedal, _scheduleTime) {
@@ -1876,120 +1860,89 @@ export class Midy {
1876
1860
  channel.state.softPedal = softPedal / 127;
1877
1861
  }
1878
1862
  setFilterResonance(channelNumber, filterResonance, scheduleTime) {
1863
+ scheduleTime ??= this.audioContext.currentTime;
1879
1864
  const channel = this.channels[channelNumber];
1880
1865
  const state = channel.state;
1881
1866
  state.filterResonance = filterResonance / 64;
1882
- channel.scheduledNotes.forEach((noteList) => {
1883
- for (let i = 0; i < noteList.length; i++) {
1884
- const note = noteList[i];
1885
- if (!note)
1886
- continue;
1887
- const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1888
- note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1889
- }
1867
+ this.processScheduledNotes(channel, (note) => {
1868
+ const Q = note.voiceParams.initialFilterQ / 5 * state.filterResonance;
1869
+ note.filterNode.Q.setValueAtTime(Q, scheduleTime);
1890
1870
  });
1891
1871
  }
1892
1872
  setReleaseTime(channelNumber, releaseTime, _scheduleTime) {
1873
+ scheduleTime ??= this.audioContext.currentTime;
1893
1874
  const channel = this.channels[channelNumber];
1894
1875
  channel.state.releaseTime = releaseTime / 64;
1895
1876
  }
1896
1877
  setAttackTime(channelNumber, attackTime, scheduleTime) {
1878
+ scheduleTime ??= this.audioContext.currentTime;
1897
1879
  const channel = this.channels[channelNumber];
1898
1880
  channel.state.attackTime = attackTime / 64;
1899
- channel.scheduledNotes.forEach((noteList) => {
1900
- for (let i = 0; i < noteList.length; i++) {
1901
- const note = noteList[i];
1902
- if (!note)
1903
- continue;
1904
- if (note.startTime < scheduleTime)
1905
- continue;
1906
- this.setVolumeEnvelope(channel, note);
1907
- }
1881
+ this.processScheduledNotes(channel, (note) => {
1882
+ if (note.startTime < scheduleTime)
1883
+ return false;
1884
+ this.setVolumeEnvelope(channel, note);
1908
1885
  });
1909
1886
  }
1910
1887
  setBrightness(channelNumber, brightness, scheduleTime) {
1888
+ scheduleTime ??= this.audioContext.currentTime;
1911
1889
  const channel = this.channels[channelNumber];
1912
1890
  channel.state.brightness = brightness / 64;
1913
- channel.scheduledNotes.forEach((noteList) => {
1914
- for (let i = 0; i < noteList.length; i++) {
1915
- const note = noteList[i];
1916
- if (!note)
1917
- continue;
1918
- if (note.portamento) {
1919
- this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1920
- }
1921
- else {
1922
- this.setFilterEnvelope(channel, note);
1923
- }
1891
+ this.processScheduledNotes(channel, (note) => {
1892
+ if (note.portamento) {
1893
+ this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
1894
+ }
1895
+ else {
1896
+ this.setFilterEnvelope(channel, note);
1924
1897
  }
1925
1898
  });
1926
1899
  }
1927
1900
  setDecayTime(channelNumber, dacayTime, scheduleTime) {
1901
+ scheduleTime ??= this.audioContext.currentTime;
1928
1902
  const channel = this.channels[channelNumber];
1929
1903
  channel.state.decayTime = dacayTime / 64;
1930
- channel.scheduledNotes.forEach((noteList) => {
1931
- for (let i = 0; i < noteList.length; i++) {
1932
- const note = noteList[i];
1933
- if (!note)
1934
- continue;
1935
- this.setVolumeEnvelope(channel, note, scheduleTime);
1936
- }
1904
+ this.processScheduledNotes(channel, (note) => {
1905
+ this.setVolumeEnvelope(channel, note, scheduleTime);
1937
1906
  });
1938
1907
  }
1939
1908
  setVibratoRate(channelNumber, vibratoRate, scheduleTime) {
1909
+ scheduleTime ??= this.audioContext.currentTime;
1940
1910
  const channel = this.channels[channelNumber];
1941
1911
  channel.state.vibratoRate = vibratoRate / 64;
1942
1912
  if (channel.vibratoDepth <= 0)
1943
1913
  return;
1944
- channel.scheduledNotes.forEach((noteList) => {
1945
- for (let i = 0; i < noteList.length; i++) {
1946
- const note = noteList[i];
1947
- if (!note)
1948
- continue;
1949
- this.setVibLfoToPitch(channel, note, scheduleTime);
1950
- }
1914
+ this.processScheduledNotes(channel, (note) => {
1915
+ this.setVibLfoToPitch(channel, note, scheduleTime);
1951
1916
  });
1952
1917
  }
1953
1918
  setVibratoDepth(channelNumber, vibratoDepth, scheduleTime) {
1919
+ scheduleTime ??= this.audioContext.currentTime;
1954
1920
  const channel = this.channels[channelNumber];
1955
1921
  const prev = channel.state.vibratoDepth;
1956
1922
  channel.state.vibratoDepth = vibratoDepth / 64;
1957
1923
  if (0 < prev) {
1958
- channel.scheduledNotes.forEach((noteList) => {
1959
- for (let i = 0; i < noteList.length; i++) {
1960
- const note = noteList[i];
1961
- if (!note)
1962
- continue;
1963
- this.setFreqVibLFO(channel, note, scheduleTime);
1964
- }
1924
+ this.processScheduledNotes(channel, (note) => {
1925
+ this.setFreqVibLFO(channel, note, scheduleTime);
1965
1926
  });
1966
1927
  }
1967
1928
  else {
1968
- channel.scheduledNotes.forEach((noteList) => {
1969
- for (let i = 0; i < noteList.length; i++) {
1970
- const note = noteList[i];
1971
- if (!note)
1972
- continue;
1973
- this.startVibrato(channel, note, scheduleTime);
1974
- }
1929
+ this.processScheduledNotes(channel, (note) => {
1930
+ this.startVibrato(channel, note, scheduleTime);
1975
1931
  });
1976
1932
  }
1977
1933
  }
1978
1934
  setVibratoDelay(channelNumber, vibratoDelay) {
1935
+ scheduleTime ??= this.audioContext.currentTime;
1979
1936
  const channel = this.channels[channelNumber];
1980
1937
  channel.state.vibratoDelay = vibratoDelay / 64;
1981
1938
  if (0 < channel.state.vibratoDepth) {
1982
- channel.scheduledNotes.forEach((noteList) => {
1983
- for (let i = 0; i < noteList.length; i++) {
1984
- const note = noteList[i];
1985
- if (!note)
1986
- continue;
1987
- this.startVibrato(channel, note, scheduleTime);
1988
- }
1939
+ this.processScheduledNotes(channel, (note) => {
1940
+ this.startVibrato(channel, note, scheduleTime);
1989
1941
  });
1990
1942
  }
1991
1943
  }
1992
1944
  setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
1945
+ scheduleTime ??= this.audioContext.currentTime;
1993
1946
  const channel = this.channels[channelNumber];
1994
1947
  const state = channel.state;
1995
1948
  const reverbEffect = this.reverbEffect;
@@ -2001,27 +1954,17 @@ export class Midy {
2001
1954
  .setValueAtTime(state.reverbSendLevel, scheduleTime);
2002
1955
  }
2003
1956
  else {
2004
- channel.scheduledNotes.forEach((noteList) => {
2005
- for (let i = 0; i < noteList.length; i++) {
2006
- const note = noteList[i];
2007
- if (!note)
2008
- continue;
2009
- if (note.voiceParams.reverbEffectsSend <= 0)
2010
- continue;
2011
- note.reverbEffectsSend.disconnect();
2012
- }
1957
+ this.processScheduledNotes(channel, (note) => {
1958
+ if (note.voiceParams.reverbEffectsSend <= 0)
1959
+ return false;
1960
+ note.reverbEffectsSend.disconnect();
2013
1961
  });
2014
1962
  }
2015
1963
  }
2016
1964
  else {
2017
1965
  if (0 < reverbSendLevel) {
2018
- channel.scheduledNotes.forEach((noteList) => {
2019
- for (let i = 0; i < noteList.length; i++) {
2020
- const note = noteList[i];
2021
- if (!note)
2022
- continue;
2023
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2024
- }
1966
+ this.processScheduledNotes(channel, (note) => {
1967
+ this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2025
1968
  });
2026
1969
  state.reverbSendLevel = reverbSendLevel / 127;
2027
1970
  reverbEffect.input.gain
@@ -2031,6 +1974,7 @@ export class Midy {
2031
1974
  }
2032
1975
  }
2033
1976
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
1977
+ scheduleTime ??= this.audioContext.currentTime;
2034
1978
  const channel = this.channels[channelNumber];
2035
1979
  const state = channel.state;
2036
1980
  const chorusEffect = this.chorusEffect;
@@ -2042,27 +1986,17 @@ export class Midy {
2042
1986
  .setValueAtTime(state.chorusSendLevel, scheduleTime);
2043
1987
  }
2044
1988
  else {
2045
- channel.scheduledNotes.forEach((noteList) => {
2046
- for (let i = 0; i < noteList.length; i++) {
2047
- const note = noteList[i];
2048
- if (!note)
2049
- continue;
2050
- if (note.voiceParams.chorusEffectsSend <= 0)
2051
- continue;
2052
- note.chorusEffectsSend.disconnect();
2053
- }
1989
+ this.processScheduledNotes(channel, (note) => {
1990
+ if (note.voiceParams.chorusEffectsSend <= 0)
1991
+ return false;
1992
+ note.chorusEffectsSend.disconnect();
2054
1993
  });
2055
1994
  }
2056
1995
  }
2057
1996
  else {
2058
1997
  if (0 < chorusSendLevel) {
2059
- channel.scheduledNotes.forEach((noteList) => {
2060
- for (let i = 0; i < noteList.length; i++) {
2061
- const note = noteList[i];
2062
- if (!note)
2063
- continue;
2064
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2065
- }
1998
+ this.processScheduledNotes(channel, (note) => {
1999
+ this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2066
2000
  });
2067
2001
  state.chorusSendLevel = chorusSendLevel / 127;
2068
2002
  chorusEffect.input.gain
@@ -2107,15 +2041,15 @@ export class Midy {
2107
2041
  break;
2108
2042
  case 1:
2109
2043
  channel.dataLSB += value;
2110
- this.handleFineTuningRPN(channelNumber);
2044
+ this.handleFineTuningRPN(channelNumber, scheduleTime);
2111
2045
  break;
2112
2046
  case 2:
2113
2047
  channel.dataMSB += value;
2114
- this.handleCoarseTuningRPN(channelNumber);
2048
+ this.handleCoarseTuningRPN(channelNumber, scheduleTime);
2115
2049
  break;
2116
2050
  case 5:
2117
2051
  channel.dataLSB += value;
2118
- this.handleModulationDepthRangeRPN(channelNumber);
2052
+ this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
2119
2053
  break;
2120
2054
  default:
2121
2055
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
@@ -2156,44 +2090,47 @@ export class Midy {
2156
2090
  this.updateChannelDetune(channel, scheduleTime);
2157
2091
  this.applyVoiceParams(channel, 16, scheduleTime);
2158
2092
  }
2159
- handleFineTuningRPN(channelNumber) {
2093
+ handleFineTuningRPN(channelNumber, scheduleTime) {
2160
2094
  const channel = this.channels[channelNumber];
2161
2095
  this.limitData(channel, 0, 127, 0, 127);
2162
2096
  const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
2163
- this.setFineTuning(channelNumber, fineTuning);
2097
+ this.setFineTuning(channelNumber, fineTuning, scheduleTime);
2164
2098
  }
2165
- setFineTuning(channelNumber, value) {
2099
+ setFineTuning(channelNumber, value, scheduleTime) {
2100
+ scheduleTime ??= this.audioContext.currentTime;
2166
2101
  const channel = this.channels[channelNumber];
2167
2102
  const prev = channel.fineTuning;
2168
2103
  const next = (value - 8192) / 8.192; // cent
2169
2104
  channel.fineTuning = next;
2170
2105
  channel.detune += next - prev;
2171
- this.updateChannelDetune(channel);
2106
+ this.updateChannelDetune(channel, scheduleTime);
2172
2107
  }
2173
- handleCoarseTuningRPN(channelNumber) {
2108
+ handleCoarseTuningRPN(channelNumber, scheduleTime) {
2174
2109
  const channel = this.channels[channelNumber];
2175
2110
  this.limitDataMSB(channel, 0, 127);
2176
2111
  const coarseTuning = channel.dataMSB;
2177
- this.setCoarseTuning(channelNumber, coarseTuning);
2112
+ this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
2178
2113
  }
2179
- setCoarseTuning(channelNumber, value) {
2114
+ setCoarseTuning(channelNumber, value, scheduleTime) {
2115
+ scheduleTime ??= this.audioContext.currentTime;
2180
2116
  const channel = this.channels[channelNumber];
2181
2117
  const prev = channel.coarseTuning;
2182
2118
  const next = (value - 64) * 100; // cent
2183
2119
  channel.coarseTuning = next;
2184
2120
  channel.detune += next - prev;
2185
- this.updateChannelDetune(channel);
2121
+ this.updateChannelDetune(channel, scheduleTime);
2186
2122
  }
2187
- handleModulationDepthRangeRPN(channelNumber) {
2123
+ handleModulationDepthRangeRPN(channelNumber, scheduleTime) {
2188
2124
  const channel = this.channels[channelNumber];
2189
2125
  this.limitData(channel, 0, 127, 0, 127);
2190
2126
  const modulationDepthRange = (dataMSB + dataLSB / 128) * 100;
2191
- this.setModulationDepthRange(channelNumber, modulationDepthRange);
2127
+ this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
2192
2128
  }
2193
- setModulationDepthRange(channelNumber, modulationDepthRange) {
2129
+ setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
2130
+ scheduleTime ??= this.audioContext.currentTime;
2194
2131
  const channel = this.channels[channelNumber];
2195
2132
  channel.modulationDepthRange = modulationDepthRange;
2196
- this.updateModulation(channel);
2133
+ this.updateModulation(channel, scheduleTime);
2197
2134
  }
2198
2135
  allSoundOff(channelNumber, _value, scheduleTime) {
2199
2136
  scheduleTime ??= this.audioContext.currentTime;
@@ -2229,17 +2166,21 @@ export class Midy {
2229
2166
  scheduleTime ??= this.audioContext.currentTime;
2230
2167
  return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
2231
2168
  }
2232
- omniOff() {
2233
- this.omni = false;
2169
+ omniOff(channelNumber, value, scheduleTime) {
2170
+ this.allNotesOff(channelNumber, value, scheduleTime);
2234
2171
  }
2235
- omniOn() {
2236
- this.omni = true;
2172
+ omniOn(channelNumber, value, scheduleTime) {
2173
+ this.allNotesOff(channelNumber, value, scheduleTime);
2237
2174
  }
2238
- monoOn() {
2239
- this.mono = true;
2175
+ monoOn(channelNumber, value, scheduleTime) {
2176
+ const channel = this.channels[channelNumber];
2177
+ this.allNotesOff(channelNumber, value, scheduleTime);
2178
+ channel.mono = true;
2240
2179
  }
2241
- polyOn() {
2242
- this.mono = false;
2180
+ polyOn(channelNumber, value, scheduleTime) {
2181
+ const channel = this.channels[channelNumber];
2182
+ this.allNotesOff(channelNumber, value, scheduleTime);
2183
+ channel.mono = false;
2243
2184
  }
2244
2185
  handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
2245
2186
  switch (data[2]) {
@@ -2701,13 +2642,8 @@ export class Midy {
2701
2642
  const slotSize = 6;
2702
2643
  const offset = controllerType * slotSize;
2703
2644
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2704
- channel.scheduledNotes.forEach((noteList) => {
2705
- for (let i = 0; i < noteList.length; i++) {
2706
- const note = noteList[i];
2707
- if (!note)
2708
- continue;
2709
- this.setControllerParameters(channel, note, table);
2710
- }
2645
+ this.processScheduledNotes(channel, (note) => {
2646
+ this.setControllerParameters(channel, note, table);
2711
2647
  });
2712
2648
  }
2713
2649
  handleControlChangeSysEx(data) {
@@ -2774,6 +2710,7 @@ Object.defineProperty(Midy, "channelSettings", {
2774
2710
  dataLSB: 0,
2775
2711
  rpnMSB: 127,
2776
2712
  rpnLSB: 127,
2713
+ mono: false, // CC#124, CC#125
2777
2714
  fineTuning: 0, // cb
2778
2715
  coarseTuning: 0, // cb
2779
2716
  modulationDepthRange: 50, // cent