@marmooo/midy 0.2.0 → 0.2.1

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
@@ -904,16 +904,41 @@ export class Midy {
904
904
  cbToRatio(cb) {
905
905
  return Math.pow(10, cb / 200);
906
906
  }
907
+ rateToCent(rate) {
908
+ return 1200 * Math.log2(rate);
909
+ }
910
+ centToRate(cent) {
911
+ return Math.pow(2, cent / 1200);
912
+ }
907
913
  centToHz(cent) {
908
- return 8.176 * Math.pow(2, cent / 1200);
914
+ return 8.176 * this.centToRate(cent);
909
915
  }
910
- calcSemitoneOffset(channel) {
916
+ calcChannelDetune(channel) {
911
917
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
912
918
  const channelTuning = channel.coarseTuning + channel.fineTuning;
919
+ const tuning = masterTuning + channelTuning;
913
920
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
914
- const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
921
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
915
922
  const pitch = pitchWheel * pitchWheelSensitivity;
916
- return masterTuning + channelTuning + pitch;
923
+ return tuning + pitch;
924
+ }
925
+ calcNoteDetune(channel, note) {
926
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
927
+ }
928
+ updateDetune(channel) {
929
+ const now = this.audioContext.currentTime;
930
+ channel.scheduledNotes.forEach((noteList) => {
931
+ for (let i = 0; i < noteList.length; i++) {
932
+ const note = noteList[i];
933
+ if (!note)
934
+ continue;
935
+ const noteDetune = this.calcNoteDetune(channel, note);
936
+ const detune = channel.detune + noteDetune;
937
+ note.bufferSource.detune
938
+ .cancelScheduledValues(now)
939
+ .setValueAtTime(detune, now);
940
+ }
941
+ });
917
942
  }
918
943
  setPortamentoStartVolumeEnvelope(channel, note) {
919
944
  const now = this.audioContext.currentTime;
@@ -945,32 +970,28 @@ export class Midy {
945
970
  .setValueAtTime(attackVolume, volHold)
946
971
  .linearRampToValueAtTime(sustainVolume, volDecay);
947
972
  }
948
- setPlaybackRate(note) {
973
+ setPitchEnvelope(note) {
949
974
  const now = this.audioContext.currentTime;
975
+ const { voiceParams } = note;
976
+ const baseRate = voiceParams.playbackRate;
950
977
  note.bufferSource.playbackRate
951
978
  .cancelScheduledValues(now)
952
- .setValueAtTime(note.voiceParams.playbackRate, now);
953
- }
954
- setPitch(channel, note) {
955
- const now = this.audioContext.currentTime;
956
- const { startTime } = note;
957
- const basePitch = this.calcSemitoneOffset(channel) * 100;
958
- note.bufferSource.detune
959
- .cancelScheduledValues(now)
960
- .setValueAtTime(basePitch, startTime);
961
- const modEnvToPitch = note.voiceParams.modEnvToPitch;
979
+ .setValueAtTime(baseRate, now);
980
+ const modEnvToPitch = voiceParams.modEnvToPitch;
962
981
  if (modEnvToPitch === 0)
963
982
  return;
983
+ const basePitch = this.rateToCent(baseRate);
964
984
  const peekPitch = basePitch + modEnvToPitch;
985
+ const peekRate = this.centToRate(peekPitch);
965
986
  const modDelay = startTime + voiceParams.modDelay;
966
987
  const modAttack = modDelay + voiceParams.modAttack;
967
988
  const modHold = modAttack + voiceParams.modHold;
968
989
  const modDecay = modHold + voiceParams.modDecay;
969
- note.bufferSource.detune
970
- .setValueAtTime(basePitch, modDelay)
971
- .exponentialRampToValueAtTime(peekPitch, modAttack)
972
- .setValueAtTime(peekPitch, modHold)
973
- .linearRampToValueAtTime(basePitch, modDecay);
990
+ note.bufferSource.playbackRate
991
+ .setValueAtTime(baseRate, modDelay)
992
+ .exponentialRampToValueAtTime(peekRate, modAttack)
993
+ .setValueAtTime(peekRate, modHold)
994
+ .linearRampToValueAtTime(baseRate, modDecay);
974
995
  }
975
996
  clampCutoffFrequency(frequency) {
976
997
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -1081,9 +1102,8 @@ export class Midy {
1081
1102
  if (0 < state.vibratoDepth) {
1082
1103
  this.startVibrato(channel, note, startTime);
1083
1104
  }
1084
- this.setPlaybackRate(note);
1105
+ this.setPitchEnvelope(note);
1085
1106
  if (0 < state.modulationDepth) {
1086
- this.setPitch(channel, note);
1087
1107
  this.startModulation(channel, note, startTime);
1088
1108
  }
1089
1109
  if (this.mono && channel.currentBufferSource) {
@@ -1217,11 +1237,12 @@ export class Midy {
1217
1237
  }
1218
1238
  else {
1219
1239
  const portamentoTime = endTime + state.portamentoTime;
1220
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1221
- const detune = note.bufferSource.detune.value + detuneChange;
1222
- note.bufferSource.detune
1240
+ const deltaNote = portamentoNoteNumber - noteNumber;
1241
+ const baseRate = note.voiceParams.playbackRate;
1242
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1243
+ note.bufferSource.playbackRate
1223
1244
  .cancelScheduledValues(endTime)
1224
- .linearRampToValueAtTime(detune, portamentoTime);
1245
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1225
1246
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1226
1247
  }
1227
1248
  }
@@ -1326,10 +1347,11 @@ export class Midy {
1326
1347
  setPitchBend(channelNumber, value) {
1327
1348
  const channel = this.channels[channelNumber];
1328
1349
  const state = channel.state;
1350
+ const prev = state.pitchWheel * 2 - 1;
1351
+ const next = (value - 8192) / 8192;
1329
1352
  state.pitchWheel = value / 16383;
1330
- const pitchWheel = (value - 8192) / 8192;
1331
- const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1332
- this.updateDetune(channel, detuneChange);
1353
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1354
+ this.updateDetune(channel);
1333
1355
  this.applyVoiceParams(channel, 14);
1334
1356
  }
1335
1357
  setModLfoToPitch(channel, note) {
@@ -1342,6 +1364,23 @@ export class Midy {
1342
1364
  .cancelScheduledValues(now)
1343
1365
  .setValueAtTime(modulationDepth * modulationDepthSign, now);
1344
1366
  }
1367
+ setVibLfoToPitch(channel, note) {
1368
+ const now = this.audioContext.currentTime;
1369
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1370
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1371
+ 2;
1372
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1373
+ note.vibratoDepth.gain
1374
+ .cancelScheduledValues(now)
1375
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1376
+ }
1377
+ setModLfoToFilterFc(note) {
1378
+ const now = this.audioContext.currentTime;
1379
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1380
+ note.filterDepth.gain
1381
+ .cancelScheduledValues(now)
1382
+ .setValueAtTime(modLfoToFilterFc, now);
1383
+ }
1345
1384
  setModLfoToVolume(note) {
1346
1385
  const now = this.audioContext.currentTime;
1347
1386
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
@@ -1401,23 +1440,6 @@ export class Midy {
1401
1440
  }
1402
1441
  }
1403
1442
  }
1404
- setVibLfoToPitch(channel, note) {
1405
- const now = this.audioContext.currentTime;
1406
- const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1407
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1408
- 2;
1409
- const vibratoDepthSign = 0 < vibLfoToPitch;
1410
- note.vibratoDepth.gain
1411
- .cancelScheduledValues(now)
1412
- .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1413
- }
1414
- setModLfoToFilterFc(note) {
1415
- const now = this.audioContext.currentTime;
1416
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1417
- note.filterDepth.gain
1418
- .cancelScheduledValues(now)
1419
- .setValueAtTime(modLfoToFilterFc, now);
1420
- }
1421
1443
  setDelayModLFO(note) {
1422
1444
  const now = this.audioContext.currentTime;
1423
1445
  const startTime = note.startTime;
@@ -1526,7 +1548,7 @@ export class Midy {
1526
1548
  else {
1527
1549
  this.setFilterEnvelope(channel, note);
1528
1550
  }
1529
- this.setPitch(channel, note);
1551
+ this.setPitchEnvelope(note);
1530
1552
  }
1531
1553
  else if (volumeEnvelopeKeySet.has(key)) {
1532
1554
  if (appliedVolumeEnvelope)
@@ -1608,7 +1630,7 @@ export class Midy {
1608
1630
  note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1609
1631
  }
1610
1632
  else {
1611
- this.setPitch(channel, note);
1633
+ this.setPitchEnvelope(note);
1612
1634
  this.startModulation(channel, note, now);
1613
1635
  }
1614
1636
  }
@@ -1675,88 +1697,6 @@ export class Midy {
1675
1697
  setPortamento(channelNumber, value) {
1676
1698
  this.channels[channelNumber].state.portamento = value / 127;
1677
1699
  }
1678
- setReverbSendLevel(channelNumber, reverbSendLevel) {
1679
- const channel = this.channels[channelNumber];
1680
- const state = channel.state;
1681
- const reverbEffect = this.reverbEffect;
1682
- if (0 < state.reverbSendLevel) {
1683
- if (0 < reverbSendLevel) {
1684
- const now = this.audioContext.currentTime;
1685
- state.reverbSendLevel = reverbSendLevel / 127;
1686
- reverbEffect.input.gain.cancelScheduledValues(now);
1687
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1688
- }
1689
- else {
1690
- channel.scheduledNotes.forEach((noteList) => {
1691
- for (let i = 0; i < noteList.length; i++) {
1692
- const note = noteList[i];
1693
- if (!note)
1694
- continue;
1695
- if (note.voiceParams.reverbEffectsSend <= 0)
1696
- continue;
1697
- note.reverbEffectsSend.disconnect();
1698
- }
1699
- });
1700
- }
1701
- }
1702
- else {
1703
- if (0 < reverbSendLevel) {
1704
- const now = this.audioContext.currentTime;
1705
- channel.scheduledNotes.forEach((noteList) => {
1706
- for (let i = 0; i < noteList.length; i++) {
1707
- const note = noteList[i];
1708
- if (!note)
1709
- continue;
1710
- this.setReverbEffectsSend(note, 0);
1711
- }
1712
- });
1713
- state.reverbSendLevel = reverbSendLevel / 127;
1714
- reverbEffect.input.gain.cancelScheduledValues(now);
1715
- reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1716
- }
1717
- }
1718
- }
1719
- setChorusSendLevel(channelNumber, chorusSendLevel) {
1720
- const channel = this.channels[channelNumber];
1721
- const state = channel.state;
1722
- const chorusEffect = this.chorusEffect;
1723
- if (0 < state.chorusSendLevel) {
1724
- if (0 < chorusSendLevel) {
1725
- const now = this.audioContext.currentTime;
1726
- state.chorusSendLevel = chorusSendLevel / 127;
1727
- chorusEffect.input.gain.cancelScheduledValues(now);
1728
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1729
- }
1730
- else {
1731
- channel.scheduledNotes.forEach((noteList) => {
1732
- for (let i = 0; i < noteList.length; i++) {
1733
- const note = noteList[i];
1734
- if (!note)
1735
- continue;
1736
- if (note.voiceParams.chorusEffectsSend <= 0)
1737
- continue;
1738
- note.chorusEffectsSend.disconnect();
1739
- }
1740
- });
1741
- }
1742
- }
1743
- else {
1744
- if (0 < chorusSendLevel) {
1745
- const now = this.audioContext.currentTime;
1746
- channel.scheduledNotes.forEach((noteList) => {
1747
- for (let i = 0; i < noteList.length; i++) {
1748
- const note = noteList[i];
1749
- if (!note)
1750
- continue;
1751
- this.setChorusEffectsSend(note, 0);
1752
- }
1753
- });
1754
- state.chorusSendLevel = chorusSendLevel / 127;
1755
- chorusEffect.input.gain.cancelScheduledValues(now);
1756
- chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1757
- }
1758
- }
1759
- }
1760
1700
  setSostenutoPedal(channelNumber, value) {
1761
1701
  const channel = this.channels[channelNumber];
1762
1702
  channel.state.sostenutoPedal = value / 127;
@@ -1852,6 +1792,88 @@ export class Midy {
1852
1792
  const channel = this.channels[channelNumber];
1853
1793
  channel.state.vibratoDelay = vibratoDelay / 64;
1854
1794
  }
1795
+ setReverbSendLevel(channelNumber, reverbSendLevel) {
1796
+ const channel = this.channels[channelNumber];
1797
+ const state = channel.state;
1798
+ const reverbEffect = this.reverbEffect;
1799
+ if (0 < state.reverbSendLevel) {
1800
+ if (0 < reverbSendLevel) {
1801
+ const now = this.audioContext.currentTime;
1802
+ state.reverbSendLevel = reverbSendLevel / 127;
1803
+ reverbEffect.input.gain.cancelScheduledValues(now);
1804
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1805
+ }
1806
+ else {
1807
+ channel.scheduledNotes.forEach((noteList) => {
1808
+ for (let i = 0; i < noteList.length; i++) {
1809
+ const note = noteList[i];
1810
+ if (!note)
1811
+ continue;
1812
+ if (note.voiceParams.reverbEffectsSend <= 0)
1813
+ continue;
1814
+ note.reverbEffectsSend.disconnect();
1815
+ }
1816
+ });
1817
+ }
1818
+ }
1819
+ else {
1820
+ if (0 < reverbSendLevel) {
1821
+ const now = this.audioContext.currentTime;
1822
+ channel.scheduledNotes.forEach((noteList) => {
1823
+ for (let i = 0; i < noteList.length; i++) {
1824
+ const note = noteList[i];
1825
+ if (!note)
1826
+ continue;
1827
+ this.setReverbEffectsSend(note, 0);
1828
+ }
1829
+ });
1830
+ state.reverbSendLevel = reverbSendLevel / 127;
1831
+ reverbEffect.input.gain.cancelScheduledValues(now);
1832
+ reverbEffect.input.gain.setValueAtTime(state.reverbSendLevel, now);
1833
+ }
1834
+ }
1835
+ }
1836
+ setChorusSendLevel(channelNumber, chorusSendLevel) {
1837
+ const channel = this.channels[channelNumber];
1838
+ const state = channel.state;
1839
+ const chorusEffect = this.chorusEffect;
1840
+ if (0 < state.chorusSendLevel) {
1841
+ if (0 < chorusSendLevel) {
1842
+ const now = this.audioContext.currentTime;
1843
+ state.chorusSendLevel = chorusSendLevel / 127;
1844
+ chorusEffect.input.gain.cancelScheduledValues(now);
1845
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1846
+ }
1847
+ else {
1848
+ channel.scheduledNotes.forEach((noteList) => {
1849
+ for (let i = 0; i < noteList.length; i++) {
1850
+ const note = noteList[i];
1851
+ if (!note)
1852
+ continue;
1853
+ if (note.voiceParams.chorusEffectsSend <= 0)
1854
+ continue;
1855
+ note.chorusEffectsSend.disconnect();
1856
+ }
1857
+ });
1858
+ }
1859
+ }
1860
+ else {
1861
+ if (0 < chorusSendLevel) {
1862
+ const now = this.audioContext.currentTime;
1863
+ channel.scheduledNotes.forEach((noteList) => {
1864
+ for (let i = 0; i < noteList.length; i++) {
1865
+ const note = noteList[i];
1866
+ if (!note)
1867
+ continue;
1868
+ this.setChorusEffectsSend(note, 0);
1869
+ }
1870
+ });
1871
+ state.chorusSendLevel = chorusSendLevel / 127;
1872
+ chorusEffect.input.gain.cancelScheduledValues(now);
1873
+ chorusEffect.input.gain.setValueAtTime(state.chorusSendLevel, now);
1874
+ }
1875
+ }
1876
+ }
1855
1877
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
1856
1878
  if (maxLSB < channel.dataLSB) {
1857
1879
  channel.dataMSB++;
@@ -1920,59 +1942,49 @@ export class Midy {
1920
1942
  this.channels[channelNumber].dataMSB = value;
1921
1943
  this.handleRPN(channelNumber, 0);
1922
1944
  }
1923
- updateDetune(channel, detune) {
1924
- const now = this.audioContext.currentTime;
1925
- channel.scheduledNotes.forEach((noteList) => {
1926
- for (let i = 0; i < noteList.length; i++) {
1927
- const note = noteList[i];
1928
- if (!note)
1929
- continue;
1930
- const { bufferSource } = note;
1931
- bufferSource.detune
1932
- .cancelScheduledValues(now)
1933
- .setValueAtTime(detune, now);
1934
- }
1935
- });
1936
- }
1937
1945
  handlePitchBendRangeRPN(channelNumber) {
1938
1946
  const channel = this.channels[channelNumber];
1939
1947
  this.limitData(channel, 0, 127, 0, 99);
1940
1948
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1941
1949
  this.setPitchBendRange(channelNumber, pitchBendRange);
1942
1950
  }
1943
- setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1951
+ setPitchBendRange(channelNumber, value) {
1944
1952
  const channel = this.channels[channelNumber];
1945
1953
  const state = channel.state;
1946
- state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1947
- const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1948
- this.updateDetune(channel, detune);
1954
+ const prev = state.pitchWheelSensitivity;
1955
+ const next = value / 128;
1956
+ state.pitchWheelSensitivity = next;
1957
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1958
+ this.updateDetune(channel);
1949
1959
  this.applyVoiceParams(channel, 16);
1950
1960
  }
1951
1961
  handleFineTuningRPN(channelNumber) {
1952
1962
  const channel = this.channels[channelNumber];
1953
1963
  this.limitData(channel, 0, 127, 0, 127);
1954
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
1964
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1955
1965
  this.setFineTuning(channelNumber, fineTuning);
1956
1966
  }
1957
- setFineTuning(channelNumber, fineTuning) {
1967
+ setFineTuning(channelNumber, value) {
1958
1968
  const channel = this.channels[channelNumber];
1959
- const prevFineTuning = channel.fineTuning;
1960
- channel.fineTuning = fineTuning;
1961
- const detuneChange = channel.fineTuning - prevFineTuning;
1962
- this.updateDetune(channel, detuneChange);
1969
+ const prev = channel.fineTuning;
1970
+ const next = (value - 8192) / 8.192; // cent
1971
+ channel.fineTuning = next;
1972
+ channel.detune += next - prev;
1973
+ this.updateDetune(channel);
1963
1974
  }
1964
1975
  handleCoarseTuningRPN(channelNumber) {
1965
1976
  const channel = this.channels[channelNumber];
1966
1977
  this.limitDataMSB(channel, 0, 127);
1967
- const coarseTuning = channel.dataMSB - 64;
1968
- this.setFineTuning(channelNumber, coarseTuning);
1978
+ const coarseTuning = channel.dataMSB;
1979
+ this.setCoarseTuning(channelNumber, coarseTuning);
1969
1980
  }
1970
- setCoarseTuning(channelNumber, coarseTuning) {
1981
+ setCoarseTuning(channelNumber, value) {
1971
1982
  const channel = this.channels[channelNumber];
1972
- const prevCoarseTuning = channel.coarseTuning;
1973
- channel.coarseTuning = coarseTuning;
1974
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1975
- this.updateDetune(channel, detuneChange);
1983
+ const prev = channel.coarseTuning;
1984
+ const next = (value - 64) * 100; // cent
1985
+ channel.coarseTuning = next;
1986
+ channel.detune += next - prev;
1987
+ this.updateDetune(channel);
1976
1988
  }
1977
1989
  handleModulationDepthRangeRPN(channelNumber) {
1978
1990
  const channel = this.channels[channelNumber];
@@ -2032,6 +2044,15 @@ export class Midy {
2032
2044
  }
2033
2045
  handleUniversalNonRealTimeExclusiveMessage(data) {
2034
2046
  switch (data[2]) {
2047
+ case 8:
2048
+ switch (data[3]) {
2049
+ case 8:
2050
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2051
+ return this.handleScaleOctaveTuning1ByteFormat(data);
2052
+ default:
2053
+ console.warn(`Unsupported Exclusive Message: ${data}`);
2054
+ }
2055
+ break;
2035
2056
  case 9:
2036
2057
  switch (data[3]) {
2037
2058
  case 1:
@@ -2088,9 +2109,10 @@ export class Midy {
2088
2109
  break;
2089
2110
  case 8:
2090
2111
  switch (data[3]) {
2091
- // case 8:
2092
- // // TODO
2093
- // return this.handleScaleOctaveTuning1ByteFormat();
2112
+ case 8:
2113
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
2114
+ // TODO: realtime
2115
+ return this.handleScaleOctaveTuning1ByteFormat(data);
2094
2116
  default:
2095
2117
  console.warn(`Unsupported Exclusive Message: ${data}`);
2096
2118
  }
@@ -2135,27 +2157,59 @@ export class Midy {
2135
2157
  }
2136
2158
  }
2137
2159
  handleMasterFineTuningSysEx(data) {
2138
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2160
+ const fineTuning = data[5] * 128 + data[4];
2139
2161
  this.setMasterFineTuning(fineTuning);
2140
2162
  }
2141
- setMasterFineTuning(fineTuning) {
2142
- if (fineTuning < -1 && 1 < fineTuning) {
2143
- console.error("Master Fine Tuning value is out of range");
2144
- }
2145
- else {
2146
- this.masterFineTuning = fineTuning;
2147
- }
2163
+ setMasterFineTuning(value) {
2164
+ const prev = this.masterFineTuning;
2165
+ const next = (value - 8192) / 8.192; // cent
2166
+ this.masterFineTuning = next;
2167
+ channel.detune += next - prev;
2168
+ this.updateDetune(channel);
2148
2169
  }
2149
2170
  handleMasterCoarseTuningSysEx(data) {
2150
2171
  const coarseTuning = data[4];
2151
2172
  this.setMasterCoarseTuning(coarseTuning);
2152
2173
  }
2153
- setMasterCoarseTuning(coarseTuning) {
2154
- if (coarseTuning < 0 && 127 < coarseTuning) {
2155
- console.error("Master Coarse Tuning value is out of range");
2174
+ setMasterCoarseTuning(value) {
2175
+ const prev = this.masterCoarseTuning;
2176
+ const next = (value - 64) * 100; // cent
2177
+ this.masterCoarseTuning = next;
2178
+ channel.detune += next - prev;
2179
+ this.updateDetune(channel);
2180
+ }
2181
+ getChannelBitmap(data) {
2182
+ const bitmap = new Array(16).fill(false);
2183
+ const ff = data[4] & 0b11;
2184
+ const gg = data[5] & 0x7F;
2185
+ const hh = data[6] & 0x7F;
2186
+ for (let bit = 0; bit < 7; bit++) {
2187
+ if (hh & (1 << bit))
2188
+ bitmap[bit] = true;
2189
+ }
2190
+ for (let bit = 0; bit < 7; bit++) {
2191
+ if (gg & (1 << bit))
2192
+ bitmap[bit + 7] = true;
2193
+ }
2194
+ for (let bit = 0; bit < 2; bit++) {
2195
+ if (ff & (1 << bit))
2196
+ bitmap[bit + 14] = true;
2197
+ }
2198
+ return bitmap;
2199
+ }
2200
+ handleScaleOctaveTuning1ByteFormat(data) {
2201
+ if (data.length < 18) {
2202
+ console.error("Data length is too short");
2203
+ return;
2156
2204
  }
2157
- else {
2158
- this.masterCoarseTuning = coarseTuning - 64;
2205
+ const channelBitmap = this.getChannelBitmap(data);
2206
+ for (let i = 0; i < channelBitmap.length; i++) {
2207
+ if (!channelBitmap[i])
2208
+ continue;
2209
+ for (let j = 0; j < 12; j++) {
2210
+ const value = data[j + 7] - 64; // cent
2211
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2212
+ }
2159
2213
  }
2160
2214
  }
2161
2215
  handleGlobalParameterControlSysEx(data) {
@@ -2375,6 +2429,8 @@ Object.defineProperty(Midy, "channelSettings", {
2375
2429
  writable: true,
2376
2430
  value: {
2377
2431
  currentBufferSource: null,
2432
+ detune: 0,
2433
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2378
2434
  program: 0,
2379
2435
  bank: 121 * 128,
2380
2436
  bankMSB: 121,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marmooo/midy",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A MIDI player/synthesizer written in JavaScript that supports GM-Lite/GM1 and SF2/SF3.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,7 @@
1
1
  export class MidyGM1 {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ detune: number;
4
5
  program: number;
5
6
  bank: number;
6
7
  dataMSB: number;
@@ -92,11 +93,13 @@ export class MidyGM1 {
92
93
  getActiveNotes(channel: any, time: any): Map<any, any>;
93
94
  getActiveNote(noteList: any, time: any): any;
94
95
  cbToRatio(cb: any): number;
96
+ rateToCent(rate: any): number;
97
+ centToRate(cent: any): number;
95
98
  centToHz(cent: any): number;
96
- calcSemitoneOffset(channel: any): any;
99
+ calcChannelDetune(channel: any): any;
100
+ updateDetune(channel: any): void;
97
101
  setVolumeEnvelope(note: any): void;
98
- setPlaybackRate(note: any): void;
99
- setPitch(channel: any, note: any): void;
102
+ setPitchEnvelope(note: any): void;
100
103
  clampCutoffFrequency(frequency: any): number;
101
104
  setFilterEnvelope(note: any): void;
102
105
  startModulation(channel: any, note: any, startTime: any): void;
@@ -112,9 +115,9 @@ export class MidyGM1 {
112
115
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
113
116
  setPitchBend(channelNumber: any, value: any): void;
114
117
  setModLfoToPitch(channel: any, note: any): void;
115
- setModLfoToVolume(note: any): void;
116
118
  setVibLfoToPitch(channel: any, note: any): void;
117
119
  setModLfoToFilterFc(note: any): void;
120
+ setModLfoToVolume(note: any): void;
118
121
  setDelayModLFO(note: any): void;
119
122
  setFreqModLFO(note: any): void;
120
123
  createVoiceParamsHandlers(): {
@@ -164,13 +167,12 @@ export class MidyGM1 {
164
167
  setRPNMSB(channelNumber: any, value: any): void;
165
168
  setRPNLSB(channelNumber: any, value: any): void;
166
169
  dataEntryMSB(channelNumber: any, value: any): void;
167
- updateDetune(channel: any, detune: any): void;
168
170
  handlePitchBendRangeRPN(channelNumber: any): void;
169
- setPitchBendRange(channelNumber: any, pitchWheelSensitivity: any): void;
171
+ setPitchBendRange(channelNumber: any, value: any): void;
170
172
  handleFineTuningRPN(channelNumber: any): void;
171
- setFineTuning(channelNumber: any, fineTuning: any): void;
173
+ setFineTuning(channelNumber: any, value: any): void;
172
174
  handleCoarseTuningRPN(channelNumber: any): void;
173
- setCoarseTuning(channelNumber: any, coarseTuning: any): void;
175
+ setCoarseTuning(channelNumber: any, value: any): void;
174
176
  allSoundOff(channelNumber: any): Promise<void>;
175
177
  resetAllControllers(channelNumber: any): void;
176
178
  allNotesOff(channelNumber: any): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqFA;IAoBE;;;;;;;;;;;MAWE;IAEF,+BAQC;IAxCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,iCAA8B;IAgB5B,kBAAgC;IAChC,gBAA4C;IAC5C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,iEAUC;IAED,2EA+CC;IAED,mCAOC;IAED,0BAkDC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,4BAEC;IAED,sCAMC;IAED,mCAgBC;IAED,iCAKC;IAED,wCAmBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,gHA4BC;IAED,kGAgDC;IAED,0EAGC;IAED,qFA4BC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDAOC;IAED,gDASC;IAED,mCAQC;IAED,gDASC;IAED,qCAMC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MA2CC;IAED,+EAMC;IAED,0DA6CC;IAED;;;;;;;;;;;;;MAeC;IAED,+EASC;IAED,qCAiBC;IAED,8DAKC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,wCAWC;IAED,sDAKC;IAED,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,8CAYC;IAED,kDAKC;IAED,wEAOC;IAED,8CAKC;IAED,yDAMC;IAED,gDAKC;IAED,6DAMC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAzyCD;IAUE,0FAMC;IAfD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IAGX,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GM1.d.ts","sourceRoot":"","sources":["../src/midy-GM1.js"],"names":[],"mappings":"AAqFA;IAoBE;;;;;;;;;;;;MAYE;IAEF,+BAQC;IAzCD,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,iCAA8B;IAiB5B,kBAAgC;IAChC,gBAA4C;IAC5C;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAKnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAUC;IAED,6DA2BC;IAED,iEAUC;IAED,2EA+CC;IAED,mCAOC;IAED,0BAkDC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,+EAmBC;IAED,qDAKC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,uDASC;IAED,6CAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,qCAMC;IAED,iCAWC;IAED,mCAgBC;IAED,kCAqBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,gHA2BC;IAED,kGAgDC;IAED,0EAGC;IAED,qFA4BC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDASC;IAED,gDASC;IAED,gDASC;IAED,qCAMC;IAED,mCAQC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MA2CC;IAED,+EAMC;IAED,0DA6CC;IAED;;;;;;;;;;;;;MAeC;IAED,+EASC;IAED,qCAiBC;IAED,8DAKC;IACD,iDAIC;IAED;;;MAMC;IAED,2CAIC;IAED,yDAIC;IAED,mDAGC;IAED,wCAWC;IAED,sDAKC;IAED,kFAeC;IAED,2DAMC;IAED,oCAkBC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,kDAKC;IAED,wDASC;IAED,8CAKC;IAED,oDAOC;IAED,gDAKC;IAED,sDAOC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAjzCD;IAUE,0FAMC;IAfD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAChB,gBAAW;IACX,kBAAa;IAGX,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}