spessasynth_core 4.3.5 → 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.d.ts +622 -425
- package/dist/index.js +833 -370
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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
|
-
|
|
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: "
|
|
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: "
|
|
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
|
|
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
|
|
1838
|
+
return MIDIMessage.systemExclusive(ticks, this.gs(a1, a2, a3, data));
|
|
1661
1839
|
}
|
|
1662
1840
|
/**
|
|
1663
|
-
* Gets
|
|
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
|
+
];
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Gets a XG System Exclusive MIDI message.
|
|
1664
1861
|
* @param ticks The tick time of the message.
|
|
1665
|
-
* @param
|
|
1666
|
-
* @param
|
|
1667
|
-
*
|
|
1862
|
+
* @param a1 Address 1
|
|
1863
|
+
* @param a2 Address 2
|
|
1864
|
+
* @param a3 Address 3
|
|
1865
|
+
* @param data Data, can be multiple bytes.
|
|
1866
|
+
*/
|
|
1867
|
+
static xgMessage(ticks, a1, a2, a3, data) {
|
|
1868
|
+
return MIDIMessage.systemExclusive(ticks, this.xg(a1, a2, a3, data));
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
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.
|
|
1668
1874
|
*/
|
|
1669
|
-
static
|
|
1670
|
-
|
|
1671
|
-
|
|
1875
|
+
static deviceControl(subID, data) {
|
|
1876
|
+
return [
|
|
1877
|
+
127,
|
|
1878
|
+
127,
|
|
1879
|
+
4,
|
|
1880
|
+
subID,
|
|
1881
|
+
...data,
|
|
1882
|
+
247
|
|
1883
|
+
];
|
|
1672
1884
|
}
|
|
1673
1885
|
/**
|
|
1674
|
-
* Gets a
|
|
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
|
|
1678
|
-
return
|
|
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: "
|
|
1686
|
-
|
|
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: "
|
|
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 {
|
|
1714
|
-
|
|
1715
|
-
|
|
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: "
|
|
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: "
|
|
1999
|
+
type: "Global MIDI Param",
|
|
2000
|
+
parameter: "keyShift",
|
|
1732
2001
|
value: data - 64
|
|
1733
2002
|
};
|
|
1734
2003
|
case 126:
|
|
1735
|
-
case 127: return {
|
|
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: "
|
|
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[
|
|
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 {
|
|
1868
|
-
|
|
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: "
|
|
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: "
|
|
2199
|
+
type: "Channel MIDI Param",
|
|
1899
2200
|
channel,
|
|
1900
|
-
|
|
1901
|
-
value:
|
|
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: "
|
|
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: "
|
|
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 {
|
|
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 "
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
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,
|
|
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.
|
|
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 === "
|
|
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 "
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
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 "
|
|
2522
|
-
|
|
2523
|
-
|
|
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;
|
|
@@ -2585,13 +2903,13 @@ function getNoteTimesInternal(midi, minDrumLength = 0) {
|
|
|
2585
2903
|
const unfinishedNotes = [];
|
|
2586
2904
|
for (let i = 0; i < 16; i++) unfinishedNotes.push(/* @__PURE__ */ new Map());
|
|
2587
2905
|
const noteOff = (midiNote, channel) => {
|
|
2588
|
-
const
|
|
2589
|
-
|
|
2906
|
+
const noteIndexes = unfinishedNotes[channel].get(midiNote);
|
|
2907
|
+
if (noteIndexes === void 0) return;
|
|
2908
|
+
const noteIndex = noteIndexes.shift();
|
|
2590
2909
|
if (noteIndex === void 0) return;
|
|
2591
2910
|
const note = noteTimes[channel][noteIndex];
|
|
2592
2911
|
const time = elapsedTime - note.start;
|
|
2593
2912
|
note.length = channel === 9 ? Math.max(time, minDrumLength) : time;
|
|
2594
|
-
ch.delete(midiNote);
|
|
2595
2913
|
unfinished--;
|
|
2596
2914
|
};
|
|
2597
2915
|
const { timeline, tracks } = midi;
|
|
@@ -2606,7 +2924,6 @@ function getNoteTimesInternal(midi, minDrumLength = 0) {
|
|
|
2606
2924
|
const velocity = event.data[1];
|
|
2607
2925
|
if (velocity === 0) noteOff(midiNote, channel);
|
|
2608
2926
|
else {
|
|
2609
|
-
noteOff(midiNote, channel);
|
|
2610
2927
|
const noteTime = {
|
|
2611
2928
|
midiNote,
|
|
2612
2929
|
start: elapsedTime,
|
|
@@ -2615,14 +2932,17 @@ function getNoteTimesInternal(midi, minDrumLength = 0) {
|
|
|
2615
2932
|
};
|
|
2616
2933
|
const times = noteTimes[channel];
|
|
2617
2934
|
times.push(noteTime);
|
|
2618
|
-
unfinishedNotes[channel]
|
|
2935
|
+
const unfinishedChannel = unfinishedNotes[channel];
|
|
2936
|
+
if (!unfinishedChannel.has(midiNote)) unfinishedChannel.set(midiNote, []);
|
|
2937
|
+
unfinishedNotes[channel].get(midiNote)?.push(times.length - 1);
|
|
2619
2938
|
unfinished++;
|
|
2620
2939
|
}
|
|
2621
2940
|
} else if (event.statusByte === 81) oneTickToSeconds = 60 / (getTempo(event) * midi.timeDivision);
|
|
2622
2941
|
if (++i >= timeline.length) break;
|
|
2623
|
-
|
|
2942
|
+
const nextTimeline = timeline[i];
|
|
2943
|
+
elapsedTime += oneTickToSeconds * (tracks[nextTimeline.tr].events[nextTimeline.ev].ticks - event.ticks);
|
|
2624
2944
|
}
|
|
2625
|
-
if (unfinished > 0) for (let channel = 0; channel < unfinishedNotes.length; channel++) for (const
|
|
2945
|
+
if (unfinished > 0) for (let channel = 0; channel < unfinishedNotes.length; channel++) for (const noteIndexes of unfinishedNotes[channel].values()) for (const noteIndex of noteIndexes) {
|
|
2626
2946
|
const note = noteTimes[channel][noteIndex];
|
|
2627
2947
|
note.length = elapsedTime - note.start;
|
|
2628
2948
|
}
|
|
@@ -2660,9 +2980,6 @@ const delayAddressMap = {
|
|
|
2660
2980
|
feedback: 89,
|
|
2661
2981
|
sendLevelToReverb: 90
|
|
2662
2982
|
};
|
|
2663
|
-
function getControllerChange(channel, cc, value, ticks) {
|
|
2664
|
-
return new MIDIMessage(ticks, MIDIMessageTypes.controllerChange | channel % 16, new IndexedByteArray([cc, value]));
|
|
2665
|
-
}
|
|
2666
2983
|
/**
|
|
2667
2984
|
* Allows easy editing of the file by removing channels, changing programs,
|
|
2668
2985
|
* changing controllers and transposing channels. Note that this modifies the MIDI in-place.
|
|
@@ -2681,8 +2998,8 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2681
2998
|
const channelChanges = /* @__PURE__ */ new Map();
|
|
2682
2999
|
if (channels) for (const [channel, ch] of channels) if (ch === "clear") clearedChannels.add(channel);
|
|
2683
3000
|
else channelChanges.set(channel, ch);
|
|
2684
|
-
let system = "gs";
|
|
2685
|
-
let
|
|
3001
|
+
let system = (opts.midiParams?.system === "clear" ? void 0 : opts.midiParams?.system) ?? "gs";
|
|
3002
|
+
let addedReset = false;
|
|
2686
3003
|
let resetTrack = 0;
|
|
2687
3004
|
let resetIndex = 0;
|
|
2688
3005
|
/**
|
|
@@ -2718,7 +3035,9 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2718
3035
|
data: true
|
|
2719
3036
|
},
|
|
2720
3037
|
keyShift: channelChanges.get(i)?.keyShift ?? 0,
|
|
2721
|
-
fineTune: channelChanges.get(i)?.fineTune ?? 0
|
|
3038
|
+
fineTune: channelChanges.get(i)?.fineTune ?? 0,
|
|
3039
|
+
currentFineTune: 0,
|
|
3040
|
+
currentKeyShift: 0
|
|
2722
3041
|
});
|
|
2723
3042
|
midi.iterate((e, trackNum, eventIndexes) => {
|
|
2724
3043
|
const track = midi.tracks[trackNum];
|
|
@@ -2751,10 +3070,18 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2751
3070
|
ch.clearedParams.pLSB = true;
|
|
2752
3071
|
}
|
|
2753
3072
|
};
|
|
2754
|
-
const addEventBefore = (e
|
|
2755
|
-
track.addEvents(index
|
|
3073
|
+
const addEventBefore = (e) => {
|
|
3074
|
+
track.addEvents(index, e);
|
|
2756
3075
|
eventIndexes[trackNum]++;
|
|
2757
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
|
+
};
|
|
2758
3085
|
const portOffset = midiPortChannelOffsets[midiPorts[trackNum]] || 0;
|
|
2759
3086
|
if (e.statusByte === MIDIMessageTypes.midiPort) {
|
|
2760
3087
|
assignMIDIPort(trackNum, e.data[0]);
|
|
@@ -2777,17 +3104,17 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2777
3104
|
channelStatus.isFirstNoteOn = false;
|
|
2778
3105
|
if (channelChange.controllers) for (const [cc, value] of channelChange.controllers) {
|
|
2779
3106
|
if (value === "clear") continue;
|
|
2780
|
-
addEventBefore(
|
|
3107
|
+
addEventBefore(MIDIMessage.controllerChange(e.ticks, midiChannel, cc, value));
|
|
2781
3108
|
}
|
|
2782
|
-
if (
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
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;
|
|
2791
3118
|
}
|
|
2792
3119
|
const patch = channelChange.patch;
|
|
2793
3120
|
if (patch && patch !== "clear") {
|
|
@@ -2795,9 +3122,9 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2795
3122
|
let desiredBankMSB = patch.bankMSB;
|
|
2796
3123
|
let desiredBankLSB = patch.bankLSB;
|
|
2797
3124
|
const desiredProgram = patch.program;
|
|
2798
|
-
addEventBefore(
|
|
3125
|
+
addEventBefore(MIDIMessage.programChange(e.ticks, midiChannel, desiredProgram));
|
|
2799
3126
|
const addBank = (isLSB, v) => {
|
|
2800
|
-
addEventBefore(
|
|
3127
|
+
addEventBefore(MIDIMessage.controllerChange(e.ticks, midiChannel, isLSB ? MIDIControllers.bankSelectLSB : MIDIControllers.bankSelect, v));
|
|
2801
3128
|
};
|
|
2802
3129
|
if (BankSelectHacks.isSystemXG(system) && patch.isGMGSDrum) {
|
|
2803
3130
|
SpessaLog.info(`%cAdding XG Drum change on track %c${trackNum}`, ConsoleColors.recognized, ConsoleColors.value);
|
|
@@ -2808,15 +3135,20 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2808
3135
|
addBank(true, desiredBankLSB);
|
|
2809
3136
|
if (patch.isGMGSDrum && !BankSelectHacks.isSystemXG(system) && midiChannel !== 9) {
|
|
2810
3137
|
SpessaLog.info(`%cAdding GS Drum change on track %c${trackNum}`, ConsoleColors.recognized, ConsoleColors.value);
|
|
2811
|
-
|
|
3138
|
+
const chanAddress = 16 | MIDIUtils.channelToSyx(midiChannel);
|
|
3139
|
+
addEventBefore(MIDIUtils.gsMessage(e.ticks, 40, chanAddress, 21, [1]));
|
|
2812
3140
|
}
|
|
2813
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
|
+
}
|
|
2814
3146
|
}
|
|
2815
|
-
e.data[0] += channelStatus.keyShift;
|
|
3147
|
+
e.data[0] += channelStatus.keyShift + channelStatus.currentKeyShift;
|
|
2816
3148
|
break;
|
|
2817
3149
|
case MIDIMessageTypes.noteOff:
|
|
2818
3150
|
if (!channelChange) break;
|
|
2819
|
-
e.data[0] += channelStatus.keyShift;
|
|
3151
|
+
e.data[0] += channelStatus.keyShift + channelStatus.currentKeyShift;
|
|
2820
3152
|
break;
|
|
2821
3153
|
case MIDIMessageTypes.programChange:
|
|
2822
3154
|
if (channelChange?.patch) {
|
|
@@ -2824,6 +3156,12 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2824
3156
|
return;
|
|
2825
3157
|
}
|
|
2826
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;
|
|
2827
3165
|
case MIDIMessageTypes.controllerChange: {
|
|
2828
3166
|
const ccNum = e.data[0];
|
|
2829
3167
|
const value = e.data[1];
|
|
@@ -2863,17 +3201,17 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2863
3201
|
if ((ccNum === MIDIControllers.bankSelect || ccNum === MIDIControllers.bankSelectLSB) && channelChange?.patch) deleteParameter(channel);
|
|
2864
3202
|
break;
|
|
2865
3203
|
}
|
|
2866
|
-
case "
|
|
2867
|
-
if (channelStatus.fineTune) {
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
}
|
|
2876
|
-
|
|
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;
|
|
2877
3215
|
}
|
|
2878
3216
|
channelStatus.clearedParams.pLSB = true;
|
|
2879
3217
|
channelStatus.clearedParams.pMSB = true;
|
|
@@ -2886,56 +3224,6 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2886
3224
|
const syx = MIDIUtils.analyzeSysEx(e.data);
|
|
2887
3225
|
switch (syx.type) {
|
|
2888
3226
|
default: return;
|
|
2889
|
-
case "XG Reset":
|
|
2890
|
-
SpessaLog.info("%cXG system on detected", ConsoleColors.info);
|
|
2891
|
-
system = "xg";
|
|
2892
|
-
addedGs = true;
|
|
2893
|
-
resetTrack = trackNum;
|
|
2894
|
-
resetIndex = index;
|
|
2895
|
-
for (const ch of channelStatuses) {
|
|
2896
|
-
ch.param.reset();
|
|
2897
|
-
ch.clearedParams = {
|
|
2898
|
-
pLSB: true,
|
|
2899
|
-
pMSB: true,
|
|
2900
|
-
data: true
|
|
2901
|
-
};
|
|
2902
|
-
}
|
|
2903
|
-
return;
|
|
2904
|
-
case "GM2 On":
|
|
2905
|
-
SpessaLog.info("%cGM2 system on detected", ConsoleColors.info);
|
|
2906
|
-
system = "gm2";
|
|
2907
|
-
addedGs = true;
|
|
2908
|
-
resetTrack = trackNum;
|
|
2909
|
-
resetIndex = index;
|
|
2910
|
-
for (const ch of channelStatuses) {
|
|
2911
|
-
ch.param.reset();
|
|
2912
|
-
ch.clearedParams = {
|
|
2913
|
-
pLSB: true,
|
|
2914
|
-
pMSB: true,
|
|
2915
|
-
data: true
|
|
2916
|
-
};
|
|
2917
|
-
}
|
|
2918
|
-
return;
|
|
2919
|
-
case "GS Reset":
|
|
2920
|
-
SpessaLog.info("%cGS on detected!", ConsoleColors.recognized);
|
|
2921
|
-
addedGs = true;
|
|
2922
|
-
resetTrack = trackNum;
|
|
2923
|
-
resetIndex = index;
|
|
2924
|
-
for (const ch of channelStatuses) {
|
|
2925
|
-
ch.param.reset();
|
|
2926
|
-
ch.clearedParams = {
|
|
2927
|
-
pLSB: true,
|
|
2928
|
-
pMSB: true,
|
|
2929
|
-
data: true
|
|
2930
|
-
};
|
|
2931
|
-
}
|
|
2932
|
-
return;
|
|
2933
|
-
case "GM Off":
|
|
2934
|
-
case "GM On":
|
|
2935
|
-
SpessaLog.info("%cGM on detected, removing!", ConsoleColors.info);
|
|
2936
|
-
deleteThisEvent();
|
|
2937
|
-
addedGs = false;
|
|
2938
|
-
return;
|
|
2939
3227
|
case "Drum Setup":
|
|
2940
3228
|
if (clearDrumParams) deleteThisEvent();
|
|
2941
3229
|
return;
|
|
@@ -2954,15 +3242,79 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2954
3242
|
case "Program Change":
|
|
2955
3243
|
if (channelChanges.get(syx.channel + portOffset)?.patch) deleteThisEvent();
|
|
2956
3244
|
return;
|
|
2957
|
-
case "
|
|
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": {
|
|
2958
3303
|
const syxChannel = channelChanges.get(syx.channel + portOffset);
|
|
2959
|
-
|
|
2960
|
-
if (syxStatus.isFirstNoteOn && syxChannel) {
|
|
2961
|
-
const newTune = syxStatus.fineTune + syx.value;
|
|
2962
|
-
syxStatus.keyShift += Math.trunc(newTune / 100);
|
|
2963
|
-
syxStatus.fineTune = newTune % 100;
|
|
2964
|
-
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]) {
|
|
2965
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;
|
|
2966
3318
|
}
|
|
2967
3319
|
break;
|
|
2968
3320
|
}
|
|
@@ -2980,17 +3332,25 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
2980
3332
|
}
|
|
2981
3333
|
}
|
|
2982
3334
|
});
|
|
2983
|
-
if (!
|
|
3335
|
+
if (!addedReset && [...channelChanges.values()].some((c) => c.patch && c.patch !== "clear")) {
|
|
2984
3336
|
let index = 0;
|
|
2985
3337
|
if (midi.tracks[0].events[0].statusByte === MIDIMessageTypes.trackName) index++;
|
|
2986
|
-
|
|
3338
|
+
const targetSystem = (opts.midiParams?.system === "clear" ? void 0 : opts.midiParams?.system) ?? "gs";
|
|
3339
|
+
midi.tracks[0].addEvents(index, MIDIUtils.reset(0, targetSystem));
|
|
2987
3340
|
resetTrack = 0;
|
|
2988
3341
|
resetIndex = index;
|
|
2989
|
-
|
|
3342
|
+
system = targetSystem;
|
|
3343
|
+
SpessaLog.info(`%c${targetSystem} reset on not detected. Adding it.`, ConsoleColors.info);
|
|
2990
3344
|
}
|
|
2991
3345
|
const targetTicks = Math.max(0, midi.firstNoteOn);
|
|
2992
3346
|
const targetTrack = midi.tracks[resetTrack];
|
|
2993
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
|
+
}
|
|
2994
3354
|
if (reverbParams && reverbParams !== "clear") {
|
|
2995
3355
|
const m = reverbAddressMap;
|
|
2996
3356
|
const p = reverbParams;
|
|
@@ -3008,7 +3368,6 @@ function modifyMIDIInternal(midi, opts) {
|
|
|
3008
3368
|
}
|
|
3009
3369
|
if (insertionParams && insertionParams !== "clear") {
|
|
3010
3370
|
const p = insertionParams;
|
|
3011
|
-
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]));
|
|
3012
3371
|
for (let param = 0; param < p.params.length; param++) {
|
|
3013
3372
|
const value = p.params[param];
|
|
3014
3373
|
if (value === 255) continue;
|
|
@@ -3612,8 +3971,10 @@ function loadXMF(midi, binaryData, fileName) {
|
|
|
3612
3971
|
//#endregion
|
|
3613
3972
|
//#region src/midi/midi_tools/apply_snapshot.ts
|
|
3614
3973
|
/**
|
|
3615
|
-
* Modifies the sequence according to the locked presets and controllers in the given snapshot.
|
|
3616
|
-
*
|
|
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.
|
|
3617
3978
|
*/
|
|
3618
3979
|
function applySnapshotInternal(midi, snapshot) {
|
|
3619
3980
|
const channels = /* @__PURE__ */ new Map();
|
|
@@ -3635,20 +3996,26 @@ function applySnapshotInternal(midi, snapshot) {
|
|
|
3635
3996
|
const targetValue = channelSnapshot.midiControllers[ccNumber] >> 7;
|
|
3636
3997
|
controllers.set(ccNumber, targetValue);
|
|
3637
3998
|
}
|
|
3999
|
+
const midiParams = {};
|
|
4000
|
+
for (const [parameter, value] of Object.entries(channelSnapshot.midiParameters)) if (channelSnapshot.lockedMIDIParameters[parameter]) midiParams[parameter] = value;
|
|
3638
4001
|
channels.set(channelNumber, {
|
|
3639
4002
|
keyShift,
|
|
3640
4003
|
fineTune,
|
|
3641
4004
|
patch,
|
|
3642
|
-
controllers
|
|
4005
|
+
controllers,
|
|
4006
|
+
midiParams
|
|
3643
4007
|
});
|
|
3644
4008
|
}
|
|
4009
|
+
const midiParams = {};
|
|
4010
|
+
for (const [parameter, value] of Object.entries(snapshot.midiParameters)) if (snapshot.lockedMIDIParameters[parameter]) midiParams[parameter] = value;
|
|
3645
4011
|
midi.modify({
|
|
3646
4012
|
channels,
|
|
3647
4013
|
drumSetupParams: snapshot.systemParameters.drumLock ? "clear" : void 0,
|
|
3648
4014
|
reverbParams: snapshot.systemParameters.reverbLock ? snapshot.reverbProcessor : void 0,
|
|
3649
4015
|
chorusParams: snapshot.systemParameters.chorusLock ? snapshot.chorusProcessor : void 0,
|
|
3650
4016
|
delayParams: snapshot.systemParameters.delayLock ? snapshot.delayProcessor : void 0,
|
|
3651
|
-
insertionParams: snapshot.systemParameters.insertionEffectLock ? snapshot.insertionProcessor : void 0
|
|
4017
|
+
insertionParams: snapshot.systemParameters.insertionEffectLock ? snapshot.insertionProcessor : void 0,
|
|
4018
|
+
midiParams
|
|
3652
4019
|
});
|
|
3653
4020
|
}
|
|
3654
4021
|
//#endregion
|
|
@@ -3785,7 +4152,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
3785
4152
|
* It supports Standard MIDI Files (SMF), RIFF MIDI (RMIDI), and Extensible Music Format (XMF).
|
|
3786
4153
|
* It also handles embedded soundbanks in RMIDI files.
|
|
3787
4154
|
* If the file is an RMIDI file, it will extract the embedded soundbank and store
|
|
3788
|
-
* it in the `
|
|
4155
|
+
* it in the `embeddedSoundBank` property of the BasicMIDI instance.
|
|
3789
4156
|
* If the file is an XMF file, it will parse the XMF structure and extract the MIDI data.
|
|
3790
4157
|
*/
|
|
3791
4158
|
static fromArrayBuffer(arrayBuffer, fileName = "") {
|
|
@@ -3945,7 +4312,9 @@ var BasicMIDI = class BasicMIDI {
|
|
|
3945
4312
|
}
|
|
3946
4313
|
/**
|
|
3947
4314
|
* Modifies the sequence *in-place* according to the locked presets and controllers in the given snapshot.
|
|
3948
|
-
*
|
|
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.
|
|
3949
4318
|
* @param snapshot the snapshot to apply.
|
|
3950
4319
|
*/
|
|
3951
4320
|
applySnapshot(snapshot) {
|
|
@@ -4130,15 +4499,15 @@ var BasicMIDI = class BasicMIDI {
|
|
|
4130
4499
|
switch (e.statusByte & 240) {
|
|
4131
4500
|
case MIDIMessageTypes.controllerChange:
|
|
4132
4501
|
switch (e.data[0]) {
|
|
4133
|
-
case
|
|
4134
|
-
case
|
|
4502
|
+
case MIDIControllers.breathController:
|
|
4503
|
+
case MIDIControllers.undefinedCC111LSB:
|
|
4135
4504
|
if (e.data[1] === 0) loopStart = e.ticks;
|
|
4136
4505
|
break;
|
|
4137
|
-
case
|
|
4506
|
+
case MIDIControllers.undefinedCC116LSB:
|
|
4138
4507
|
loopStart = e.ticks;
|
|
4139
4508
|
break;
|
|
4140
|
-
case
|
|
4141
|
-
case
|
|
4509
|
+
case MIDIControllers.footController:
|
|
4510
|
+
case MIDIControllers.undefinedCC117LSB:
|
|
4142
4511
|
if (loopEnd === null && (e.data[0] !== 4 || e.data[0] === 4 && e.data[1] === 0)) {
|
|
4143
4512
|
loopType = "soft";
|
|
4144
4513
|
loopEnd = e.ticks;
|
|
@@ -4147,7 +4516,7 @@ var BasicMIDI = class BasicMIDI {
|
|
|
4147
4516
|
loopType = "hard";
|
|
4148
4517
|
}
|
|
4149
4518
|
break;
|
|
4150
|
-
case
|
|
4519
|
+
case MIDIControllers.bankSelect: if (this.isDLSRMIDI && e.data[1] !== 0 && e.data[1] !== 127) {
|
|
4151
4520
|
SpessaLog.info("%cDLS RMIDI with offset 1 detected!", ConsoleColors.recognized);
|
|
4152
4521
|
this.bankOffset = 1;
|
|
4153
4522
|
}
|
|
@@ -4664,9 +5033,6 @@ function loadNewSequenceInternal(parsedMIDI) {
|
|
|
4664
5033
|
}
|
|
4665
5034
|
//#endregion
|
|
4666
5035
|
//#region src/synthesizer/audio_engine/channel/reset.ts
|
|
4667
|
-
function resetPortamento() {
|
|
4668
|
-
this.lastNote = this.channelSystem === "xg" ? 60 : -1;
|
|
4669
|
-
}
|
|
4670
5036
|
/**
|
|
4671
5037
|
* An array with the default MIDI controller values.
|
|
4672
5038
|
* Note that these are 14-bit, requiring a 7-bit shift to the right for 7-bit values!
|
|
@@ -4712,27 +5078,30 @@ function resetChannelInternal(sendCCEvents = true) {
|
|
|
4712
5078
|
const resetValue = DEFAULT_MIDI_CONTROLLERS[cc];
|
|
4713
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);
|
|
4714
5080
|
}
|
|
4715
|
-
|
|
5081
|
+
this.setMIDIParameter("pressure", 0);
|
|
5082
|
+
this.setMIDIParameter("pitchWheelRange", 2);
|
|
5083
|
+
this.setMIDIParameter("modulationDepth", 50);
|
|
4716
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);
|
|
4717
5089
|
this.setMIDIParameter("assignMode", 2);
|
|
4718
5090
|
this.setMIDIParameter("randomPan", false);
|
|
4719
5091
|
this.setMIDIParameter("cc1", 16);
|
|
4720
5092
|
this.setMIDIParameter("cc2", 17);
|
|
4721
5093
|
this.setMIDIParameter("drumMap", this.channel % 16 === 9 ? 1 : 0);
|
|
5094
|
+
this.setMIDIParameter("velocitySenseOffset", 64);
|
|
5095
|
+
this.setMIDIParameter("velocitySenseDepth", 64);
|
|
4722
5096
|
this.pitchWheel(8192);
|
|
4723
|
-
this.pitchWheelRange(2, false);
|
|
4724
|
-
this.keyShift(0, false);
|
|
4725
|
-
this.fineTune(0, false);
|
|
4726
|
-
this.setMIDIParameter("pressure", 0);
|
|
4727
|
-
this.modulationDepth(50, false);
|
|
4728
|
-
if (!this.lockedControllers[MIDIControllers.monoModeOn] && !this.lockedControllers[MIDIControllers.polyModeOn]) this.setMIDIParameter("polyMode", true);
|
|
4729
5097
|
this.octaveTuning.fill(0);
|
|
4730
|
-
|
|
5098
|
+
this.lastPortamentoNote = this.channelSystem === "xg" ? 60 : -1;
|
|
4731
5099
|
this.resetDrumParams();
|
|
4732
5100
|
this.resetGeneratorOverrides();
|
|
4733
5101
|
this.resetGeneratorOffsets();
|
|
4734
5102
|
this.dynamicModulators.resetModulators();
|
|
4735
5103
|
this.sf2NRPNGeneratorLSB = 0;
|
|
5104
|
+
this.playingNotes.fill(false);
|
|
4736
5105
|
this.lastParameterIsRegistered = true;
|
|
4737
5106
|
this._midiControllers[MIDIControllers.nonRegisteredParameterLSB] = 0;
|
|
4738
5107
|
this._midiControllers[MIDIControllers.nonRegisteredParameterMSB] = 0;
|
|
@@ -5265,7 +5634,7 @@ var SpessaSynthSequencer = class {
|
|
|
5265
5634
|
this.synth.reset();
|
|
5266
5635
|
return;
|
|
5267
5636
|
}
|
|
5268
|
-
this.sendMIDISysEx(MIDIUtils.
|
|
5637
|
+
this.sendMIDISysEx(MIDIUtils.gs(64, 0, 127, [0]));
|
|
5269
5638
|
}
|
|
5270
5639
|
loadCurrentSong() {
|
|
5271
5640
|
let index = this._songIndex;
|
|
@@ -6014,15 +6383,16 @@ function applySnapshot$1(snapshot) {
|
|
|
6014
6383
|
for (const [key, value] of Object.entries(this.chorusProcessor)) this.chorusProcessor[key] = value;
|
|
6015
6384
|
for (const [key, value] of Object.entries(this.delayProcessor)) this.delayProcessor[key] = value;
|
|
6016
6385
|
const is = snapshot.insertionProcessor;
|
|
6017
|
-
this.systemExclusive(MIDIUtils.
|
|
6018
|
-
for (let i = 0; i < is.params.length; i++) if (is.params[i] !== 255) this.systemExclusive(MIDIUtils.
|
|
6019
|
-
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]]));
|
|
6020
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);
|
|
6021
6390
|
for (const [parameter, value] of Object.entries(snapshot.systemParameters)) this.setSystemParameter(parameter, value);
|
|
6022
6391
|
}
|
|
6023
6392
|
function getSynthesizerSnapshot() {
|
|
6024
6393
|
return {
|
|
6025
6394
|
midiParameters: { ...this.midiParameters },
|
|
6395
|
+
lockedMIDIParameters: { ...this.lockedMIDIParameters },
|
|
6026
6396
|
systemParameters: { ...this.systemParameters },
|
|
6027
6397
|
midiChannels: this.midiChannels.map((c) => c.getSnapshot()),
|
|
6028
6398
|
keyMappings: this.keyModifierManager.getMappings(),
|
|
@@ -6229,7 +6599,7 @@ var LowpassFilter = class LowpassFilter {
|
|
|
6229
6599
|
LowpassFilter.smoothingConstant = FILTER_SMOOTHING_FACTOR * (44100 / sampleRate);
|
|
6230
6600
|
const dummy = new LowpassFilter(sampleRate);
|
|
6231
6601
|
dummy.resonanceCb = 0;
|
|
6232
|
-
for (let i =
|
|
6602
|
+
for (let i = 8e3; i < 13500; i++) {
|
|
6233
6603
|
dummy.currentInitialFc = i;
|
|
6234
6604
|
dummy.calculateCoefficients(i);
|
|
6235
6605
|
}
|
|
@@ -7389,6 +7759,11 @@ var Voice = class {
|
|
|
7389
7759
|
*/
|
|
7390
7760
|
channel = 0;
|
|
7391
7761
|
/**
|
|
7762
|
+
* Grouping voices for specific Note On messages.
|
|
7763
|
+
* Used for overlapping Note Ons.
|
|
7764
|
+
*/
|
|
7765
|
+
noteID = 0;
|
|
7766
|
+
/**
|
|
7392
7767
|
* MIDI note number of the voice.
|
|
7393
7768
|
* Direct number from the Note On message and is
|
|
7394
7769
|
* used for Note Off and external parameters:
|
|
@@ -7511,7 +7886,7 @@ var Voice = class {
|
|
|
7511
7886
|
this.releaseStartTime = currentTime;
|
|
7512
7887
|
if (this.releaseStartTime - this.startTime < minNoteLength) this.releaseStartTime = this.startTime + minNoteLength;
|
|
7513
7888
|
}
|
|
7514
|
-
setup(currentTime, channel, midiNote) {
|
|
7889
|
+
setup(currentTime, channel, midiNote, noteID) {
|
|
7515
7890
|
this.isActive = true;
|
|
7516
7891
|
this.isInRelease = false;
|
|
7517
7892
|
this.hasRendered = false;
|
|
@@ -7526,6 +7901,7 @@ var Voice = class {
|
|
|
7526
7901
|
this.startTime = currentTime;
|
|
7527
7902
|
this.channel = channel;
|
|
7528
7903
|
this.midiNote = midiNote;
|
|
7904
|
+
this.noteID = noteID;
|
|
7529
7905
|
}
|
|
7530
7906
|
};
|
|
7531
7907
|
//#endregion
|
|
@@ -7548,7 +7924,7 @@ var ModulatorSource = class ModulatorSource {
|
|
|
7548
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
|
|
7549
7925
|
* called Unipolar. Thus, it behaves similar to the Modulation Wheel controller of the MIDI specification.
|
|
7550
7926
|
*
|
|
7551
|
-
* If this field is set to true, the controller
|
|
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
|
|
7552
7928
|
* called Bipolar. Thus, it behaves similar to the Pitch Wheel controller of the MIDI specification.
|
|
7553
7929
|
*/
|
|
7554
7930
|
isBipolar;
|
|
@@ -8879,7 +9255,8 @@ var SoundFontSample = class extends BasicSample {
|
|
|
8879
9255
|
}
|
|
8880
9256
|
const audioData = new Float32Array(byteLength / 2);
|
|
8881
9257
|
const convertedSigned16 = new Int16Array(this.s16leData.buffer);
|
|
8882
|
-
|
|
9258
|
+
const l = convertedSigned16.length;
|
|
9259
|
+
for (let i = 0; i < l; i++) audioData[i] = convertedSigned16[i] / 32768;
|
|
8883
9260
|
this.audioData = audioData;
|
|
8884
9261
|
return audioData;
|
|
8885
9262
|
}
|
|
@@ -9421,7 +9798,8 @@ function readPCM(data, bytesPerSample) {
|
|
|
9421
9798
|
const sampleData = new Float32Array(sampleLength);
|
|
9422
9799
|
if (bytesPerSample === 2) {
|
|
9423
9800
|
const s16 = new Int16Array(data.buffer);
|
|
9424
|
-
|
|
9801
|
+
const s16l = s16.length;
|
|
9802
|
+
for (let i = 0; i < s16l; i++) sampleData[i] = s16[i] / 32768;
|
|
9425
9803
|
} else for (let i = 0; i < sampleData.length; i++) {
|
|
9426
9804
|
let sample = readLittleEndianIndexed(data, bytesPerSample);
|
|
9427
9805
|
if (isUnsigned) sampleData[i] = sample / normalizationFactor - .5;
|
|
@@ -12170,7 +12548,7 @@ function controllerChange(controller, value, sendEvent = true) {
|
|
|
12170
12548
|
}
|
|
12171
12549
|
break;
|
|
12172
12550
|
case MIDIControllers.portamentoControl:
|
|
12173
|
-
this.
|
|
12551
|
+
this.lastPortamentoNote = value;
|
|
12174
12552
|
this.portamentoForce = true;
|
|
12175
12553
|
break;
|
|
12176
12554
|
default:
|
|
@@ -12329,22 +12707,30 @@ function dataEntry() {
|
|
|
12329
12707
|
default:
|
|
12330
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);
|
|
12331
12709
|
break;
|
|
12332
|
-
case RegisteredParameterTypes.pitchWheelRange:
|
|
12333
|
-
|
|
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");
|
|
12334
12714
|
break;
|
|
12715
|
+
}
|
|
12335
12716
|
case RegisteredParameterTypes.coarseTuning: {
|
|
12336
12717
|
const semitones = (dataValue >> 7) - 64;
|
|
12337
|
-
this.keyShift
|
|
12718
|
+
this.setMIDIParameter("keyShift", semitones);
|
|
12719
|
+
SpessaLog.coolInfo(`Key shift for ${this.channel}`, semitones);
|
|
12338
12720
|
break;
|
|
12339
12721
|
}
|
|
12340
12722
|
case RegisteredParameterTypes.fineTuning: {
|
|
12341
|
-
const
|
|
12342
|
-
this.fineTune
|
|
12723
|
+
const cents = (dataValue - 8192) / 81.92;
|
|
12724
|
+
this.setMIDIParameter("fineTune", cents);
|
|
12725
|
+
SpessaLog.coolInfo(`Fine tuning for ${this.channel}`, Math.round(cents), "cents");
|
|
12343
12726
|
break;
|
|
12344
12727
|
}
|
|
12345
|
-
case RegisteredParameterTypes.modulationDepth:
|
|
12346
|
-
|
|
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");
|
|
12347
12732
|
break;
|
|
12733
|
+
}
|
|
12348
12734
|
case RegisteredParameterTypes.resetParameters: break;
|
|
12349
12735
|
}
|
|
12350
12736
|
return;
|
|
@@ -12583,15 +12969,16 @@ const clamp = (num, min, max) => Math.max(min, Math.min(max, num));
|
|
|
12583
12969
|
* Sends a "MIDI Note on" message and starts a note.
|
|
12584
12970
|
* @param midiNote The MIDI note number (0-127).
|
|
12585
12971
|
* @param velocity The velocity of the note (0-127). If less than 1, it will send a note off instead.
|
|
12972
|
+
* @param emit If the note on should be updated and emitted (non-internal)
|
|
12586
12973
|
*/
|
|
12587
|
-
function noteOn(midiNote, velocity) {
|
|
12974
|
+
function noteOn(midiNote, velocity, emit = true) {
|
|
12588
12975
|
if (velocity < 1) {
|
|
12589
12976
|
this.noteOff(midiNote);
|
|
12590
12977
|
return;
|
|
12591
12978
|
}
|
|
12592
|
-
velocity = clamp(velocity, 0, 127);
|
|
12593
12979
|
const black = this.synthCore.systemParameters.blackMIDIMode;
|
|
12594
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);
|
|
12595
12982
|
let soundBankNote = midiNote + this.currentKeyShift;
|
|
12596
12983
|
if (midiNote > 127 || midiNote < 0) return;
|
|
12597
12984
|
const program = this.preset.program;
|
|
@@ -12599,9 +12986,9 @@ function noteOn(midiNote, velocity) {
|
|
|
12599
12986
|
if (tune >= 0) soundBankNote = Math.trunc(tune);
|
|
12600
12987
|
if ((this._systemParameters.monophonicRetrigger ?? this.synthCore.systemParameters.monophonicRetrigger) || this._midiParameters.assignMode === 0) this.killNote(midiNote);
|
|
12601
12988
|
const keyVel = this.synthCore.keyModifierManager.getVelocity(this.channel, midiNote);
|
|
12602
|
-
if (keyVel > -1)
|
|
12989
|
+
if (keyVel > -1) realVelocity = keyVel;
|
|
12603
12990
|
let voiceGain = this.synthCore.keyModifierManager.getGain(this.channel, midiNote);
|
|
12604
|
-
const previousNote = this.
|
|
12991
|
+
const previousNote = this.lastPortamentoNote;
|
|
12605
12992
|
const portamentoEnabled = this.portamentoForce || this._midiControllers[MIDIControllers.portamentoOnOff] >= 8192;
|
|
12606
12993
|
const portamentoTime = this._midiControllers[MIDIControllers.portamentoTime] >> 7;
|
|
12607
12994
|
const canApplyPortamento = portamentoEnabled && !this._drumChannel && previousNote >= 0 && previousNote !== midiNote && portamentoTime > 0;
|
|
@@ -12613,17 +13000,14 @@ function noteOn(midiNote, velocity) {
|
|
|
12613
13000
|
portaTime = portamentoTimeToSeconds(portamentoTime, keyDistance);
|
|
12614
13001
|
this.portamentoForce = false;
|
|
12615
13002
|
}
|
|
12616
|
-
this.
|
|
13003
|
+
this.lastPortamentoNote = midiNote;
|
|
13004
|
+
this.playingNotes[midiNote] = true;
|
|
12617
13005
|
if (!this._midiParameters.polyMode) {
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12621
|
-
v.exclusiveRelease(this.synthCore.currentTime, 0);
|
|
12622
|
-
if (++vc >= this._voiceCount) break;
|
|
12623
|
-
}
|
|
12624
|
-
}
|
|
13006
|
+
if (this.lastMonoNote >= 0 && this.lastMonoNote !== midiNote) this.killNote(this.lastMonoNote);
|
|
13007
|
+
this.lastMonoNote = midiNote;
|
|
13008
|
+
this.lastMonoVelocity = velocity;
|
|
12625
13009
|
}
|
|
12626
|
-
const voices = this.synthCore.getVoices(this.channel, soundBankNote,
|
|
13010
|
+
const voices = this.synthCore.getVoices(this.channel, soundBankNote, realVelocity);
|
|
12627
13011
|
let panOverride = 0;
|
|
12628
13012
|
let exclusiveOverride = 0;
|
|
12629
13013
|
let pitchOffset = 0;
|
|
@@ -12647,10 +13031,11 @@ function noteOn(midiNote, velocity) {
|
|
|
12647
13031
|
delaySend = p.delayGain;
|
|
12648
13032
|
if (voiceGain === 1) voiceGain = p.gain;
|
|
12649
13033
|
}
|
|
13034
|
+
const noteID = emit ? this.noteOnID[midiNote]++ : this.noteOnID[midiNote];
|
|
12650
13035
|
for (const cached of voices) {
|
|
12651
13036
|
const voice = this.synthCore.assignVoice();
|
|
12652
13037
|
const now = this.synthCore.currentTime;
|
|
12653
|
-
voice.setup(now, this.channel, midiNote);
|
|
13038
|
+
voice.setup(now, this.channel, midiNote, noteID);
|
|
12654
13039
|
voice.wavetable = voice.oscillators[this._systemParameters.interpolationType ?? this.synthCore.systemParameters.interpolationType];
|
|
12655
13040
|
voice.targetKey = cached.targetKey;
|
|
12656
13041
|
voice.velocity = cached.velocity;
|
|
@@ -12723,7 +13108,7 @@ function noteOn(midiNote, velocity) {
|
|
|
12723
13108
|
voice.currentPan = Math.max(-500, Math.min(500, panOverride || voice.modulatedGenerators[GeneratorTypes.pan]));
|
|
12724
13109
|
}
|
|
12725
13110
|
this._voiceCount += voices.length;
|
|
12726
|
-
this.synthCore.callEvent("noteOn", {
|
|
13111
|
+
if (emit) this.synthCore.callEvent("noteOn", {
|
|
12727
13112
|
midiNote,
|
|
12728
13113
|
channel: this.channel,
|
|
12729
13114
|
velocity
|
|
@@ -12747,10 +13132,14 @@ function noteOff(midiNote) {
|
|
|
12747
13132
|
});
|
|
12748
13133
|
return;
|
|
12749
13134
|
}
|
|
12750
|
-
|
|
13135
|
+
this.playingNotes[midiNote] = false;
|
|
13136
|
+
const mono = !this._midiParameters.polyMode;
|
|
13137
|
+
const sustain = this._midiControllers[MIDIControllers.sustainPedal] >= 8192 && !mono;
|
|
12751
13138
|
let vc = 0;
|
|
13139
|
+
const noteID = this.noteOffID[midiNote];
|
|
13140
|
+
if (noteID < this.noteOnID[midiNote]) this.noteOffID[midiNote]++;
|
|
12752
13141
|
if (this._voiceCount > 0) {
|
|
12753
|
-
for (const v of this.synthCore.voices) if (v.channel === this.channel && v.isActive && v.midiNote === midiNote && !v.isInRelease) {
|
|
13142
|
+
for (const v of this.synthCore.voices) if (v.channel === this.channel && v.isActive && v.midiNote === midiNote && v.noteID === noteID && !v.isInRelease) {
|
|
12754
13143
|
if (sustain) v.isHeld = true;
|
|
12755
13144
|
else v.releaseVoice(this.synthCore.currentTime);
|
|
12756
13145
|
if (++vc >= this._voiceCount) break;
|
|
@@ -12760,6 +13149,11 @@ function noteOff(midiNote) {
|
|
|
12760
13149
|
midiNote,
|
|
12761
13150
|
channel: this.channel
|
|
12762
13151
|
});
|
|
13152
|
+
if (mono) {
|
|
13153
|
+
const highest = this.playingNotes.lastIndexOf(true);
|
|
13154
|
+
if (highest === -1) this.lastMonoNote = -1;
|
|
13155
|
+
else if (this.lastMonoNote === midiNote) this.noteOn(highest, this.lastMonoVelocity, false);
|
|
13156
|
+
}
|
|
12763
13157
|
}
|
|
12764
13158
|
//#endregion
|
|
12765
13159
|
//#region src/synthesizer/audio_engine/channel/program_change.ts
|
|
@@ -12924,7 +13318,7 @@ function computeModulator(voice, pitchWheel, modulatorIndex) {
|
|
|
12924
13318
|
let computedValue = sourceValue * secondSrcValue * transformAmount;
|
|
12925
13319
|
if (modulator.transformType === 2) computedValue = Math.abs(computedValue);
|
|
12926
13320
|
if (modulator.isDefaultResonantModulator) voice.resonanceOffset = Math.max(0, computedValue / 2);
|
|
12927
|
-
if (modulator.isModWheelModulator) computedValue *= this._midiParameters.modulationDepth;
|
|
13321
|
+
if (modulator.isModWheelModulator) computedValue *= this._midiParameters.modulationDepth / 50;
|
|
12928
13322
|
voice.modulatorValues[modulatorIndex] = computedValue;
|
|
12929
13323
|
return computedValue;
|
|
12930
13324
|
}
|
|
@@ -13047,12 +13441,13 @@ function getChannelSnapshot() {
|
|
|
13047
13441
|
overrides: this.generators.overrides.slice()
|
|
13048
13442
|
},
|
|
13049
13443
|
midiParameters: { ...this._midiParameters },
|
|
13444
|
+
lockedMIDIParameters: { ...this.lockedMIDIParameters },
|
|
13050
13445
|
systemParameters: { ...this._systemParameters },
|
|
13051
13446
|
octaveTuning: this.octaveTuning.slice(),
|
|
13052
13447
|
perNotePitch: this.perNotePitch,
|
|
13053
13448
|
drumParams: this.drumParams.map((d) => ({ ...d })),
|
|
13054
13449
|
drumChannel: this._drumChannel,
|
|
13055
|
-
|
|
13450
|
+
channel: this.channel
|
|
13056
13451
|
};
|
|
13057
13452
|
}
|
|
13058
13453
|
function applySnapshot(snapshot) {
|
|
@@ -13071,6 +13466,7 @@ function applySnapshot(snapshot) {
|
|
|
13071
13466
|
if (snapshot.patch) this.setPatch(snapshot.patch);
|
|
13072
13467
|
this.lockedSystem = snapshot.lockedSystem;
|
|
13073
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);
|
|
13074
13470
|
for (const [parameter, value] of Object.entries(snapshot.systemParameters)) this.setSystemParameter(parameter, value);
|
|
13075
13471
|
}
|
|
13076
13472
|
//#endregion
|
|
@@ -13079,18 +13475,53 @@ const DEFAULT_CHANNEL_MIDI_PARAMETERS = {
|
|
|
13079
13475
|
pitchWheel: 8192,
|
|
13080
13476
|
pitchWheelRange: 2,
|
|
13081
13477
|
pressure: 0,
|
|
13082
|
-
modulationDepth:
|
|
13478
|
+
modulationDepth: 50,
|
|
13083
13479
|
rxChannel: 0,
|
|
13084
13480
|
polyMode: true,
|
|
13085
13481
|
keyShift: 0,
|
|
13482
|
+
fineTune: 0,
|
|
13086
13483
|
randomPan: false,
|
|
13087
13484
|
assignMode: 2,
|
|
13088
13485
|
efxAssign: false,
|
|
13089
13486
|
cc1: 16,
|
|
13090
13487
|
cc2: 17,
|
|
13091
13488
|
drumMap: 0,
|
|
13092
|
-
|
|
13489
|
+
velocitySenseDepth: 64,
|
|
13490
|
+
velocitySenseOffset: 64
|
|
13093
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
|
+
}
|
|
13094
13525
|
//#endregion
|
|
13095
13526
|
//#region src/synthesizer/audio_engine/channel/parameters/system.ts
|
|
13096
13527
|
const DEFAULT_CHANNEL_SYSTEM_PARAMETERS = {
|
|
@@ -13137,13 +13568,6 @@ var MIDIChannel = class {
|
|
|
13137
13568
|
*/
|
|
13138
13569
|
pitchWheels = new Int16Array(128).fill(8192);
|
|
13139
13570
|
/**
|
|
13140
|
-
* An array indicating if a controller, at the equivalent index in the midiControllers array, is locked
|
|
13141
|
-
* (i.e., not allowed changing).
|
|
13142
|
-
* A locked controller cannot be modified.
|
|
13143
|
-
* @internal
|
|
13144
|
-
*/
|
|
13145
|
-
lockedControllers = new Array(128).fill(false);
|
|
13146
|
-
/**
|
|
13147
13571
|
* Parameters for each drum instrument.
|
|
13148
13572
|
* @internal
|
|
13149
13573
|
*/
|
|
@@ -13194,6 +13618,13 @@ var MIDIChannel = class {
|
|
|
13194
13618
|
*/
|
|
13195
13619
|
setSystemParameter = setSystemParameterInternal.bind(this);
|
|
13196
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
|
+
/**
|
|
13197
13628
|
* Sends a "MIDI Note on" message and starts a note.
|
|
13198
13629
|
* @param midiNote The MIDI note number (0-127).
|
|
13199
13630
|
* @param velocity The velocity of the note (0-127). If less than 1, it will send a note off instead.
|
|
@@ -13246,6 +13677,20 @@ var MIDIChannel = class {
|
|
|
13246
13677
|
*/
|
|
13247
13678
|
renderVoice = renderVoice.bind(this);
|
|
13248
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
|
+
/**
|
|
13249
13694
|
* An array of MIDI controllers for the channel.
|
|
13250
13695
|
* This array is used to store the state of various MIDI controllers
|
|
13251
13696
|
* such as volume, pan, modulation, etc.
|
|
@@ -13274,6 +13719,13 @@ var MIDIChannel = class {
|
|
|
13274
13719
|
* @internal
|
|
13275
13720
|
*/
|
|
13276
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]));
|
|
13277
13729
|
_midiParameters = { ...DEFAULT_CHANNEL_MIDI_PARAMETERS };
|
|
13278
13730
|
/**
|
|
13279
13731
|
* All system parameters of this channel.
|
|
@@ -13281,6 +13733,20 @@ var MIDIChannel = class {
|
|
|
13281
13733
|
*/
|
|
13282
13734
|
_systemParameters = { ...DEFAULT_CHANNEL_SYSTEM_PARAMETERS };
|
|
13283
13735
|
/**
|
|
13736
|
+
* Note On message tracking, for grouping voices for specific Note On messages.
|
|
13737
|
+
* Used for overlapping Note Ons.
|
|
13738
|
+
* MIDI note: current note on ID
|
|
13739
|
+
* @protected
|
|
13740
|
+
*/
|
|
13741
|
+
noteOnID = new Array(128).fill(0);
|
|
13742
|
+
/**
|
|
13743
|
+
* Note Off message tracking, for grouping voices for specific Note On messages.
|
|
13744
|
+
* Used for overlapping Note Ons.
|
|
13745
|
+
* MIDI note: current note on ID
|
|
13746
|
+
* @protected
|
|
13747
|
+
*/
|
|
13748
|
+
noteOffID = new Array(128).fill(0);
|
|
13749
|
+
/**
|
|
13284
13750
|
* If the last Parameter was RPN.
|
|
13285
13751
|
* If false then the last parameter was NRPN.
|
|
13286
13752
|
* @protected
|
|
@@ -13315,21 +13781,39 @@ var MIDIChannel = class {
|
|
|
13315
13781
|
*/
|
|
13316
13782
|
currentGain = 0;
|
|
13317
13783
|
/**
|
|
13318
|
-
* The last pressed note on this channel.
|
|
13784
|
+
* The last pressed note on this channel for portamento tracking.
|
|
13319
13785
|
* -1 means none.
|
|
13320
13786
|
* This is not a `ChannelMIDIParameter` and is strictly internal,
|
|
13321
13787
|
* mostly because we don't want to send events for every note on message.
|
|
13322
13788
|
* It can be set with Portamento Control CC anyway.
|
|
13323
13789
|
* @protected
|
|
13324
13790
|
*/
|
|
13325
|
-
|
|
13791
|
+
lastPortamentoNote = -1;
|
|
13326
13792
|
/**
|
|
13327
13793
|
* If the portamento should be executed once regardless of Portamento on/off.
|
|
13328
13794
|
* Adhering to the MIDI spec, CC#84 ignores on/off.
|
|
13329
|
-
* This is also not a `ChannelMIDIParameter` for the same reason as `
|
|
13795
|
+
* This is also not a `ChannelMIDIParameter` for the same reason as `lastPortamentoNote`
|
|
13330
13796
|
* @protected
|
|
13331
13797
|
*/
|
|
13332
13798
|
portamentoForce = false;
|
|
13799
|
+
/**
|
|
13800
|
+
* The last pressed note on this channel in mono mode.
|
|
13801
|
+
* Used for tracking and releasing this note on a new Note On event.
|
|
13802
|
+
* -1 means none.
|
|
13803
|
+
* @protected
|
|
13804
|
+
*/
|
|
13805
|
+
lastMonoNote = -1;
|
|
13806
|
+
/**
|
|
13807
|
+
* The last pressed note's velocity on this channel in mono mode.
|
|
13808
|
+
* @protected
|
|
13809
|
+
*/
|
|
13810
|
+
lastMonoVelocity = 0;
|
|
13811
|
+
/**
|
|
13812
|
+
* For Mono Mode restoring notes.
|
|
13813
|
+
* playingNotes[midiNote]
|
|
13814
|
+
* @protected
|
|
13815
|
+
*/
|
|
13816
|
+
playingNotes = new Array(128).fill(false);
|
|
13333
13817
|
generators = {
|
|
13334
13818
|
offsets: new Int16Array(GENERATORS_AMOUNT),
|
|
13335
13819
|
offsetsEnabled: false,
|
|
@@ -13442,6 +13926,9 @@ var MIDIChannel = class {
|
|
|
13442
13926
|
* @param force If true, stops all notes immediately, otherwise applies release time.
|
|
13443
13927
|
*/
|
|
13444
13928
|
stopAllNotes(force = false) {
|
|
13929
|
+
this.noteOnID.fill(0);
|
|
13930
|
+
this.noteOffID.fill(0);
|
|
13931
|
+
this.playingNotes.fill(false);
|
|
13445
13932
|
if (force) {
|
|
13446
13933
|
let vc = 0;
|
|
13447
13934
|
if (this._voiceCount > 0) {
|
|
@@ -13466,29 +13953,6 @@ var MIDIChannel = class {
|
|
|
13466
13953
|
});
|
|
13467
13954
|
}
|
|
13468
13955
|
/**
|
|
13469
|
-
* Sets a channel MIDI parameter of the synthesizer.
|
|
13470
|
-
* @param parameter The type of the channel MIDI parameter to set.
|
|
13471
|
-
* @param value The value to set for the channel MIDI parameter.
|
|
13472
|
-
* @internal
|
|
13473
|
-
*/
|
|
13474
|
-
setMIDIParameter(parameter, value) {
|
|
13475
|
-
this._midiParameters[parameter] = value;
|
|
13476
|
-
switch (parameter) {
|
|
13477
|
-
case "pitchWheel":
|
|
13478
|
-
this.computeModulatorsAll(0, ModulatorControllerSources.pitchWheel);
|
|
13479
|
-
break;
|
|
13480
|
-
case "pressure":
|
|
13481
|
-
this.computeModulatorsAll(0, ModulatorControllerSources.channelPressure);
|
|
13482
|
-
break;
|
|
13483
|
-
}
|
|
13484
|
-
this.updateInternalParams();
|
|
13485
|
-
this.synthCore.callEvent("channelParamChange", {
|
|
13486
|
-
channel: this.channel,
|
|
13487
|
-
parameter,
|
|
13488
|
-
value
|
|
13489
|
-
});
|
|
13490
|
-
}
|
|
13491
|
-
/**
|
|
13492
13956
|
* @internal
|
|
13493
13957
|
*/
|
|
13494
13958
|
clearVoiceCount() {
|
|
@@ -13506,49 +13970,6 @@ var MIDIChannel = class {
|
|
|
13506
13970
|
for (let i = 0; i < 128; i++) this.octaveTuning[i] = tuning[i % 12];
|
|
13507
13971
|
}
|
|
13508
13972
|
/**
|
|
13509
|
-
* Sets the modulation depth for the channel.
|
|
13510
|
-
* @param cents The modulation depth in cents to set.
|
|
13511
|
-
* @param log If true, logs the change to the console.
|
|
13512
|
-
* @remarks
|
|
13513
|
-
* This method sets the modulation depth for the channel by converting the given cents value into a
|
|
13514
|
-
* multiplier. The MIDI specification assumes the default modulation depth is 50 cents,
|
|
13515
|
-
* but it may vary for different sound banks.
|
|
13516
|
-
* For example, if you want a modulation depth of 100 cents,
|
|
13517
|
-
* the multiplier will be 2,
|
|
13518
|
-
* which, for a preset with a depth of 50,
|
|
13519
|
-
* will create a total modulation depth of 100 cents.
|
|
13520
|
-
* @internal
|
|
13521
|
-
*/
|
|
13522
|
-
modulationDepth(cents, log = true) {
|
|
13523
|
-
this.setMIDIParameter("modulationDepth", cents / 50);
|
|
13524
|
-
if (!log) return;
|
|
13525
|
-
SpessaLog.info(`%cChannel ${this.channel} modulation depth. Cents: %c${Math.round(cents)}`, ConsoleColors.info, ConsoleColors.value);
|
|
13526
|
-
}
|
|
13527
|
-
/**
|
|
13528
|
-
* Sets the channel's key shift (MIDI).
|
|
13529
|
-
* @param shift the key shift.
|
|
13530
|
-
* @param log If true, logs the change to the console.
|
|
13531
|
-
* @internal
|
|
13532
|
-
*/
|
|
13533
|
-
keyShift(shift, log = true) {
|
|
13534
|
-
if (this._drumChannel) shift = 0;
|
|
13535
|
-
if (this._midiParameters.keyShift === shift) return;
|
|
13536
|
-
this.setMIDIParameter("keyShift", shift);
|
|
13537
|
-
if (!log) return;
|
|
13538
|
-
SpessaLog.info(`%cKey shift for %c${this.channel}%c is now set to %c${shift}.`, ConsoleColors.info, ConsoleColors.recognized, ConsoleColors.info, ConsoleColors.value);
|
|
13539
|
-
}
|
|
13540
|
-
/**
|
|
13541
|
-
* Sets the channel's tuning.
|
|
13542
|
-
* @param cents The tuning in cents to set.
|
|
13543
|
-
* @param log If true, logs the change to the console.
|
|
13544
|
-
* @internal
|
|
13545
|
-
*/
|
|
13546
|
-
fineTune(cents, log = true) {
|
|
13547
|
-
this.setMIDIParameter("fineTune", cents);
|
|
13548
|
-
if (!log) return;
|
|
13549
|
-
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);
|
|
13550
|
-
}
|
|
13551
|
-
/**
|
|
13552
13973
|
* Sets the pitch of the given channel.
|
|
13553
13974
|
* @param pitch The pitch (0 - 16383)
|
|
13554
13975
|
* @param midiNote The MIDI note number, pass -1 for the regular pitch wheel
|
|
@@ -13588,17 +14009,6 @@ var MIDIChannel = class {
|
|
|
13588
14009
|
});
|
|
13589
14010
|
}
|
|
13590
14011
|
/**
|
|
13591
|
-
* Sets the pitch wheel range for this channel.
|
|
13592
|
-
* @param range range in semitones.
|
|
13593
|
-
* @param log If true, logs the change to the console.
|
|
13594
|
-
* @internal
|
|
13595
|
-
*/
|
|
13596
|
-
pitchWheelRange(range, log = true) {
|
|
13597
|
-
this.setMIDIParameter("pitchWheelRange", range);
|
|
13598
|
-
if (!log) return;
|
|
13599
|
-
SpessaLog.coolInfo(`Pitch Wheel Range for ${this.channel}`, range, "semitones");
|
|
13600
|
-
}
|
|
13601
|
-
/**
|
|
13602
14012
|
* @internal
|
|
13603
14013
|
*/
|
|
13604
14014
|
updateInternalParams() {
|
|
@@ -13644,6 +14054,8 @@ var MIDIChannel = class {
|
|
|
13644
14054
|
*/
|
|
13645
14055
|
killNote(midiNote, releaseTime = -12e3) {
|
|
13646
14056
|
let vc = 0;
|
|
14057
|
+
this.noteOffID[midiNote] = 0;
|
|
14058
|
+
this.noteOnID[midiNote] = 0;
|
|
13647
14059
|
if (this._voiceCount > 0) {
|
|
13648
14060
|
for (const v of this.synthCore.voices) if (v.channel === this.channel && v.isActive && v.midiNote === midiNote) {
|
|
13649
14061
|
v.overrideReleaseVolEnv = releaseTime;
|
|
@@ -13674,7 +14086,7 @@ var MIDIChannel = class {
|
|
|
13674
14086
|
* @param midiNote
|
|
13675
14087
|
*/
|
|
13676
14088
|
setLastNote(midiNote) {
|
|
13677
|
-
this.
|
|
14089
|
+
this.lastPortamentoNote = midiNote;
|
|
13678
14090
|
}
|
|
13679
14091
|
/**
|
|
13680
14092
|
* @internal
|
|
@@ -13757,7 +14169,7 @@ var MIDIChannel = class {
|
|
|
13757
14169
|
setDrumFlag(isDrum) {
|
|
13758
14170
|
if (this._systemParameters.presetLock || !this.preset || this._drumChannel === isDrum) return;
|
|
13759
14171
|
this._drumChannel = isDrum;
|
|
13760
|
-
this.
|
|
14172
|
+
this.updateInternalParams();
|
|
13761
14173
|
}
|
|
13762
14174
|
};
|
|
13763
14175
|
//#endregion
|
|
@@ -13918,7 +14330,8 @@ function universalSystemExclusive(syx, channelOffset = 0) {
|
|
|
13918
14330
|
break;
|
|
13919
14331
|
case 1: {
|
|
13920
14332
|
const vol = syx[5] << 7 | syx[4];
|
|
13921
|
-
|
|
14333
|
+
const gain = Math.pow(vol / 16383, 2);
|
|
14334
|
+
this.setMIDIParameter("gain", gain);
|
|
13922
14335
|
SpessaLog.gmInfo("Master Volume", vol);
|
|
13923
14336
|
break;
|
|
13924
14337
|
}
|
|
@@ -13929,7 +14342,7 @@ function universalSystemExclusive(syx, channelOffset = 0) {
|
|
|
13929
14342
|
break;
|
|
13930
14343
|
}
|
|
13931
14344
|
case 3: {
|
|
13932
|
-
const cents = ((syx[5] << 7 | syx[
|
|
14345
|
+
const cents = ((syx[5] << 7 | syx[4]) - 8192) / 81.92;
|
|
13933
14346
|
this.setMIDIParameter("fineTune", cents);
|
|
13934
14347
|
SpessaLog.gmInfo("Master Fine Tuning", cents, "cents");
|
|
13935
14348
|
break;
|
|
@@ -14100,6 +14513,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14100
14513
|
}
|
|
14101
14514
|
case 4:
|
|
14102
14515
|
SpessaLog.gsInfo("Master Volume", data);
|
|
14516
|
+
this.setMIDIParameter("gain", data / 127);
|
|
14103
14517
|
break;
|
|
14104
14518
|
case 5: {
|
|
14105
14519
|
const transpose = data - 64;
|
|
@@ -14107,10 +14521,12 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14107
14521
|
this.setMIDIParameter("keyShift", transpose);
|
|
14108
14522
|
break;
|
|
14109
14523
|
}
|
|
14110
|
-
case 6:
|
|
14111
|
-
|
|
14112
|
-
|
|
14524
|
+
case 6: {
|
|
14525
|
+
const pan = (data - 64) / 63;
|
|
14526
|
+
SpessaLog.gsInfo("Master Pan", pan);
|
|
14527
|
+
this.setMIDIParameter("pan", pan);
|
|
14113
14528
|
break;
|
|
14529
|
+
}
|
|
14114
14530
|
case 127:
|
|
14115
14531
|
if (data === 0) {
|
|
14116
14532
|
SpessaLog.coolInfo("MIDI System", "Roland GS");
|
|
@@ -14141,6 +14557,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14141
14557
|
case 0: {
|
|
14142
14558
|
const patchName = readBinaryString(syx, 16, 7);
|
|
14143
14559
|
SpessaLog.gsInfo("Patch name", patchName);
|
|
14560
|
+
this.callEvent("displayMessage", [...syx]);
|
|
14144
14561
|
break;
|
|
14145
14562
|
}
|
|
14146
14563
|
case 48:
|
|
@@ -14476,12 +14893,21 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14476
14893
|
}
|
|
14477
14894
|
case 22: {
|
|
14478
14895
|
const keyShift = data - 64;
|
|
14479
|
-
ch.
|
|
14896
|
+
ch.setMIDIParameter("keyShift", keyShift);
|
|
14897
|
+
SpessaLog.gsInfo(`Key Shift for ${channel}`, keyShift);
|
|
14480
14898
|
return;
|
|
14481
14899
|
}
|
|
14482
14900
|
case 25:
|
|
14483
14901
|
ch.controllerChange(MIDIControllers.mainVolume, data);
|
|
14484
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;
|
|
14485
14911
|
case 28: {
|
|
14486
14912
|
const panPosition = data;
|
|
14487
14913
|
const randomPan = panPosition === 0;
|
|
@@ -14492,11 +14918,11 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14492
14918
|
}
|
|
14493
14919
|
case 31:
|
|
14494
14920
|
ch.setMIDIParameter("cc1", data);
|
|
14495
|
-
SpessaLog.gsInfo(
|
|
14921
|
+
SpessaLog.gsInfo(`CC1 Controller Number for ${channel}`, data);
|
|
14496
14922
|
break;
|
|
14497
14923
|
case 32:
|
|
14498
14924
|
ch.setMIDIParameter("cc2", data);
|
|
14499
|
-
SpessaLog.gsInfo(
|
|
14925
|
+
SpessaLog.gsInfo(`CC2 Controller Number for ${channel}`, data);
|
|
14500
14926
|
break;
|
|
14501
14927
|
case 33:
|
|
14502
14928
|
ch.controllerChange(MIDIControllers.chorusDepth, data);
|
|
@@ -14505,8 +14931,9 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14505
14931
|
ch.controllerChange(MIDIControllers.reverbDepth, data);
|
|
14506
14932
|
break;
|
|
14507
14933
|
case 42: {
|
|
14508
|
-
const
|
|
14509
|
-
ch.fineTune
|
|
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");
|
|
14510
14937
|
break;
|
|
14511
14938
|
}
|
|
14512
14939
|
case 44:
|
|
@@ -14541,7 +14968,7 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14541
14968
|
const newTuning = new Int8Array(12);
|
|
14542
14969
|
for (let i = 0; i < tuningBytes; i++) newTuning[i] = syx[i + 7] - 64;
|
|
14543
14970
|
ch.setOctaveTuning(newTuning);
|
|
14544
|
-
SpessaLog.gsInfo(`Octave Scale Tuning
|
|
14971
|
+
SpessaLog.gsInfo(`Octave Scale Tuning for ${channel}`, newTuning.join(", "));
|
|
14545
14972
|
break;
|
|
14546
14973
|
}
|
|
14547
14974
|
}
|
|
@@ -14557,7 +14984,8 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14557
14984
|
case 0:
|
|
14558
14985
|
if ((a3 & 15) === 4) {
|
|
14559
14986
|
const cents = data / 127 * 600;
|
|
14560
|
-
ch.modulationDepth
|
|
14987
|
+
ch.setMIDIParameter("modulationDepth", cents);
|
|
14988
|
+
SpessaLog.gsInfo(`Modulation depth for ${channel}`, Math.round(cents), "cents");
|
|
14561
14989
|
break;
|
|
14562
14990
|
}
|
|
14563
14991
|
ch.dynamicModulators.setupReceiver(a3, data, MIDIControllers.modulationWheel, true, "mod wheel");
|
|
@@ -14565,7 +14993,8 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14565
14993
|
case 16:
|
|
14566
14994
|
if ((a3 & 15) === 0) {
|
|
14567
14995
|
const centeredValue = data - 64;
|
|
14568
|
-
ch.pitchWheelRange
|
|
14996
|
+
ch.setMIDIParameter("pitchWheelRange", centeredValue);
|
|
14997
|
+
SpessaLog.gsInfo(`Pitch Wheel Range for ${channel}`, centeredValue, "semitones");
|
|
14569
14998
|
break;
|
|
14570
14999
|
}
|
|
14571
15000
|
ch.dynamicModulators.setupReceiver(a3, data, ModulatorControllerSources.pitchWheel, false, "pitch wheel", true);
|
|
@@ -14595,7 +15024,6 @@ function rolandSystemExclusive(syx, channelOffset = 0) {
|
|
|
14595
15024
|
ch.controllerChange(MIDIControllers.bankSelectLSB, data);
|
|
14596
15025
|
break;
|
|
14597
15026
|
case 34: {
|
|
14598
|
-
if (this.systemParameters.insertionEffectLock) return;
|
|
14599
15027
|
const efx = data === 1;
|
|
14600
15028
|
ch.setMIDIParameter("efxAssign", efx);
|
|
14601
15029
|
this.insertionActive ||= efx;
|
|
@@ -14805,12 +15233,21 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
|
|
|
14805
15233
|
}
|
|
14806
15234
|
case 8: {
|
|
14807
15235
|
const keyShift = data - 64;
|
|
14808
|
-
ch.
|
|
15236
|
+
ch.setMIDIParameter("keyShift", keyShift);
|
|
15237
|
+
SpessaLog.xgInfo(`Key Shift on ${channel}`, keyShift);
|
|
14809
15238
|
break;
|
|
14810
15239
|
}
|
|
14811
15240
|
case 11:
|
|
14812
15241
|
ch.controllerChange(MIDIControllers.mainVolume, data);
|
|
14813
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;
|
|
14814
15251
|
case 14: {
|
|
14815
15252
|
const pan = data;
|
|
14816
15253
|
const randomPan = pan === 0;
|
|
@@ -14851,7 +15288,8 @@ function yamahaSystemExclusive(syx, channelOffset = 0) {
|
|
|
14851
15288
|
break;
|
|
14852
15289
|
case 35: {
|
|
14853
15290
|
const centeredValue = data - 64;
|
|
14854
|
-
ch.pitchWheelRange
|
|
15291
|
+
ch.setMIDIParameter("pitchWheelRange", centeredValue);
|
|
15292
|
+
SpessaLog.xgInfo(`Pitch Wheel Range for ${channel}`, centeredValue, "semitones");
|
|
14855
15293
|
}
|
|
14856
15294
|
}
|
|
14857
15295
|
return;
|
|
@@ -19314,6 +19752,7 @@ const DEFAULT_GLOBAL_MIDI_PARAMETERS = {
|
|
|
19314
19752
|
* @param value The value to set for the global MIDI parameter.
|
|
19315
19753
|
*/
|
|
19316
19754
|
function setMIDIParameterInternal(parameter, value) {
|
|
19755
|
+
if (this.lockedMIDIParameters[parameter]) return;
|
|
19317
19756
|
this.midiParameters[parameter] = value;
|
|
19318
19757
|
for (const ch of this.midiChannels) ch.updateInternalParams();
|
|
19319
19758
|
this.callEvent("globalParamChange", {
|
|
@@ -19322,15 +19761,13 @@ function setMIDIParameterInternal(parameter, value) {
|
|
|
19322
19761
|
});
|
|
19323
19762
|
}
|
|
19324
19763
|
/**
|
|
19325
|
-
*
|
|
19326
|
-
*
|
|
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.
|
|
19327
19768
|
*/
|
|
19328
|
-
function
|
|
19329
|
-
this.
|
|
19330
|
-
this.setMIDIParameter("pan", 0);
|
|
19331
|
-
this.setMIDIParameter("keyShift", 0);
|
|
19332
|
-
this.setMIDIParameter("fineTune", 0);
|
|
19333
|
-
this.setMIDIParameter("system", system);
|
|
19769
|
+
function lockMIDIParameterInternal(parameter, isLocked) {
|
|
19770
|
+
this.lockedMIDIParameters[parameter] = isLocked;
|
|
19334
19771
|
}
|
|
19335
19772
|
//#endregion
|
|
19336
19773
|
//#region src/synthesizer/audio_engine/synthesizer_core.ts
|
|
@@ -19396,6 +19833,13 @@ var SynthesizerCore = class {
|
|
|
19396
19833
|
*/
|
|
19397
19834
|
tunings = new Float32Array(16384).fill(-1);
|
|
19398
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
|
+
/**
|
|
19399
19843
|
* The global MIDI parameters of the synthesizer.
|
|
19400
19844
|
*/
|
|
19401
19845
|
midiParameters = { ...DEFAULT_GLOBAL_MIDI_PARAMETERS };
|
|
@@ -19436,6 +19880,13 @@ var SynthesizerCore = class {
|
|
|
19436
19880
|
*/
|
|
19437
19881
|
cachedVoices = /* @__PURE__ */ new Map();
|
|
19438
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
|
+
/**
|
|
19439
19890
|
* Sets a system parameter of the synthesizer.
|
|
19440
19891
|
* @param type The type of the system parameter to set.
|
|
19441
19892
|
* @param value The value to set for the system parameter.
|
|
@@ -19465,7 +19916,6 @@ var SynthesizerCore = class {
|
|
|
19465
19916
|
* @param value The value to set for the global MIDI parameter.
|
|
19466
19917
|
*/
|
|
19467
19918
|
setMIDIParameter = setMIDIParameterInternal.bind(this);
|
|
19468
|
-
resetMIDIParameters = resetMIDIParametersInternal.bind(this);
|
|
19469
19919
|
/**
|
|
19470
19920
|
* The fallback processor when the requested insertion is not available.
|
|
19471
19921
|
*/
|
|
@@ -19683,10 +20133,15 @@ var SynthesizerCore = class {
|
|
|
19683
20133
|
* Executes a full system reset of the synthesizer.
|
|
19684
20134
|
* This will reset all controllers to their default values,
|
|
19685
20135
|
* except for the locked controllers.
|
|
20136
|
+
* @param system The MIDI system to reset the synthesizer to. Defaults to `gs`.
|
|
19686
20137
|
*/
|
|
19687
20138
|
reset(system = "gs") {
|
|
19688
20139
|
this.callEvent("reset", system);
|
|
19689
|
-
this.
|
|
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);
|
|
19690
20145
|
this.tunings.fill(-1);
|
|
19691
20146
|
this.portSelectChannelOffset = 0;
|
|
19692
20147
|
this.customChannelNumbers = false;
|
|
@@ -19702,7 +20157,7 @@ var SynthesizerCore = class {
|
|
|
19702
20157
|
this.processSplit([[left, right]], left, right, startIndex, sampleCount);
|
|
19703
20158
|
}
|
|
19704
20159
|
/**
|
|
19705
|
-
* The main rendering pipeline, renders all voices
|
|
20160
|
+
* The main rendering pipeline, renders all voices and processes the effects:
|
|
19706
20161
|
* ```
|
|
19707
20162
|
* ┌────────────────────────────────┐
|
|
19708
20163
|
* │ Voice Processor │
|
|
@@ -19820,8 +20275,7 @@ var SynthesizerCore = class {
|
|
|
19820
20275
|
getInsertionSnapshot() {
|
|
19821
20276
|
return {
|
|
19822
20277
|
type: this.insertionProcessor.type,
|
|
19823
|
-
params: this.insertionParams.slice()
|
|
19824
|
-
channels: this.midiChannels.map((c) => c.midiParameters.efxAssign)
|
|
20278
|
+
params: this.insertionParams.slice()
|
|
19825
20279
|
};
|
|
19826
20280
|
}
|
|
19827
20281
|
resetInsertionParams() {
|
|
@@ -20353,7 +20807,7 @@ var SpessaSynthProcessor = class {
|
|
|
20353
20807
|
*/
|
|
20354
20808
|
synthCore;
|
|
20355
20809
|
/**
|
|
20356
|
-
*
|
|
20810
|
+
* For applying the snapshot after an override sound bank too.
|
|
20357
20811
|
*/
|
|
20358
20812
|
savedSnapshot;
|
|
20359
20813
|
/**
|
|
@@ -20456,12 +20910,21 @@ var SpessaSynthProcessor = class {
|
|
|
20456
20910
|
SpessaLog.warn(`No preset found for ${MIDIPatchTools.toMIDIString(patch)}! Did you forget to add a sound bank?`);
|
|
20457
20911
|
};
|
|
20458
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
|
+
/**
|
|
20459
20922
|
* Sets a system parameter of the synthesizer.
|
|
20460
|
-
* @param
|
|
20923
|
+
* @param parameter The type of the system parameter to set.
|
|
20461
20924
|
* @param value The value to set for the system parameter.
|
|
20462
20925
|
*/
|
|
20463
|
-
setSystemParameter(
|
|
20464
|
-
this.synthCore.setSystemParameter(
|
|
20926
|
+
setSystemParameter(parameter, value) {
|
|
20927
|
+
this.synthCore.setSystemParameter(parameter, value);
|
|
20465
20928
|
}
|
|
20466
20929
|
/**
|
|
20467
20930
|
* Executes a full synthesizer reset.
|