@marmooo/midy 0.3.5 → 0.3.6

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
@@ -68,13 +68,13 @@ class Note {
68
68
  writable: true,
69
69
  value: void 0
70
70
  });
71
- Object.defineProperty(this, "reverbEffectsSend", {
71
+ Object.defineProperty(this, "reverbSend", {
72
72
  enumerable: true,
73
73
  configurable: true,
74
74
  writable: true,
75
75
  value: void 0
76
76
  });
77
- Object.defineProperty(this, "chorusEffectsSend", {
77
+ Object.defineProperty(this, "chorusSend", {
78
78
  enumerable: true,
79
79
  configurable: true,
80
80
  writable: true,
@@ -362,17 +362,17 @@ export class MidyGM2 {
362
362
  writable: true,
363
363
  value: []
364
364
  });
365
- Object.defineProperty(this, "instruments", {
365
+ Object.defineProperty(this, "notePromises", {
366
366
  enumerable: true,
367
367
  configurable: true,
368
368
  writable: true,
369
369
  value: []
370
370
  });
371
- Object.defineProperty(this, "notePromises", {
371
+ Object.defineProperty(this, "instruments", {
372
372
  enumerable: true,
373
373
  configurable: true,
374
374
  writable: true,
375
- value: []
375
+ value: new Set()
376
376
  });
377
377
  Object.defineProperty(this, "exclusiveClassNotes", {
378
378
  enumerable: true,
@@ -504,7 +504,7 @@ export class MidyGM2 {
504
504
  const soundFont = this.soundFonts[soundFontIndex];
505
505
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
506
506
  const { instrument, sampleID } = voice.generators;
507
- return `${soundFontIndex}:${instrument}:${sampleID}`;
507
+ return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
508
508
  }
509
509
  createChannelAudioNodes(audioContext) {
510
510
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
@@ -904,13 +904,11 @@ export class MidyGM2 {
904
904
  return impulse;
905
905
  }
906
906
  createConvolutionReverb(audioContext, impulse) {
907
- const input = new GainNode(audioContext);
908
907
  const convolverNode = new ConvolverNode(audioContext, {
909
908
  buffer: impulse,
910
909
  });
911
- input.connect(convolverNode);
912
910
  return {
913
- input,
911
+ input: convolverNode,
914
912
  output: convolverNode,
915
913
  convolverNode,
916
914
  };
@@ -1340,12 +1338,8 @@ export class MidyGM2 {
1340
1338
  }
1341
1339
  note.bufferSource.connect(note.filterNode);
1342
1340
  note.filterNode.connect(note.volumeEnvelopeNode);
1343
- if (0 < state.chorusSendLevel) {
1344
- this.setChorusEffectsSend(channel, note, 0, now);
1345
- }
1346
- if (0 < state.reverbSendLevel) {
1347
- this.setReverbEffectsSend(channel, note, 0, now);
1348
- }
1341
+ this.setChorusSend(channel, note, now);
1342
+ this.setReverbSend(channel, note, now);
1349
1343
  note.bufferSource.start(startTime);
1350
1344
  return note;
1351
1345
  }
@@ -1411,10 +1405,14 @@ export class MidyGM2 {
1411
1405
  return;
1412
1406
  const note = await this.createNote(channel, voice, noteNumber, velocity, startTime);
1413
1407
  if (channel.isDrum) {
1414
- const audioContext = this.audioContext;
1415
- const { gainL, gainR } = this.createChannelAudioNodes(audioContext);
1416
- channel.keyBasedGainLs[noteNumber] = gainL;
1417
- channel.keyBasedGainRs[noteNumber] = gainR;
1408
+ const { keyBasedGainLs, keyBasedGainRs } = channel;
1409
+ let gainL = keyBasedGainLs[noteNumber];
1410
+ let gainR = keyBasedGainRs[noteNumber];
1411
+ if (!gainL) {
1412
+ const audioNodes = this.createChannelAudioNodes(this.audioContext);
1413
+ gainL = keyBasedGainLs[noteNumber] = audioNodes.gainL;
1414
+ gainR = keyBasedGainRs[noteNumber] = audioNodes.gainR;
1415
+ }
1418
1416
  note.volumeEnvelopeNode.connect(gainL);
1419
1417
  note.volumeEnvelopeNode.connect(gainR);
1420
1418
  }
@@ -1448,11 +1446,11 @@ export class MidyGM2 {
1448
1446
  note.vibratoDepth.disconnect();
1449
1447
  note.vibratoLFO.stop();
1450
1448
  }
1451
- if (note.reverbEffectsSend) {
1452
- note.reverbEffectsSend.disconnect();
1449
+ if (note.reverbSend) {
1450
+ note.reverbSend.disconnect();
1453
1451
  }
1454
- if (note.chorusEffectsSend) {
1455
- note.chorusEffectsSend.disconnect();
1452
+ if (note.chorusSend) {
1453
+ note.chorusSend.disconnect();
1456
1454
  }
1457
1455
  }
1458
1456
  releaseNote(channel, note, endTime) {
@@ -1603,7 +1601,7 @@ export class MidyGM2 {
1603
1601
  }
1604
1602
  const table = channel.channelPressureTable;
1605
1603
  this.processActiveNotes(channel, scheduleTime, (note) => {
1606
- this.setControllerParameters(channel, note, table);
1604
+ this.setEffects(channel, note, table);
1607
1605
  });
1608
1606
  this.applyVoiceParams(channel, 13);
1609
1607
  }
@@ -1625,13 +1623,18 @@ export class MidyGM2 {
1625
1623
  this.applyVoiceParams(channel, 14, scheduleTime);
1626
1624
  }
1627
1625
  setModLfoToPitch(channel, note, scheduleTime) {
1628
- const modLfoToPitch = note.voiceParams.modLfoToPitch +
1629
- this.getLFOPitchDepth(channel);
1630
- const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1631
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1632
- note.modulationDepth.gain
1633
- .cancelScheduledValues(scheduleTime)
1634
- .setValueAtTime(modulationDepth, scheduleTime);
1626
+ if (note.modulationDepth) {
1627
+ const modLfoToPitch = note.voiceParams.modLfoToPitch +
1628
+ this.getLFOPitchDepth(channel, note);
1629
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1630
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1631
+ note.modulationDepth.gain
1632
+ .cancelScheduledValues(scheduleTime)
1633
+ .setValueAtTime(modulationDepth, scheduleTime);
1634
+ }
1635
+ else {
1636
+ this.startModulation(channel, note, scheduleTime);
1637
+ }
1635
1638
  }
1636
1639
  setVibLfoToPitch(channel, note, scheduleTime) {
1637
1640
  const vibLfoToPitch = note.voiceParams.vibLfoToPitch;
@@ -1658,63 +1661,63 @@ export class MidyGM2 {
1658
1661
  .cancelScheduledValues(scheduleTime)
1659
1662
  .setValueAtTime(volumeDepth, scheduleTime);
1660
1663
  }
1661
- setReverbEffectsSend(channel, note, prevValue, scheduleTime) {
1662
- let value = note.voiceParams.reverbEffectsSend;
1664
+ setReverbSend(channel, note, scheduleTime) {
1665
+ let value = note.voiceParams.reverbEffectsSend *
1666
+ channel.state.reverbSendLevel;
1663
1667
  if (channel.isDrum) {
1664
1668
  const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 91);
1665
- if (0 <= keyBasedValue) {
1666
- value *= keyBasedValue / 127 / channel.state.reverbSendLevel;
1667
- }
1669
+ if (0 <= keyBasedValue)
1670
+ value = keyBasedValue / 127;
1668
1671
  }
1669
- if (0 < prevValue) {
1672
+ if (!note.reverbSend) {
1670
1673
  if (0 < value) {
1671
- note.reverbEffectsSend.gain
1672
- .cancelScheduledValues(scheduleTime)
1673
- .setValueAtTime(value, scheduleTime);
1674
- }
1675
- else {
1676
- note.reverbEffectsSend.disconnect();
1674
+ note.reverbSend = new GainNode(this.audioContext, { gain: value });
1675
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1676
+ note.reverbSend.connect(this.reverbEffect.input);
1677
1677
  }
1678
1678
  }
1679
1679
  else {
1680
+ note.reverbSend.gain
1681
+ .cancelScheduledValues(scheduleTime)
1682
+ .setValueAtTime(value, scheduleTime);
1680
1683
  if (0 < value) {
1681
- if (!note.reverbEffectsSend) {
1682
- note.reverbEffectsSend = new GainNode(this.audioContext, {
1683
- gain: value,
1684
- });
1685
- note.volumeNode.connect(note.reverbEffectsSend);
1684
+ note.volumeEnvelopeNode.connect(note.reverbSend);
1685
+ }
1686
+ else {
1687
+ try {
1688
+ note.volumeEnvelopeNode.disconnect(note.reverbSend);
1686
1689
  }
1687
- note.reverbEffectsSend.connect(this.reverbEffect.input);
1690
+ catch { /* empty */ }
1688
1691
  }
1689
1692
  }
1690
1693
  }
1691
- setChorusEffectsSend(channel, note, prevValue, scheduleTime) {
1692
- let value = note.voiceParams.chorusEffectsSend;
1694
+ setChorusSend(channel, note, scheduleTime) {
1695
+ let value = note.voiceParams.chorusEffectsSend *
1696
+ channel.state.chorusSendLevel;
1693
1697
  if (channel.isDrum) {
1694
1698
  const keyBasedValue = this.getKeyBasedValue(channel, note.noteNumber, 93);
1695
- if (0 <= keyBasedValue) {
1696
- value *= keyBasedValue / 127 / channel.state.chorusSendLevel;
1697
- }
1699
+ if (0 <= keyBasedValue)
1700
+ value = keyBasedValue / 127;
1698
1701
  }
1699
- if (0 < prevValue) {
1702
+ if (!note.chorusSend) {
1700
1703
  if (0 < value) {
1701
- note.chorusEffectsSend.gain
1702
- .cancelScheduledValues(scheduleTime)
1703
- .setValueAtTime(value, scheduleTime);
1704
- }
1705
- else {
1706
- note.chorusEffectsSend.disconnect();
1704
+ note.chorusSend = new GainNode(this.audioContext, { gain: value });
1705
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1706
+ note.chorusSend.connect(this.chorusEffect.input);
1707
1707
  }
1708
1708
  }
1709
1709
  else {
1710
+ note.chorusSend.gain
1711
+ .cancelScheduledValues(scheduleTime)
1712
+ .setValueAtTime(value, scheduleTime);
1710
1713
  if (0 < value) {
1711
- if (!note.chorusEffectsSend) {
1712
- note.chorusEffectsSend = new GainNode(this.audioContext, {
1713
- gain: value,
1714
- });
1715
- note.volumeEnvelopeNode.connect(note.chorusEffectsSend);
1714
+ note.volumeEnvelopeNode.connect(note.chorusSend);
1715
+ }
1716
+ else {
1717
+ try {
1718
+ note.volumeEnvelopeNode.disconnect(note.chorusSend);
1716
1719
  }
1717
- note.chorusEffectsSend.connect(this.chorusEffect.input);
1720
+ catch { /* empty */ }
1718
1721
  }
1719
1722
  }
1720
1723
  }
@@ -1863,7 +1866,7 @@ export class MidyGM2 {
1863
1866
  handler.call(this, channelNumber, value, scheduleTime);
1864
1867
  const channel = this.channels[channelNumber];
1865
1868
  this.applyVoiceParams(channel, controllerType + 128, scheduleTime);
1866
- this.applyControlTable(channel, controllerType, scheduleTime);
1869
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
1867
1870
  }
1868
1871
  else {
1869
1872
  console.warn(`Unsupported Control change: controllerType=${controllerType} value=${value}`);
@@ -1879,7 +1882,6 @@ export class MidyGM2 {
1879
1882
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1880
1883
  }
1881
1884
  else {
1882
- this.setPitchEnvelope(note, scheduleTime);
1883
1885
  this.startModulation(channel, note, scheduleTime);
1884
1886
  }
1885
1887
  });
@@ -1924,8 +1926,14 @@ export class MidyGM2 {
1924
1926
  scheduleTime ??= this.audioContext.currentTime;
1925
1927
  const channel = this.channels[channelNumber];
1926
1928
  channel.state.volume = volume / 127;
1927
- this.updateChannelVolume(channel, scheduleTime);
1928
- this.updateKeyBasedVolume(channel, scheduleTime);
1929
+ if (channel.isDrum) {
1930
+ for (let i = 0; i < 128; i++) {
1931
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1932
+ }
1933
+ }
1934
+ else {
1935
+ this.updateChannelVolume(channel, scheduleTime);
1936
+ }
1929
1937
  }
1930
1938
  panToGain(pan) {
1931
1939
  const theta = Math.PI / 2 * Math.max(0, pan * 127 - 1) / 126;
@@ -1938,8 +1946,14 @@ export class MidyGM2 {
1938
1946
  scheduleTime ??= this.audioContext.currentTime;
1939
1947
  const channel = this.channels[channelNumber];
1940
1948
  channel.state.pan = pan / 127;
1941
- this.updateChannelVolume(channel, scheduleTime);
1942
- this.updateKeyBasedVolume(channel, scheduleTime);
1949
+ if (channel.isDrum) {
1950
+ for (let i = 0; i < 128; i++) {
1951
+ this.updateKeyBasedVolume(channel, i, scheduleTime);
1952
+ }
1953
+ }
1954
+ else {
1955
+ this.updateChannelVolume(channel, scheduleTime);
1956
+ }
1943
1957
  }
1944
1958
  setExpression(channelNumber, expression, scheduleTime) {
1945
1959
  scheduleTime ??= this.audioContext.currentTime;
@@ -1965,33 +1979,27 @@ export class MidyGM2 {
1965
1979
  .cancelScheduledValues(scheduleTime)
1966
1980
  .setValueAtTime(volume * gainRight, scheduleTime);
1967
1981
  }
1968
- updateKeyBasedVolume(channel, scheduleTime) {
1969
- if (!channel.isDrum)
1970
- return;
1982
+ updateKeyBasedVolume(channel, keyNumber, scheduleTime) {
1971
1983
  const state = channel.state;
1972
1984
  const defaultVolume = state.volume * state.expression;
1973
1985
  const defaultPan = state.pan;
1974
- for (let i = 0; i < 128; i++) {
1975
- const gainL = channel.keyBasedGainLs[i];
1976
- const gainR = channel.keyBasedGainLs[i];
1977
- if (!gainL)
1978
- continue;
1979
- if (!gainR)
1980
- continue;
1981
- const keyBasedVolume = this.getKeyBasedValue(channel, i, 7);
1982
- const volume = (0 <= keyBasedVolume)
1983
- ? defaultVolume * keyBasedVolume / 64
1984
- : defaultVolume;
1985
- const keyBasedPan = this.getKeyBasedValue(channel, i, 10);
1986
- const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1987
- const { gainLeft, gainRight } = this.panToGain(pan);
1988
- gainL.gain
1989
- .cancelScheduledValues(scheduleTime)
1990
- .setValueAtTime(volume * gainLeft, scheduleTime);
1991
- gainR.gain
1992
- .cancelScheduledValues(scheduleTime)
1993
- .setValueAtTime(volume * gainRight, scheduleTime);
1994
- }
1986
+ const gainL = channel.keyBasedGainLs[keyNumber];
1987
+ const gainR = channel.keyBasedGainRs[keyNumber];
1988
+ if (!gainL)
1989
+ return;
1990
+ const keyBasedVolume = this.getKeyBasedValue(channel, keyNumber, 7);
1991
+ const volume = (0 <= keyBasedVolume)
1992
+ ? defaultVolume * keyBasedVolume / 64
1993
+ : defaultVolume;
1994
+ const keyBasedPan = this.getKeyBasedValue(channel, keyNumber, 10);
1995
+ const pan = (0 <= keyBasedPan) ? keyBasedPan / 127 : defaultPan;
1996
+ const { gainLeft, gainRight } = this.panToGain(pan);
1997
+ gainL.gain
1998
+ .cancelScheduledValues(scheduleTime)
1999
+ .setValueAtTime(volume * gainLeft, scheduleTime);
2000
+ gainR.gain
2001
+ .cancelScheduledValues(scheduleTime)
2002
+ .setValueAtTime(volume * gainRight, scheduleTime);
1995
2003
  }
1996
2004
  setSustainPedal(channelNumber, value, scheduleTime) {
1997
2005
  const channel = this.channels[channelNumber];
@@ -2058,67 +2066,19 @@ export class MidyGM2 {
2058
2066
  scheduleTime ??= this.audioContext.currentTime;
2059
2067
  const channel = this.channels[channelNumber];
2060
2068
  const state = channel.state;
2061
- const reverbEffect = this.reverbEffect;
2062
- if (0 < state.reverbSendLevel) {
2063
- if (0 < reverbSendLevel) {
2064
- state.reverbSendLevel = reverbSendLevel / 127;
2065
- reverbEffect.input.gain
2066
- .cancelScheduledValues(scheduleTime)
2067
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2068
- }
2069
- else {
2070
- this.processScheduledNotes(channel, (note) => {
2071
- if (note.voiceParams.reverbEffectsSend <= 0)
2072
- return false;
2073
- if (note.reverbEffectsSend)
2074
- note.reverbEffectsSend.disconnect();
2075
- });
2076
- }
2077
- }
2078
- else {
2079
- if (0 < reverbSendLevel) {
2080
- this.processScheduledNotes(channel, (note) => {
2081
- this.setReverbEffectsSend(channel, note, 0, scheduleTime);
2082
- });
2083
- state.reverbSendLevel = reverbSendLevel / 127;
2084
- reverbEffect.input.gain
2085
- .cancelScheduledValues(scheduleTime)
2086
- .setValueAtTime(state.reverbSendLevel, scheduleTime);
2087
- }
2088
- }
2069
+ state.reverbSendLevel = reverbSendLevel / 127;
2070
+ this.processScheduledNotes(channel, (note) => {
2071
+ this.setReverbSend(channel, note, scheduleTime);
2072
+ });
2089
2073
  }
2090
2074
  setChorusSendLevel(channelNumber, chorusSendLevel, scheduleTime) {
2091
2075
  scheduleTime ??= this.audioContext.currentTime;
2092
2076
  const channel = this.channels[channelNumber];
2093
2077
  const state = channel.state;
2094
- const chorusEffect = this.chorusEffect;
2095
- if (0 < state.chorusSendLevel) {
2096
- if (0 < chorusSendLevel) {
2097
- state.chorusSendLevel = chorusSendLevel / 127;
2098
- chorusEffect.input.gain
2099
- .cancelScheduledValues(scheduleTime)
2100
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2101
- }
2102
- else {
2103
- this.processScheduledNotes(channel, (note) => {
2104
- if (note.voiceParams.chorusEffectsSend <= 0)
2105
- return false;
2106
- if (note.chorusEffectsSend)
2107
- note.chorusEffectsSend.disconnect();
2108
- });
2109
- }
2110
- }
2111
- else {
2112
- if (0 < chorusSendLevel) {
2113
- this.processScheduledNotes(channel, (note) => {
2114
- this.setChorusEffectsSend(channel, note, 0, scheduleTime);
2115
- });
2116
- state.chorusSendLevel = chorusSendLevel / 127;
2117
- chorusEffect.input.gain
2118
- .cancelScheduledValues(scheduleTime)
2119
- .setValueAtTime(state.chorusSendLevel, scheduleTime);
2120
- }
2121
- }
2078
+ state.chorusSendLevel = chorusSendLevel / 127;
2079
+ this.processScheduledNotes(channel, (note) => {
2080
+ this.setChorusSend(channel, note, scheduleTime);
2081
+ });
2122
2082
  }
2123
2083
  limitData(channel, minMSB, maxMSB, minLSB, maxLSB) {
2124
2084
  if (maxLSB < channel.dataLSB) {
@@ -2400,9 +2360,9 @@ export class MidyGM2 {
2400
2360
  case 9:
2401
2361
  switch (data[3]) {
2402
2362
  case 1: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2403
- return this.handlePressureSysEx(data, "channelPressureTable");
2363
+ return this.handlePressureSysEx(data, "channelPressureTable", scheduleTime);
2404
2364
  case 3: // https://amei.or.jp/midistandardcommittee/Recommended_Practice/e/ca22.pdf
2405
- return this.handleControlChangeSysEx(data);
2365
+ return this.handleControlChangeSysEx(data, scheduleTime);
2406
2366
  default:
2407
2367
  console.warn(`Unsupported Exclusive Message: ${data}`);
2408
2368
  }
@@ -2721,9 +2681,9 @@ export class MidyGM2 {
2721
2681
  : 0;
2722
2682
  return channelPressure / 127;
2723
2683
  }
2724
- setControllerParameters(channel, note, table, scheduleTime) {
2684
+ setEffects(channel, note, table, scheduleTime) {
2725
2685
  if (0 <= table[0])
2726
- this.updateDetune(channel, note, scueduleTime);
2686
+ this.updateDetune(channel, note, scheduleTime);
2727
2687
  if (0.5 <= channel.state.portamemento && 0 <= note.portamentoNoteNumber) {
2728
2688
  if (0 <= table[1]) {
2729
2689
  this.setPortamentoFilterEnvelope(channel, note, scheduleTime);
@@ -2745,7 +2705,7 @@ export class MidyGM2 {
2745
2705
  if (0 <= table[5])
2746
2706
  this.setModLfoToVolume(channel, note, scheduleTime);
2747
2707
  }
2748
- handlePressureSysEx(data, tableName) {
2708
+ handlePressureSysEx(data, tableName, scheduleTime) {
2749
2709
  const channelNumber = data[4];
2750
2710
  const channel = this.channels[channelNumber];
2751
2711
  if (channel.isDrum)
@@ -2756,32 +2716,38 @@ export class MidyGM2 {
2756
2716
  const rr = data[i + 1];
2757
2717
  table[pp] = rr;
2758
2718
  }
2719
+ this.processActiveNotes(channel, scheduleTime, (note) => {
2720
+ this.setEffects(channel, note, table, scheduleTime);
2721
+ });
2759
2722
  }
2760
2723
  initControlTable() {
2761
2724
  const ccCount = 128;
2762
2725
  const slotSize = 6;
2763
2726
  return new Int8Array(ccCount * slotSize).fill(-1);
2764
2727
  }
2765
- applyControlTable(channel, controllerType, scheduleTime) {
2728
+ setControlChangeEffects(channel, controllerType, scheduleTime) {
2766
2729
  const slotSize = 6;
2767
2730
  const offset = controllerType * slotSize;
2768
2731
  const table = channel.controlTable.subarray(offset, offset + slotSize);
2769
2732
  this.processScheduledNotes(channel, (note) => {
2770
- this.setControllerParameters(channel, note, table, scheduleTime);
2733
+ this.setEffects(channel, note, table, scheduleTime);
2771
2734
  });
2772
2735
  }
2773
- handleControlChangeSysEx(data) {
2736
+ handleControlChangeSysEx(data, scheduleTime) {
2774
2737
  const channelNumber = data[4];
2775
2738
  const channel = this.channels[channelNumber];
2776
2739
  if (channel.isDrum)
2777
2740
  return;
2741
+ const slotSize = 6;
2778
2742
  const controllerType = data[5];
2779
- const table = channel.controlTable[controllerType];
2780
- for (let i = 6; i < data.length - 1; i += 2) {
2743
+ const offset = controllerType * slotSize;
2744
+ const table = channel.controlTable;
2745
+ for (let i = 6; i < data.length; i += 2) {
2781
2746
  const pp = data[i];
2782
2747
  const rr = data[i + 1];
2783
- table[pp] = rr;
2748
+ table[offset + pp] = rr;
2784
2749
  }
2750
+ this.setControlChangeEffects(channel, controllerType, scheduleTime);
2785
2751
  }
2786
2752
  getKeyBasedValue(channel, keyNumber, controllerType) {
2787
2753
  const index = keyNumber * 128 + controllerType;
@@ -2795,13 +2761,20 @@ export class MidyGM2 {
2795
2761
  return;
2796
2762
  const keyNumber = data[5];
2797
2763
  const table = channel.keyBasedInstrumentControlTable;
2798
- for (let i = 6; i < data.length - 1; i += 2) {
2764
+ for (let i = 6; i < data.length; i += 2) {
2799
2765
  const controllerType = data[i];
2800
2766
  const value = data[i + 1];
2801
2767
  const index = keyNumber * 128 + controllerType;
2802
2768
  table[index] = value;
2769
+ switch (controllerType) {
2770
+ case 7:
2771
+ case 10:
2772
+ this.updateKeyBasedVolume(channel, keyNumber, scheduleTime);
2773
+ break;
2774
+ default: // TODO
2775
+ this.setControlChange(channelNumber, controllerType, value, scheduleTime);
2776
+ }
2803
2777
  }
2804
- this.setChannelPressure(channelNumber, channel.state.channelPressure * 127, scheduleTime);
2805
2778
  }
2806
2779
  handleSysEx(data, scheduleTime) {
2807
2780
  switch (data[0]) {
@@ -30,8 +30,8 @@ export class MidyGMLite {
30
30
  isStopping: boolean;
31
31
  isSeeking: boolean;
32
32
  timeline: any[];
33
- instruments: any[];
34
33
  notePromises: any[];
34
+ instruments: Set<any>;
35
35
  exclusiveClassNotes: any[];
36
36
  drumExclusiveClassNotes: any[];
37
37
  audioContext: any;
@@ -58,7 +58,7 @@ export class MidyGMLite {
58
58
  loadSoundFont(input: any): Promise<void>;
59
59
  loadMIDI(input: any): Promise<void>;
60
60
  cacheVoiceIds(): void;
61
- getVoiceId(channel: any, noteNumber: any, velocity: any): string | undefined;
61
+ getVoiceId(channel: any, noteNumber: any, velocity: any): any;
62
62
  createChannelAudioNodes(audioContext: any): {
63
63
  gainL: any;
64
64
  gainR: any;
@@ -1 +1 @@
1
- {"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AA0GA;IA2BE;;;;;;;;;;MAUE;IAEF,+BAcC;IApDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,6EAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEASC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,6FAyBC;IAED,oGAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;IAED,qCAeC;IAED,+FAWC;IAED,wDAUC;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,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAt/CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,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":"AA0GA;IA2BE;;;;;;;;;;MAUE;IAEF,+BAcC;IApDD,aAAa;IACb,oBAAiB;IACjB,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,4BAAyB;IACzB,0BAAuB;IACvB,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,oBAAkB;IAClB,sBAAwB;IACxB,2BAAqC;IACrC,+BAEE;IAeA,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D,6BAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCASC;IAED,2DAYC;IAED,yCAmBC;IAED,oCASC;IAED,sBAoCC;IAED,8DAcC;IAED;;;;MAeC;IAED,yCAaC;IAED,kDAUC;IAED,0EAUC;IAED,+EAkDC;IAED,mCAOC;IAED,0BAiEC;IAED,uDAEC;IAED,wDAEC;IAED;;;MAgEC;IAED,kGAeC;IAED,mGAeC;IAED,wEAMC;IAED,uBAMC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,yEASC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,6FAyBC;IAED,oGAuCC;IAED,0EAiBC;IAED,8EAiBC;IAED,kGAoCC;IAED,6FASC;IAED,gCASC;IAED,iEAoBC;IAED,qGAkBC;IAED,6CAUC;IAED,qDAUC;IAED,qFASC;IAED,sFAeC;IAED,oGA2BC;IAED,mFAGC;IAED,wFAGC;IAED,sEAUC;IAED,mEAWC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EAgCC;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,yCAqBC;IAGD,8EAgCC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAGD,6DAgBC;CACF;AAx/CD;IAWE,0FAMC;IAhBD,cAAW;IACX,gBAAe;IACf,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
@@ -263,17 +263,17 @@ export class MidyGMLite {
263
263
  writable: true,
264
264
  value: []
265
265
  });
266
- Object.defineProperty(this, "instruments", {
266
+ Object.defineProperty(this, "notePromises", {
267
267
  enumerable: true,
268
268
  configurable: true,
269
269
  writable: true,
270
270
  value: []
271
271
  });
272
- Object.defineProperty(this, "notePromises", {
272
+ Object.defineProperty(this, "instruments", {
273
273
  enumerable: true,
274
274
  configurable: true,
275
275
  writable: true,
276
- value: []
276
+ value: new Set()
277
277
  });
278
278
  Object.defineProperty(this, "exclusiveClassNotes", {
279
279
  enumerable: true,
@@ -401,7 +401,7 @@ export class MidyGMLite {
401
401
  const soundFont = this.soundFonts[soundFontIndex];
402
402
  const voice = soundFont.getVoice(bankNumber, channel.programNumber, noteNumber, velocity);
403
403
  const { instrument, sampleID } = voice.generators;
404
- return `${soundFontIndex}:${instrument}:${sampleID}`;
404
+ return soundFontIndex * (2 ** 32) + (instrument << 16) + sampleID;
405
405
  }
406
406
  createChannelAudioNodes(audioContext) {
407
407
  const { gainLeft, gainRight } = this.panToGain(defaultControllerState.pan.defaultValue);
@@ -1055,13 +1055,17 @@ export class MidyGMLite {
1055
1055
  this.applyVoiceParams(channel, 14, scheduleTime);
1056
1056
  }
1057
1057
  setModLfoToPitch(channel, note, scheduleTime) {
1058
- const modLfoToPitch = note.voiceParams.modLfoToPitch;
1059
- const baseDepth = Math.abs(modLfoToPitch) +
1060
- channel.state.modulationDepth;
1061
- const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1062
- note.modulationDepth.gain
1063
- .cancelScheduledValues(scheduleTime)
1064
- .setValueAtTime(modulationDepth, scheduleTime);
1058
+ if (note.modulationDepth) {
1059
+ const modLfoToPitch = note.voiceParams.modLfoToPitch;
1060
+ const baseDepth = Math.abs(modLfoToPitch) + channel.state.modulationDepth;
1061
+ const modulationDepth = baseDepth * Math.sign(modLfoToPitch);
1062
+ note.modulationDepth.gain
1063
+ .cancelScheduledValues(scheduleTime)
1064
+ .setValueAtTime(modulationDepth, scheduleTime);
1065
+ }
1066
+ else {
1067
+ this.startModulation(channel, note, scheduleTime);
1068
+ }
1065
1069
  }
1066
1070
  setModLfoToFilterFc(note, scheduleTime) {
1067
1071
  const modLfoToFilterFc = note.voiceParams.modLfoToFilterFc;
@@ -1190,7 +1194,6 @@ export class MidyGMLite {
1190
1194
  note.modulationDepth.gain.setValueAtTime(depth, scheduleTime);
1191
1195
  }
1192
1196
  else {
1193
- this.setPitchEnvelope(note, scheduleTime);
1194
1197
  this.startModulation(channel, note, scheduleTime);
1195
1198
  }
1196
1199
  });