@marmooo/midy 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/esm/midy-GM2.js CHANGED
@@ -240,6 +240,16 @@ const releaseCurve = 1 / (-Math.log(cbToRatio(-600)));
240
240
  export class MidyGM2 extends EventTarget {
241
241
  constructor(audioContext) {
242
242
  super();
243
+ // https://pmc.ncbi.nlm.nih.gov/articles/PMC4191557/
244
+ // https://pubmed.ncbi.nlm.nih.gov/12488797/
245
+ // Gap detection studies indicate humans detect temporal discontinuities
246
+ // around 2–3 ms. Smoothing over ~4 ms is perceived as continuous.
247
+ Object.defineProperty(this, "perceptualSmoothingTime", {
248
+ enumerable: true,
249
+ configurable: true,
250
+ writable: true,
251
+ value: 0.004
252
+ });
243
253
  Object.defineProperty(this, "mode", {
244
254
  enumerable: true,
245
255
  configurable: true,
@@ -698,9 +708,9 @@ export class MidyGM2 extends EventTarget {
698
708
  this.voiceCache.clear();
699
709
  this.realtimeVoiceCache.clear();
700
710
  const channels = this.channels;
701
- for (let i = 0; i < channels.length; i++) {
702
- channels[i].scheduledNotes = [];
703
- this.resetChannelStates(i);
711
+ for (let ch = 0; i < channels.length; ch++) {
712
+ channels[ch].scheduledNotes = [];
713
+ this.resetChannelStates(ch);
704
714
  }
705
715
  }
706
716
  updateStates(queueIndex, nextQueueIndex) {
@@ -928,8 +938,8 @@ export class MidyGM2 extends EventTarget {
928
938
  }
929
939
  stopNotes(velocity, force, scheduleTime) {
930
940
  const channels = this.channels;
931
- for (let i = 0; i < channels.length; i++) {
932
- this.stopChannelNotes(i, velocity, force, scheduleTime);
941
+ for (let ch = 0; ch < channels.length; ch++) {
942
+ this.stopChannelNotes(ch, velocity, force, scheduleTime);
933
943
  }
934
944
  const stopPromise = Promise.all(this.notePromises);
935
945
  this.notePromises = [];
@@ -970,6 +980,13 @@ export class MidyGM2 extends EventTarget {
970
980
  this.isSeeking = true;
971
981
  }
972
982
  }
983
+ tempoChange(tempo) {
984
+ const timeScale = this.tempo / tempo;
985
+ this.resumeTime = this.resumeTime * timeScale;
986
+ this.tempo = tempo;
987
+ this.totalTime = this.calcTotalTime();
988
+ this.seekTo(this.currentTime() * timeScale);
989
+ }
973
990
  calcTotalTime() {
974
991
  const totalTimeEventTypes = this.totalTimeEventTypes;
975
992
  const timeline = this.timeline;
@@ -1192,31 +1209,24 @@ export class MidyGM2 extends EventTarget {
1192
1209
  return tuning + pitch;
1193
1210
  }
1194
1211
  }
1195
- calcNoteDetune(channel, note) {
1196
- return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1197
- }
1198
1212
  updateChannelDetune(channel, scheduleTime) {
1199
1213
  this.processScheduledNotes(channel, (note) => {
1200
- this.updateDetune(channel, note, scheduleTime);
1214
+ if (this.isPortamento(channel, note)) {
1215
+ this.setPortamentoDetune(channel, note, scheduleTime);
1216
+ }
1217
+ else {
1218
+ this.setDetune(channel, note, scheduleTime);
1219
+ }
1201
1220
  });
1202
1221
  }
1203
- updateDetune(channel, note, scheduleTime) {
1204
- const noteDetune = this.calcNoteDetune(channel, note);
1205
- const detune = channel.detune + noteDetune;
1206
- if (this.isPortamento(channel, note)) {
1207
- const startTime = note.startTime;
1208
- const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1209
- const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1210
- note.bufferSource.detune
1211
- .cancelScheduledValues(scheduleTime)
1212
- .setValueAtTime(detune - deltaCent, scheduleTime)
1213
- .linearRampToValueAtTime(detune, portamentoTime);
1214
- }
1215
- else {
1216
- note.bufferSource.detune
1217
- .cancelScheduledValues(scheduleTime)
1218
- .setValueAtTime(detune, scheduleTime);
1219
- }
1222
+ calcScaleOctaveTuning(channel, note) {
1223
+ return channel.scaleOctaveTuningTable[note.noteNumber % 12];
1224
+ }
1225
+ calcNoteDetune(channel, note) {
1226
+ const noteDetune = note.voiceParams.detune +
1227
+ this.calcScaleOctaveTuning(channel, note);
1228
+ const pitchControl = this.getPitchControl(channel, note);
1229
+ return channel.detune + noteDetune + pitchControl;
1220
1230
  }
1221
1231
  getPortamentoTime(channel, note) {
1222
1232
  const deltaSemitone = Math.abs(note.noteNumber - note.portamentoNoteNumber);
@@ -1287,7 +1297,7 @@ export class MidyGM2 extends EventTarget {
1287
1297
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1288
1298
  note.volumeEnvelopeNode.gain
1289
1299
  .cancelScheduledValues(scheduleTime)
1290
- .linearRampToValueAtTime(sustainVolume, portamentoTime);
1300
+ .exponentialRampToValueAtTime(sustainVolume, portamentoTime);
1291
1301
  }
1292
1302
  setVolumeEnvelope(channel, note, scheduleTime) {
1293
1303
  const { voiceParams, startTime } = note;
@@ -1301,38 +1311,56 @@ export class MidyGM2 extends EventTarget {
1301
1311
  note.volumeEnvelopeNode.gain
1302
1312
  .cancelScheduledValues(scheduleTime)
1303
1313
  .setValueAtTime(0, startTime)
1304
- .setValueAtTime(0, volDelay)
1305
- .linearRampToValueAtTime(attackVolume, volAttack)
1314
+ .setValueAtTime(1e-6, volDelay)
1315
+ .exponentialRampToValueAtTime(attackVolume, volAttack)
1306
1316
  .setValueAtTime(attackVolume, volHold)
1307
1317
  .setTargetAtTime(sustainVolume, volHold, decayDuration * decayCurve);
1308
1318
  }
1309
- setPortamentoPitchEnvelope(note, scheduleTime) {
1319
+ setPortamentoDetune(channel, note, scheduleTime) {
1320
+ const detune = this.calcNoteDetune(channel, note);
1321
+ const startTime = note.startTime;
1322
+ const deltaCent = (note.noteNumber - note.portamentoNoteNumber) * 100;
1323
+ const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1324
+ note.bufferSource.detune
1325
+ .cancelScheduledValues(scheduleTime)
1326
+ .setValueAtTime(detune - deltaCent, scheduleTime)
1327
+ .linearRampToValueAtTime(detune, portamentoTime);
1328
+ }
1329
+ setDetune(channel, note, scheduleTime) {
1330
+ const detune = this.calcNoteDetune(channel, note);
1331
+ note.bufferSource.detune
1332
+ .cancelScheduledValues(scheduleTime)
1333
+ .setValueAtTime(detune, scheduleTime);
1334
+ const timeConstant = this.perceptualSmoothingTime / 5; // 99.3% (5 * tau)
1335
+ note.bufferSource.detune
1336
+ .cancelAndHoldAtTime(scheduleTime)
1337
+ .setTargetAtTime(detune, scheduleTime, timeConstant);
1338
+ }
1339
+ setPortamentoPitchEnvelope(channel, note, scheduleTime) {
1310
1340
  const baseRate = note.voiceParams.playbackRate;
1311
1341
  const portamentoTime = note.startTime +
1312
1342
  this.getPortamentoTime(channel, note);
1313
1343
  note.bufferSource.playbackRate
1314
1344
  .cancelScheduledValues(scheduleTime)
1315
- .linearRampToValueAtTime(baseRate, portamentoTime);
1345
+ .exponentialRampToValueAtTime(baseRate, portamentoTime);
1316
1346
  }
1317
1347
  setPitchEnvelope(note, scheduleTime) {
1318
- const { voiceParams } = note;
1348
+ const { bufferSource, voiceParams } = note;
1319
1349
  const baseRate = voiceParams.playbackRate;
1320
- note.bufferSource.playbackRate
1350
+ bufferSource.playbackRate
1321
1351
  .cancelScheduledValues(scheduleTime)
1322
- .setValueAtTime(baseRate, note.startTime);
1352
+ .setValueAtTime(baseRate, scheduleTime);
1323
1353
  const modEnvToPitch = voiceParams.modEnvToPitch;
1324
1354
  if (modEnvToPitch === 0)
1325
1355
  return;
1326
- const basePitch = this.rateToCent(baseRate);
1327
- const peekPitch = basePitch + modEnvToPitch;
1328
- const peekRate = this.centToRate(peekPitch);
1356
+ const peekRate = baseRate * this.centToRate(modEnvToPitch);
1329
1357
  const modDelay = note.startTime + voiceParams.modDelay;
1330
1358
  const modAttack = modDelay + voiceParams.modAttack;
1331
1359
  const modHold = modAttack + voiceParams.modHold;
1332
1360
  const decayDuration = voiceParams.modDecay;
1333
- note.bufferSource.playbackRate
1361
+ bufferSource.playbackRate
1334
1362
  .setValueAtTime(baseRate, modDelay)
1335
- .linearRampToValueAtTime(peekRate, modAttack)
1363
+ .exponentialRampToValueAtTime(peekRate, modAttack)
1336
1364
  .setValueAtTime(peekRate, modHold)
1337
1365
  .setTargetAtTime(baseRate, modHold, decayDuration * decayCurve);
1338
1366
  }
@@ -1348,10 +1376,10 @@ export class MidyGM2 extends EventTarget {
1348
1376
  this.getFilterCutoffControl(channel);
1349
1377
  const sustainCent = baseCent +
1350
1378
  voiceParams.modEnvToFilterFc * (1 - voiceParams.modSustain);
1351
- const baseFreq = this.centToHz(baseCent);
1352
- const sustainFreq = this.centToHz(sustainCent);
1353
- const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq * scale);
1354
- const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq * scale);
1379
+ const baseFreq = this.centToHz(baseCent) * scale;
1380
+ const sustainFreq = this.centToHz(sustainCent) * scale;
1381
+ const adjustedBaseFreq = this.clampCutoffFrequency(baseFreq);
1382
+ const adjustedSustainFreq = this.clampCutoffFrequency(sustainFreq);
1355
1383
  const portamentoTime = startTime + this.getPortamentoTime(channel, note);
1356
1384
  const modDelay = startTime + voiceParams.modDelay;
1357
1385
  note.adjustedBaseFreq = adjustedSustainFreq;
@@ -1359,7 +1387,7 @@ export class MidyGM2 extends EventTarget {
1359
1387
  .cancelScheduledValues(scheduleTime)
1360
1388
  .setValueAtTime(adjustedBaseFreq, startTime)
1361
1389
  .setValueAtTime(adjustedBaseFreq, modDelay)
1362
- .linearRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1390
+ .exponentialRampToValueAtTime(adjustedSustainFreq, portamentoTime);
1363
1391
  }
1364
1392
  setFilterEnvelope(channel, note, scheduleTime) {
1365
1393
  const { voiceParams, startTime } = note;
@@ -1385,7 +1413,7 @@ export class MidyGM2 extends EventTarget {
1385
1413
  .cancelScheduledValues(scheduleTime)
1386
1414
  .setValueAtTime(adjustedBaseFreq, startTime)
1387
1415
  .setValueAtTime(adjustedBaseFreq, modDelay)
1388
- .linearRampToValueAtTime(adjustedPeekFreq, modAttack)
1416
+ .exponentialRampToValueAtTime(adjustedPeekFreq, modAttack)
1389
1417
  .setValueAtTime(adjustedPeekFreq, modHold)
1390
1418
  .setTargetAtTime(adjustedSustainFreq, modHold, decayDuration * decayCurve);
1391
1419
  }
@@ -1474,14 +1502,15 @@ export class MidyGM2 extends EventTarget {
1474
1502
  if (!channel.isDrum && this.isPortamento(channel, note)) {
1475
1503
  this.setPortamentoVolumeEnvelope(channel, note, now);
1476
1504
  this.setPortamentoFilterEnvelope(channel, note, now);
1477
- this.setPortamentoPitchEnvelope(note, now);
1505
+ this.setPortamentoPitchEnvelope(channel, note, now);
1506
+ this.setPortamentoDetune(channel, note, now);
1478
1507
  }
1479
1508
  else {
1480
1509
  this.setVolumeEnvelope(channel, note, now);
1481
1510
  this.setFilterEnvelope(channel, note, now);
1482
1511
  this.setPitchEnvelope(note, now);
1512
+ this.setDetune(channel, note, now);
1483
1513
  }
1484
- this.updateDetune(channel, note, now);
1485
1514
  if (0 < state.vibratoDepth) {
1486
1515
  this.startVibrato(channel, note, now);
1487
1516
  }
@@ -1615,15 +1644,14 @@ export class MidyGM2 extends EventTarget {
1615
1644
  }
1616
1645
  releaseNote(channel, note, endTime) {
1617
1646
  endTime ??= this.audioContext.currentTime;
1618
- const duration = note.voiceParams.volRelease;
1619
- const volRelease = endTime + duration;
1620
- const modRelease = endTime + note.voiceParams.modRelease;
1647
+ const volDuration = note.voiceParams.volRelease;
1648
+ const volRelease = endTime + volDuration;
1621
1649
  note.filterNode.frequency
1622
1650
  .cancelScheduledValues(endTime)
1623
- .linearRampToValueAtTime(note.adjustedBaseFreq, modRelease);
1651
+ .setTargetAtTime(note.adjustedBaseFreq, endTime, note.voiceParams.modRelease * releaseCurve);
1624
1652
  note.volumeEnvelopeNode.gain
1625
1653
  .cancelScheduledValues(endTime)
1626
- .setTargetAtTime(0, endTime, duration * releaseCurve);
1654
+ .setTargetAtTime(0, endTime, volDuration * releaseCurve);
1627
1655
  return new Promise((resolve) => {
1628
1656
  this.scheduleTask(() => {
1629
1657
  const bufferSource = note.bufferSource;
@@ -1909,13 +1937,6 @@ export class MidyGM2 extends EventTarget {
1909
1937
  .cancelScheduledValues(scheduleTime)
1910
1938
  .setValueAtTime(freqModLFO, scheduleTime);
1911
1939
  }
1912
- setFreqVibLFO(channel, note, scheduleTime) {
1913
- const vibratoRate = channel.state.vibratoRate * 2;
1914
- const freqVibLFO = note.voiceParams.freqVibLFO;
1915
- note.vibratoLFO.frequency
1916
- .cancelScheduledValues(scheduleTime)
1917
- .setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
1918
- }
1919
1940
  setDelayVibLFO(channel, note) {
1920
1941
  const vibratoDelay = channel.state.vibratoDelay * 2;
1921
1942
  const value = note.voiceParams.delayVibLFO;
@@ -1925,6 +1946,13 @@ export class MidyGM2 extends EventTarget {
1925
1946
  }
1926
1947
  catch { /* empty */ }
1927
1948
  }
1949
+ setFreqVibLFO(channel, note, scheduleTime) {
1950
+ const vibratoRate = channel.state.vibratoRate * 2;
1951
+ const freqVibLFO = note.voiceParams.freqVibLFO;
1952
+ note.vibratoLFO.frequency
1953
+ .cancelScheduledValues(scheduleTime)
1954
+ .setValueAtTime(freqVibLFO * vibratoRate, scheduleTime);
1955
+ }
1928
1956
  createVoiceParamsHandlers() {
1929
1957
  return {
1930
1958
  modLfoToPitch: (channel, note, scheduleTime) => {
@@ -1973,6 +2001,14 @@ export class MidyGM2 extends EventTarget {
1973
2001
  this.setFreqVibLFO(channel, note, scheduleTime);
1974
2002
  }
1975
2003
  },
2004
+ detune: (channel, note, scheduleTime) => {
2005
+ if (this.isPortamento(channel, note)) {
2006
+ this.setPortamentoDetune(channel, note, scheduleTime);
2007
+ }
2008
+ else {
2009
+ this.setDetune(channel, note, scheduleTime);
2010
+ }
2011
+ },
1976
2012
  };
1977
2013
  }
1978
2014
  getControllerState(channel, noteNumber, velocity) {
@@ -2072,13 +2108,13 @@ export class MidyGM2 extends EventTarget {
2072
2108
  }
2073
2109
  });
2074
2110
  }
2075
- setModulationDepth(channelNumber, modulation, scheduleTime) {
2111
+ setModulationDepth(channelNumber, value, scheduleTime) {
2076
2112
  const channel = this.channels[channelNumber];
2077
2113
  if (channel.isDrum)
2078
2114
  return;
2079
2115
  if (!(0 <= scheduleTime))
2080
2116
  scheduleTime = this.audioContext.currentTime;
2081
- channel.state.modulationDepthMSB = modulation / 127;
2117
+ channel.state.modulationDepthMSB = value / 127;
2082
2118
  this.updateModulation(channel, scheduleTime);
2083
2119
  }
2084
2120
  updatePortamento(channel, scheduleTime) {
@@ -2088,31 +2124,31 @@ export class MidyGM2 extends EventTarget {
2088
2124
  if (this.isPortamento(channel, note)) {
2089
2125
  this.setPortamentoVolumeEnvelope(channel, note, scheduleTime);
2090
2126
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
2091
- this.setPortamentoPitchEnvelope(note, scheduleTime);
2092
- this.updateDetune(channel, note, scheduleTime);
2127
+ this.setPortamentoPitchEnvelope(channel, note, scheduleTime);
2128
+ this.setPortamentoDetune(channel, note, scheduleTime);
2093
2129
  }
2094
2130
  else {
2095
2131
  this.setVolumeEnvelope(channel, note, scheduleTime);
2096
2132
  this.setFilterEnvelope(channel, note, scheduleTime);
2097
2133
  this.setPitchEnvelope(note, scheduleTime);
2098
- this.updateDetune(channel, note, scheduleTime);
2134
+ this.setDetune(channel, note, scheduleTime);
2099
2135
  }
2100
2136
  });
2101
2137
  }
2102
- setPortamentoTime(channelNumber, portamentoTime, scheduleTime) {
2138
+ setPortamentoTime(channelNumber, value, scheduleTime) {
2103
2139
  if (!(0 <= scheduleTime))
2104
2140
  scheduleTime = this.audioContext.currentTime;
2105
2141
  const channel = this.channels[channelNumber];
2106
- channel.state.portamentoTimeMSB = portamentoTime / 127;
2142
+ channel.state.portamentoTimeMSB = value / 127;
2107
2143
  if (channel.isDrum)
2108
2144
  return;
2109
2145
  this.updatePortamento(channel, scheduleTime);
2110
2146
  }
2111
- setVolume(channelNumber, volume, scheduleTime) {
2147
+ setVolume(channelNumber, value, scheduleTime) {
2112
2148
  if (!(0 <= scheduleTime))
2113
2149
  scheduleTime = this.audioContext.currentTime;
2114
2150
  const channel = this.channels[channelNumber];
2115
- channel.state.volumeMSB = volume / 127;
2151
+ channel.state.volumeMSB = value / 127;
2116
2152
  if (channel.isDrum) {
2117
2153
  for (let i = 0; i < 128; i++) {
2118
2154
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2129,11 +2165,11 @@ export class MidyGM2 extends EventTarget {
2129
2165
  gainRight: Math.sin(theta),
2130
2166
  };
2131
2167
  }
2132
- setPan(channelNumber, pan, scheduleTime) {
2168
+ setPan(channelNumber, value, scheduleTime) {
2133
2169
  if (!(0 <= scheduleTime))
2134
2170
  scheduleTime = this.audioContext.currentTime;
2135
2171
  const channel = this.channels[channelNumber];
2136
- channel.state.panMSB = pan / 127;
2172
+ channel.state.panMSB = value / 127;
2137
2173
  if (channel.isDrum) {
2138
2174
  for (let i = 0; i < 128; i++) {
2139
2175
  this.updateKeyBasedVolume(channel, i, scheduleTime);
@@ -2143,11 +2179,11 @@ export class MidyGM2 extends EventTarget {
2143
2179
  this.updateChannelVolume(channel, scheduleTime);
2144
2180
  }
2145
2181
  }
2146
- setExpression(channelNumber, expression, scheduleTime) {
2182
+ setExpression(channelNumber, value, scheduleTime) {
2147
2183
  if (!(0 <= scheduleTime))
2148
2184
  scheduleTime = this.audioContext.currentTime;
2149
2185
  const channel = this.channels[channelNumber];
2150
- channel.state.expressionMSB = expression / 127;
2186
+ channel.state.expressionMSB = value / 127;
2151
2187
  this.updateChannelVolume(channel, scheduleTime);
2152
2188
  }
2153
2189
  setBankLSB(channelNumber, lsb) {
@@ -2159,14 +2195,14 @@ export class MidyGM2 extends EventTarget {
2159
2195
  }
2160
2196
  updateChannelVolume(channel, scheduleTime) {
2161
2197
  const state = channel.state;
2162
- const volume = state.volumeMSB * state.expressionMSB;
2198
+ const gain = state.volumeMSB * state.expressionMSB;
2163
2199
  const { gainLeft, gainRight } = this.panToGain(state.panMSB);
2164
2200
  channel.gainL.gain
2165
2201
  .cancelScheduledValues(scheduleTime)
2166
- .setValueAtTime(volume * gainLeft, scheduleTime);
2202
+ .setValueAtTime(gain * gainLeft, scheduleTime);
2167
2203
  channel.gainR.gain
2168
2204
  .cancelScheduledValues(scheduleTime)
2169
- .setValueAtTime(volume * gainRight, scheduleTime);
2205
+ .setValueAtTime(gain * gainRight, scheduleTime);
2170
2206
  }
2171
2207
  updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
2172
2208
  const gainL = channel.keyBasedGainLs[keyNumber];
@@ -2174,21 +2210,21 @@ export class MidyGM2 extends EventTarget {
2174
2210
  return;
2175
2211
  const gainR = channel.keyBasedGainRs[keyNumber];
2176
2212
  const state = channel.state;
2177
- const defaultVolume = state.volumeMSB * state.expressionMSB;
2213
+ const defaultGain = state.volumeMSB * state.expressionMSB;
2178
2214
  const defaultPan = state.panMSB;
2179
2215
  const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
2180
- const volume = (0 <= keyBasedVolume)
2181
- ? defaultVolume * keyBasedVolume / 64
2182
- : defaultVolume;
2216
+ const gain = (0 <= keyBasedVolume)
2217
+ ? defaultGain * keyBasedVolume / 64
2218
+ : defaultGain;
2183
2219
  const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
2184
2220
  const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
2185
2221
  const { gainLeft, gainRight } = this.panToGain(pan);
2186
2222
  gainL.gain
2187
2223
  .cancelScheduledValues(scheduleTime)
2188
- .setValueAtTime(volume * gainLeft, scheduleTime);
2224
+ .setValueAtTime(gain * gainLeft, scheduleTime);
2189
2225
  gainR.gain
2190
2226
  .cancelScheduledValues(scheduleTime)
2191
- .setValueAtTime(volume * gainRight, scheduleTime);
2227
+ .setValueAtTime(gain * gainRight, scheduleTime);
2192
2228
  }
2193
2229
  setSustainPedal(channelNumber, value, scheduleTime) {
2194
2230
  const channel = this.channels[channelNumber];
@@ -2320,6 +2356,8 @@ export class MidyGM2 extends EventTarget {
2320
2356
  case 5:
2321
2357
  this.handleModulationDepthRangeRPN(channelNumber, scheduleTime);
2322
2358
  break;
2359
+ case 16383: // NULL
2360
+ break;
2323
2361
  default:
2324
2362
  console.warn(`Channel ${channelNumber}: Unsupported RPN MSB=${channel.rpnMSB} LSB=${channel.rpnLSB}`);
2325
2363
  }
@@ -2521,9 +2559,9 @@ export class MidyGM2 extends EventTarget {
2521
2559
  if (!(0 <= scheduleTime))
2522
2560
  scheduleTime = this.audioContext.currentTime;
2523
2561
  this.mode = "GM1";
2524
- for (let i = 0; i < channels.length; i++) {
2525
- this.allSoundOff(i, 0, scheduleTime);
2526
- const channel = channels[i];
2562
+ for (let ch = 0; ch < channels.length; ch++) {
2563
+ this.allSoundOff(ch, 0, scheduleTime);
2564
+ const channel = channels[ch];
2527
2565
  channel.bankMSB = 0;
2528
2566
  channel.bankLSB = 0;
2529
2567
  channel.isDrum = false;
@@ -2536,9 +2574,9 @@ export class MidyGM2 extends EventTarget {
2536
2574
  if (!(0 <= scheduleTime))
2537
2575
  scheduleTime = this.audioContext.currentTime;
2538
2576
  this.mode = "GM2";
2539
- for (let i = 0; i < channels.length; i++) {
2540
- this.allSoundOff(i, 0, scheduleTime);
2541
- const channel = channels[i];
2577
+ for (let ch = 0; ch < channels.length; ch++) {
2578
+ this.allSoundOff(ch, 0, scheduleTime);
2579
+ const channel = channels[ch];
2542
2580
  channel.bankMSB = 121;
2543
2581
  channel.bankLSB = 0;
2544
2582
  channel.isDrum = false;
@@ -2605,8 +2643,9 @@ export class MidyGM2 extends EventTarget {
2605
2643
  const next = value;
2606
2644
  this.masterFineTuning = next;
2607
2645
  const detuneChange = next - prev;
2608
- for (let i = 0; i < this.channels.length; i++) {
2609
- const channel = this.channels[i];
2646
+ const channels = this.channels;
2647
+ for (let ch = 0; i < channels.length; ch++) {
2648
+ const channel = this.channels[ch];
2610
2649
  if (channel.isDrum)
2611
2650
  continue;
2612
2651
  channel.detune += detuneChange;
@@ -2622,8 +2661,9 @@ export class MidyGM2 extends EventTarget {
2622
2661
  const next = value;
2623
2662
  this.masterCoarseTuning = next;
2624
2663
  const detuneChange = next - prev;
2625
- for (let i = 0; i < this.channels.length; i++) {
2626
- const channel = this.channels[i];
2664
+ const channels = this.channels;
2665
+ for (let ch = 0; ch < channels.length; ch++) {
2666
+ const channel = this.channels[ch];
2627
2667
  if (channel.isDrum)
2628
2668
  continue;
2629
2669
  channel.detune += detuneChange;
@@ -2884,8 +2924,14 @@ export class MidyGM2 extends EventTarget {
2884
2924
  return channelPressure / 127;
2885
2925
  }
2886
2926
  setEffects(channel, note, table, scheduleTime) {
2887
- if (0 < table[0])
2888
- this.updateDetune(channel, note, scheduleTime);
2927
+ if (0 < table[0]) {
2928
+ if (this.isPortamento(channel, note)) {
2929
+ this.setPortamentoDetune(channel, note, scheduleTime);
2930
+ }
2931
+ else {
2932
+ this.setDetune(channel, note, scheduleTime);
2933
+ }
2934
+ }
2889
2935
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2890
2936
  if (0 < table[1]) {
2891
2937
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -10,6 +10,7 @@ export class MidyGMLite extends EventTarget {
10
10
  modulationDepthRange: number;
11
11
  };
12
12
  constructor(audioContext: any);
13
+ perceptualSmoothingTime: number;
13
14
  mode: string;
14
15
  numChannels: number;
15
16
  ticksPerBeat: number;
@@ -54,6 +55,7 @@ export class MidyGMLite extends EventTarget {
54
55
  freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
55
56
  delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
56
57
  freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
58
+ detune: (channel: any, note: any, scheduleTime: any) => void;
57
59
  };
58
60
  controlChangeHandlers: any[];
59
61
  channels: any[];
@@ -91,6 +93,7 @@ export class MidyGMLite extends EventTarget {
91
93
  pause(): Promise<void>;
92
94
  resume(): Promise<void>;
93
95
  seekTo(second: any): void;
96
+ tempoChange(tempo: any): void;
94
97
  calcTotalTime(): number;
95
98
  currentTime(): number;
96
99
  processScheduledNotes(channel: any, callback: any): Promise<void>;
@@ -100,8 +103,9 @@ export class MidyGMLite extends EventTarget {
100
103
  centToHz(cent: any): number;
101
104
  calcChannelDetune(channel: any): number;
102
105
  updateChannelDetune(channel: any, scheduleTime: any): void;
103
- updateDetune(channel: any, note: any, scheduleTime: any): void;
106
+ calcNoteDetune(channel: any, note: any): any;
104
107
  setVolumeEnvelope(note: any, scheduleTime: any): void;
108
+ setDetune(channel: any, note: any, scheduleTime: any): void;
105
109
  setPitchEnvelope(note: any, scheduleTime: any): void;
106
110
  clampCutoffFrequency(frequency: any): number;
107
111
  setFilterEnvelope(note: any, scheduleTime: any): void;
@@ -140,20 +144,21 @@ export class MidyGMLite extends EventTarget {
140
144
  freqModLFO: (_channel: any, note: any, scheduleTime: any) => void;
141
145
  delayVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
142
146
  freqVibLFO: (_channel: any, _note: any, _scheduleTime: any) => void;
147
+ detune: (channel: any, note: any, scheduleTime: any) => void;
143
148
  };
144
149
  getControllerState(channel: any, noteNumber: any, velocity: any): Float32Array<any>;
145
150
  applyVoiceParams(channel: any, controllerType: any, scheduleTime: any): void;
146
151
  createControlChangeHandlers(): any[];
147
152
  setControlChange(channelNumber: any, controllerType: any, value: any, scheduleTime: any): void;
148
153
  updateModulation(channel: any, scheduleTime: any): void;
149
- setModulationDepth(channelNumber: any, modulation: any, scheduleTime: any): void;
150
- setVolume(channelNumber: any, volume: any, scheduleTime: any): void;
154
+ setModulationDepth(channelNumber: any, value: any, scheduleTime: any): void;
155
+ setVolume(channelNumber: any, value: any, scheduleTime: any): void;
151
156
  panToGain(pan: any): {
152
157
  gainLeft: number;
153
158
  gainRight: number;
154
159
  };
155
- setPan(channelNumber: any, pan: any, scheduleTime: any): void;
156
- setExpression(channelNumber: any, expression: any, scheduleTime: any): void;
160
+ setPan(channelNumber: any, value: any, scheduleTime: any): void;
161
+ setExpression(channelNumber: any, value: any, scheduleTime: any): void;
157
162
  dataEntryLSB(channelNumber: any, value: any, scheduleTime: any): void;
158
163
  updateChannelVolume(channel: any, scheduleTime: any): void;
159
164
  setSustainPedal(channelNumber: any, value: any, scheduleTime: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqHA;IAkCE;;;;;;;;;MASE;IAEF,+BAgBC;IA5DD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iCAEG;IACH,cAAU;IACV,cAAa;IACb,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAeC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,0EAUC;IAED,gEAsDC;IAED,mCASC;IAED,uBAUC;IAED,yDAqCC;IAED,2BA0EC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAQC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,wBAYC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDA6BC;IAED,kEAqBC;IAED,4GAkCC;IAED,uEA6CC;IAED,0EAiBC;IAED,8EAiBC;IAED,oEAUC;IAED,0FAwBC;IAED,gCASC;IAED,iEAqBC;IAED,4FAsBC;IAED,6CAUC;IAED,qDAUC;IAED,qFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,sFA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAYC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MAiCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDASC;IAED,iFAKC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAUC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}
1
+ {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAqHA;IAuCE;;;;;;;;;MASE;IAEF,+BAgBC;IA7DD,gCAAgC;IAChC,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,0BAAuD;IACvD,4BAAyB;IACzB,0BAAuB;IACvB,kCAA+B;IAC/B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,iCAEG;IACH,cAAU;IACV,cAAa;IACb,iBAAY;IACZ,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF,uBAAmD;IACnD;;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAeC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDASC;IAED,0EAUC;IAED,gEAsDC;IAED,mCASC;IAED,uBAUC;IAED,yDAqCC;IAED,2BA0EC;IAED,uDAEC;IAED,wDAEC;IAED,qCAKC;IAED;;;MAwDC;IAED,kGAeC;IAED,mGAeC;IAED,wEAQC;IAED,uBAMC;IAED,sBAIC;IAED,uBAMC;IAED,wBAIC;IAED,0BAKC;IAED,8BAMC;IAED,wBAYC;IAED,sBAIC;IAED,kEAWC;IAED,kFAYC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,6CAEC;IAED,sDAeC;IAED,4DASC;IAED,qDAkBC;IAED,6CAIC;IAED,sDA6BC;IAED,kEAqBC;IAED,4GAkCC;IAED,uEA6CC;IAED,0EAiBC;IAED,8EAiBC;IAED,oEAUC;IAED,0FAwBC;IAED,gCASC;IAED,iEAwBC;IAED,4FAsBC;IAED,6CAUC;IAED,qDAUC;IAED,qFAeC;IAED,+BAmBC;IAED,kDAOC;IAED,sFA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAYC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;;MAoCC;IAED,oFAMC;IAED,6EA2BC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;IAED,4EAKC;IAED,mEAKC;IAED;;;MAMC;IAED,gEAKC;IAED,uEAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAWC;IAED,kFAeC;IAED,uDAcC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAUC;IAED,gFAGC;IAED,6CAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAUC;IAED,4EAaC;IAED,4DAGC;IAED,qDAKC;IAED,gDAYC;IAGD,6DAgBC;CACF"}