spessasynth_core 4.3.6 → 4.3.7

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/dist/index.js CHANGED
@@ -1124,7 +1124,11 @@ const NonRegisteredLSB = {
1124
1124
  };
1125
1125
  //#endregion
1126
1126
  //#region src/midi/midi_message.ts
1127
- var MIDIMessage = class {
1127
+ /**
1128
+ * Midi_message.ts
1129
+ * purpose: contains enums for midi events and controllers and functions to parse them
1130
+ */
1131
+ var MIDIMessage = class MIDIMessage {
1128
1132
  /**
1129
1133
  * Absolute number of MIDI ticks from the start of the track.
1130
1134
  */
@@ -1148,6 +1152,68 @@ var MIDIMessage = class {
1148
1152
  this.statusByte = byte;
1149
1153
  this.data = data;
1150
1154
  }
1155
+ /**
1156
+ * Returns a new MIDI Pitch Wheel message.
1157
+ * @param ticks time of this message in absolute MIDI ticks.
1158
+ * @param channel the channel number of this message.
1159
+ * @param value the new value, between 0 and 16383, where 8192 is the center (no pitch change).
1160
+ */
1161
+ static pitchWheel(ticks, channel, value) {
1162
+ return new MIDIMessage(ticks, MIDIMessageTypes.pitchWheel | channel % 16, new Uint8Array([value & 127, value >> 7 & 127]));
1163
+ }
1164
+ /**
1165
+ * Returns a new MIDI Channel Pressure message.
1166
+ * @param ticks time of this message in absolute MIDI ticks.
1167
+ * @param channel the channel number of this message.
1168
+ * @param value the new value, between 0 and 127.
1169
+ */
1170
+ static channelPressure(ticks, channel, value) {
1171
+ return new MIDIMessage(ticks, MIDIMessageTypes.channelPressure | channel % 16, new Uint8Array([value]));
1172
+ }
1173
+ /**
1174
+ * Returns a new MIDI Program Change message.
1175
+ * @param ticks time of this message in absolute MIDI ticks.
1176
+ * @param channel the channel number of this message.
1177
+ * @param program the new MIDI program number, between 0 and 127.
1178
+ */
1179
+ static programChange(ticks, channel, program) {
1180
+ return new MIDIMessage(ticks, MIDIMessageTypes.programChange | channel % 16, new Uint8Array([program]));
1181
+ }
1182
+ /**
1183
+ * Returns a new MIDI Controller Change message.
1184
+ * @param ticks time of this message in absolute MIDI ticks.
1185
+ * @param channel the channel number of this message.
1186
+ * @param controller the MIDI controller.
1187
+ * @param value the new value.
1188
+ */
1189
+ static controllerChange(ticks, channel, controller, value) {
1190
+ return new MIDIMessage(ticks, MIDIMessageTypes.controllerChange | channel % 16, new Uint8Array([controller, value]));
1191
+ }
1192
+ /**
1193
+ * Returns a new MIDI System Exclusive message.
1194
+ * @param ticks time of this message in absolute MIDI ticks.
1195
+ * @param data the data of the system exclusive message,
1196
+ * excluding the starting 0xF0 byte.
1197
+ */
1198
+ static systemExclusive(ticks, data) {
1199
+ return new MIDIMessage(ticks, MIDIMessageTypes.systemExclusive, new Uint8Array(data));
1200
+ }
1201
+ /**
1202
+ * Returns a new MIDI Registered Parameter message. Sends both data MSB and LSB.
1203
+ * @param ticks time of this message in absolute MIDI ticks.
1204
+ * @param channel the channel number of this message.
1205
+ * @param parameter the 14-bit MIDI registered parameter number.
1206
+ * @param value the 14-bit new value.
1207
+ */
1208
+ static registeredParameter(ticks, channel, parameter, value) {
1209
+ if (parameter > 16383 || parameter < 0 || value > 16383 || value < 0) throw new Error("Parameter and value must be between 0 and 16383.");
1210
+ return [
1211
+ MIDIMessage.controllerChange(ticks, channel, MIDIControllers.registeredParameterMSB, parameter >> 7),
1212
+ MIDIMessage.controllerChange(ticks, channel, MIDIControllers.registeredParameterLSB, parameter & 127),
1213
+ MIDIMessage.controllerChange(ticks, channel, MIDIControllers.dataEntryMSB, value >> 7),
1214
+ MIDIMessage.controllerChange(ticks, channel, MIDIControllers.dataEntryLSB, value & 127)
1215
+ ];
1216
+ }
1151
1217
  };
1152
1218
  //#endregion
1153
1219
  //#region src/midi/write/midi.ts
@@ -1494,16 +1560,30 @@ var MIDIUtils = class MIDIUtils {
1494
1560
  static analyzeRPN(channel, rpn, value) {
1495
1561
  switch (rpn) {
1496
1562
  default: return OTHER;
1563
+ case RegisteredParameterTypes.pitchWheelRange: return {
1564
+ type: "Channel MIDI Param",
1565
+ channel,
1566
+ parameter: "pitchWheelRange",
1567
+ value: value / 128
1568
+ };
1497
1569
  case RegisteredParameterTypes.fineTuning: return {
1498
- type: "Fine Tune",
1570
+ type: "Channel MIDI Param",
1499
1571
  channel,
1572
+ parameter: "fineTune",
1500
1573
  value: (value - 8192) / 81.92
1501
1574
  };
1502
1575
  case RegisteredParameterTypes.coarseTuning: return {
1503
- type: "Key Shift",
1576
+ type: "Channel MIDI Param",
1504
1577
  channel,
1578
+ parameter: "keyShift",
1505
1579
  value: (value >> 7) - 64
1506
1580
  };
1581
+ case RegisteredParameterTypes.modulationDepth: return {
1582
+ type: "Channel MIDI Param",
1583
+ channel,
1584
+ parameter: "modulationDepth",
1585
+ value: value / 1.28
1586
+ };
1507
1587
  }
1508
1588
  }
1509
1589
  /**
@@ -1579,6 +1659,104 @@ var MIDIUtils = class MIDIUtils {
1579
1659
  }
1580
1660
  }
1581
1661
  /**
1662
+ * Returns a list of MIDI events needed to set the given parameter.
1663
+ * @param ticks The ticks for all events.
1664
+ * @param system If the message has multiple ways of setting it,
1665
+ * this selects the preferred way. Otherwise, it prefers Universal (GM).
1666
+ * @param parameter The parameter to set.
1667
+ * @param value The value to set it to.
1668
+ */
1669
+ static setGlobalMIDIParameter(ticks, system, parameter, value) {
1670
+ switch (parameter) {
1671
+ case "system": return [MIDIUtils.reset(ticks, value)];
1672
+ case "keyShift": switch (system) {
1673
+ default: return [MIDIUtils.deviceControlMessage(ticks, 4, [0, value + 64])];
1674
+ case "xg": return [MIDIUtils.xgMessage(ticks, 0, 0, 6, [value + 64])];
1675
+ case "gs": return [MIDIUtils.gsMessage(ticks, 64, 0, 5, [value + 64])];
1676
+ }
1677
+ case "fineTune": switch (system) {
1678
+ default: {
1679
+ const tuneValue = Math.floor(value * 81.92 + 8192);
1680
+ return [MIDIUtils.deviceControlMessage(ticks, 3, [tuneValue & 127, tuneValue >> 7 & 127])];
1681
+ }
1682
+ case "xg": {
1683
+ const tuneValue = Math.floor(value * 10 + 1024);
1684
+ return [MIDIUtils.xgMessage(ticks, 0, 0, 0, [
1685
+ tuneValue >> 12 & 15,
1686
+ tuneValue >> 8 & 15,
1687
+ tuneValue >> 4 & 15,
1688
+ tuneValue & 15
1689
+ ])];
1690
+ }
1691
+ case "gs": {
1692
+ const tuneValue = Math.floor(value * 10 + 1024);
1693
+ return [MIDIUtils.gsMessage(ticks, 64, 0, 0, [
1694
+ tuneValue >> 12 & 15,
1695
+ tuneValue >> 8 & 15,
1696
+ tuneValue >> 4 & 15,
1697
+ tuneValue & 15
1698
+ ])];
1699
+ }
1700
+ }
1701
+ case "gain": switch (system) {
1702
+ default: {
1703
+ const gainValue = Math.floor(Math.sqrt(value) * 16383);
1704
+ return [MIDIUtils.deviceControlMessage(ticks, 1, [gainValue & 127, gainValue >> 7 & 127])];
1705
+ }
1706
+ case "xg": {
1707
+ const gainValue = Math.floor(value * 127);
1708
+ return [MIDIUtils.xgMessage(ticks, 0, 0, 4, [gainValue])];
1709
+ }
1710
+ case "gs": {
1711
+ const gainValue = Math.floor(value * 127);
1712
+ return [MIDIUtils.gsMessage(ticks, 64, 0, 4, [gainValue])];
1713
+ }
1714
+ }
1715
+ case "pan": switch (system) {
1716
+ default: {
1717
+ const balance = Math.floor(value * 8192) + 8192;
1718
+ return [MIDIUtils.deviceControlMessage(ticks, 2, [balance & 127, balance >> 7 & 127])];
1719
+ }
1720
+ case "gs": {
1721
+ const balance = Math.floor(value * 63) + 64;
1722
+ return [MIDIUtils.gsMessage(ticks, 64, 0, 6, [balance])];
1723
+ }
1724
+ }
1725
+ }
1726
+ }
1727
+ /**
1728
+ * Returns a list of MIDI events needed to set the given parameter.
1729
+ * @param ticks The ticks for all events.
1730
+ * @param channel The channel number.
1731
+ * @param system If the message has multiple ways of setting it,
1732
+ * this selects the preferred way. Otherwise, it prefers Universal (GM).
1733
+ * @param parameter The parameter to set.
1734
+ * @param value The value to set it to.
1735
+ * @returns The list of `MIDIMessage`s that set the parameter.
1736
+ */
1737
+ static setChannelMIDIParameter(ticks, channel, system, parameter, value) {
1738
+ channel %= 16;
1739
+ const gsChannel = MIDIUtils.channelToSyx(channel);
1740
+ switch (parameter) {
1741
+ case "pressure": return [MIDIMessage.channelPressure(ticks, channel, value)];
1742
+ case "pitchWheel": return [MIDIMessage.pitchWheel(ticks, channel, value)];
1743
+ case "pitchWheelRange": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.pitchWheelRange, Math.floor(value * 128));
1744
+ case "modulationDepth": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.modulationDepth, Math.floor(value * 1.28));
1745
+ case "rxChannel": return system === "xg" ? [MIDIUtils.xgMessage(ticks, 8, channel, 4, [value])] : [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 2, [value])];
1746
+ case "polyMode": return value ? [MIDIMessage.controllerChange(ticks, channel, MIDIControllers.polyModeOn, 0)] : [MIDIMessage.controllerChange(ticks, channel, MIDIControllers.monoModeOn, 0)];
1747
+ case "keyShift": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.coarseTuning, value + 64 << 7);
1748
+ case "fineTune": return MIDIMessage.registeredParameter(ticks, channel, RegisteredParameterTypes.fineTuning, Math.floor(value * 81.92 + 8192));
1749
+ case "randomPan": return system === "xg" ? [MIDIUtils.xgMessage(ticks, 8, channel, 14, [0])] : [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 28, [0])];
1750
+ case "assignMode": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 20, [value])];
1751
+ case "efxAssign": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 34, [value])];
1752
+ case "cc1": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 31, [value])];
1753
+ case "cc2": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 32, [value])];
1754
+ case "drumMap": return [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 21, [value])];
1755
+ case "velocitySenseDepth": return system === "xg" ? [MIDIUtils.xgMessage(ticks, 8, channel, 12, [value])] : [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 26, [value])];
1756
+ case "velocitySenseOffset": return system === "xg" ? [MIDIUtils.xgMessage(ticks, 8, channel, 13, [value])] : [MIDIUtils.gsMessage(ticks, 64, 16 | gsChannel, 27, [value])];
1757
+ }
1758
+ }
1759
+ /**
1582
1760
  * Converts GS/XG "part number" to MIDI channel number.
1583
1761
  * @param part The part number.
1584
1762
  */
@@ -1633,7 +1811,7 @@ var MIDIUtils = class MIDIUtils {
1633
1811
  * @param a3 Address 3
1634
1812
  * @param data Data, can be multiple bytes.
1635
1813
  */
1636
- static gsData(a1, a2, a3, data) {
1814
+ static gs(a1, a2, a3, data) {
1637
1815
  const checksum = 128 - (a1 + a2 + a3 + data.reduce((sum, cur) => sum + cur, 0)) % 128 & 127;
1638
1816
  return [
1639
1817
  65,
@@ -1657,36 +1835,112 @@ var MIDIUtils = class MIDIUtils {
1657
1835
  * @param data Data, can be multiple bytes.
1658
1836
  */
1659
1837
  static gsMessage(ticks, a1, a2, a3, data) {
1660
- return new MIDIMessage(ticks, MIDIMessageTypes.systemExclusive, new Uint8Array(this.gsData(a1, a2, a3, data)));
1838
+ return MIDIMessage.systemExclusive(ticks, this.gs(a1, a2, a3, data));
1839
+ }
1840
+ /**
1841
+ * Gets raw XG System Exclusive message bytes, without the 0xF0 status byte.
1842
+ * @param a1 Address 1
1843
+ * @param a2 Address 2
1844
+ * @param a3 Address 3
1845
+ * @param data Data, can be multiple bytes.
1846
+ */
1847
+ static xg(a1, a2, a3, data) {
1848
+ return [
1849
+ 67,
1850
+ 16,
1851
+ 76,
1852
+ a1,
1853
+ a2,
1854
+ a3,
1855
+ ...data,
1856
+ 247
1857
+ ];
1661
1858
  }
1662
1859
  /**
1663
- * Gets a GS reset message System Exclusive MIDI message.
1860
+ * Gets a XG System Exclusive MIDI message.
1664
1861
  * @param ticks The tick time of the message.
1665
- * @param channel The MIDI channel number.
1666
- * @param drumMap The drum map to use. 0 turns the channel into a melodic channel,
1667
- * while other values turn it into a drum channel.
1862
+ * @param a1 Address 1
1863
+ * @param a2 Address 2
1864
+ * @param a3 Address 3
1865
+ * @param data Data, can be multiple bytes.
1668
1866
  */
1669
- static gsDrumChange(ticks, channel, drumMap) {
1670
- const chanAddress = 16 | this.channelToSyx(channel);
1671
- return this.gsMessage(ticks, 40, chanAddress, 21, [drumMap]);
1867
+ static xgMessage(ticks, a1, a2, a3, data) {
1868
+ return MIDIMessage.systemExclusive(ticks, this.xg(a1, a2, a3, data));
1672
1869
  }
1673
1870
  /**
1674
- * Gets a GS reset message System Exclusive MIDI message.
1871
+ * Gets a raw Device Control System Exclusive message bytes, without the 0xF0 status byte.
1872
+ * @param subID The sub ID.
1873
+ * @param data Data, can be multiple bytes.
1874
+ */
1875
+ static deviceControl(subID, data) {
1876
+ return [
1877
+ 127,
1878
+ 127,
1879
+ 4,
1880
+ subID,
1881
+ ...data,
1882
+ 247
1883
+ ];
1884
+ }
1885
+ /**
1886
+ * Gets a Device Control System Exclusive MIDI message.
1675
1887
  * @param ticks The tick time of the message.
1888
+ * @param subID The sub ID.
1889
+ * @param data Data, can be multiple bytes.
1676
1890
  */
1677
- static gsReset(ticks) {
1678
- return this.gsMessage(ticks, 64, 0, 127, [0]);
1891
+ static deviceControlMessage(ticks, subID, data) {
1892
+ return MIDIMessage.systemExclusive(ticks, this.deviceControl(subID, data));
1893
+ }
1894
+ /**
1895
+ * Gets a selected reset System Exclusive MIDI message.
1896
+ * @param ticks The tick time of the message.
1897
+ * @param system The system to reset into.
1898
+ */
1899
+ static reset(ticks, system) {
1900
+ switch (system) {
1901
+ case "gs": return this.gsMessage(ticks, 64, 0, 127, [0]);
1902
+ case "xg": return this.xgMessage(ticks, 0, 0, 126, [0]);
1903
+ case "gm": return MIDIMessage.systemExclusive(ticks, [
1904
+ 126,
1905
+ 127,
1906
+ 9,
1907
+ 1,
1908
+ 127
1909
+ ]);
1910
+ case "gm2": return MIDIMessage.systemExclusive(ticks, [
1911
+ 126,
1912
+ 127,
1913
+ 9,
1914
+ 3,
1915
+ 127
1916
+ ]);
1917
+ }
1679
1918
  }
1680
1919
  static analyzeGM(syx) {
1681
1920
  if (syx.length < 4) return OTHER;
1682
1921
  if (syx[2] === 4) switch (syx[3]) {
1683
1922
  default: return OTHER;
1923
+ case 1: {
1924
+ const value = (syx[5] << 7 | syx[4]) / 16383;
1925
+ return {
1926
+ type: "Global MIDI Param",
1927
+ parameter: "gain",
1928
+ value: Math.pow(value, 2)
1929
+ };
1930
+ }
1931
+ case 2: return {
1932
+ type: "Global MIDI Param",
1933
+ parameter: "pan",
1934
+ value: ((syx[5] << 7 | syx[4]) - 8192) / 8192
1935
+ };
1684
1936
  case 3: return {
1685
- type: "Master Fine Tune",
1686
- value: ((syx[5] << 7 | syx[6]) - 8192) / 81.92
1937
+ type: "Global MIDI Param",
1938
+ parameter: "fineTune",
1939
+ value: ((syx[5] << 7 | syx[4]) - 8192) / 81.92
1687
1940
  };
1688
1941
  case 4: return {
1689
- type: "Master Key Shift",
1942
+ type: "Global MIDI Param",
1943
+ parameter: "keyShift",
1690
1944
  value: syx[5] - 64
1691
1945
  };
1692
1946
  case 5:
@@ -1710,9 +1964,21 @@ var MIDIUtils = class MIDIUtils {
1710
1964
  if (syx[2] !== 9) return OTHER;
1711
1965
  switch (syx[3]) {
1712
1966
  default: return OTHER;
1713
- case 1: return { type: "GM On" };
1714
- case 2: return { type: "GM Off" };
1715
- case 3: return { type: "GM2 On" };
1967
+ case 1: return {
1968
+ type: "Global MIDI Param",
1969
+ parameter: "system",
1970
+ value: "gm"
1971
+ };
1972
+ case 2: return {
1973
+ type: "Global MIDI Param",
1974
+ parameter: "system",
1975
+ value: "gm"
1976
+ };
1977
+ case 3: return {
1978
+ type: "Global MIDI Param",
1979
+ parameter: "system",
1980
+ value: "gm2"
1981
+ };
1716
1982
  }
1717
1983
  }
1718
1984
  static analyzeXG(syx) {
@@ -1721,18 +1987,25 @@ var MIDIUtils = class MIDIUtils {
1721
1987
  const a2 = syx[4];
1722
1988
  const a3 = syx[5];
1723
1989
  const data = syx[6];
1990
+ if (a1 === 6 || a1 === 7) return { type: "Display Data" };
1724
1991
  if (a1 === 0 && a2 === 0) switch (a3) {
1725
1992
  default: return OTHER;
1726
1993
  case 0: return {
1727
- type: "Master Fine Tune",
1994
+ type: "Global MIDI Param",
1995
+ parameter: "fineTune",
1728
1996
  value: (((syx[6] & 15) << 12 | (syx[7] & 15) << 8 | (syx[8] & 15) << 4 | syx[9] & 15) - 1024) / 10
1729
1997
  };
1730
1998
  case 6: return {
1731
- type: "Master Key Shift",
1999
+ type: "Global MIDI Param",
2000
+ parameter: "keyShift",
1732
2001
  value: data - 64
1733
2002
  };
1734
2003
  case 126:
1735
- case 127: return { type: "XG Reset" };
2004
+ case 127: return {
2005
+ type: "Global MIDI Param",
2006
+ parameter: "system",
2007
+ value: "xg"
2008
+ };
1736
2009
  }
1737
2010
  if (a1 === 2 && a2 === 1) {
1738
2011
  if (a3 <= 21) return { type: "Reverb Param" };
@@ -1774,8 +2047,9 @@ var MIDIUtils = class MIDIUtils {
1774
2047
  isDrum: data > 0
1775
2048
  };
1776
2049
  case 8: return {
1777
- type: "Key Shift",
2050
+ type: "Channel MIDI Param",
1778
2051
  channel,
2052
+ parameter: "keyShift",
1779
2053
  value: data - 64
1780
2054
  };
1781
2055
  case 11: return {
@@ -1856,27 +2130,54 @@ var MIDIUtils = class MIDIUtils {
1856
2130
  return OTHER;
1857
2131
  }
1858
2132
  static analyzeGS(syx) {
1859
- if (syx.length < 10 || syx[2] !== 66 || syx[3] !== 18) return OTHER;
2133
+ if (syx.length < 10 || syx[3] !== 18) return OTHER;
2134
+ if (syx[2] === 69) return { type: "Display Data" };
2135
+ if (syx[2] !== 66) return OTHER;
1860
2136
  const a1 = syx[4];
1861
2137
  const a2 = syx[5];
1862
2138
  const a3 = syx[6];
1863
2139
  const data = syx[7];
1864
2140
  if ((a1 === 0 || a1 === 64) && a2 === 0) switch (a3) {
2141
+ case 0: return {
2142
+ type: "Global MIDI Param",
2143
+ parameter: "fineTune",
2144
+ value: ((data << 12 | syx[8] << 8 | syx[9] << 4 | syx[10]) - 1024) / 10
2145
+ };
2146
+ case 4: return {
2147
+ type: "Global MIDI Param",
2148
+ parameter: "gain",
2149
+ value: data / 127
2150
+ };
2151
+ case 5: return {
2152
+ type: "Global MIDI Param",
2153
+ parameter: "keyShift",
2154
+ value: data - 64
2155
+ };
2156
+ case 6: return {
2157
+ type: "Global MIDI Param",
2158
+ parameter: "pan",
2159
+ value: (data - 64) / 63
2160
+ };
1865
2161
  case 127:
1866
2162
  switch (data) {
1867
- case 0: return { type: "GS Reset" };
1868
- case 127: return { type: "GM On" };
2163
+ case 0: return {
2164
+ type: "Global MIDI Param",
2165
+ parameter: "system",
2166
+ value: "gs"
2167
+ };
2168
+ case 127: return {
2169
+ type: "Global MIDI Param",
2170
+ parameter: "system",
2171
+ value: "gm"
2172
+ };
1869
2173
  }
1870
2174
  return OTHER;
1871
- case 0: return {
1872
- type: "Master Fine Tune",
1873
- value: ((data << 12 | syx[8] << 8 | syx[9] << 4 | syx[10]) - 1024) / 10
1874
- };
1875
2175
  }
1876
2176
  if (a1 === 65) return { type: "Drum Setup" };
1877
2177
  if (a1 !== 64) return OTHER;
1878
2178
  if (a2 === 0 && a3 === 5) return {
1879
- type: "Master Key Shift",
2179
+ type: "Global MIDI Param",
2180
+ parameter: "keyShift",
1880
2181
  value: data - 64
1881
2182
  };
1882
2183
  if (a2 === 1) {
@@ -1895,10 +2196,16 @@ var MIDIUtils = class MIDIUtils {
1895
2196
  value: data
1896
2197
  };
1897
2198
  case 19: return {
1898
- type: "Controller Change",
2199
+ type: "Channel MIDI Param",
1899
2200
  channel,
1900
- controller: data === 1 ? MIDIControllers.polyModeOn : MIDIControllers.monoModeOn,
1901
- value: 0
2201
+ parameter: "polyMode",
2202
+ value: data === 1
2203
+ };
2204
+ case 20: return {
2205
+ type: "Channel MIDI Param",
2206
+ channel,
2207
+ parameter: "assignMode",
2208
+ value: data
1902
2209
  };
1903
2210
  case 21: return {
1904
2211
  type: "Drums On",
@@ -1906,8 +2213,9 @@ var MIDIUtils = class MIDIUtils {
1906
2213
  isDrum: data > 0
1907
2214
  };
1908
2215
  case 22: return {
1909
- type: "Key Shift",
2216
+ type: "Channel MIDI Param",
1910
2217
  channel,
2218
+ parameter: "keyShift",
1911
2219
  value: data - 64
1912
2220
  };
1913
2221
  case 25: return {
@@ -1916,12 +2224,36 @@ var MIDIUtils = class MIDIUtils {
1916
2224
  controller: MIDIControllers.mainVolume,
1917
2225
  value: data
1918
2226
  };
2227
+ case 26: return {
2228
+ type: "Channel MIDI Param",
2229
+ channel,
2230
+ parameter: "velocitySenseDepth",
2231
+ value: data
2232
+ };
2233
+ case 27: return {
2234
+ type: "Channel MIDI Param",
2235
+ channel,
2236
+ parameter: "velocitySenseOffset",
2237
+ value: data
2238
+ };
1919
2239
  case 28: return {
1920
2240
  type: "Controller Change",
1921
2241
  channel,
1922
2242
  controller: MIDIControllers.pan,
1923
2243
  value: data
1924
2244
  };
2245
+ case 31: return {
2246
+ type: "Channel MIDI Param",
2247
+ channel,
2248
+ parameter: "cc1",
2249
+ value: data
2250
+ };
2251
+ case 32: return {
2252
+ type: "Channel MIDI Param",
2253
+ channel,
2254
+ parameter: "cc2",
2255
+ value: data
2256
+ };
1925
2257
  case 33: return {
1926
2258
  type: "Controller Change",
1927
2259
  channel,
@@ -1935,8 +2267,9 @@ var MIDIUtils = class MIDIUtils {
1935
2267
  value: data
1936
2268
  };
1937
2269
  case 42: return {
1938
- type: "Fine Tune",
2270
+ type: "Channel MIDI Param",
1939
2271
  channel,
2272
+ parameter: "fineTune",
1940
2273
  value: ((data << 7 | syx[8]) - 8192) / 81.92
1941
2274
  };
1942
2275
  case 44: return {
@@ -2006,7 +2339,12 @@ var MIDIUtils = class MIDIUtils {
2006
2339
  controller: MIDIControllers.bankSelectLSB,
2007
2340
  value: data
2008
2341
  };
2009
- case 34: return { type: "Insertion Param" };
2342
+ case 34: return {
2343
+ type: "Channel MIDI Param",
2344
+ channel,
2345
+ parameter: "efxAssign",
2346
+ value: data === 1
2347
+ };
2010
2348
  }
2011
2349
  }
2012
2350
  return OTHER;
@@ -2054,26 +2392,18 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2054
2392
  channels[sysexChannel].isDrum = syx.isDrum;
2055
2393
  return;
2056
2394
  }
2057
- case "XG Reset":
2058
- system = "xg";
2059
- return;
2060
- case "GS Reset":
2061
- system = "gs";
2062
- return;
2063
- case "GM Off":
2064
- case "GM On":
2065
- system = "gm";
2066
- unwantedSystems.push({
2067
- tNum: trackNum,
2068
- e
2069
- });
2070
- return;
2071
- case "GM2 On":
2072
- system = "gm2";
2073
- return;
2395
+ case "Global MIDI Param":
2396
+ if (syx.parameter === "system") {
2397
+ system = syx.value;
2398
+ if (syx.value === "gm") unwantedSystems.push({
2399
+ tNum: trackNum,
2400
+ e
2401
+ });
2402
+ }
2403
+ break;
2074
2404
  case "Controller Change": {
2075
2405
  const t = mid.tracks[trackNum];
2076
- const newEvent = new MIDIMessage(e.ticks, MIDIMessageTypes.controllerChange | syx.channel, new Uint8Array([syx.controller, syx.value]));
2406
+ const newEvent = MIDIMessage.controllerChange(e.ticks, syx.channel, syx.controller, syx.value);
2077
2407
  t.events[eventIndexes[trackNum]] = newEvent;
2078
2408
  e = newEvent;
2079
2409
  SpessaLog.info("%cReplaced a system exclusive with controller change!", ConsoleColors.info);
@@ -2081,7 +2411,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2081
2411
  }
2082
2412
  case "Program Change": {
2083
2413
  const t = mid.tracks[trackNum];
2084
- const newEvent = new MIDIMessage(e.ticks, MIDIMessageTypes.programChange | syx.channel, new Uint8Array([syx.value]));
2414
+ const newEvent = MIDIMessage.programChange(e.ticks, syx.channel, syx.value);
2085
2415
  t.events[eventIndexes[trackNum]] = newEvent;
2086
2416
  e = newEvent;
2087
2417
  SpessaLog.info("%cReplaced a system exclusive with program change!", ConsoleColors.info);
@@ -2134,7 +2464,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2134
2464
  program: 0,
2135
2465
  isGMGSDrum: false
2136
2466
  }, system).program;
2137
- track.addEvents(programIndex, new MIDIMessage(programTicks, MIDIMessageTypes.programChange | midiChannel, new IndexedByteArray([targetProgram])));
2467
+ track.addEvents(programIndex, MIDIMessage.programChange(programTicks, midiChannel, targetProgram));
2138
2468
  indexToAdd = programIndex;
2139
2469
  }
2140
2470
  SpessaLog.info(`%cAdding bank select for %c${chNum}`, ConsoleColors.info, ConsoleColors.recognized);
@@ -2146,7 +2476,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2146
2476
  isGMGSDrum: ch.isDrum
2147
2477
  }, system);
2148
2478
  const targetBank = BankSelectHacks.addBankOffset(targetPreset.bankMSB, bankOffset, system === "xg");
2149
- track.addEvents(indexToAdd, new MIDIMessage(ticks, MIDIMessageTypes.controllerChange | midiChannel, new IndexedByteArray([MIDIControllers.bankSelect, targetBank])));
2479
+ track.addEvents(indexToAdd, MIDIMessage.controllerChange(ticks, midiChannel, MIDIControllers.bankSelect, targetBank));
2150
2480
  }
2151
2481
  if (system === "gm" && !BankSelectHacks.isSystemXG(system)) {
2152
2482
  for (const m of unwantedSystems) {
@@ -2155,7 +2485,7 @@ function correctBankOffsetInternal(mid, bankOffset, soundBank) {
2155
2485
  }
2156
2486
  let index = 0;
2157
2487
  if (mid.tracks[0].events[0].statusByte === MIDIMessageTypes.trackName) index++;
2158
- mid.tracks[0].addEvents(index, MIDIUtils.gsReset(0));
2488
+ mid.tracks[0].addEvents(index, MIDIUtils.reset(0, "gs"));
2159
2489
  }
2160
2490
  mid.flush();
2161
2491
  }
@@ -2461,7 +2791,7 @@ function getUsedProgramsAndKeys(mid, soundBank) {
2461
2791
  case MIDIControllers.dataEntryMSB:
2462
2792
  case MIDIControllers.dataEntryLSB: {
2463
2793
  const analyzed = ch.param.controllerChange(cc, value, trackNum, event);
2464
- if (analyzed?.type === "Key Shift") ch.keyShift = ch.isDrum ? 0 : analyzed.value;
2794
+ if (analyzed?.type === "Channel MIDI Param" && analyzed.parameter === "keyShift") ch.keyShift = ch.isDrum ? 0 : analyzed.value;
2465
2795
  break;
2466
2796
  }
2467
2797
  case MIDIControllers.resetAllControllers:
@@ -2498,31 +2828,19 @@ function getUsedProgramsAndKeys(mid, soundBank) {
2498
2828
  const syx = MIDIUtils.analyzeSysEx(e.data);
2499
2829
  switch (syx.type) {
2500
2830
  default: break;
2501
- case "XG Reset":
2502
- reset("xg");
2503
- SpessaLog.info("%cXG on detected!", ConsoleColors.recognized);
2504
- break;
2505
- case "GM2 On":
2506
- reset("gm2");
2507
- SpessaLog.info("%cGM2 on detected!", ConsoleColors.recognized);
2508
- break;
2509
- case "GM On":
2510
- reset("gm");
2511
- SpessaLog.info("%cGM on detected!", ConsoleColors.recognized);
2512
- break;
2513
- case "GM Off":
2514
- case "GS Reset":
2515
- reset("gs");
2516
- SpessaLog.info("%cGS on detected!", ConsoleColors.recognized);
2517
- break;
2518
- case "Master Key Shift":
2519
- masterKeyShift = syx.value;
2831
+ case "Global MIDI Param":
2832
+ if (syx.parameter === "keyShift") masterKeyShift = syx.value;
2833
+ else if (syx.parameter === "system") {
2834
+ reset(syx.value);
2835
+ SpessaLog.info(`%c${syx.value.toUpperCase()} on detected!`, ConsoleColors.recognized);
2836
+ }
2520
2837
  break;
2521
- case "Key Shift": {
2522
- const ch = channels[syx.channel];
2523
- ch.keyShift = ch.isDrum ? 0 : syx.value;
2838
+ case "Channel MIDI Param":
2839
+ if (syx.parameter === "keyShift") {
2840
+ const ch = channels[syx.channel];
2841
+ ch.keyShift = ch.isDrum ? 0 : syx.value;
2842
+ }
2524
2843
  break;
2525
- }
2526
2844
  case "Drums On": {
2527
2845
  const sysexChannel = syx.channel + channelOffset;
2528
2846
  channels[sysexChannel].isDrum = syx.isDrum;
@@ -2662,9 +2980,6 @@ const delayAddressMap = {
2662
2980
  feedback: 89,
2663
2981
  sendLevelToReverb: 90
2664
2982
  };
2665
- function getControllerChange(channel, cc, value, ticks) {
2666
- return new MIDIMessage(ticks, MIDIMessageTypes.controllerChange | channel % 16, new IndexedByteArray([cc, value]));
2667
- }
2668
2983
  /**
2669
2984
  * Allows easy editing of the file by removing channels, changing programs,
2670
2985
  * changing controllers and transposing channels. Note that this modifies the MIDI in-place.
@@ -2683,8 +2998,8 @@ function modifyMIDIInternal(midi, opts) {
2683
2998
  const channelChanges = /* @__PURE__ */ new Map();
2684
2999
  if (channels) for (const [channel, ch] of channels) if (ch === "clear") clearedChannels.add(channel);
2685
3000
  else channelChanges.set(channel, ch);
2686
- let system = "gs";
2687
- let addedGs = false;
3001
+ let system = (opts.midiParams?.system === "clear" ? void 0 : opts.midiParams?.system) ?? "gs";
3002
+ let addedReset = false;
2688
3003
  let resetTrack = 0;
2689
3004
  let resetIndex = 0;
2690
3005
  /**
@@ -2720,7 +3035,9 @@ function modifyMIDIInternal(midi, opts) {
2720
3035
  data: true
2721
3036
  },
2722
3037
  keyShift: channelChanges.get(i)?.keyShift ?? 0,
2723
- fineTune: channelChanges.get(i)?.fineTune ?? 0
3038
+ fineTune: channelChanges.get(i)?.fineTune ?? 0,
3039
+ currentFineTune: 0,
3040
+ currentKeyShift: 0
2724
3041
  });
2725
3042
  midi.iterate((e, trackNum, eventIndexes) => {
2726
3043
  const track = midi.tracks[trackNum];
@@ -2753,10 +3070,18 @@ function modifyMIDIInternal(midi, opts) {
2753
3070
  ch.clearedParams.pLSB = true;
2754
3071
  }
2755
3072
  };
2756
- const addEventBefore = (e, offset = 0) => {
2757
- track.addEvents(index + offset, e);
3073
+ const addEventBefore = (e) => {
3074
+ track.addEvents(index, e);
2758
3075
  eventIndexes[trackNum]++;
2759
3076
  };
3077
+ /**
3078
+ * This function adds the events IN ORDER they are in the array,
3079
+ * So the first event in the array will end up as the first one before the current event.
3080
+ * @param events
3081
+ */
3082
+ const addEventsBefore = (...events) => {
3083
+ for (let i = events.length - 1; i >= 0; i--) addEventBefore(events[i]);
3084
+ };
2760
3085
  const portOffset = midiPortChannelOffsets[midiPorts[trackNum]] || 0;
2761
3086
  if (e.statusByte === MIDIMessageTypes.midiPort) {
2762
3087
  assignMIDIPort(trackNum, e.data[0]);
@@ -2779,17 +3104,17 @@ function modifyMIDIInternal(midi, opts) {
2779
3104
  channelStatus.isFirstNoteOn = false;
2780
3105
  if (channelChange.controllers) for (const [cc, value] of channelChange.controllers) {
2781
3106
  if (value === "clear") continue;
2782
- addEventBefore(getControllerChange(midiChannel, cc, value, e.ticks));
3107
+ addEventBefore(MIDIMessage.controllerChange(e.ticks, midiChannel, cc, value));
2783
3108
  }
2784
- if (channelStatus.fineTune !== 0) {
2785
- const data = Math.floor(channelStatus.fineTune * 81.92) + 8192;
2786
- const rpnCoarse = getControllerChange(midiChannel, MIDIControllers.registeredParameterMSB, 0, e.ticks);
2787
- const rpnFine = getControllerChange(midiChannel, MIDIControllers.registeredParameterLSB, 1, e.ticks);
2788
- const dataEntryCoarse = getControllerChange(channel, MIDIControllers.dataEntryMSB, data >> 7 & 127, e.ticks);
2789
- addEventBefore(getControllerChange(midiChannel, MIDIControllers.dataEntryLSB, data & 127, e.ticks));
2790
- addEventBefore(dataEntryCoarse);
2791
- addEventBefore(rpnFine);
2792
- addEventBefore(rpnCoarse);
3109
+ if (channelChange.midiParams?.fineTune !== void 0 && channelChange.midiParams.fineTune !== "clear") {
3110
+ const newTune = channelStatus.fineTune + channelChange.midiParams.fineTune;
3111
+ channelStatus.currentKeyShift = Math.trunc(newTune / 100);
3112
+ channelChange.midiParams.fineTune = newTune % 100;
3113
+ } else {
3114
+ const newTune = channelStatus.fineTune + channelStatus.currentFineTune;
3115
+ channelStatus.currentKeyShift = Math.trunc(newTune / 100);
3116
+ channelChange.midiParams ??= {};
3117
+ channelChange.midiParams.fineTune = newTune % 100;
2793
3118
  }
2794
3119
  const patch = channelChange.patch;
2795
3120
  if (patch && patch !== "clear") {
@@ -2797,9 +3122,9 @@ function modifyMIDIInternal(midi, opts) {
2797
3122
  let desiredBankMSB = patch.bankMSB;
2798
3123
  let desiredBankLSB = patch.bankLSB;
2799
3124
  const desiredProgram = patch.program;
2800
- addEventBefore(new MIDIMessage(e.ticks, MIDIMessageTypes.programChange | midiChannel, new IndexedByteArray([desiredProgram])));
3125
+ addEventBefore(MIDIMessage.programChange(e.ticks, midiChannel, desiredProgram));
2801
3126
  const addBank = (isLSB, v) => {
2802
- addEventBefore(getControllerChange(midiChannel, isLSB ? MIDIControllers.bankSelectLSB : MIDIControllers.bankSelect, v, e.ticks));
3127
+ addEventBefore(MIDIMessage.controllerChange(e.ticks, midiChannel, isLSB ? MIDIControllers.bankSelectLSB : MIDIControllers.bankSelect, v));
2803
3128
  };
2804
3129
  if (BankSelectHacks.isSystemXG(system) && patch.isGMGSDrum) {
2805
3130
  SpessaLog.info(`%cAdding XG Drum change on track %c${trackNum}`, ConsoleColors.recognized, ConsoleColors.value);
@@ -2810,15 +3135,20 @@ function modifyMIDIInternal(midi, opts) {
2810
3135
  addBank(true, desiredBankLSB);
2811
3136
  if (patch.isGMGSDrum && !BankSelectHacks.isSystemXG(system) && midiChannel !== 9) {
2812
3137
  SpessaLog.info(`%cAdding GS Drum change on track %c${trackNum}`, ConsoleColors.recognized, ConsoleColors.value);
2813
- addEventBefore(MIDIUtils.gsDrumChange(e.ticks, midiChannel, 1));
3138
+ const chanAddress = 16 | MIDIUtils.channelToSyx(midiChannel);
3139
+ addEventBefore(MIDIUtils.gsMessage(e.ticks, 40, chanAddress, 21, [1]));
2814
3140
  }
2815
3141
  }
3142
+ if (channelChange.midiParams) for (const [param, value] of Object.entries(channelChange.midiParams)) {
3143
+ if (value === "clear") continue;
3144
+ addEventsBefore(...MIDIUtils.setChannelMIDIParameter(e.ticks, midiChannel, system, param, value));
3145
+ }
2816
3146
  }
2817
- e.data[0] += channelStatus.keyShift;
3147
+ e.data[0] += channelStatus.keyShift + channelStatus.currentKeyShift;
2818
3148
  break;
2819
3149
  case MIDIMessageTypes.noteOff:
2820
3150
  if (!channelChange) break;
2821
- e.data[0] += channelStatus.keyShift;
3151
+ e.data[0] += channelStatus.keyShift + channelStatus.currentKeyShift;
2822
3152
  break;
2823
3153
  case MIDIMessageTypes.programChange:
2824
3154
  if (channelChange?.patch) {
@@ -2826,6 +3156,12 @@ function modifyMIDIInternal(midi, opts) {
2826
3156
  return;
2827
3157
  }
2828
3158
  break;
3159
+ case MIDIMessageTypes.pitchWheel:
3160
+ if (channelChange?.midiParams?.pitchWheel) deleteThisEvent();
3161
+ break;
3162
+ case MIDIMessageTypes.channelPressure:
3163
+ if (channelChange?.midiParams?.pressure) deleteThisEvent();
3164
+ break;
2829
3165
  case MIDIMessageTypes.controllerChange: {
2830
3166
  const ccNum = e.data[0];
2831
3167
  const value = e.data[1];
@@ -2865,17 +3201,17 @@ function modifyMIDIInternal(midi, opts) {
2865
3201
  if ((ccNum === MIDIControllers.bankSelect || ccNum === MIDIControllers.bankSelectLSB) && channelChange?.patch) deleteParameter(channel);
2866
3202
  break;
2867
3203
  }
2868
- case "Fine Tune":
2869
- if (channelStatus.fineTune) {
2870
- if (channelStatus.isFirstNoteOn) {
2871
- const newTune = channelStatus.fineTune + data.value;
2872
- channelStatus.keyShift += Math.trunc(newTune / 100);
2873
- channelStatus.fineTune = newTune % 100;
2874
- SpessaLog.info(`%cFine tuning already present on ${channel}, new relative tune: %c${newTune} cents`, ConsoleColors.info, ConsoleColors.recognized);
2875
- }
2876
- deleteParameter(channel);
2877
- }
2878
- return;
3204
+ case "Channel MIDI Param":
3205
+ if (data.parameter === "fineTune" && channelStatus.fineTune) {
3206
+ channelStatus.currentFineTune = data.value;
3207
+ const newTune = channelStatus.fineTune + data.value;
3208
+ channelStatus.currentKeyShift = Math.trunc(newTune / 100);
3209
+ const targetTune = newTune % 100;
3210
+ SpessaLog.info(`%cFine tuning already present on ${channel}%c (${data.value})%c, new relative tune: %c${newTune}%c cents. Key shift: %c${channelStatus.currentKeyShift}%c semitones. Actual RPN value to set: %c${targetTune} cents.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.value, ConsoleColors.info, ConsoleColors.value, ConsoleColors.info, ConsoleColors.value);
3211
+ const updatedData = Math.floor(targetTune * 81.92) + 8192;
3212
+ e.data[1] = ccNum === MIDIControllers.dataEntryMSB ? updatedData >> 7 : updatedData & 127;
3213
+ } else if (channelChange?.midiParams?.[data.parameter]) deleteParameter(channel);
3214
+ break;
2879
3215
  }
2880
3216
  channelStatus.clearedParams.pLSB = true;
2881
3217
  channelStatus.clearedParams.pMSB = true;
@@ -2888,56 +3224,6 @@ function modifyMIDIInternal(midi, opts) {
2888
3224
  const syx = MIDIUtils.analyzeSysEx(e.data);
2889
3225
  switch (syx.type) {
2890
3226
  default: return;
2891
- case "XG Reset":
2892
- SpessaLog.info("%cXG system on detected", ConsoleColors.info);
2893
- system = "xg";
2894
- addedGs = true;
2895
- resetTrack = trackNum;
2896
- resetIndex = index;
2897
- for (const ch of channelStatuses) {
2898
- ch.param.reset();
2899
- ch.clearedParams = {
2900
- pLSB: true,
2901
- pMSB: true,
2902
- data: true
2903
- };
2904
- }
2905
- return;
2906
- case "GM2 On":
2907
- SpessaLog.info("%cGM2 system on detected", ConsoleColors.info);
2908
- system = "gm2";
2909
- addedGs = true;
2910
- resetTrack = trackNum;
2911
- resetIndex = index;
2912
- for (const ch of channelStatuses) {
2913
- ch.param.reset();
2914
- ch.clearedParams = {
2915
- pLSB: true,
2916
- pMSB: true,
2917
- data: true
2918
- };
2919
- }
2920
- return;
2921
- case "GS Reset":
2922
- SpessaLog.info("%cGS on detected!", ConsoleColors.recognized);
2923
- addedGs = true;
2924
- resetTrack = trackNum;
2925
- resetIndex = index;
2926
- for (const ch of channelStatuses) {
2927
- ch.param.reset();
2928
- ch.clearedParams = {
2929
- pLSB: true,
2930
- pMSB: true,
2931
- data: true
2932
- };
2933
- }
2934
- return;
2935
- case "GM Off":
2936
- case "GM On":
2937
- SpessaLog.info("%cGM on detected, removing!", ConsoleColors.info);
2938
- deleteThisEvent();
2939
- addedGs = false;
2940
- return;
2941
3227
  case "Drum Setup":
2942
3228
  if (clearDrumParams) deleteThisEvent();
2943
3229
  return;
@@ -2956,15 +3242,79 @@ function modifyMIDIInternal(midi, opts) {
2956
3242
  case "Program Change":
2957
3243
  if (channelChanges.get(syx.channel + portOffset)?.patch) deleteThisEvent();
2958
3244
  return;
2959
- case "Fine Tune": {
3245
+ case "Global MIDI Param":
3246
+ if (opts.midiParams?.[syx.parameter]) {
3247
+ deleteThisEvent();
3248
+ return;
3249
+ }
3250
+ if (syx.parameter === "system") switch (syx.value) {
3251
+ case "xg":
3252
+ SpessaLog.info("%cXG system on detected", ConsoleColors.info);
3253
+ system = "xg";
3254
+ addedReset = true;
3255
+ resetTrack = trackNum;
3256
+ resetIndex = index;
3257
+ for (const ch of channelStatuses) {
3258
+ ch.param.reset();
3259
+ ch.clearedParams = {
3260
+ pLSB: true,
3261
+ pMSB: true,
3262
+ data: true
3263
+ };
3264
+ }
3265
+ return;
3266
+ case "gm2":
3267
+ SpessaLog.info("%cGM2 system on detected", ConsoleColors.info);
3268
+ system = "gm2";
3269
+ addedReset = true;
3270
+ resetTrack = trackNum;
3271
+ resetIndex = index;
3272
+ for (const ch of channelStatuses) {
3273
+ ch.param.reset();
3274
+ ch.clearedParams = {
3275
+ pLSB: true,
3276
+ pMSB: true,
3277
+ data: true
3278
+ };
3279
+ }
3280
+ return;
3281
+ case "gs":
3282
+ SpessaLog.info("%cGS on detected!", ConsoleColors.recognized);
3283
+ addedReset = true;
3284
+ resetTrack = trackNum;
3285
+ resetIndex = index;
3286
+ for (const ch of channelStatuses) {
3287
+ ch.param.reset();
3288
+ ch.clearedParams = {
3289
+ pLSB: true,
3290
+ pMSB: true,
3291
+ data: true
3292
+ };
3293
+ }
3294
+ return;
3295
+ case "gm":
3296
+ SpessaLog.info("%cGM on detected, removing!", ConsoleColors.info);
3297
+ deleteThisEvent();
3298
+ addedReset = false;
3299
+ return;
3300
+ }
3301
+ break;
3302
+ case "Channel MIDI Param": {
2960
3303
  const syxChannel = channelChanges.get(syx.channel + portOffset);
2961
- const syxStatus = channelStatuses[syx.channel + portOffset];
2962
- if (syxStatus.isFirstNoteOn && syxChannel) {
2963
- const newTune = syxStatus.fineTune + syx.value;
2964
- syxStatus.keyShift += Math.trunc(newTune / 100);
2965
- syxStatus.fineTune = newTune % 100;
2966
- SpessaLog.info(`%cFine tuning already present on ${syx.channel + portOffset}, new relative tune: %c${newTune} cents`, ConsoleColors.info, ConsoleColors.recognized);
3304
+ if (syxChannel?.midiParams?.[syx.parameter]) {
2967
3305
  deleteThisEvent();
3306
+ return;
3307
+ }
3308
+ if (syx.parameter === "fineTune") {
3309
+ const syxStatus = channelStatuses[syx.channel + portOffset];
3310
+ if (syxStatus.isFirstNoteOn && syxChannel) {
3311
+ const newTune = syxStatus.fineTune + syx.value;
3312
+ syxStatus.currentKeyShift = Math.trunc(newTune / 100);
3313
+ syxStatus.fineTune = newTune % 100;
3314
+ SpessaLog.info(`%cFine tuning already present on ${syx.channel + portOffset}, new relative tune: %c${newTune} cents`, ConsoleColors.info, ConsoleColors.recognized);
3315
+ deleteThisEvent();
3316
+ }
3317
+ break;
2968
3318
  }
2969
3319
  break;
2970
3320
  }
@@ -2982,17 +3332,25 @@ function modifyMIDIInternal(midi, opts) {
2982
3332
  }
2983
3333
  }
2984
3334
  });
2985
- if (!addedGs && [...channelChanges.values()].some((c) => c.patch && c.patch !== "clear")) {
3335
+ if (!addedReset && [...channelChanges.values()].some((c) => c.patch && c.patch !== "clear")) {
2986
3336
  let index = 0;
2987
3337
  if (midi.tracks[0].events[0].statusByte === MIDIMessageTypes.trackName) index++;
2988
- midi.tracks[0].addEvents(index, MIDIUtils.gsReset(0));
3338
+ const targetSystem = (opts.midiParams?.system === "clear" ? void 0 : opts.midiParams?.system) ?? "gs";
3339
+ midi.tracks[0].addEvents(index, MIDIUtils.reset(0, targetSystem));
2989
3340
  resetTrack = 0;
2990
3341
  resetIndex = index;
2991
- SpessaLog.info("%cGS on not detected. Adding it.", ConsoleColors.info);
3342
+ system = targetSystem;
3343
+ SpessaLog.info(`%c${targetSystem} reset on not detected. Adding it.`, ConsoleColors.info);
2992
3344
  }
2993
3345
  const targetTicks = Math.max(0, midi.firstNoteOn);
2994
3346
  const targetTrack = midi.tracks[resetTrack];
2995
3347
  const targetIndex = resetIndex + 1;
3348
+ for (const param of Object.keys(opts.midiParams ?? {})) {
3349
+ if (param === "system") continue;
3350
+ const value = opts.midiParams?.[param];
3351
+ if (!value || value === "clear") continue;
3352
+ targetTrack.addEvents(targetIndex, ...MIDIUtils.setGlobalMIDIParameter(targetTicks, system, param, value));
3353
+ }
2996
3354
  if (reverbParams && reverbParams !== "clear") {
2997
3355
  const m = reverbAddressMap;
2998
3356
  const p = reverbParams;
@@ -3010,7 +3368,6 @@ function modifyMIDIInternal(midi, opts) {
3010
3368
  }
3011
3369
  if (insertionParams && insertionParams !== "clear") {
3012
3370
  const p = insertionParams;
3013
- for (let channel = 0; channel < p.channels.length; channel++) if (p.channels[channel]) targetTrack.addEvents(targetTicks, MIDIUtils.gsMessage(targetTicks, 64, 64 | MIDIUtils.channelToSyx(channel), 34, [1]));
3014
3371
  for (let param = 0; param < p.params.length; param++) {
3015
3372
  const value = p.params[param];
3016
3373
  if (value === 255) continue;
@@ -3614,8 +3971,10 @@ function loadXMF(midi, binaryData, fileName) {
3614
3971
  //#endregion
3615
3972
  //#region src/midi/midi_tools/apply_snapshot.ts
3616
3973
  /**
3617
- * Modifies the sequence according to the locked presets and controllers in the given snapshot.
3618
- * Note that this ignores the MIDI parameters and only applies system parameter tuning.
3974
+ * Modifies the sequence *in-place* according to the locked presets and controllers in the given snapshot.
3975
+ *
3976
+ * Note that System Parameters `fineTune` and `keyShift` are passed to the relative tuning parameters of the channels.
3977
+ * Only locked MIDI parameters and controllers are applied.
3619
3978
  */
3620
3979
  function applySnapshotInternal(midi, snapshot) {
3621
3980
  const channels = /* @__PURE__ */ new Map();
@@ -3637,20 +3996,26 @@ function applySnapshotInternal(midi, snapshot) {
3637
3996
  const targetValue = channelSnapshot.midiControllers[ccNumber] >> 7;
3638
3997
  controllers.set(ccNumber, targetValue);
3639
3998
  }
3999
+ const midiParams = {};
4000
+ for (const [parameter, value] of Object.entries(channelSnapshot.midiParameters)) if (channelSnapshot.lockedMIDIParameters[parameter]) midiParams[parameter] = value;
3640
4001
  channels.set(channelNumber, {
3641
4002
  keyShift,
3642
4003
  fineTune,
3643
4004
  patch,
3644
- controllers
4005
+ controllers,
4006
+ midiParams
3645
4007
  });
3646
4008
  }
4009
+ const midiParams = {};
4010
+ for (const [parameter, value] of Object.entries(snapshot.midiParameters)) if (snapshot.lockedMIDIParameters[parameter]) midiParams[parameter] = value;
3647
4011
  midi.modify({
3648
4012
  channels,
3649
4013
  drumSetupParams: snapshot.systemParameters.drumLock ? "clear" : void 0,
3650
4014
  reverbParams: snapshot.systemParameters.reverbLock ? snapshot.reverbProcessor : void 0,
3651
4015
  chorusParams: snapshot.systemParameters.chorusLock ? snapshot.chorusProcessor : void 0,
3652
4016
  delayParams: snapshot.systemParameters.delayLock ? snapshot.delayProcessor : void 0,
3653
- insertionParams: snapshot.systemParameters.insertionEffectLock ? snapshot.insertionProcessor : void 0
4017
+ insertionParams: snapshot.systemParameters.insertionEffectLock ? snapshot.insertionProcessor : void 0,
4018
+ midiParams
3654
4019
  });
3655
4020
  }
3656
4021
  //#endregion
@@ -3787,7 +4152,7 @@ var BasicMIDI = class BasicMIDI {
3787
4152
  * It supports Standard MIDI Files (SMF), RIFF MIDI (RMIDI), and Extensible Music Format (XMF).
3788
4153
  * It also handles embedded soundbanks in RMIDI files.
3789
4154
  * If the file is an RMIDI file, it will extract the embedded soundbank and store
3790
- * it in the `embeddedSoundFont` property of the BasicMIDI instance.
4155
+ * it in the `embeddedSoundBank` property of the BasicMIDI instance.
3791
4156
  * If the file is an XMF file, it will parse the XMF structure and extract the MIDI data.
3792
4157
  */
3793
4158
  static fromArrayBuffer(arrayBuffer, fileName = "") {
@@ -3947,7 +4312,9 @@ var BasicMIDI = class BasicMIDI {
3947
4312
  }
3948
4313
  /**
3949
4314
  * Modifies the sequence *in-place* according to the locked presets and controllers in the given snapshot.
3950
- * Note that this ignores the MIDI parameters and only applies system parameter tuning.
4315
+ *
4316
+ * Note that System Parameters `fineTune` and `keyShift` are passed to the relative tuning parameters of the channels.
4317
+ * Only locked MIDI parameters and controllers are applied.
3951
4318
  * @param snapshot the snapshot to apply.
3952
4319
  */
3953
4320
  applySnapshot(snapshot) {
@@ -4132,15 +4499,15 @@ var BasicMIDI = class BasicMIDI {
4132
4499
  switch (e.statusByte & 240) {
4133
4500
  case MIDIMessageTypes.controllerChange:
4134
4501
  switch (e.data[0]) {
4135
- case 2:
4136
- case 111:
4502
+ case MIDIControllers.breathController:
4503
+ case MIDIControllers.undefinedCC111LSB:
4137
4504
  if (e.data[1] === 0) loopStart = e.ticks;
4138
4505
  break;
4139
- case 116:
4506
+ case MIDIControllers.undefinedCC116LSB:
4140
4507
  loopStart = e.ticks;
4141
4508
  break;
4142
- case 4:
4143
- case 117:
4509
+ case MIDIControllers.footController:
4510
+ case MIDIControllers.undefinedCC117LSB:
4144
4511
  if (loopEnd === null && (e.data[0] !== 4 || e.data[0] === 4 && e.data[1] === 0)) {
4145
4512
  loopType = "soft";
4146
4513
  loopEnd = e.ticks;
@@ -4149,7 +4516,7 @@ var BasicMIDI = class BasicMIDI {
4149
4516
  loopType = "hard";
4150
4517
  }
4151
4518
  break;
4152
- case 0: if (this.isDLSRMIDI && e.data[1] !== 0 && e.data[1] !== 127) {
4519
+ case MIDIControllers.bankSelect: if (this.isDLSRMIDI && e.data[1] !== 0 && e.data[1] !== 127) {
4153
4520
  SpessaLog.info("%cDLS RMIDI with offset 1 detected!", ConsoleColors.recognized);
4154
4521
  this.bankOffset = 1;
4155
4522
  }
@@ -4666,9 +5033,6 @@ function loadNewSequenceInternal(parsedMIDI) {
4666
5033
  }
4667
5034
  //#endregion
4668
5035
  //#region src/synthesizer/audio_engine/channel/reset.ts
4669
- function resetPortamento() {
4670
- this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
4671
- }
4672
5036
  /**
4673
5037
  * An array with the default MIDI controller values.
4674
5038
  * Note that these are 14-bit, requiring a 7-bit shift to the right for 7-bit values!
@@ -4714,22 +5078,24 @@ function resetChannelInternal(sendCCEvents = true) {
4714
5078
  const resetValue = DEFAULT_MIDI_CONTROLLERS[cc];
4715
5079
  if (this._midiControllers[cc] !== resetValue && cc !== MIDIControllers.portamentoControl && cc !== MIDIControllers.dataEntryMSB && cc !== MIDIControllers.registeredParameterMSB && cc !== MIDIControllers.registeredParameterLSB && cc !== MIDIControllers.nonRegisteredParameterMSB && cc !== MIDIControllers.nonRegisteredParameterLSB) this.controllerChange(cc, resetValue >> 7, sendCCEvents);
4716
5080
  }
4717
- if (!this.synthCore.systemParameters.insertionEffectLock) this.setMIDIParameter("efxAssign", false);
5081
+ this.setMIDIParameter("pressure", 0);
5082
+ this.setMIDIParameter("pitchWheelRange", 2);
5083
+ this.setMIDIParameter("modulationDepth", 50);
4718
5084
  this.setMIDIParameter("rxChannel", this.channel);
5085
+ this.setMIDIParameter("efxAssign", false);
5086
+ this.setMIDIParameter("polyMode", true);
5087
+ this.setMIDIParameter("keyShift", 0);
5088
+ this.setMIDIParameter("fineTune", 0);
4719
5089
  this.setMIDIParameter("assignMode", 2);
4720
5090
  this.setMIDIParameter("randomPan", false);
4721
5091
  this.setMIDIParameter("cc1", 16);
4722
5092
  this.setMIDIParameter("cc2", 17);
4723
5093
  this.setMIDIParameter("drumMap", this.channel % 16 === 9 ? 1 : 0);
5094
+ this.setMIDIParameter("velocitySenseOffset", 64);
5095
+ this.setMIDIParameter("velocitySenseDepth", 64);
4724
5096
  this.pitchWheel(8192);
4725
- this.pitchWheelRange(2, false);
4726
- this.keyShift(0, false);
4727
- this.fineTune(0, false);
4728
- this.setMIDIParameter("pressure", 0);
4729
- this.modulationDepth(50, false);
4730
- if (!this.lockedControllers[MIDIControllers.monoModeOn] && !this.lockedControllers[MIDIControllers.polyModeOn]) this.setMIDIParameter("polyMode", true);
4731
5097
  this.octaveTuning.fill(0);
4732
- resetPortamento.call(this);
5098
+ this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
4733
5099
  this.resetDrumParams();
4734
5100
  this.resetGeneratorOverrides();
4735
5101
  this.resetGeneratorOffsets();
@@ -5268,7 +5634,7 @@ var SpessaSynthSequencer = class {
5268
5634
  this.synth.reset();
5269
5635
  return;
5270
5636
  }
5271
- this.sendMIDISysEx(MIDIUtils.gsData(64, 0, 127, [0]));
5637
+ this.sendMIDISysEx(MIDIUtils.gs(64, 0, 127, [0]));
5272
5638
  }
5273
5639
  loadCurrentSong() {
5274
5640
  let index = this._songIndex;
@@ -6017,15 +6383,16 @@ function applySnapshot$1(snapshot) {
6017
6383
  for (const [key, value] of Object.entries(this.chorusProcessor)) this.chorusProcessor[key] = value;
6018
6384
  for (const [key, value] of Object.entries(this.delayProcessor)) this.delayProcessor[key] = value;
6019
6385
  const is = snapshot.insertionProcessor;
6020
- this.systemExclusive(MIDIUtils.gsData(64, 3, 0, [is.type >> 8, is.type & 127]));
6021
- for (let i = 0; i < is.params.length; i++) if (is.params[i] !== 255) this.systemExclusive(MIDIUtils.gsData(64, 3, 3 + i, [is.params[i]]));
6022
- for (let channel = 0; channel < is.channels.length; channel++) this.systemExclusive(MIDIUtils.gsData(64, 64 | MIDIUtils.channelToSyx(channel), 34, [is.channels[channel] ? 1 : 0]));
6386
+ this.systemExclusive(MIDIUtils.gs(64, 3, 0, [is.type >> 8, is.type & 127]));
6387
+ for (let i = 0; i < is.params.length; i++) if (is.params[i] !== 255) this.systemExclusive(MIDIUtils.gs(64, 3, 3 + i, [is.params[i]]));
6023
6388
  for (const [parameter, value] of Object.entries(snapshot.midiParameters)) this.setMIDIParameter(parameter, value);
6389
+ for (const [parameter, isLocked] of Object.entries(snapshot.lockedMIDIParameters)) this.lockMIDIParameter(parameter, isLocked);
6024
6390
  for (const [parameter, value] of Object.entries(snapshot.systemParameters)) this.setSystemParameter(parameter, value);
6025
6391
  }
6026
6392
  function getSynthesizerSnapshot() {
6027
6393
  return {
6028
6394
  midiParameters: { ...this.midiParameters },
6395
+ lockedMIDIParameters: { ...this.lockedMIDIParameters },
6029
6396
  systemParameters: { ...this.systemParameters },
6030
6397
  midiChannels: this.midiChannels.map((c) => c.getSnapshot()),
6031
6398
  keyMappings: this.keyModifierManager.getMappings(),
@@ -6232,7 +6599,7 @@ var LowpassFilter = class LowpassFilter {
6232
6599
  LowpassFilter.smoothingConstant = FILTER_SMOOTHING_FACTOR * (44100 / sampleRate);
6233
6600
  const dummy = new LowpassFilter(sampleRate);
6234
6601
  dummy.resonanceCb = 0;
6235
- for (let i = 1500; i < 13500; i++) {
6602
+ for (let i = 8e3; i < 13500; i++) {
6236
6603
  dummy.currentInitialFc = i;
6237
6604
  dummy.calculateCoefficients(i);
6238
6605
  }
@@ -7557,7 +7924,7 @@ var ModulatorSource = class ModulatorSource {
7557
7924
  * If this field is set to false, the controller should be mapped with a minimum value of 0 and a maximum value of 1. This is also
7558
7925
  * called Unipolar. Thus, it behaves similar to the Modulation Wheel controller of the MIDI specification.
7559
7926
  *
7560
- * If this field is set to true, the controller sound be mapped with a minimum value of -1 and a maximum value of 1. This is also
7927
+ * If this field is set to true, the controller should be mapped with a minimum value of -1 and a maximum value of 1. This is also
7561
7928
  * called Bipolar. Thus, it behaves similar to the Pitch Wheel controller of the MIDI specification.
7562
7929
  */
7563
7930
  isBipolar;
@@ -8888,7 +9255,8 @@ var SoundFontSample = class extends BasicSample {
8888
9255
  }
8889
9256
  const audioData = new Float32Array(byteLength / 2);
8890
9257
  const convertedSigned16 = new Int16Array(this.s16leData.buffer);
8891
- for (const [i, element] of convertedSigned16.entries()) audioData[i] = element / 32768;
9258
+ const l = convertedSigned16.length;
9259
+ for (let i = 0; i < l; i++) audioData[i] = convertedSigned16[i] / 32768;
8892
9260
  this.audioData = audioData;
8893
9261
  return audioData;
8894
9262
  }
@@ -9430,7 +9798,8 @@ function readPCM(data, bytesPerSample) {
9430
9798
  const sampleData = new Float32Array(sampleLength);
9431
9799
  if (bytesPerSample === 2) {
9432
9800
  const s16 = new Int16Array(data.buffer);
9433
- for (const [i, element] of s16.entries()) sampleData[i] = element / 32768;
9801
+ const s16l = s16.length;
9802
+ for (let i = 0; i < s16l; i++) sampleData[i] = s16[i] / 32768;
9434
9803
  } else for (let i = 0; i < sampleData.length; i++) {
9435
9804
  let sample = readLittleEndianIndexed(data, bytesPerSample);
9436
9805
  if (isUnsigned) sampleData[i] = sample / normalizationFactor - .5;
@@ -12338,22 +12707,30 @@ function dataEntry() {
12338
12707
  default:
12339
12708
  SpessaLog.info(`%cUnrecognized RPN for %c${this.channel}%c: %c(0x${rpnValue.toString(16)})%c data value: %c${dataValue}`, ConsoleColors.warn, ConsoleColors.recognized, ConsoleColors.warn, ConsoleColors.unrecognized, ConsoleColors.warn, ConsoleColors.value);
12340
12709
  break;
12341
- case RegisteredParameterTypes.pitchWheelRange:
12342
- this.pitchWheelRange(dataValue / 128);
12710
+ case RegisteredParameterTypes.pitchWheelRange: {
12711
+ const range = dataValue / 128;
12712
+ this.setMIDIParameter("pitchWheelRange", range);
12713
+ SpessaLog.coolInfo(`Pitch Wheel Range for ${this.channel}`, range, "semitones");
12343
12714
  break;
12715
+ }
12344
12716
  case RegisteredParameterTypes.coarseTuning: {
12345
12717
  const semitones = (dataValue >> 7) - 64;
12346
- this.keyShift(semitones);
12718
+ this.setMIDIParameter("keyShift", semitones);
12719
+ SpessaLog.coolInfo(`Key shift for ${this.channel}`, semitones);
12347
12720
  break;
12348
12721
  }
12349
12722
  case RegisteredParameterTypes.fineTuning: {
12350
- const finalTuning = dataValue - 8192;
12351
- this.fineTune(finalTuning / 81.92);
12723
+ const cents = (dataValue - 8192) / 81.92;
12724
+ this.setMIDIParameter("fineTune", cents);
12725
+ SpessaLog.coolInfo(`Fine tuning for ${this.channel}`, Math.round(cents), "cents");
12352
12726
  break;
12353
12727
  }
12354
- case RegisteredParameterTypes.modulationDepth:
12355
- this.modulationDepth(dataValue / 1.28);
12728
+ case RegisteredParameterTypes.modulationDepth: {
12729
+ const cents = dataValue / 1.28;
12730
+ this.setMIDIParameter("modulationDepth", cents);
12731
+ SpessaLog.coolInfo(`Modulation depth for ${this.channel}`, Math.round(cents), "cents");
12356
12732
  break;
12733
+ }
12357
12734
  case RegisteredParameterTypes.resetParameters: break;
12358
12735
  }
12359
12736
  return;
@@ -12599,9 +12976,9 @@ function noteOn(midiNote, velocity, emit = true) {
12599
12976
  this.noteOff(midiNote);
12600
12977
  return;
12601
12978
  }
12602
- velocity = clamp(velocity, 0, 127);
12603
12979
  const black = this.synthCore.systemParameters.blackMIDIMode;
12604
12980
  if (black && this.synthCore.voiceCount > 200 && velocity < 40 || black && velocity < 10 || this._systemParameters.isMuted || !this.preset) return;
12981
+ let realVelocity = clamp(velocity * (this._midiParameters.velocitySenseDepth / 64) + (this._midiParameters.velocitySenseOffset - 64) * 2, 0, 127);
12605
12982
  let soundBankNote = midiNote + this.currentKeyShift;
12606
12983
  if (midiNote > 127 || midiNote < 0) return;
12607
12984
  const program = this.preset.program;
@@ -12609,7 +12986,7 @@ function noteOn(midiNote, velocity, emit = true) {
12609
12986
  if (tune >= 0) soundBankNote = Math.trunc(tune);
12610
12987
  if ((this._systemParameters.monophonicRetrigger ?? this.synthCore.systemParameters.monophonicRetrigger) || this._midiParameters.assignMode === 0) this.killNote(midiNote);
12611
12988
  const keyVel = this.synthCore.keyModifierManager.getVelocity(this.channel, midiNote);
12612
- if (keyVel > -1) velocity = keyVel;
12989
+ if (keyVel > -1) realVelocity = keyVel;
12613
12990
  let voiceGain = this.synthCore.keyModifierManager.getGain(this.channel, midiNote);
12614
12991
  const previousNote = this.lastPortamentoNote;
12615
12992
  const portamentoEnabled = this.portamentoForce || this._midiControllers[MIDIControllers.portamentoOnOff] >= 8192;
@@ -12630,7 +13007,7 @@ function noteOn(midiNote, velocity, emit = true) {
12630
13007
  this.lastMonoNote = midiNote;
12631
13008
  this.lastMonoVelocity = velocity;
12632
13009
  }
12633
- const voices = this.synthCore.getVoices(this.channel, soundBankNote, velocity);
13010
+ const voices = this.synthCore.getVoices(this.channel, soundBankNote, realVelocity);
12634
13011
  let panOverride = 0;
12635
13012
  let exclusiveOverride = 0;
12636
13013
  let pitchOffset = 0;
@@ -12941,7 +13318,7 @@ function computeModulator(voice, pitchWheel, modulatorIndex) {
12941
13318
  let computedValue = sourceValue * secondSrcValue * transformAmount;
12942
13319
  if (modulator.transformType === 2) computedValue = Math.abs(computedValue);
12943
13320
  if (modulator.isDefaultResonantModulator) voice.resonanceOffset = Math.max(0, computedValue / 2);
12944
- if (modulator.isModWheelModulator) computedValue *= this._midiParameters.modulationDepth;
13321
+ if (modulator.isModWheelModulator) computedValue *= this._midiParameters.modulationDepth / 50;
12945
13322
  voice.modulatorValues[modulatorIndex] = computedValue;
12946
13323
  return computedValue;
12947
13324
  }
@@ -13064,12 +13441,13 @@ function getChannelSnapshot() {
13064
13441
  overrides: this.generators.overrides.slice()
13065
13442
  },
13066
13443
  midiParameters: { ...this._midiParameters },
13444
+ lockedMIDIParameters: { ...this.lockedMIDIParameters },
13067
13445
  systemParameters: { ...this._systemParameters },
13068
13446
  octaveTuning: this.octaveTuning.slice(),
13069
13447
  perNotePitch: this.perNotePitch,
13070
13448
  drumParams: this.drumParams.map((d) => ({ ...d })),
13071
13449
  drumChannel: this._drumChannel,
13072
- channelNumber: this.channel
13450
+ channel: this.channel
13073
13451
  };
13074
13452
  }
13075
13453
  function applySnapshot(snapshot) {
@@ -13088,6 +13466,7 @@ function applySnapshot(snapshot) {
13088
13466
  if (snapshot.patch) this.setPatch(snapshot.patch);
13089
13467
  this.lockedSystem = snapshot.lockedSystem;
13090
13468
  for (const [parameter, value] of Object.entries(snapshot.midiParameters)) this.setMIDIParameter(parameter, value);
13469
+ for (const [parameter, isLocked] of Object.entries(snapshot.lockedMIDIParameters)) this.lockMIDIParameter(parameter, isLocked);
13091
13470
  for (const [parameter, value] of Object.entries(snapshot.systemParameters)) this.setSystemParameter(parameter, value);
13092
13471
  }
13093
13472
  //#endregion
@@ -13096,18 +13475,53 @@ const DEFAULT_CHANNEL_MIDI_PARAMETERS = {
13096
13475
  pitchWheel: 8192,
13097
13476
  pitchWheelRange: 2,
13098
13477
  pressure: 0,
13099
- modulationDepth: 1,
13478
+ modulationDepth: 50,
13100
13479
  rxChannel: 0,
13101
13480
  polyMode: true,
13102
13481
  keyShift: 0,
13482
+ fineTune: 0,
13103
13483
  randomPan: false,
13104
13484
  assignMode: 2,
13105
13485
  efxAssign: false,
13106
13486
  cc1: 16,
13107
13487
  cc2: 17,
13108
13488
  drumMap: 0,
13109
- fineTune: 0
13489
+ velocitySenseDepth: 64,
13490
+ velocitySenseOffset: 64
13110
13491
  };
13492
+ /**
13493
+ * Sets a channel MIDI parameter of the synthesizer.
13494
+ * @param parameter The type of the channel MIDI parameter to set.
13495
+ * @param value The value to set for the channel MIDI parameter.
13496
+ * @internal
13497
+ */
13498
+ function setMIDIParameterInternal$1(parameter, value) {
13499
+ if (this.lockedMIDIParameters[parameter]) return;
13500
+ this._midiParameters[parameter] = value;
13501
+ switch (parameter) {
13502
+ case "pitchWheel":
13503
+ this.computeModulatorsAll(0, ModulatorControllerSources.pitchWheel);
13504
+ break;
13505
+ case "pressure":
13506
+ this.computeModulatorsAll(0, ModulatorControllerSources.channelPressure);
13507
+ break;
13508
+ }
13509
+ this.updateInternalParams();
13510
+ this.synthCore.callEvent("channelParamChange", {
13511
+ channel: this.channel,
13512
+ parameter,
13513
+ value
13514
+ });
13515
+ }
13516
+ /**
13517
+ * Locks or unlocks a given Channel MIDI Parameter.
13518
+ * This prevents any changes to it until it's unlocked.
13519
+ * @param parameter The Channel MIDI Parameter to lock.
13520
+ * @param isLocked If the parameter should be locked.
13521
+ */
13522
+ function lockMIDIParameterInternal$1(parameter, isLocked) {
13523
+ this.lockedMIDIParameters[parameter] = isLocked;
13524
+ }
13111
13525
  //#endregion
13112
13526
  //#region src/synthesizer/audio_engine/channel/parameters/system.ts
13113
13527
  const DEFAULT_CHANNEL_SYSTEM_PARAMETERS = {
@@ -13154,13 +13568,6 @@ var MIDIChannel = class {
13154
13568
  */
13155
13569
  pitchWheels = new Int16Array(128).fill(8192);
13156
13570
  /**
13157
- * An array indicating if a controller, at the equivalent index in the midiControllers array, is locked
13158
- * (i.e., not allowed changing).
13159
- * A locked controller cannot be modified.
13160
- * @internal
13161
- */
13162
- lockedControllers = new Array(128).fill(false);
13163
- /**
13164
13571
  * Parameters for each drum instrument.
13165
13572
  * @internal
13166
13573
  */
@@ -13211,6 +13618,13 @@ var MIDIChannel = class {
13211
13618
  */
13212
13619
  setSystemParameter = setSystemParameterInternal.bind(this);
13213
13620
  /**
13621
+ * Locks or unlocks a given Channel MIDI Parameter.
13622
+ * This prevents any changes to it until it's unlocked.
13623
+ * @param parameter The Channel MIDI Parameter to lock.
13624
+ * @param isLocked If the parameter should be locked.
13625
+ */
13626
+ lockMIDIParameter = lockMIDIParameterInternal$1.bind(this);
13627
+ /**
13214
13628
  * Sends a "MIDI Note on" message and starts a note.
13215
13629
  * @param midiNote The MIDI note number (0-127).
13216
13630
  * @param velocity The velocity of the note (0-127). If less than 1, it will send a note off instead.
@@ -13263,6 +13677,20 @@ var MIDIChannel = class {
13263
13677
  */
13264
13678
  renderVoice = renderVoice.bind(this);
13265
13679
  /**
13680
+ * Sets a channel MIDI parameter of the synthesizer.
13681
+ * @param parameter The type of the channel MIDI parameter to set.
13682
+ * @param value The value to set for the channel MIDI parameter.
13683
+ * @internal
13684
+ */
13685
+ setMIDIParameter = setMIDIParameterInternal$1.bind(this);
13686
+ /**
13687
+ * An array indicating if a controller, at the equivalent index in the midiControllers array, is locked
13688
+ * (i.e., not allowed changing).
13689
+ * A locked controller cannot be modified.
13690
+ * @internal
13691
+ */
13692
+ lockedControllers = new Array(128).fill(false);
13693
+ /**
13266
13694
  * An array of MIDI controllers for the channel.
13267
13695
  * This array is used to store the state of various MIDI controllers
13268
13696
  * such as volume, pan, modulation, etc.
@@ -13291,6 +13719,13 @@ var MIDIChannel = class {
13291
13719
  * @internal
13292
13720
  */
13293
13721
  dataEntry = dataEntry.bind(this);
13722
+ /**
13723
+ * An object indicating if a Channel MIDI parameter, at the equivalent key, is locked
13724
+ * (i.e., not allowed changing).
13725
+ * A locked parameter cannot be modified.
13726
+ * @internal
13727
+ */
13728
+ lockedMIDIParameters = Object.fromEntries(Object.keys(DEFAULT_CHANNEL_MIDI_PARAMETERS).map((key) => [key, false]));
13294
13729
  _midiParameters = { ...DEFAULT_CHANNEL_MIDI_PARAMETERS };
13295
13730
  /**
13296
13731
  * All system parameters of this channel.
@@ -13518,29 +13953,6 @@ var MIDIChannel = class {
13518
13953
  });
13519
13954
  }
13520
13955
  /**
13521
- * Sets a channel MIDI parameter of the synthesizer.
13522
- * @param parameter The type of the channel MIDI parameter to set.
13523
- * @param value The value to set for the channel MIDI parameter.
13524
- * @internal
13525
- */
13526
- setMIDIParameter(parameter, value) {
13527
- this._midiParameters[parameter] = value;
13528
- switch (parameter) {
13529
- case "pitchWheel":
13530
- this.computeModulatorsAll(0, ModulatorControllerSources.pitchWheel);
13531
- break;
13532
- case "pressure":
13533
- this.computeModulatorsAll(0, ModulatorControllerSources.channelPressure);
13534
- break;
13535
- }
13536
- this.updateInternalParams();
13537
- this.synthCore.callEvent("channelParamChange", {
13538
- channel: this.channel,
13539
- parameter,
13540
- value
13541
- });
13542
- }
13543
- /**
13544
13956
  * @internal
13545
13957
  */
13546
13958
  clearVoiceCount() {
@@ -13558,49 +13970,6 @@ var MIDIChannel = class {
13558
13970
  for (let i = 0; i < 128; i++) this.octaveTuning[i] = tuning[i % 12];
13559
13971
  }
13560
13972
  /**
13561
- * Sets the modulation depth for the channel.
13562
- * @param cents The modulation depth in cents to set.
13563
- * @param log If true, logs the change to the console.
13564
- * @remarks
13565
- * This method sets the modulation depth for the channel by converting the given cents value into a
13566
- * multiplier. The MIDI specification assumes the default modulation depth is 50 cents,
13567
- * but it may vary for different sound banks.
13568
- * For example, if you want a modulation depth of 100 cents,
13569
- * the multiplier will be 2,
13570
- * which, for a preset with a depth of 50,
13571
- * will create a total modulation depth of 100 cents.
13572
- * @internal
13573
- */
13574
- modulationDepth(cents, log = true) {
13575
- this.setMIDIParameter("modulationDepth", cents / 50);
13576
- if (!log) return;
13577
- SpessaLog.info(`%cChannel ${this.channel} modulation depth. Cents: %c${Math.round(cents)}`, ConsoleColors.info, ConsoleColors.value);
13578
- }
13579
- /**
13580
- * Sets the channel's key shift (MIDI).
13581
- * @param shift the key shift.
13582
- * @param log If true, logs the change to the console.
13583
- * @internal
13584
- */
13585
- keyShift(shift, log = true) {
13586
- if (this._drumChannel) shift = 0;
13587
- if (this._midiParameters.keyShift === shift) return;
13588
- this.setMIDIParameter("keyShift", shift);
13589
- if (!log) return;
13590
- SpessaLog.info(`%cKey shift for %c${this.channel}%c is now set to %c${shift}.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.value);
13591
- }
13592
- /**
13593
- * Sets the channel's tuning.
13594
- * @param cents The tuning in cents to set.
13595
- * @param log If true, logs the change to the console.
13596
- * @internal
13597
- */
13598
- fineTune(cents, log = true) {
13599
- this.setMIDIParameter("fineTune", cents);
13600
- if (!log) return;
13601
- SpessaLog.info(`%cFine tuning for %c${this.channel}%c is now set to %c${Math.round(cents)}%c cents.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.value, ConsoleColors.info);
13602
- }
13603
- /**
13604
13973
  * Sets the pitch of the given channel.
13605
13974
  * @param pitch The pitch (0 - 16383)
13606
13975
  * @param midiNote The MIDI note number, pass -1 for the regular pitch wheel
@@ -13640,17 +14009,6 @@ var MIDIChannel = class {
13640
14009
  });
13641
14010
  }
13642
14011
  /**
13643
- * Sets the pitch wheel range for this channel.
13644
- * @param range range in semitones.
13645
- * @param log If true, logs the change to the console.
13646
- * @internal
13647
- */
13648
- pitchWheelRange(range, log = true) {
13649
- this.setMIDIParameter("pitchWheelRange", range);
13650
- if (!log) return;
13651
- SpessaLog.coolInfo(`Pitch Wheel Range for ${this.channel}`, range, "semitones");
13652
- }
13653
- /**
13654
14012
  * @internal
13655
14013
  */
13656
14014
  updateInternalParams() {
@@ -13811,7 +14169,7 @@ var MIDIChannel = class {
13811
14169
  setDrumFlag(isDrum) {
13812
14170
  if (this._systemParameters.presetLock || !this.preset || this._drumChannel === isDrum) return;
13813
14171
  this._drumChannel = isDrum;
13814
- this.keyShift(this._midiParameters.keyShift, false);
14172
+ this.updateInternalParams();
13815
14173
  }
13816
14174
  };
13817
14175
  //#endregion
@@ -13972,7 +14330,8 @@ function universalSystemExclusive(syx, channelOffset = 0) {
13972
14330
  break;
13973
14331
  case 1: {
13974
14332
  const vol = syx[5] << 7 | syx[4];
13975
- this.setMIDIParameter("gain", vol / 16384);
14333
+ const gain = Math.pow(vol / 16383, 2);
14334
+ this.setMIDIParameter("gain", gain);
13976
14335
  SpessaLog.gmInfo("Master Volume", vol);
13977
14336
  break;
13978
14337
  }
@@ -13983,7 +14342,7 @@ function universalSystemExclusive(syx, channelOffset = 0) {
13983
14342
  break;
13984
14343
  }
13985
14344
  case 3: {
13986
- const cents = ((syx[5] << 7 | syx[6]) - 8192) / 81.92;
14345
+ const cents = ((syx[5] << 7 | syx[4]) - 8192) / 81.92;
13987
14346
  this.setMIDIParameter("fineTune", cents);
13988
14347
  SpessaLog.gmInfo("Master Fine Tuning", cents, "cents");
13989
14348
  break;
@@ -14154,6 +14513,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14154
14513
  }
14155
14514
  case 4:
14156
14515
  SpessaLog.gsInfo("Master Volume", data);
14516
+ this.setMIDIParameter("gain", data / 127);
14157
14517
  break;
14158
14518
  case 5: {
14159
14519
  const transpose = data - 64;
@@ -14161,10 +14521,12 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14161
14521
  this.setMIDIParameter("keyShift", transpose);
14162
14522
  break;
14163
14523
  }
14164
- case 6:
14165
- SpessaLog.gsInfo("Master Pan", data);
14166
- this.setMIDIParameter("pan", (data - 64) / 64);
14524
+ case 6: {
14525
+ const pan = (data - 64) / 63;
14526
+ SpessaLog.gsInfo("Master Pan", pan);
14527
+ this.setMIDIParameter("pan", pan);
14167
14528
  break;
14529
+ }
14168
14530
  case 127:
14169
14531
  if (data === 0) {
14170
14532
  SpessaLog.coolInfo("MIDI System", "Roland GS");
@@ -14195,6 +14557,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14195
14557
  case 0: {
14196
14558
  const patchName = readBinaryString(syx, 16, 7);
14197
14559
  SpessaLog.gsInfo("Patch name", patchName);
14560
+ this.callEvent("displayMessage", [...syx]);
14198
14561
  break;
14199
14562
  }
14200
14563
  case 48:
@@ -14530,12 +14893,21 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14530
14893
  }
14531
14894
  case 22: {
14532
14895
  const keyShift = data - 64;
14533
- ch.keyShift(keyShift);
14896
+ ch.setMIDIParameter("keyShift", keyShift);
14897
+ SpessaLog.gsInfo(`Key Shift for ${channel}`, keyShift);
14534
14898
  return;
14535
14899
  }
14536
14900
  case 25:
14537
14901
  ch.controllerChange(MIDIControllers.mainVolume, data);
14538
14902
  return;
14903
+ case 26:
14904
+ ch.setMIDIParameter("velocitySenseDepth", data);
14905
+ SpessaLog.gsInfo(`Velocity Sense Depth for ${channel}`, data);
14906
+ return;
14907
+ case 27:
14908
+ ch.setMIDIParameter("velocitySenseOffset", data);
14909
+ SpessaLog.gsInfo(`Velocity Sense Offset for ${channel}`, data);
14910
+ return;
14539
14911
  case 28: {
14540
14912
  const panPosition = data;
14541
14913
  const randomPan = panPosition === 0;
@@ -14546,11 +14918,11 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14546
14918
  }
14547
14919
  case 31:
14548
14920
  ch.setMIDIParameter("cc1", data);
14549
- SpessaLog.gsInfo("CC1 Controller Number", data);
14921
+ SpessaLog.gsInfo(`CC1 Controller Number for ${channel}`, data);
14550
14922
  break;
14551
14923
  case 32:
14552
14924
  ch.setMIDIParameter("cc2", data);
14553
- SpessaLog.gsInfo("CC2 Controller Number", data);
14925
+ SpessaLog.gsInfo(`CC2 Controller Number for ${channel}`, data);
14554
14926
  break;
14555
14927
  case 33:
14556
14928
  ch.controllerChange(MIDIControllers.chorusDepth, data);
@@ -14559,8 +14931,9 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14559
14931
  ch.controllerChange(MIDIControllers.reverbDepth, data);
14560
14932
  break;
14561
14933
  case 42: {
14562
- const tuneCents = ((data << 7 | syx[8]) - 8192) / 81.92;
14563
- ch.fineTune(tuneCents);
14934
+ const cents = ((data << 7 | syx[8]) - 8192) / 81.92;
14935
+ ch.setMIDIParameter("fineTune", cents);
14936
+ SpessaLog.gsInfo(`Fine tuning for ${channel}`, Math.round(cents), "cents");
14564
14937
  break;
14565
14938
  }
14566
14939
  case 44:
@@ -14595,7 +14968,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14595
14968
  const newTuning = new Int8Array(12);
14596
14969
  for (let i = 0; i < tuningBytes; i++) newTuning[i] = syx[i + 7] - 64;
14597
14970
  ch.setOctaveTuning(newTuning);
14598
- SpessaLog.gsInfo(`Octave Scale Tuning on ${channel}`, newTuning.join(", "));
14971
+ SpessaLog.gsInfo(`Octave Scale Tuning for ${channel}`, newTuning.join(", "));
14599
14972
  break;
14600
14973
  }
14601
14974
  }
@@ -14611,7 +14984,8 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14611
14984
  case 0:
14612
14985
  if ((a3 & 15) === 4) {
14613
14986
  const cents = data / 127 * 600;
14614
- ch.modulationDepth(cents);
14987
+ ch.setMIDIParameter("modulationDepth", cents);
14988
+ SpessaLog.gsInfo(`Modulation depth for ${channel}`, Math.round(cents), "cents");
14615
14989
  break;
14616
14990
  }
14617
14991
  ch.dynamicModulators.setupReceiver(a3, data, MIDIControllers.modulationWheel, true, "mod wheel");
@@ -14619,7 +14993,8 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14619
14993
  case 16:
14620
14994
  if ((a3 & 15) === 0) {
14621
14995
  const centeredValue = data - 64;
14622
- ch.pitchWheelRange(centeredValue);
14996
+ ch.setMIDIParameter("pitchWheelRange", centeredValue);
14997
+ SpessaLog.gsInfo(`Pitch Wheel Range for ${channel}`, centeredValue, "semitones");
14623
14998
  break;
14624
14999
  }
14625
15000
  ch.dynamicModulators.setupReceiver(a3, data, ModulatorControllerSources.pitchWheel, false, "pitch wheel", true);
@@ -14649,7 +15024,6 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
14649
15024
  ch.controllerChange(MIDIControllers.bankSelectLSB, data);
14650
15025
  break;
14651
15026
  case 34: {
14652
- if (this.systemParameters.insertionEffectLock) return;
14653
15027
  const efx = data === 1;
14654
15028
  ch.setMIDIParameter("efxAssign", efx);
14655
15029
  this.insertionActive ||= efx;
@@ -14859,12 +15233,21 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
14859
15233
  }
14860
15234
  case 8: {
14861
15235
  const keyShift = data - 64;
14862
- ch.keyShift(keyShift);
15236
+ ch.setMIDIParameter("keyShift", keyShift);
15237
+ SpessaLog.xgInfo(`Key Shift on ${channel}`, keyShift);
14863
15238
  break;
14864
15239
  }
14865
15240
  case 11:
14866
15241
  ch.controllerChange(MIDIControllers.mainVolume, data);
14867
15242
  break;
15243
+ case 12:
15244
+ ch.setMIDIParameter("velocitySenseDepth", data);
15245
+ SpessaLog.xgInfo(`Velocity Sense Depth on ${channel}`, data);
15246
+ return;
15247
+ case 13:
15248
+ ch.setMIDIParameter("velocitySenseOffset", data);
15249
+ SpessaLog.xgInfo(`Velocity Sense Offset on ${channel}`, data);
15250
+ return;
14868
15251
  case 14: {
14869
15252
  const pan = data;
14870
15253
  const randomPan = pan === 0;
@@ -14905,7 +15288,8 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
14905
15288
  break;
14906
15289
  case 35: {
14907
15290
  const centeredValue = data - 64;
14908
- ch.pitchWheelRange(centeredValue);
15291
+ ch.setMIDIParameter("pitchWheelRange", centeredValue);
15292
+ SpessaLog.xgInfo(`Pitch Wheel Range for ${channel}`, centeredValue, "semitones");
14909
15293
  }
14910
15294
  }
14911
15295
  return;
@@ -19368,6 +19752,7 @@ const DEFAULT_GLOBAL_MIDI_PARAMETERS = {
19368
19752
  * @param value The value to set for the global MIDI parameter.
19369
19753
  */
19370
19754
  function setMIDIParameterInternal(parameter, value) {
19755
+ if (this.lockedMIDIParameters[parameter]) return;
19371
19756
  this.midiParameters[parameter] = value;
19372
19757
  for (const ch of this.midiChannels) ch.updateInternalParams();
19373
19758
  this.callEvent("globalParamChange", {
@@ -19376,15 +19761,13 @@ function setMIDIParameterInternal(parameter, value) {
19376
19761
  });
19377
19762
  }
19378
19763
  /**
19379
- * Resets all global MIDI parameters to their default values.
19380
- * @param system the MIDI system to set when resetting.
19764
+ * Locks or unlocks a given Global MIDI Parameter.
19765
+ * This prevents any changes to it until it's unlocked.
19766
+ * @param parameter The Global MIDI Parameter to lock.
19767
+ * @param isLocked If the parameter should be locked.
19381
19768
  */
19382
- function resetMIDIParametersInternal(system) {
19383
- this.setMIDIParameter("gain", 1);
19384
- this.setMIDIParameter("pan", 0);
19385
- this.setMIDIParameter("keyShift", 0);
19386
- this.setMIDIParameter("fineTune", 0);
19387
- this.setMIDIParameter("system", system);
19769
+ function lockMIDIParameterInternal(parameter, isLocked) {
19770
+ this.lockedMIDIParameters[parameter] = isLocked;
19388
19771
  }
19389
19772
  //#endregion
19390
19773
  //#region src/synthesizer/audio_engine/synthesizer_core.ts
@@ -19450,6 +19833,13 @@ var SynthesizerCore = class {
19450
19833
  */
19451
19834
  tunings = new Float32Array(16384).fill(-1);
19452
19835
  /**
19836
+ * An object indicating if a Global MIDI parameter, at the equivalent key, is locked
19837
+ * (i.e., not allowed changing).
19838
+ * A locked parameter cannot be modified.
19839
+ * @internal
19840
+ */
19841
+ lockedMIDIParameters = Object.fromEntries(Object.keys(DEFAULT_GLOBAL_MIDI_PARAMETERS).map((key) => [key, false]));
19842
+ /**
19453
19843
  * The global MIDI parameters of the synthesizer.
19454
19844
  */
19455
19845
  midiParameters = { ...DEFAULT_GLOBAL_MIDI_PARAMETERS };
@@ -19490,6 +19880,13 @@ var SynthesizerCore = class {
19490
19880
  */
19491
19881
  cachedVoices = /* @__PURE__ */ new Map();
19492
19882
  /**
19883
+ * Locks or unlocks a given Global MIDI Parameter.
19884
+ * This prevents any changes to it until it's unlocked.
19885
+ * @param parameter The Global MIDI Parameter to lock.
19886
+ * @param isLocked If the parameter should be locked.
19887
+ */
19888
+ lockMIDIParameter = lockMIDIParameterInternal.bind(this);
19889
+ /**
19493
19890
  * Sets a system parameter of the synthesizer.
19494
19891
  * @param type The type of the system parameter to set.
19495
19892
  * @param value The value to set for the system parameter.
@@ -19519,7 +19916,6 @@ var SynthesizerCore = class {
19519
19916
  * @param value The value to set for the global MIDI parameter.
19520
19917
  */
19521
19918
  setMIDIParameter = setMIDIParameterInternal.bind(this);
19522
- resetMIDIParameters = resetMIDIParametersInternal.bind(this);
19523
19919
  /**
19524
19920
  * The fallback processor when the requested insertion is not available.
19525
19921
  */
@@ -19737,10 +20133,15 @@ var SynthesizerCore = class {
19737
20133
  * Executes a full system reset of the synthesizer.
19738
20134
  * This will reset all controllers to their default values,
19739
20135
  * except for the locked controllers.
20136
+ * @param system The MIDI system to reset the synthesizer to. Defaults to `gs`.
19740
20137
  */
19741
20138
  reset(system = "gs") {
19742
20139
  this.callEvent("reset", system);
19743
- this.resetMIDIParameters(system);
20140
+ this.setMIDIParameter("system", system);
20141
+ this.setMIDIParameter("gain", 1);
20142
+ this.setMIDIParameter("pan", 0);
20143
+ this.setMIDIParameter("keyShift", 0);
20144
+ this.setMIDIParameter("fineTune", 0);
19744
20145
  this.tunings.fill(-1);
19745
20146
  this.portSelectChannelOffset = 0;
19746
20147
  this.customChannelNumbers = false;
@@ -19756,7 +20157,7 @@ var SynthesizerCore = class {
19756
20157
  this.processSplit([[left, right]], left, right, startIndex, sampleCount);
19757
20158
  }
19758
20159
  /**
19759
- * The main rendering pipeline, renders all voices the processes the effects:
20160
+ * The main rendering pipeline, renders all voices and processes the effects:
19760
20161
  * ```
19761
20162
  * ┌────────────────────────────────┐
19762
20163
  * │ Voice Processor │
@@ -19874,8 +20275,7 @@ var SynthesizerCore = class {
19874
20275
  getInsertionSnapshot() {
19875
20276
  return {
19876
20277
  type: this.insertionProcessor.type,
19877
- params: this.insertionParams.slice(),
19878
- channels: this.midiChannels.map((c) => c.midiParameters.efxAssign)
20278
+ params: this.insertionParams.slice()
19879
20279
  };
19880
20280
  }
19881
20281
  resetInsertionParams() {
@@ -20407,7 +20807,7 @@ var SpessaSynthProcessor = class {
20407
20807
  */
20408
20808
  synthCore;
20409
20809
  /**
20410
- * Tor applying the snapshot after an override sound bank too.
20810
+ * For applying the snapshot after an override sound bank too.
20411
20811
  */
20412
20812
  savedSnapshot;
20413
20813
  /**
@@ -20510,12 +20910,21 @@ var SpessaSynthProcessor = class {
20510
20910
  SpessaLog.warn(`No preset found for ${MIDIPatchTools.toMIDIString(patch)}! Did you forget to add a sound bank?`);
20511
20911
  };
20512
20912
  /**
20913
+ * Locks or unlocks a given Global MIDI Parameter.
20914
+ * This prevents any changes to it until it's unlocked.
20915
+ * @param parameter The Global MIDI Parameter to lock.
20916
+ * @param isLocked If the parameter should be locked.
20917
+ */
20918
+ lockMIDIParameter(parameter, isLocked) {
20919
+ this.synthCore.lockMIDIParameter(parameter, isLocked);
20920
+ }
20921
+ /**
20513
20922
  * Sets a system parameter of the synthesizer.
20514
- * @param type The type of the system parameter to set.
20923
+ * @param parameter The type of the system parameter to set.
20515
20924
  * @param value The value to set for the system parameter.
20516
20925
  */
20517
- setSystemParameter(type, value) {
20518
- this.synthCore.setSystemParameter(type, value);
20926
+ setSystemParameter(parameter, value) {
20927
+ this.synthCore.setSystemParameter(parameter, value);
20519
20928
  }
20520
20929
  /**
20521
20930
  * Executes a full synthesizer reset.