@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.
@@ -901,16 +901,42 @@ class MidyGM2 {
901
901
  cbToRatio(cb) {
902
902
  return Math.pow(10, cb / 200);
903
903
  }
904
+ rateToCent(rate) {
905
+ return 1200 * Math.log2(rate);
906
+ }
907
+ centToRate(cent) {
908
+ return Math.pow(2, cent / 1200);
909
+ }
904
910
  centToHz(cent) {
905
- return 8.176 * Math.pow(2, cent / 1200);
911
+ return 8.176 * this.centToRate(cent);
906
912
  }
907
- calcSemitoneOffset(channel) {
913
+ calcDetune(channel, note) {
908
914
  const masterTuning = this.masterCoarseTuning + this.masterFineTuning;
909
915
  const channelTuning = channel.coarseTuning + channel.fineTuning;
916
+ const scaleOctaveTuning = channel.scaleOctaveTuningTable[note.noteNumber % 12];
917
+ const tuning = masterTuning + channelTuning + scaleOctaveTuning;
910
918
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
911
- const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
919
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
912
920
  const pitch = pitchWheel * pitchWheelSensitivity;
913
- return masterTuning + channelTuning + pitch;
921
+ return tuning + pitch;
922
+ }
923
+ calcNoteDetune(channel, note) {
924
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
925
+ }
926
+ updateDetune(channel) {
927
+ const now = this.audioContext.currentTime;
928
+ channel.scheduledNotes.forEach((noteList) => {
929
+ for (let i = 0; i < noteList.length; i++) {
930
+ const note = noteList[i];
931
+ if (!note)
932
+ continue;
933
+ const noteDetune = this.calcNoteDetune(channel, note);
934
+ const detune = channel.detune + noteDetune;
935
+ note.bufferSource.detune
936
+ .cancelScheduledValues(now)
937
+ .setValueAtTime(detune, now);
938
+ }
939
+ });
914
940
  }
915
941
  setPortamentoStartVolumeEnvelope(channel, note) {
916
942
  const now = this.audioContext.currentTime;
@@ -941,32 +967,28 @@ class MidyGM2 {
941
967
  .setValueAtTime(attackVolume, volHold)
942
968
  .linearRampToValueAtTime(sustainVolume, volDecay);
943
969
  }
944
- setPlaybackRate(note) {
970
+ setPitchEnvelope(note) {
945
971
  const now = this.audioContext.currentTime;
972
+ const { voiceParams } = note;
973
+ const baseRate = voiceParams.playbackRate;
946
974
  note.bufferSource.playbackRate
947
975
  .cancelScheduledValues(now)
948
- .setValueAtTime(note.voiceParams.playbackRate, now);
949
- }
950
- setPitch(channel, note) {
951
- const now = this.audioContext.currentTime;
952
- const { startTime } = note;
953
- const basePitch = this.calcSemitoneOffset(channel) * 100;
954
- note.bufferSource.detune
955
- .cancelScheduledValues(now)
956
- .setValueAtTime(basePitch, startTime);
957
- const modEnvToPitch = note.voiceParams.modEnvToPitch;
976
+ .setValueAtTime(baseRate, now);
977
+ const modEnvToPitch = voiceParams.modEnvToPitch;
958
978
  if (modEnvToPitch === 0)
959
979
  return;
980
+ const basePitch = this.rateToCent(baseRate);
960
981
  const peekPitch = basePitch + modEnvToPitch;
982
+ const peekRate = this.centToRate(peekPitch);
961
983
  const modDelay = startTime + voiceParams.modDelay;
962
984
  const modAttack = modDelay + voiceParams.modAttack;
963
985
  const modHold = modAttack + voiceParams.modHold;
964
986
  const modDecay = modHold + voiceParams.modDecay;
965
- note.bufferSource.detune
966
- .setValueAtTime(basePitch, modDelay)
967
- .exponentialRampToValueAtTime(peekPitch, modAttack)
968
- .setValueAtTime(peekPitch, modHold)
969
- .linearRampToValueAtTime(basePitch, modDecay);
987
+ note.bufferSource.playbackRate
988
+ .setValueAtTime(baseRate, modDelay)
989
+ .exponentialRampToValueAtTime(peekRate, modAttack)
990
+ .setValueAtTime(peekRate, modHold)
991
+ .linearRampToValueAtTime(baseRate, modDecay);
970
992
  }
971
993
  clampCutoffFrequency(frequency) {
972
994
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -1077,9 +1099,8 @@ class MidyGM2 {
1077
1099
  if (0 < state.vibratoDepth) {
1078
1100
  this.startVibrato(channel, note, startTime);
1079
1101
  }
1080
- this.setPlaybackRate(note);
1102
+ this.setPitchEnvelope(note);
1081
1103
  if (0 < state.modulationDepth) {
1082
- this.setPitch(channel, note);
1083
1104
  this.startModulation(channel, note, startTime);
1084
1105
  }
1085
1106
  if (this.mono && channel.currentBufferSource) {
@@ -1212,11 +1233,12 @@ class MidyGM2 {
1212
1233
  }
1213
1234
  else {
1214
1235
  const portamentoTime = endTime + state.portamentoTime;
1215
- const detuneChange = (portamentoNoteNumber - noteNumber) * 100;
1216
- const detune = note.bufferSource.detune.value + detuneChange;
1217
- note.bufferSource.detune
1236
+ const deltaNote = portamentoNoteNumber - noteNumber;
1237
+ const baseRate = note.voiceParams.playbackRate;
1238
+ const targetRate = baseRate * Math.pow(2, deltaNote / 12);
1239
+ note.bufferSource.playbackRate
1218
1240
  .cancelScheduledValues(endTime)
1219
- .linearRampToValueAtTime(detune, portamentoTime);
1241
+ .linearRampToValueAtTime(targetRate, portamentoTime);
1220
1242
  return this.stopNote(endTime, portamentoTime, scheduledNotes, i);
1221
1243
  }
1222
1244
  }
@@ -1303,10 +1325,11 @@ class MidyGM2 {
1303
1325
  setPitchBend(channelNumber, value) {
1304
1326
  const channel = this.channels[channelNumber];
1305
1327
  const state = channel.state;
1328
+ const prev = state.pitchWheel * 2 - 1;
1329
+ const next = (value - 8192) / 8192;
1306
1330
  state.pitchWheel = value / 16383;
1307
- const pitchWheel = (value - 8192) / 8192;
1308
- const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
1309
- this.updateDetune(channel, detuneChange);
1331
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
1332
+ this.updateDetune(channel);
1310
1333
  this.applyVoiceParams(channel, 14);
1311
1334
  }
1312
1335
  setModLfoToPitch(channel, note) {
@@ -1319,6 +1342,23 @@ class MidyGM2 {
1319
1342
  .cancelScheduledValues(now)
1320
1343
  .setValueAtTime(modulationDepth * modulationDepthSign, now);
1321
1344
  }
1345
+ setVibLfoToPitch(channel, note) {
1346
+ const now = this.audioContext.currentTime;
1347
+ const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1348
+ const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1349
+ 2;
1350
+ const vibratoDepthSign = 0 < vibLfoToPitch;
1351
+ note.vibratoDepth.gain
1352
+ .cancelScheduledValues(now)
1353
+ .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1354
+ }
1355
+ setModLfoToFilterFc(note) {
1356
+ const now = this.audioContext.currentTime;
1357
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1358
+ note.filterDepth.gain
1359
+ .cancelScheduledValues(now)
1360
+ .setValueAtTime(modLfoToFilterFc, now);
1361
+ }
1322
1362
  setModLfoToVolume(note) {
1323
1363
  const now = this.audioContext.currentTime;
1324
1364
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
@@ -1378,23 +1418,6 @@ class MidyGM2 {
1378
1418
  }
1379
1419
  }
1380
1420
  }
1381
- setVibLfoToPitch(channel, note) {
1382
- const now = this.audioContext.currentTime;
1383
- const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
1384
- const vibratoDepth = Math.abs(vibLfoToPitch) * channel.state.vibratoDepth *
1385
- 2;
1386
- const vibratoDepthSign = 0 < vibLfoToPitch;
1387
- note.vibratoDepth.gain
1388
- .cancelScheduledValues(now)
1389
- .setValueAtTime(vibratoDepth * vibratoDepthSign, now);
1390
- }
1391
- setModLfoToFilterFc(note) {
1392
- const now = this.audioContext.currentTime;
1393
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
1394
- note.filterDepth.gain
1395
- .cancelScheduledValues(now)
1396
- .setValueAtTime(modLfoToFilterFc, now);
1397
- }
1398
1421
  setDelayModLFO(note) {
1399
1422
  const now = this.audioContext.currentTime;
1400
1423
  const startTime = note.startTime;
@@ -1503,7 +1526,7 @@ class MidyGM2 {
1503
1526
  else {
1504
1527
  this.setFilterEnvelope(channel, note);
1505
1528
  }
1506
- this.setPitch(channel, note);
1529
+ this.setPitchEnvelope(note);
1507
1530
  }
1508
1531
  else if (volumeEnvelopeKeySet.has(key)) {
1509
1532
  if (appliedVolumeEnvelope)
@@ -1575,7 +1598,7 @@ class MidyGM2 {
1575
1598
  note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1576
1599
  }
1577
1600
  else {
1578
- this.setPitch(channel, note);
1601
+ this.setPitchEnvelope(note);
1579
1602
  this.startModulation(channel, note, now);
1580
1603
  }
1581
1604
  }
@@ -1796,59 +1819,49 @@ class MidyGM2 {
1796
1819
  this.channels[channelNumber].dataMSB = value;
1797
1820
  this.handleRPN(channelNumber);
1798
1821
  }
1799
- updateDetune(channel, detune) {
1800
- const now = this.audioContext.currentTime;
1801
- channel.scheduledNotes.forEach((noteList) => {
1802
- for (let i = 0; i < noteList.length; i++) {
1803
- const note = noteList[i];
1804
- if (!note)
1805
- continue;
1806
- const { bufferSource } = note;
1807
- bufferSource.detune
1808
- .cancelScheduledValues(now)
1809
- .setValueAtTime(detune, now);
1810
- }
1811
- });
1812
- }
1813
1822
  handlePitchBendRangeRPN(channelNumber) {
1814
1823
  const channel = this.channels[channelNumber];
1815
1824
  this.limitData(channel, 0, 127, 0, 99);
1816
1825
  const pitchBendRange = channel.dataMSB + channel.dataLSB / 100;
1817
1826
  this.setPitchBendRange(channelNumber, pitchBendRange);
1818
1827
  }
1819
- setPitchBendRange(channelNumber, pitchWheelSensitivity) {
1828
+ setPitchBendRange(channelNumber, value) {
1820
1829
  const channel = this.channels[channelNumber];
1821
1830
  const state = channel.state;
1822
- state.pitchWheelSensitivity = pitchWheelSensitivity / 128;
1823
- const detune = (state.pitchWheel * 2 - 1) * pitchWheelSensitivity * 100;
1824
- this.updateDetune(channel, detune);
1831
+ const prev = state.pitchWheelSensitivity;
1832
+ const next = value / 128;
1833
+ state.pitchWheelSensitivity = next;
1834
+ channel.detune += (state.pitchWheel * 2 - 1) * (next - prev) * 12800;
1835
+ this.updateDetune(channel);
1825
1836
  this.applyVoiceParams(channel, 16);
1826
1837
  }
1827
1838
  handleFineTuningRPN(channelNumber) {
1828
1839
  const channel = this.channels[channelNumber];
1829
1840
  this.limitData(channel, 0, 127, 0, 127);
1830
- const fineTuning = (channel.dataMSB * 128 + channel.dataLSB - 8192) / 8192;
1841
+ const fineTuning = channel.dataMSB * 128 + channel.dataLSB;
1831
1842
  this.setFineTuning(channelNumber, fineTuning);
1832
1843
  }
1833
- setFineTuning(channelNumber, fineTuning) {
1844
+ setFineTuning(channelNumber, value) {
1834
1845
  const channel = this.channels[channelNumber];
1835
- const prevFineTuning = channel.fineTuning;
1836
- channel.fineTuning = fineTuning;
1837
- const detuneChange = channel.fineTuning - prevFineTuning;
1838
- this.updateDetune(channel, detuneChange);
1846
+ const prev = channel.fineTuning;
1847
+ const next = (value - 8192) / 8.192; // cent
1848
+ channel.fineTuning = next;
1849
+ channel.detune += next - prev;
1850
+ this.updateDetune(channel);
1839
1851
  }
1840
1852
  handleCoarseTuningRPN(channelNumber) {
1841
1853
  const channel = this.channels[channelNumber];
1842
1854
  this.limitDataMSB(channel, 0, 127);
1843
- const coarseTuning = channel.dataMSB - 64;
1844
- this.setFineTuning(channelNumber, coarseTuning);
1855
+ const coarseTuning = channel.dataMSB;
1856
+ this.setCoarseTuning(channelNumber, coarseTuning);
1845
1857
  }
1846
- setCoarseTuning(channelNumber, coarseTuning) {
1858
+ setCoarseTuning(channelNumber, value) {
1847
1859
  const channel = this.channels[channelNumber];
1848
- const prevCoarseTuning = channel.coarseTuning;
1849
- channel.coarseTuning = coarseTuning;
1850
- const detuneChange = channel.coarseTuning - prevCoarseTuning;
1851
- this.updateDetune(channel, detuneChange);
1860
+ const prev = channel.coarseTuning;
1861
+ const next = (value - 64) * 100; // cent
1862
+ channel.coarseTuning = next;
1863
+ channel.detune += next - prev;
1864
+ this.updateDetune(channel);
1852
1865
  }
1853
1866
  handleModulationDepthRangeRPN(channelNumber) {
1854
1867
  const channel = this.channels[channelNumber];
@@ -1908,6 +1921,15 @@ class MidyGM2 {
1908
1921
  }
1909
1922
  handleUniversalNonRealTimeExclusiveMessage(data) {
1910
1923
  switch (data[2]) {
1924
+ case 8:
1925
+ switch (data[3]) {
1926
+ case 8:
1927
+ // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca21.pdf
1928
+ return this.handleScaleOctaveTuning1ByteFormat(data);
1929
+ default:
1930
+ console.warn(`Unsupported Exclusive Message: ${data}`);
1931
+ }
1932
+ break;
1911
1933
  case 9:
1912
1934
  switch (data[3]) {
1913
1935
  case 1:
@@ -1962,15 +1984,6 @@ class MidyGM2 {
1962
1984
  console.warn(`Unsupported Exclusive Message: ${data}`);
1963
1985
  }
1964
1986
  break;
1965
- case 8:
1966
- switch (data[3]) {
1967
- // case 8:
1968
- // // TODO
1969
- // return this.handleScaleOctaveTuning1ByteFormat();
1970
- default:
1971
- console.warn(`Unsupported Exclusive Message: ${data}`);
1972
- }
1973
- break;
1974
1987
  case 9:
1975
1988
  switch (data[3]) {
1976
1989
  // case 1:
@@ -2011,27 +2024,59 @@ class MidyGM2 {
2011
2024
  }
2012
2025
  }
2013
2026
  handleMasterFineTuningSysEx(data) {
2014
- const fineTuning = (data[5] * 128 + data[4] - 8192) / 8192;
2027
+ const fineTuning = data[5] * 128 + data[4];
2015
2028
  this.setMasterFineTuning(fineTuning);
2016
2029
  }
2017
- setMasterFineTuning(fineTuning) {
2018
- if (fineTuning < -1 && 1 < fineTuning) {
2019
- console.error("Master Fine Tuning value is out of range");
2020
- }
2021
- else {
2022
- this.masterFineTuning = fineTuning;
2023
- }
2030
+ setMasterFineTuning(value) {
2031
+ const prev = this.masterFineTuning;
2032
+ const next = (value - 8192) / 8.192; // cent
2033
+ this.masterFineTuning = next;
2034
+ channel.detune += next - prev;
2035
+ this.updateDetune(channel);
2024
2036
  }
2025
2037
  handleMasterCoarseTuningSysEx(data) {
2026
2038
  const coarseTuning = data[4];
2027
2039
  this.setMasterCoarseTuning(coarseTuning);
2028
2040
  }
2029
- setMasterCoarseTuning(coarseTuning) {
2030
- if (coarseTuning < 0 && 127 < coarseTuning) {
2031
- console.error("Master Coarse Tuning value is out of range");
2041
+ setMasterCoarseTuning(value) {
2042
+ const prev = this.masterCoarseTuning;
2043
+ const next = (value - 64) * 100; // cent
2044
+ this.masterCoarseTuning = next;
2045
+ channel.detune += next - prev;
2046
+ this.updateDetune(channel);
2047
+ }
2048
+ getChannelBitmap(data) {
2049
+ const bitmap = new Array(16).fill(false);
2050
+ const ff = data[4] & 0b11;
2051
+ const gg = data[5] & 0x7F;
2052
+ const hh = data[6] & 0x7F;
2053
+ for (let bit = 0; bit < 7; bit++) {
2054
+ if (hh & (1 << bit))
2055
+ bitmap[bit] = true;
2056
+ }
2057
+ for (let bit = 0; bit < 7; bit++) {
2058
+ if (gg & (1 << bit))
2059
+ bitmap[bit + 7] = true;
2060
+ }
2061
+ for (let bit = 0; bit < 2; bit++) {
2062
+ if (ff & (1 << bit))
2063
+ bitmap[bit + 14] = true;
2064
+ }
2065
+ return bitmap;
2066
+ }
2067
+ handleScaleOctaveTuning1ByteFormat(data) {
2068
+ if (data.length < 18) {
2069
+ console.error("Data length is too short");
2070
+ return;
2032
2071
  }
2033
- else {
2034
- this.masterCoarseTuning = coarseTuning - 64;
2072
+ const channelBitmap = this.getChannelBitmap(data);
2073
+ for (let i = 0; i < channelBitmap.length; i++) {
2074
+ if (!channelBitmap[i])
2075
+ continue;
2076
+ for (let j = 0; j < 12; j++) {
2077
+ const value = data[j + 7] - 64; // cent
2078
+ this.channels[i].scaleOctaveTuningTable[j] = value;
2079
+ }
2035
2080
  }
2036
2081
  }
2037
2082
  handleGlobalParameterControlSysEx(data) {
@@ -2252,6 +2297,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
2252
2297
  writable: true,
2253
2298
  value: {
2254
2299
  currentBufferSource: null,
2300
+ detune: 0,
2301
+ scaleOctaveTuningTable: new Array(12).fill(0), // cent
2255
2302
  program: 0,
2256
2303
  bank: 121 * 128,
2257
2304
  bankMSB: 121,
@@ -1,6 +1,7 @@
1
1
  export class MidyGMLite {
2
2
  static channelSettings: {
3
3
  currentBufferSource: null;
4
+ detune: number;
4
5
  program: number;
5
6
  bank: number;
6
7
  dataMSB: number;
@@ -89,11 +90,13 @@ export class MidyGMLite {
89
90
  getActiveNotes(channel: any, time: any): Map<any, any>;
90
91
  getActiveNote(noteList: any, time: any): any;
91
92
  cbToRatio(cb: any): number;
93
+ rateToCent(rate: any): number;
94
+ centToRate(cent: any): number;
92
95
  centToHz(cent: any): number;
93
- calcSemitoneOffset(channel: any): number;
96
+ calcChannelDetune(channel: any): number;
97
+ updateDetune(channel: any): void;
94
98
  setVolumeEnvelope(note: any): void;
95
- setPlaybackRate(note: any): void;
96
- setPitch(channel: any, note: any): void;
99
+ setPitchEnvelope(note: any): void;
97
100
  clampCutoffFrequency(frequency: any): number;
98
101
  setFilterEnvelope(note: any): void;
99
102
  startModulation(channel: any, note: any, startTime: any): void;
@@ -109,8 +112,8 @@ export class MidyGMLite {
109
112
  handlePitchBendMessage(channelNumber: any, lsb: any, msb: any): void;
110
113
  setPitchBend(channelNumber: any, value: any): void;
111
114
  setModLfoToPitch(channel: any, note: any): void;
112
- setModLfoToVolume(note: any): void;
113
115
  setModLfoToFilterFc(note: any): void;
116
+ setModLfoToVolume(note: any): void;
114
117
  setDelayModLFO(note: any): void;
115
118
  setFreqModLFO(note: any): void;
116
119
  createVoiceParamsHandlers(): {
@@ -159,7 +162,6 @@ export class MidyGMLite {
159
162
  setRPNMSB(channelNumber: any, value: any): void;
160
163
  setRPNLSB(channelNumber: any, value: any): void;
161
164
  dataEntryMSB(channelNumber: any, value: any): void;
162
- updateDetune(channel: any, detune: any): void;
163
165
  handlePitchBendRangeRPN(channelNumber: any): void;
164
166
  setPitchBendRange(channelNumber: any, pitchWheelSensitivity: any): void;
165
167
  allSoundOff(channelNumber: any): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAmFA;IAoBE;;;;;;;;MAQE;IAEF,+BAQC;IArCD,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;IAa5B,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,yCAIC;IAED,mCAgBC;IAED,iCAKC;IAED,wCAmBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,gHA4BC;IAED,kGAgDC;IAED,0EAGC;IAED,qFAwBC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDAOC;IAED,gDASC;IAED,mCAQC;IAED,qCAMC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MAqBC;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,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,8CAYC;IAED,kDAKC;IAED,wEAOC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AAjtCD;IAQE,0FAMC;IAbD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAmFA;IAoBE;;;;;;;;;MASE;IAEF,+BAQC;IAtCD,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;IAc5B,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,wCAIC;IAED,iCAWC;IAED,mCAgBC;IAED,kCAqBC;IAED,6CAIC;IAED,mCAuBC;IAED,+DAoBC;IAED,gHA2BC;IAED,kGAgDC;IAED,0EAGC;IAED,qFAwBC;IAED,6HAuBC;IAED,0FAGC;IAED,kEAeC;IAED,gFAiBC;IAED,4DAGC;IAED,qEAGC;IAED,mDASC;IAED,gDASC;IAED,qCAMC;IAED,mCAQC;IAED,gCAOC;IAED,+BAMC;IAED;;;;;;;;;;;MAqBC;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,oCAYC;IAED,gDAEC;IAED,gDAEC;IAED,mDAGC;IAED,kDAKC;IAED,wEAOC;IAED,+CAEC;IAED,8CAqBC;IAED,+CAEC;IAED,4DAgBC;IAED,oBAMC;IAED,yDAaC;IAED,yCAGC;IAED,mCAQC;IAED,wCAEC;IAED,6BASC;IAED,0DAUC;CACF;AArtCD;IAQE,0FAMC;IAbD,kBAAa;IACb,gBAAW;IACX,gBAAW;IACX,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -584,14 +584,33 @@ class MidyGMLite {
584
584
  cbToRatio(cb) {
585
585
  return Math.pow(10, cb / 200);
586
586
  }
587
+ rateToCent(rate) {
588
+ return 1200 * Math.log2(rate);
589
+ }
590
+ centToRate(cent) {
591
+ return Math.pow(2, cent / 1200);
592
+ }
587
593
  centToHz(cent) {
588
- return 8.176 * Math.pow(2, cent / 1200);
594
+ return 8.176 * this.centToRate(cent);
589
595
  }
590
- calcSemitoneOffset(channel) {
596
+ calcChannelDetune(channel) {
591
597
  const pitchWheel = channel.state.pitchWheel * 2 - 1;
592
- const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 128;
598
+ const pitchWheelSensitivity = channel.state.pitchWheelSensitivity * 12800;
593
599
  return pitchWheel * pitchWheelSensitivity;
594
600
  }
601
+ updateDetune(channel) {
602
+ const now = this.audioContext.currentTime;
603
+ channel.scheduledNotes.forEach((noteList) => {
604
+ for (let i = 0; i < noteList.length; i++) {
605
+ const note = noteList[i];
606
+ if (!note)
607
+ continue;
608
+ note.bufferSource.detune
609
+ .cancelScheduledValues(now)
610
+ .setValueAtTime(channel.detune, now);
611
+ }
612
+ });
613
+ }
595
614
  setVolumeEnvelope(note) {
596
615
  const now = this.audioContext.currentTime;
597
616
  const { voiceParams, startTime } = note;
@@ -609,32 +628,28 @@ class MidyGMLite {
609
628
  .setValueAtTime(attackVolume, volHold)
610
629
  .linearRampToValueAtTime(sustainVolume, volDecay);
611
630
  }
612
- setPlaybackRate(note) {
631
+ setPitchEnvelope(note) {
613
632
  const now = this.audioContext.currentTime;
633
+ const { voiceParams } = note;
634
+ const baseRate = voiceParams.playbackRate;
614
635
  note.bufferSource.playbackRate
615
636
  .cancelScheduledValues(now)
616
- .setValueAtTime(note.voiceParams.playbackRate, now);
617
- }
618
- setPitch(channel, note) {
619
- const now = this.audioContext.currentTime;
620
- const { startTime } = note;
621
- const basePitch = this.calcSemitoneOffset(channel) * 100;
622
- note.bufferSource.detune
623
- .cancelScheduledValues(now)
624
- .setValueAtTime(basePitch, startTime);
625
- const modEnvToPitch = note.voiceParams.modEnvToPitch;
637
+ .setValueAtTime(baseRate, now);
638
+ const modEnvToPitch = voiceParams.modEnvToPitch;
626
639
  if (modEnvToPitch === 0)
627
640
  return;
641
+ const basePitch = this.rateToCent(baseRate);
628
642
  const peekPitch = basePitch + modEnvToPitch;
643
+ const peekRate = this.centToRate(peekPitch);
629
644
  const modDelay = startTime + voiceParams.modDelay;
630
645
  const modAttack = modDelay + voiceParams.modAttack;
631
646
  const modHold = modAttack + voiceParams.modHold;
632
647
  const modDecay = modHold + voiceParams.modDecay;
633
- note.bufferSource.detune
634
- .setValueAtTime(basePitch, modDelay)
635
- .exponentialRampToValueAtTime(peekPitch, modAttack)
636
- .setValueAtTime(peekPitch, modHold)
637
- .linearRampToValueAtTime(basePitch, modDecay);
648
+ note.bufferSource.playbackRate
649
+ .setValueAtTime(baseRate, modDelay)
650
+ .exponentialRampToValueAtTime(peekRate, modAttack)
651
+ .setValueAtTime(peekRate, modHold)
652
+ .linearRampToValueAtTime(baseRate, modDecay);
638
653
  }
639
654
  clampCutoffFrequency(frequency) {
640
655
  const minFrequency = 20; // min Hz of initialFilterFc
@@ -695,9 +710,8 @@ class MidyGMLite {
695
710
  });
696
711
  this.setVolumeEnvelope(note);
697
712
  this.setFilterEnvelope(note);
698
- this.setPlaybackRate(note);
713
+ this.setPitchEnvelope(note);
699
714
  if (0 < state.modulationDepth) {
700
- this.setPitch(channel, note);
701
715
  this.startModulation(channel, note, startTime);
702
716
  }
703
717
  note.bufferSource.connect(note.filterNode);
@@ -841,10 +855,12 @@ class MidyGMLite {
841
855
  setPitchBend(channelNumber, value) {
842
856
  const channel = this.channels[channelNumber];
843
857
  const state = channel.state;
858
+ const prev = state.pitchWheel * 2 - 1;
859
+ const next = (value - 8192) / 8192;
844
860
  state.pitchWheel = value / 16383;
845
- const pitchWheel = (value - 8192) / 8192;
846
- const detuneChange = pitchWheel * state.pitchWheelSensitivity * 12800;
847
- this.updateDetune(channel, detuneChange);
861
+ channel.detune += (next - prev) * state.pitchWheelSensitivity * 12800;
862
+ this.updateDetune(channel);
863
+ this.applyVoiceParams(channel, 14);
848
864
  }
849
865
  setModLfoToPitch(channel, note) {
850
866
  const now = this.audioContext.currentTime;
@@ -856,6 +872,13 @@ class MidyGMLite {
856
872
  .cancelScheduledValues(now)
857
873
  .setValueAtTime(modulationDepth * modulationDepthSign, now);
858
874
  }
875
+ setModLfoToFilterFc(note) {
876
+ const now = this.audioContext.currentTime;
877
+ const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
878
+ note.filterDepth.gain
879
+ .cancelScheduledValues(now)
880
+ .setValueAtTime(modLfoToFilterFc, now);
881
+ }
859
882
  setModLfoToVolume(note) {
860
883
  const now = this.audioContext.currentTime;
861
884
  const modLfoToVolume = note.voiceParams.modLfoToVolume;
@@ -865,13 +888,6 @@ class MidyGMLite {
865
888
  .cancelScheduledValues(now)
866
889
  .setValueAtTime(volumeDepth * volumeDepthSign, now);
867
890
  }
868
- setModLfoToFilterFc(note) {
869
- const now = this.audioContext.currentTime;
870
- const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
871
- note.filterDepth.gain
872
- .cancelScheduledValues(now)
873
- .setValueAtTime(modLfoToFilterFc, now);
874
- }
875
891
  setDelayModLFO(note) {
876
892
  const now = this.audioContext.currentTime;
877
893
  const startTime = note.startTime;
@@ -948,7 +964,7 @@ class MidyGMLite {
948
964
  noteVoiceParams[key] = voiceParams[key];
949
965
  }
950
966
  this.setFilterEnvelope(channel, note);
951
- this.setPitch(channel, note);
967
+ this.setPitchEnvelope(note);
952
968
  }
953
969
  else if (volumeEnvelopeKeySet.has(key)) {
954
970
  if (appliedVolumeEnvelope)
@@ -1002,7 +1018,7 @@ class MidyGMLite {
1002
1018
  note.modulationDepth.gain.setValueAtTime(channel.state.modulationDepth, now);
1003
1019
  }
1004
1020
  else {
1005
- this.setPitch(channel, note);
1021
+ this.setPitchEnvelope(note);
1006
1022
  this.startModulation(channel, note, now);
1007
1023
  }
1008
1024
  }
@@ -1097,20 +1113,6 @@ class MidyGMLite {
1097
1113
  this.channels[channelNumber].dataMSB = value;
1098
1114
  this.handleRPN(channelNumber);
1099
1115
  }
1100
- updateDetune(channel, detune) {
1101
- const now = this.audioContext.currentTime;
1102
- channel.scheduledNotes.forEach((noteList) => {
1103
- for (let i = 0; i < noteList.length; i++) {
1104
- const note = noteList[i];
1105
- if (!note)
1106
- continue;
1107
- const { bufferSource } = note;
1108
- bufferSource.detune
1109
- .cancelScheduledValues(now)
1110
- .setValueAtTime(detune, now);
1111
- }
1112
- });
1113
- }
1114
1116
  handlePitchBendRangeRPN(channelNumber) {
1115
1117
  const channel = this.channels[channelNumber];
1116
1118
  this.limitData(channel, 0, 127, 0, 99);
@@ -1237,6 +1239,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
1237
1239
  writable: true,
1238
1240
  value: {
1239
1241
  currentBufferSource: null,
1242
+ detune: 0,
1240
1243
  program: 0,
1241
1244
  bank: 0,
1242
1245
  dataMSB: 0,