@marmooo/midy 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/midy-GM1.d.ts +8 -4
- package/esm/midy-GM1.d.ts.map +1 -1
- package/esm/midy-GM1.js +54 -17
- package/esm/midy-GM2.d.ts +13 -8
- package/esm/midy-GM2.d.ts.map +1 -1
- package/esm/midy-GM2.js +147 -40
- package/esm/midy-GMLite.d.ts +9 -3
- package/esm/midy-GMLite.d.ts.map +1 -1
- package/esm/midy-GMLite.js +57 -16
- package/esm/midy.d.ts +13 -8
- package/esm/midy.d.ts.map +1 -1
- package/esm/midy.js +187 -51
- package/package.json +1 -1
- package/script/midy-GM1.d.ts +8 -4
- package/script/midy-GM1.d.ts.map +1 -1
- package/script/midy-GM1.js +54 -17
- package/script/midy-GM2.d.ts +13 -8
- package/script/midy-GM2.d.ts.map +1 -1
- package/script/midy-GM2.js +147 -40
- package/script/midy-GMLite.d.ts +9 -3
- package/script/midy-GMLite.d.ts.map +1 -1
- package/script/midy-GMLite.js +57 -16
- package/script/midy.d.ts +13 -8
- package/script/midy.d.ts.map +1 -1
- package/script/midy.js +187 -51
package/script/midy-GM2.js
CHANGED
|
@@ -242,6 +242,12 @@ const volumeEnvelopeKeys = [
|
|
|
242
242
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
243
243
|
class MidyGM2 {
|
|
244
244
|
constructor(audioContext, options = this.defaultOptions) {
|
|
245
|
+
Object.defineProperty(this, "mode", {
|
|
246
|
+
enumerable: true,
|
|
247
|
+
configurable: true,
|
|
248
|
+
writable: true,
|
|
249
|
+
value: "GM2"
|
|
250
|
+
});
|
|
245
251
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
246
252
|
enumerable: true,
|
|
247
253
|
configurable: true,
|
|
@@ -420,6 +426,11 @@ class MidyGM2 {
|
|
|
420
426
|
this.audioContext = audioContext;
|
|
421
427
|
this.options = { ...this.defaultOptions, ...options };
|
|
422
428
|
this.masterVolume = new GainNode(audioContext);
|
|
429
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
430
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
431
|
+
length: 1,
|
|
432
|
+
sampleRate: audioContext.sampleRate,
|
|
433
|
+
});
|
|
423
434
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
424
435
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
425
436
|
this.channels = this.createChannels(audioContext);
|
|
@@ -428,6 +439,7 @@ class MidyGM2 {
|
|
|
428
439
|
this.chorusEffect.output.connect(this.masterVolume);
|
|
429
440
|
this.reverbEffect.output.connect(this.masterVolume);
|
|
430
441
|
this.masterVolume.connect(audioContext.destination);
|
|
442
|
+
this.scheduler.connect(audioContext.destination);
|
|
431
443
|
this.GM2SystemOn();
|
|
432
444
|
}
|
|
433
445
|
initSoundFontTable() {
|
|
@@ -526,10 +538,24 @@ class MidyGM2 {
|
|
|
526
538
|
return audioBuffer;
|
|
527
539
|
}
|
|
528
540
|
}
|
|
529
|
-
|
|
541
|
+
calcLoopMode(channel, note, voiceParams) {
|
|
542
|
+
if (channel.isDrum) {
|
|
543
|
+
const noteNumber = note.noteNumber;
|
|
544
|
+
if (noteNumber === 88 || 47 <= noteNumber && noteNumber <= 84) {
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
return voiceParams.sampleModes % 2 !== 0;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
createBufferSource(channel, note, voiceParams, audioBuffer) {
|
|
530
556
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
531
557
|
bufferSource.buffer = audioBuffer;
|
|
532
|
-
bufferSource.loop =
|
|
558
|
+
bufferSource.loop = this.calcLoopMode(channel, note, voiceParams);
|
|
533
559
|
if (bufferSource.loop) {
|
|
534
560
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
535
561
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -1006,7 +1032,9 @@ class MidyGM2 {
|
|
|
1006
1032
|
return 8.176 * this.centToRate(cent);
|
|
1007
1033
|
}
|
|
1008
1034
|
calcChannelDetune(channel) {
|
|
1009
|
-
const masterTuning =
|
|
1035
|
+
const masterTuning = channel.isDrum
|
|
1036
|
+
? 0
|
|
1037
|
+
: this.masterCoarseTuning + this.masterFineTuning;
|
|
1010
1038
|
const channelTuning = channel.coarseTuning + channel.fineTuning;
|
|
1011
1039
|
const tuning = masterTuning + channelTuning;
|
|
1012
1040
|
const pitchWheel = channel.state.pitchWheel * 2 - 1;
|
|
@@ -1032,9 +1060,8 @@ class MidyGM2 {
|
|
|
1032
1060
|
.setValueAtTime(detune, scheduleTime);
|
|
1033
1061
|
}
|
|
1034
1062
|
getPortamentoTime(channel) {
|
|
1035
|
-
const factor = 5 * Math.log(10)
|
|
1036
|
-
|
|
1037
|
-
return Math.log(time) / factor;
|
|
1063
|
+
const factor = 5 * Math.log(10) * 127;
|
|
1064
|
+
return channel.state.portamentoTime * factor;
|
|
1038
1065
|
}
|
|
1039
1066
|
setPortamentoStartVolumeEnvelope(channel, note, scheduleTime) {
|
|
1040
1067
|
const { voiceParams, startTime } = note;
|
|
@@ -1195,7 +1222,7 @@ class MidyGM2 {
|
|
|
1195
1222
|
const voiceParams = voice.getAllParams(controllerState);
|
|
1196
1223
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
1197
1224
|
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
1198
|
-
note.bufferSource = this.
|
|
1225
|
+
note.bufferSource = this.createBufferSource(channel, note, voiceParams, audioBuffer);
|
|
1199
1226
|
note.volumeNode = new GainNode(this.audioContext);
|
|
1200
1227
|
note.gainL = new GainNode(this.audioContext);
|
|
1201
1228
|
note.gainR = new GainNode(this.audioContext);
|
|
@@ -1239,14 +1266,21 @@ class MidyGM2 {
|
|
|
1239
1266
|
note.bufferSource.start(startTime);
|
|
1240
1267
|
return note;
|
|
1241
1268
|
}
|
|
1242
|
-
calcBank(channel
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1269
|
+
calcBank(channel) {
|
|
1270
|
+
switch (this.mode) {
|
|
1271
|
+
case "GM1":
|
|
1272
|
+
if (channel.isDrum)
|
|
1273
|
+
return 128;
|
|
1274
|
+
return 0;
|
|
1275
|
+
case "GM2":
|
|
1276
|
+
if (channel.bankMSB === 121)
|
|
1277
|
+
return 0;
|
|
1278
|
+
if (channel.isDrum)
|
|
1279
|
+
return 128;
|
|
1280
|
+
return channel.bank;
|
|
1281
|
+
default:
|
|
1282
|
+
return channel.bank;
|
|
1248
1283
|
}
|
|
1249
|
-
return channel.bank;
|
|
1250
1284
|
}
|
|
1251
1285
|
async scheduleNoteOn(channelNumber, noteNumber, velocity, startTime, portamento) {
|
|
1252
1286
|
const channel = this.channels[channelNumber];
|
|
@@ -1270,7 +1304,7 @@ class MidyGM2 {
|
|
|
1270
1304
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
1271
1305
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
1272
1306
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
1273
|
-
if (!prevNote.ending) {
|
|
1307
|
+
if (prevNote && !prevNote.ending) {
|
|
1274
1308
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
1275
1309
|
startTime, true, // force
|
|
1276
1310
|
undefined);
|
|
@@ -1419,9 +1453,21 @@ class MidyGM2 {
|
|
|
1419
1453
|
const channel = this.channels[channelNumber];
|
|
1420
1454
|
channel.bank = channel.bankMSB * 128 + channel.bankLSB;
|
|
1421
1455
|
channel.program = program;
|
|
1456
|
+
if (this.mode === "GM2") {
|
|
1457
|
+
switch (channel.bankMSB) {
|
|
1458
|
+
case 120:
|
|
1459
|
+
channel.isDrum = true;
|
|
1460
|
+
break;
|
|
1461
|
+
case 121:
|
|
1462
|
+
channel.isDrum = false;
|
|
1463
|
+
break;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1422
1466
|
}
|
|
1423
1467
|
handleChannelPressure(channelNumber, value, scheduleTime) {
|
|
1424
1468
|
const channel = this.channels[channelNumber];
|
|
1469
|
+
if (channel.isDrum)
|
|
1470
|
+
return;
|
|
1425
1471
|
const prev = channel.state.channelPressure;
|
|
1426
1472
|
const next = value / 127;
|
|
1427
1473
|
channel.state.channelPressure = next;
|
|
@@ -1440,8 +1486,10 @@ class MidyGM2 {
|
|
|
1440
1486
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
1441
1487
|
}
|
|
1442
1488
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
1443
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1444
1489
|
const channel = this.channels[channelNumber];
|
|
1490
|
+
if (channel.isDrum)
|
|
1491
|
+
return;
|
|
1492
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1445
1493
|
const state = channel.state;
|
|
1446
1494
|
const prev = state.pitchWheel * 2 - 1;
|
|
1447
1495
|
const next = (value - 8192) / 8192;
|
|
@@ -1713,15 +1761,16 @@ class MidyGM2 {
|
|
|
1713
1761
|
});
|
|
1714
1762
|
}
|
|
1715
1763
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1716
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1717
1764
|
const channel = this.channels[channelNumber];
|
|
1765
|
+
if (channel.isDrum)
|
|
1766
|
+
return;
|
|
1767
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1718
1768
|
channel.state.modulationDepth = modulation / 127;
|
|
1719
1769
|
this.updateModulation(channel, scheduleTime);
|
|
1720
1770
|
}
|
|
1721
1771
|
setPortamentoTime(channelNumber, portamentoTime) {
|
|
1722
1772
|
const channel = this.channels[channelNumber];
|
|
1723
|
-
|
|
1724
|
-
channel.state.portamentoTime = Math.exp(factor * portamentoTime);
|
|
1773
|
+
channel.state.portamentoTime = portamentoTime / 127;
|
|
1725
1774
|
}
|
|
1726
1775
|
setKeyBasedVolume(channel, scheduleTime) {
|
|
1727
1776
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1793,8 +1842,10 @@ class MidyGM2 {
|
|
|
1793
1842
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1794
1843
|
}
|
|
1795
1844
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1796
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1797
1845
|
const channel = this.channels[channelNumber];
|
|
1846
|
+
if (channel.isDrum)
|
|
1847
|
+
return;
|
|
1848
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1798
1849
|
channel.state.sustainPedal = value / 127;
|
|
1799
1850
|
if (64 <= value) {
|
|
1800
1851
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1806,11 +1857,16 @@ class MidyGM2 {
|
|
|
1806
1857
|
}
|
|
1807
1858
|
}
|
|
1808
1859
|
setPortamento(channelNumber, value) {
|
|
1809
|
-
this.channels[channelNumber]
|
|
1860
|
+
const channel = this.channels[channelNumber];
|
|
1861
|
+
if (channel.isDrum)
|
|
1862
|
+
return;
|
|
1863
|
+
channel.state.portamento = value / 127;
|
|
1810
1864
|
}
|
|
1811
1865
|
setSostenutoPedal(channelNumber, value, scheduleTime) {
|
|
1812
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1813
1866
|
const channel = this.channels[channelNumber];
|
|
1867
|
+
if (channel.isDrum)
|
|
1868
|
+
return;
|
|
1869
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1814
1870
|
channel.state.sostenutoPedal = value / 127;
|
|
1815
1871
|
if (64 <= value) {
|
|
1816
1872
|
channel.sostenutoNotes = this.getActiveNotes(channel, scheduleTime);
|
|
@@ -1819,9 +1875,22 @@ class MidyGM2 {
|
|
|
1819
1875
|
this.releaseSostenutoPedal(channelNumber, value, scheduleTime);
|
|
1820
1876
|
}
|
|
1821
1877
|
}
|
|
1822
|
-
setSoftPedal(channelNumber, softPedal,
|
|
1878
|
+
setSoftPedal(channelNumber, softPedal, scheduleTime) {
|
|
1823
1879
|
const channel = this.channels[channelNumber];
|
|
1880
|
+
if (channel.isDrum)
|
|
1881
|
+
return;
|
|
1882
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1824
1883
|
channel.state.softPedal = softPedal / 127;
|
|
1884
|
+
this.processScheduledNotes(channel, (note) => {
|
|
1885
|
+
if (note.portamento) {
|
|
1886
|
+
this.setPortamentoStartVolumeEnvelope(channel, note, scheduleTime);
|
|
1887
|
+
this.setPortamentoStartFilterEnvelope(channel, note, scheduleTime);
|
|
1888
|
+
}
|
|
1889
|
+
else {
|
|
1890
|
+
this.setVolumeEnvelope(channel, note, scheduleTime);
|
|
1891
|
+
this.setFilterEnvelope(channel, note, scheduleTime);
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1825
1894
|
}
|
|
1826
1895
|
setReverbSendLevel(channelNumber, reverbSendLevel, scheduleTime) {
|
|
1827
1896
|
scheduleTime ??= this.audioContext.currentTime;
|
|
@@ -1950,8 +2019,10 @@ class MidyGM2 {
|
|
|
1950
2019
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1951
2020
|
}
|
|
1952
2021
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1953
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1954
2022
|
const channel = this.channels[channelNumber];
|
|
2023
|
+
if (channel.isDrum)
|
|
2024
|
+
return;
|
|
2025
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1955
2026
|
const state = channel.state;
|
|
1956
2027
|
const prev = state.pitchWheelSensitivity;
|
|
1957
2028
|
const next = value / 128;
|
|
@@ -1967,8 +2038,10 @@ class MidyGM2 {
|
|
|
1967
2038
|
this.setFineTuning(channelNumber, fineTuning, scheduleTime);
|
|
1968
2039
|
}
|
|
1969
2040
|
setFineTuning(channelNumber, value, scheduleTime) {
|
|
1970
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1971
2041
|
const channel = this.channels[channelNumber];
|
|
2042
|
+
if (channel.isDrum)
|
|
2043
|
+
return;
|
|
2044
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1972
2045
|
const prev = channel.fineTuning;
|
|
1973
2046
|
const next = (value - 8192) / 8.192; // cent
|
|
1974
2047
|
channel.fineTuning = next;
|
|
@@ -1982,8 +2055,10 @@ class MidyGM2 {
|
|
|
1982
2055
|
this.setCoarseTuning(channelNumber, coarseTuning, scheduleTime);
|
|
1983
2056
|
}
|
|
1984
2057
|
setCoarseTuning(channelNumber, value, scheduleTime) {
|
|
1985
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1986
2058
|
const channel = this.channels[channelNumber];
|
|
2059
|
+
if (channel.isDrum)
|
|
2060
|
+
return;
|
|
2061
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1987
2062
|
const prev = channel.coarseTuning;
|
|
1988
2063
|
const next = (value - 64) * 100; // cent
|
|
1989
2064
|
channel.coarseTuning = next;
|
|
@@ -1997,8 +2072,10 @@ class MidyGM2 {
|
|
|
1997
2072
|
this.setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime);
|
|
1998
2073
|
}
|
|
1999
2074
|
setModulationDepthRange(channelNumber, modulationDepthRange, scheduleTime) {
|
|
2000
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
2001
2075
|
const channel = this.channels[channelNumber];
|
|
2076
|
+
if (channel.isDrum)
|
|
2077
|
+
return;
|
|
2078
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2002
2079
|
channel.modulationDepthRange = modulationDepthRange;
|
|
2003
2080
|
this.updateModulation(channel, scheduleTime);
|
|
2004
2081
|
}
|
|
@@ -2066,12 +2143,12 @@ class MidyGM2 {
|
|
|
2066
2143
|
case 9:
|
|
2067
2144
|
switch (data[3]) {
|
|
2068
2145
|
case 1:
|
|
2069
|
-
this.GM1SystemOn();
|
|
2146
|
+
this.GM1SystemOn(scheduleTime);
|
|
2070
2147
|
break;
|
|
2071
2148
|
case 2: // GM System Off
|
|
2072
2149
|
break;
|
|
2073
2150
|
case 3:
|
|
2074
|
-
this.GM2SystemOn();
|
|
2151
|
+
this.GM2SystemOn(scheduleTime);
|
|
2075
2152
|
break;
|
|
2076
2153
|
default:
|
|
2077
2154
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
@@ -2081,25 +2158,35 @@ class MidyGM2 {
|
|
|
2081
2158
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
2082
2159
|
}
|
|
2083
2160
|
}
|
|
2084
|
-
GM1SystemOn() {
|
|
2161
|
+
GM1SystemOn(scheduleTime) {
|
|
2162
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2163
|
+
this.mode = "GM1";
|
|
2085
2164
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2165
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2086
2166
|
const channel = this.channels[i];
|
|
2087
2167
|
channel.bankMSB = 0;
|
|
2088
2168
|
channel.bankLSB = 0;
|
|
2089
2169
|
channel.bank = 0;
|
|
2170
|
+
channel.isDrum = false;
|
|
2090
2171
|
}
|
|
2091
2172
|
this.channels[9].bankMSB = 1;
|
|
2092
2173
|
this.channels[9].bank = 128;
|
|
2174
|
+
this.channels[9].isDrum = true;
|
|
2093
2175
|
}
|
|
2094
|
-
GM2SystemOn() {
|
|
2176
|
+
GM2SystemOn(scheduleTime) {
|
|
2177
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
2178
|
+
this.mode = "GM2";
|
|
2095
2179
|
for (let i = 0; i < this.channels.length; i++) {
|
|
2180
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
2096
2181
|
const channel = this.channels[i];
|
|
2097
2182
|
channel.bankMSB = 121;
|
|
2098
2183
|
channel.bankLSB = 0;
|
|
2099
2184
|
channel.bank = 121 * 128;
|
|
2185
|
+
channel.isDrum = false;
|
|
2100
2186
|
}
|
|
2101
2187
|
this.channels[9].bankMSB = 120;
|
|
2102
2188
|
this.channels[9].bank = 120 * 128;
|
|
2189
|
+
this.channels[9].isDrum = true;
|
|
2103
2190
|
}
|
|
2104
2191
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
2105
2192
|
switch (data[2]) {
|
|
@@ -2162,8 +2249,14 @@ class MidyGM2 {
|
|
|
2162
2249
|
const prev = this.masterFineTuning;
|
|
2163
2250
|
const next = (value - 8192) / 8.192; // cent
|
|
2164
2251
|
this.masterFineTuning = next;
|
|
2165
|
-
|
|
2166
|
-
this.
|
|
2252
|
+
const detuneChange = next - prev;
|
|
2253
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2254
|
+
const channel = this.channels[i];
|
|
2255
|
+
if (channel.isDrum)
|
|
2256
|
+
continue;
|
|
2257
|
+
channel.detune += detuneChange;
|
|
2258
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2259
|
+
}
|
|
2167
2260
|
}
|
|
2168
2261
|
handleMasterCoarseTuningSysEx(data, scheduleTime) {
|
|
2169
2262
|
const coarseTuning = data[4];
|
|
@@ -2173,8 +2266,14 @@ class MidyGM2 {
|
|
|
2173
2266
|
const prev = this.masterCoarseTuning;
|
|
2174
2267
|
const next = (value - 64) * 100; // cent
|
|
2175
2268
|
this.masterCoarseTuning = next;
|
|
2176
|
-
|
|
2177
|
-
this.
|
|
2269
|
+
const detuneChange = next - prev;
|
|
2270
|
+
for (let i = 0; i < this.channels.length; i++) {
|
|
2271
|
+
const channel = this.channels[i];
|
|
2272
|
+
if (channel.isDrum)
|
|
2273
|
+
continue;
|
|
2274
|
+
channel.detune += detuneChange;
|
|
2275
|
+
this.updateChannelDetune(channel, scheduleTime);
|
|
2276
|
+
}
|
|
2178
2277
|
}
|
|
2179
2278
|
handleGlobalParameterControlSysEx(data, scheduleTime) {
|
|
2180
2279
|
if (data[7] === 1) {
|
|
@@ -2502,13 +2601,20 @@ class MidyGM2 {
|
|
|
2502
2601
|
}
|
|
2503
2602
|
scheduleTask(callback, scheduleTime) {
|
|
2504
2603
|
return new Promise((resolve) => {
|
|
2505
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
2604
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
2605
|
+
buffer: this.schedulerBuffer,
|
|
2606
|
+
});
|
|
2607
|
+
bufferSource.connect(this.scheduler);
|
|
2506
2608
|
bufferSource.onended = () => {
|
|
2507
|
-
|
|
2508
|
-
|
|
2609
|
+
try {
|
|
2610
|
+
callback();
|
|
2611
|
+
}
|
|
2612
|
+
finally {
|
|
2613
|
+
bufferSource.disconnect();
|
|
2614
|
+
resolve();
|
|
2615
|
+
}
|
|
2509
2616
|
};
|
|
2510
2617
|
bufferSource.start(scheduleTime);
|
|
2511
|
-
bufferSource.stop(scheduleTime);
|
|
2512
2618
|
});
|
|
2513
2619
|
}
|
|
2514
2620
|
}
|
|
@@ -2519,6 +2625,7 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2519
2625
|
writable: true,
|
|
2520
2626
|
value: {
|
|
2521
2627
|
currentBufferSource: null,
|
|
2628
|
+
isDrum: false,
|
|
2522
2629
|
detune: 0,
|
|
2523
2630
|
program: 0,
|
|
2524
2631
|
bank: 121 * 128,
|
|
@@ -2529,8 +2636,8 @@ Object.defineProperty(MidyGM2, "channelSettings", {
|
|
|
2529
2636
|
rpnMSB: 127,
|
|
2530
2637
|
rpnLSB: 127,
|
|
2531
2638
|
mono: false, // CC#124, CC#125
|
|
2639
|
+
modulationDepthRange: 50, // cent
|
|
2532
2640
|
fineTuning: 0, // cb
|
|
2533
2641
|
coarseTuning: 0, // cb
|
|
2534
|
-
modulationDepthRange: 50, // cent
|
|
2535
2642
|
}
|
|
2536
2643
|
});
|
package/script/midy-GMLite.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export class MidyGMLite {
|
|
2
2
|
static channelSettings: {
|
|
3
3
|
currentBufferSource: null;
|
|
4
|
+
isDrum: boolean;
|
|
4
5
|
detune: number;
|
|
5
6
|
program: number;
|
|
6
7
|
bank: number;
|
|
@@ -8,8 +9,10 @@ export class MidyGMLite {
|
|
|
8
9
|
dataLSB: number;
|
|
9
10
|
rpnMSB: number;
|
|
10
11
|
rpnLSB: number;
|
|
12
|
+
modulationDepthRange: number;
|
|
11
13
|
};
|
|
12
14
|
constructor(audioContext: any);
|
|
15
|
+
mode: string;
|
|
13
16
|
ticksPerBeat: number;
|
|
14
17
|
totalTime: number;
|
|
15
18
|
noteCheckInterval: number;
|
|
@@ -32,6 +35,8 @@ export class MidyGMLite {
|
|
|
32
35
|
exclusiveClassMap: SparseMap;
|
|
33
36
|
audioContext: any;
|
|
34
37
|
masterVolume: any;
|
|
38
|
+
scheduler: any;
|
|
39
|
+
schedulerBuffer: any;
|
|
35
40
|
voiceParamsHandlers: {
|
|
36
41
|
modLfoToPitch: (channel: any, note: any, _prevValue: any, scheduleTime: any) => void;
|
|
37
42
|
vibLfoToPitch: (_channel: any, _note: any, _prevValue: any, _scheduleTime: any) => void;
|
|
@@ -70,7 +75,8 @@ export class MidyGMLite {
|
|
|
70
75
|
};
|
|
71
76
|
createChannels(audioContext: any): any[];
|
|
72
77
|
createNoteBuffer(voiceParams: any, isSF3: any): Promise<any>;
|
|
73
|
-
|
|
78
|
+
calcLoopMode(channel: any, voiceParams: any): boolean;
|
|
79
|
+
createBufferSource(channel: any, voiceParams: any, audioBuffer: any): any;
|
|
74
80
|
scheduleTimelineEvents(t: any, offset: any, queueIndex: any): Promise<any>;
|
|
75
81
|
getQueueIndex(second: any): number;
|
|
76
82
|
playNotes(): Promise<any>;
|
|
@@ -173,8 +179,8 @@ export class MidyGMLite {
|
|
|
173
179
|
allSoundOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
174
180
|
resetAllControllers(channelNumber: any): void;
|
|
175
181
|
allNotesOff(channelNumber: any, _value: any, scheduleTime: any): Promise<any[]>;
|
|
176
|
-
handleUniversalNonRealTimeExclusiveMessage(data: any,
|
|
177
|
-
GM1SystemOn(): void;
|
|
182
|
+
handleUniversalNonRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
183
|
+
GM1SystemOn(scheduleTime: any): void;
|
|
178
184
|
handleUniversalRealTimeExclusiveMessage(data: any, scheduleTime: any): void;
|
|
179
185
|
handleMasterVolumeSysEx(data: any, scheduleTime: any): void;
|
|
180
186
|
setMasterVolume(volume: any, scheduleTime: any): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;
|
|
1
|
+
{"version":3,"file":"midy-GMLite.d.ts","sourceRoot":"","sources":["../src/midy-GMLite.js"],"names":[],"mappings":"AAiJA;IAuBE;;;;;;;;;;;MAWE;IAEF,+BAcC;IAjDD,aAAa;IACb,qBAAmB;IACnB,kBAAc;IACd,0BAAwB;IACxB,kBAAc;IACd,mBAAiB;IACjB,kBAAc;IACd,mBAAe;IACf,kBAAgB;IAChB,sBAA2C;IAC3C,kCAA+B;IAC/B,gCAA6B;IAC7B,mBAAkB;IAClB,mBAAkB;IAClB,kBAAiB;IACjB,oBAAmB;IACnB,mBAAkB;IAClB,gBAAc;IACd,mBAAiB;IACjB,oBAAkB;IAClB,6BAAuC;IAgBrC,kBAAgC;IAChC,kBAA8C;IAC9C,eAAwD;IACxD,qBAGE;IACF;;;;;;;;;;;MAA2D;IAC3D;;;;;;;;;;;;;MAA+D;IAC/D,gBAAiD;IAMnD,4BAMC;IAED,mCAWC;IAED,gDAMC;IAED,sCASC;IAED;;;;MAeC;IAED,yCAWC;IAED,6DA2BC;IAED,sDAMC;IAED,0EASC;IAED,2EAsDC;IAED,mCAOC;IAED,0BAoDC;IAED,uDAEC;IAED,wDAEC;IAED,6EAEC;IAED;;;MA4EC;IAED,mGAgBC;IAED,wEAMC;IAED,uBAKC;IAED,aAGC;IAED,cAKC;IAED,wBAIC;IAED,0BAKC;IAED,wBAOC;IAED,sBAGC;IAED,yDAQC;IAED,2DASC;IAED,qDAQC;IAED,2BAEC;IAED,8BAEC;IAED,8BAEC;IAED,4BAEC;IAED,wCAIC;IAED,2DAIC;IAED,+DAIC;IAED,sDAeC;IAED,qDAoBC;IAED,6CAIC;IAED,sDAsBC;IAED,kEAoBC;IAED,yGAgBC;IAED,gHA4CC;IAED,kGAkDC;IAED,6FAQC;IAED,qFAwBC;IAED,yHAuBC;IAED,yGASC;IAED,4GAeC;IAED,mGA2BC;IAED,gFAGC;IAED,wFAGC;IAED,sEAWC;IAED,mEAQC;IAED,wDAKC;IAED,sDAOC;IAED,mDAMC;IAED,kDAKC;IAED;;;;;;;;;;;MA2BC;IAED,oFAMC;IAED,6EA2CC;IAED;;;;;;;;;;;;;MAeC;IAED,kGAWC;IAED,wDAUC;IAED,iFAMC;IAED,oEAKC;IAED;;;MAMC;IAED,8DAKC;IAED,4EAKC;IAED,sEAGC;IAED,2DAUC;IAED,yEAYC;IAED,kFAeC;IAED,uDAYC;IAED,gDAEC;IAED,gDAEC;IAED,sEAGC;IAED,qEAKC;IAED,2EAWC;IAED,gFAGC;IAED,8CAqBC;IAED,gFAGC;IAED,+EAgBC;IAED,qCAWC;IAED,4EAaC;IAED,4DAGC;IAED,sDASC;IAED,gDAYC;IAED,6DAgBC;CACF;AAp5CD;IACE,uBAGC;IAFC,YAA2B;IAC3B,qBAAuB;IAGzB,gCAKC;IAED,mBAEC;IAED,0BAUC;IAED,uBAEC;IAED,mBAEC;IAED,cAMC;IASD,6BAKC;IAZD,qDAKC;CAQF;AAED;IASE,0FAMC;IAdD,kBAAa;IACb,gBAAW;IACX,iBAAY;IACZ,wBAAmB;IACnB,iBAAY;IACZ,mBAAc;IACd,qBAAgB;IAGd,gBAA4B;IAC5B,cAAwB;IACxB,eAA0B;IAC1B,WAAkB;IAClB,iBAA8B;CAEjC"}
|
package/script/midy-GMLite.js
CHANGED
|
@@ -173,6 +173,12 @@ const volumeEnvelopeKeys = [
|
|
|
173
173
|
const volumeEnvelopeKeySet = new Set(volumeEnvelopeKeys);
|
|
174
174
|
class MidyGMLite {
|
|
175
175
|
constructor(audioContext) {
|
|
176
|
+
Object.defineProperty(this, "mode", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
configurable: true,
|
|
179
|
+
writable: true,
|
|
180
|
+
value: "GM1"
|
|
181
|
+
});
|
|
176
182
|
Object.defineProperty(this, "ticksPerBeat", {
|
|
177
183
|
enumerable: true,
|
|
178
184
|
configurable: true,
|
|
@@ -295,10 +301,16 @@ class MidyGMLite {
|
|
|
295
301
|
});
|
|
296
302
|
this.audioContext = audioContext;
|
|
297
303
|
this.masterVolume = new GainNode(audioContext);
|
|
304
|
+
this.scheduler = new GainNode(audioContext, { gain: 0 });
|
|
305
|
+
this.schedulerBuffer = new AudioBuffer({
|
|
306
|
+
length: 1,
|
|
307
|
+
sampleRate: audioContext.sampleRate,
|
|
308
|
+
});
|
|
298
309
|
this.voiceParamsHandlers = this.createVoiceParamsHandlers();
|
|
299
310
|
this.controlChangeHandlers = this.createControlChangeHandlers();
|
|
300
311
|
this.channels = this.createChannels(audioContext);
|
|
301
312
|
this.masterVolume.connect(audioContext.destination);
|
|
313
|
+
this.scheduler.connect(audioContext.destination);
|
|
302
314
|
this.GM1SystemOn();
|
|
303
315
|
}
|
|
304
316
|
initSoundFontTable() {
|
|
@@ -392,10 +404,18 @@ class MidyGMLite {
|
|
|
392
404
|
return audioBuffer;
|
|
393
405
|
}
|
|
394
406
|
}
|
|
395
|
-
|
|
407
|
+
calcLoopMode(channel, voiceParams) {
|
|
408
|
+
if (channel.isDrum) {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
return voiceParams.sampleModes % 2 !== 0;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
createBufferSource(channel, voiceParams, audioBuffer) {
|
|
396
416
|
const bufferSource = new AudioBufferSourceNode(this.audioContext);
|
|
397
417
|
bufferSource.buffer = audioBuffer;
|
|
398
|
-
bufferSource.loop =
|
|
418
|
+
bufferSource.loop = this.calcLoopMode(channel, voiceParams);
|
|
399
419
|
if (bufferSource.loop) {
|
|
400
420
|
bufferSource.loopStart = voiceParams.loopStart / voiceParams.sampleRate;
|
|
401
421
|
bufferSource.loopEnd = voiceParams.loopEnd / voiceParams.sampleRate;
|
|
@@ -806,7 +826,7 @@ class MidyGMLite {
|
|
|
806
826
|
const voiceParams = voice.getAllParams(controllerState);
|
|
807
827
|
const note = new Note(noteNumber, velocity, startTime, voice, voiceParams);
|
|
808
828
|
const audioBuffer = await this.getAudioBuffer(channel.program, noteNumber, velocity, voiceParams, isSF3);
|
|
809
|
-
note.bufferSource = this.
|
|
829
|
+
note.bufferSource = this.createBufferSource(channel, voiceParams, audioBuffer);
|
|
810
830
|
note.volumeEnvelopeNode = new GainNode(this.audioContext);
|
|
811
831
|
note.filterNode = new BiquadFilterNode(this.audioContext, {
|
|
812
832
|
type: "lowpass",
|
|
@@ -845,7 +865,7 @@ class MidyGMLite {
|
|
|
845
865
|
if (this.exclusiveClassMap.has(exclusiveClass)) {
|
|
846
866
|
const prevEntry = this.exclusiveClassMap.get(exclusiveClass);
|
|
847
867
|
const [prevNote, prevChannelNumber] = prevEntry;
|
|
848
|
-
if (!prevNote.ending) {
|
|
868
|
+
if (prevNote && !prevNote.ending) {
|
|
849
869
|
this.scheduleNoteOff(prevChannelNumber, prevNote.noteNumber, 0, // velocity,
|
|
850
870
|
startTime, true);
|
|
851
871
|
}
|
|
@@ -953,8 +973,10 @@ class MidyGMLite {
|
|
|
953
973
|
this.setPitchBend(channelNumber, pitchBend, scheduleTime);
|
|
954
974
|
}
|
|
955
975
|
setPitchBend(channelNumber, value, scheduleTime) {
|
|
956
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
957
976
|
const channel = this.channels[channelNumber];
|
|
977
|
+
if (channel.isDrum)
|
|
978
|
+
return;
|
|
979
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
958
980
|
const state = channel.state;
|
|
959
981
|
const prev = state.pitchWheel * 2 - 1;
|
|
960
982
|
const next = (value - 8192) / 8192;
|
|
@@ -1103,7 +1125,6 @@ class MidyGMLite {
|
|
|
1103
1125
|
}
|
|
1104
1126
|
}
|
|
1105
1127
|
updateModulation(channel, scheduleTime) {
|
|
1106
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1107
1128
|
const depth = channel.state.modulationDepth * channel.modulationDepthRange;
|
|
1108
1129
|
this.processScheduledNotes(channel, (note) => {
|
|
1109
1130
|
if (note.modulationDepth) {
|
|
@@ -1116,8 +1137,10 @@ class MidyGMLite {
|
|
|
1116
1137
|
});
|
|
1117
1138
|
}
|
|
1118
1139
|
setModulationDepth(channelNumber, modulation, scheduleTime) {
|
|
1119
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1120
1140
|
const channel = this.channels[channelNumber];
|
|
1141
|
+
if (channel.isDrum)
|
|
1142
|
+
return;
|
|
1143
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1121
1144
|
channel.state.modulationDepth = modulation / 127;
|
|
1122
1145
|
this.updateModulation(channel, scheduleTime);
|
|
1123
1146
|
}
|
|
@@ -1162,8 +1185,10 @@ class MidyGMLite {
|
|
|
1162
1185
|
.setValueAtTime(volume * gainRight, scheduleTime);
|
|
1163
1186
|
}
|
|
1164
1187
|
setSustainPedal(channelNumber, value, scheduleTime) {
|
|
1165
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1166
1188
|
const channel = this.channels[channelNumber];
|
|
1189
|
+
if (channel.isDrum)
|
|
1190
|
+
return;
|
|
1191
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1167
1192
|
channel.state.sustainPedal = value / 127;
|
|
1168
1193
|
if (64 <= value) {
|
|
1169
1194
|
this.processScheduledNotes(channel, (note) => {
|
|
@@ -1220,8 +1245,10 @@ class MidyGMLite {
|
|
|
1220
1245
|
this.setPitchBendRange(channelNumber, pitchBendRange, scheduleTime);
|
|
1221
1246
|
}
|
|
1222
1247
|
setPitchBendRange(channelNumber, value, scheduleTime) {
|
|
1223
|
-
scheduleTime ??= this.audioContext.currentTime;
|
|
1224
1248
|
const channel = this.channels[channelNumber];
|
|
1249
|
+
if (channel.isDrum)
|
|
1250
|
+
return;
|
|
1251
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1225
1252
|
const state = channel.state;
|
|
1226
1253
|
const prev = state.pitchWheelSensitivity;
|
|
1227
1254
|
const next = value / 128;
|
|
@@ -1260,12 +1287,12 @@ class MidyGMLite {
|
|
|
1260
1287
|
scheduleTime ??= this.audioContext.currentTime;
|
|
1261
1288
|
return this.stopChannelNotes(channelNumber, 0, false, scheduleTime);
|
|
1262
1289
|
}
|
|
1263
|
-
handleUniversalNonRealTimeExclusiveMessage(data,
|
|
1290
|
+
handleUniversalNonRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1264
1291
|
switch (data[2]) {
|
|
1265
1292
|
case 9:
|
|
1266
1293
|
switch (data[3]) {
|
|
1267
1294
|
case 1:
|
|
1268
|
-
this.GM1SystemOn();
|
|
1295
|
+
this.GM1SystemOn(scheduleTime);
|
|
1269
1296
|
break;
|
|
1270
1297
|
case 2: // GM System Off
|
|
1271
1298
|
break;
|
|
@@ -1277,12 +1304,17 @@ class MidyGMLite {
|
|
|
1277
1304
|
console.warn(`Unsupported Exclusive Message: ${data}`);
|
|
1278
1305
|
}
|
|
1279
1306
|
}
|
|
1280
|
-
GM1SystemOn() {
|
|
1307
|
+
GM1SystemOn(scheduleTime) {
|
|
1308
|
+
scheduleTime ??= this.audioContext.currentTime;
|
|
1309
|
+
this.mode = "GM1";
|
|
1281
1310
|
for (let i = 0; i < this.channels.length; i++) {
|
|
1311
|
+
this.allSoundOff(i, 0, scheduleTime);
|
|
1282
1312
|
const channel = this.channels[i];
|
|
1283
1313
|
channel.bank = 0;
|
|
1314
|
+
channel.isDrum = false;
|
|
1284
1315
|
}
|
|
1285
1316
|
this.channels[9].bank = 128;
|
|
1317
|
+
this.channels[9].isDrum = true;
|
|
1286
1318
|
}
|
|
1287
1319
|
handleUniversalRealTimeExclusiveMessage(data, scheduleTime) {
|
|
1288
1320
|
switch (data[2]) {
|
|
@@ -1325,13 +1357,20 @@ class MidyGMLite {
|
|
|
1325
1357
|
}
|
|
1326
1358
|
scheduleTask(callback, scheduleTime) {
|
|
1327
1359
|
return new Promise((resolve) => {
|
|
1328
|
-
const bufferSource = new AudioBufferSourceNode(this.audioContext
|
|
1360
|
+
const bufferSource = new AudioBufferSourceNode(this.audioContext, {
|
|
1361
|
+
buffer: this.schedulerBuffer,
|
|
1362
|
+
});
|
|
1363
|
+
bufferSource.connect(this.scheduler);
|
|
1329
1364
|
bufferSource.onended = () => {
|
|
1330
|
-
|
|
1331
|
-
|
|
1365
|
+
try {
|
|
1366
|
+
callback();
|
|
1367
|
+
}
|
|
1368
|
+
finally {
|
|
1369
|
+
bufferSource.disconnect();
|
|
1370
|
+
resolve();
|
|
1371
|
+
}
|
|
1332
1372
|
};
|
|
1333
1373
|
bufferSource.start(scheduleTime);
|
|
1334
|
-
bufferSource.stop(scheduleTime);
|
|
1335
1374
|
});
|
|
1336
1375
|
}
|
|
1337
1376
|
}
|
|
@@ -1342,6 +1381,7 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1342
1381
|
writable: true,
|
|
1343
1382
|
value: {
|
|
1344
1383
|
currentBufferSource: null,
|
|
1384
|
+
isDrum: false,
|
|
1345
1385
|
detune: 0,
|
|
1346
1386
|
program: 0,
|
|
1347
1387
|
bank: 0,
|
|
@@ -1349,5 +1389,6 @@ Object.defineProperty(MidyGMLite, "channelSettings", {
|
|
|
1349
1389
|
dataLSB: 0,
|
|
1350
1390
|
rpnMSB: 127,
|
|
1351
1391
|
rpnLSB: 127,
|
|
1392
|
+
modulationDepthRange: 50, // cent
|
|
1352
1393
|
}
|
|
1353
1394
|
});
|